Wishful Coding

Didn't you ever wish your computer understood you?

Sensing pressure with a SoftPot

In my previous post I connected a SoftPot to an Arduino and used a Game Boy as the synth. In this post I’m focusing more on the SoftPot, and specifically on reading pressure.

The SoftPot consists of a resistive bottom layer and a conductive top layer. When pressed together, the bottom resistor forms a voltage divider with the top conductor. When the conductive top side is pressed down, it shorts part of the bottom track, creating a lower total resistance. The harder you press down, the larger the area of the bottom track that is shorted, the lower the resistance.

So to measure both pressure and position, we’ll need to simultaneously read the voltage on the top track, and the resistance of the bottom track. To get an accurate position, we’d like to have the full voltage swing available, without any changes in voltage due to pressure. Meanwhile, we’d like to sense small DC variations in resistance of a large total resistance. This is my best attempt so far.

circuit

It uses a current mirror to maintain an almost fixed voltage on the pot (R1) and only lose 0.6V of the full swing. The current mirror then feeds into a biased transimpedance amplifier. The rightmost voltage divider is used to set the DC output voltage. (I later added a capacitor in parallel to the TIA feedback resistor to reject noise)

In reality the voltage divider on the positive TIA input is a trim pot. The changes in resistance of the SoftPot are smaller than the process variations. For example, this particular pot changes from 20.7k to 20.3k when pressed, so the bias voltage has to be adjusted for this.

sensing pressuer and pitch

In the above figure, the blue line is me sliding my finger up and down, while the yellow line is the pressure of my finger, with the green/red lines being the max/min. This seems to work just fine, except after letting it run for a while the pressure value starts to drift. Remember, we need the constant, absolute value, not some high-pass filtered version.

I was really confused, and tried various things to reproduce the problem and improve the situation. Then I blew on the circuit.

temperature sensitivity

OMG, look at that! The current mirror goes completely crazy when it gets warm. One solution is to add more degeneration, which adds voltage drop. Another is to use a Wilson current mirror, which also adds voltage drop. And finally, you could get two integrated transistors on the same die, which makes them the same temperature and improves matching. I guess that’s what I’ll have to do. To be continued.

const int pitchPin = A0;
const int pressurePin = A1;
const int pwmPin = 12;

const int pressureThreshold = 20;

int pitchValue = 0;        // value from SoftPot
int pressureValue = 0;        // value from TIA
int minPressureValue = INT16_MAX;
int maxPressureValue = 0;
int pressure = 0;
int frequency = 0;
int polarity = 1;

void setup() {
  Serial.begin(9600);
}

void loop() {
  delay(1);
  pitchValue = analogRead(pitchPin);
  delay(1);
  pressureValue = analogRead(pressurePin);
  delay(1);
  minPressureValue = min(pressureValue, minPressureValue);
  maxPressureValue = max(pressureValue, maxPressureValue);
  pressure = map(pressureValue, minPressureValue, maxPressureValue, 0, 10);
  frequency = map(pitchValue, 0, 1023, 440, 1320);
  if (pressureValue < minPressureValue+pressureThreshold) {
    pitchValue = 0;
    noTone(pwmPin);
  } else if (pressure > 6) {
    tone(pwmPin, frequency+pressure*polarity);
    polarity *= -1;
  } else {
    tone(pwmPin, frequency);
  }
  Serial.print(pitchValue);
  Serial.print(", ");
  Serial.print(minPressureValue);
  Serial.print(", ");
  Serial.print(maxPressureValue);
  Serial.print(", ");
  Serial.println(pressureValue);
  delay(50);
}
Pepijn de Vos

Visualising your WhatsApp conversations

I was curious how much I chat with my friends, so I decided to find out. It’s pretty easy and fun, I can promise.

My first thought was to go after the message database, but it turns out this is encrypted with a key that can only be obtained by rooting your device or performing some downgrade attack. Not sure what the security model is here, as the key is obviously on my device and on their servers. (Else you could not restore a backup on another phone)

Instead I went for the chat export feature that is hidden in Menu > Settings > Chats > Chat history > Export chat. This is per chat, so a bit tedious. But I have a rough idea who I talk to a lot, so it’s not too bad.

From there you can do whatever you want. Look at file sizes, grep for random things, or write a simple Python script. I stole a regex from SO, fixed it for Python, put everything in a Pandas DataFrame, and from there you can just play with the df and then call plot on it.

Most of what I did so far is count the messages/characters per month. Very interesting to see how things change over time and in response to real life events. Picture omitted as a reminder how much you can tell about someone by meta-data alone.

import re
import sys
import pandas as pd
import matplotlib.pyplot as plt

regex = """(?P<datetime>\d{1,2}\/\d{1,2}\/\d{1,4}, \d{1,2}:\d{1,2}( (?i)[ap]m)*) - (?P<name>.*(?::\s*\w+)*|[\w\s]+?)(?:\s+(?P<action>joined|left|was removed|changed the (?:subject to "\w+"|group's icon))|:\s(?P<message>(?:.+|\n(?!\d{1,2}\/\d{1,2}\/\d{1,4}, \d{1,2}:\d{1,2}( (?i)[ap]m)*))+))"""

files = sys.argv[1:]

for fname in files:
    with open(fname) as f:
        text = f.read()
    matches = re.findall(regex, text)
    messages = []
    for match in matches:
        messages.append(match[::2])

    df = pd.DataFrame(messages, columns=['datetime', 'name', 'message'])
    df.datetime = pd.to_datetime(df.datetime, dayfirst=True)
    df.set_index('datetime', inplace=True)
    lengths = df.message.str.len()
    monthly = lengths.groupby(pd.Grouper(freq='M')).sum()
    #monthly = lengths.groupby(pd.Grouper(freq='D')).count().rolling('30d', min_periods=1).sum()
    #plt.figure()
    #ax = monthly.plot(title=fname[19:-4])
    ax = monthly.plot()

ax.legend([fname[19:-4] for fname in files])
plt.ylabel("bytes/month")
#plt.ylabel("messages/month")
plt.show()
Pepijn de Vos

Making a new music instrument

A while a go I sent a message to Martin from Wintergatan that I want to help with the second version of his Modulin that he briefly talked about during one of his Marble Machine X videos.

He never replied, so I just decided to play around with my own ideas. The immediate goal is not so much to design a music instrument as it is to play around with touch input, digital signal processing, and analog filters.

For my first instrument I decided to build a chiptune violin by using a Game Boy as the synthesizer.

Step one was to figure out touch input. At first I wanted to do capacitive touch, but sensing position is not as easy as it’s made out to be. Then I found these SoftPot lineair potentiometers, which seem pretty good. At first I thought I would need a separate sensor strip to sense pressure, but by pressing down, a small track section of the SoftPot is shorted, resulting in a smaller total resistance that can be measured.

After playing with the Game Boy sound registers in the simulator for a while, I made a new copy of GALP for all the boilerplate code, then I added the following snippet to read an address and a byte from the Game Link port and simply write it to hiram. This code basically allows changing any register at all, not just the sound ones. The full code lives here.

; enable sound
  ld a,%10000000 ; enable
  ldh [rAUDENA],a
  ld a,$ff ; all channels
  ldh [rAUDTERM],a
  ld a,$77 ; max volume
  ldh [rAUDVOL],a

.loop

  call SerialTransfer
  ldh a,[rSB] ; new address
  ld c, a
  call SerialTransfer
  ldh a,[rSB] ; new data
  ld [$FF00+c],a ; write register
  jr .loop
  
SerialTransfer:
  ld a, $80 ; start external transfer
  ldh [rSC], a
.transferWait
  ldh a,[rSC]
  bit 7, a ; is transfer done?
  jr NZ, .transferWait
  ret

Then I connected the SoftPot to an Analog input of an Arduino with a 100k pull-down, and connected the Arduino SPI pins to the Game Link port. The code then simply reads the analog pin and sets the correct sound register on the Game Boy to play the corresponding note. The full code lives here.

int oldValue = 0;
void loop() {
      int sensorValue = analogRead(A9);
      bool lowValue = sensorValue < 1;
      bool oldLowValue = oldValue < 1;
      int outputValue = map(sensorValue, 0, 1023, 440, 1760);
      int oldOutputValue = map(oldValue, 0, 1023, 440, 1760);
      if(!lowValue && oldLowValue) { // rising edge
        ch2Length(63, 2);
        ch2Envelope(15, 0, 0);
        ch2Play(outputValue, 0, 1); 
      } else if (lowValue && !oldLowValue) { // falling edge
        ch2Envelope(15, 3, 0);
        ch2Play(oldOutputValue, 0, 1);
      } else if(!lowValue) { // high
        ch2Play(outputValue, 0, 0); 
      }
      oldValue = sensorValue; 
      delay(10);
}

The current code does not sense pressure and just uses a square wave channel on the Game Boy with an envelope on release. Next items on the list of things I want to try are sensing pressure and playing with some bucket brigade chips that have been sitting in my drawer.

Pepijn de Vos