Skip to main content

Sampling lots of inputs on Arduino

· 3 min read

When using a microcontroller to read something with lots of inputs, like a keyboard, there aren't enough IO pins to give one to every input. You have to turn to tricks like multiplexing.

A keyboard

I had stumbled over an old organ, and started making it playable for fun using an Arduino Leonardo. (The Leonardo has native USB and can enumerate a MIDI device.) After a while I found another old keyboard with velocity sensitive keys, and set about getting that to work too.

This other keyboard has a keybed with 61 keys. Each key contains two switches that are slightly offset. When you hit a key, you can time the difference between the two switches to measure the velocity of the hit.

Multiplexing

If you are unclear on what multiplexing means in this context there is a good explanation here.

There is a total of 122 switches (61 x 2) in the keybed, and they are arranged in 8 banks (columns) of 8 switches (rows). This, by no coincidence, fills exactly one IO port on an 8-bit microcontroller. When you "light up" one column, you can read one byte in memory and instantly have the state of all 8 switches in the selected column.

Using a prototype shield PCB, I implemented a multiplexing circuit using two 74HC138 line decoders for bank/column selection.

Multiplexing circuit

The upper nibble of PORTF is column selection, and PORTB reads the entire row of the selected column.

Velocity

The code then tracks which keys are on and off, and measure the time between the two switches on each key to determine the velocity. These times are in the order of microseconds.

It turns out that using the built-in Arduino digitalWrite() and digitalRead() functions for this is too slow. We have to manipulate PORTF and read PINB directly to get an adequate sample rate for velocity sensitivity.

There was also an issue with converting the number of microseconds measured over to a MIDI note velocity. Doing the math each time would in practice halt keyboard reading for too long each time.

The solution? Lookup tables! A Python script generates a C array with the corresponding velocities. I sacrificed a little resolution by ditching the lower 8 bits of the recorded time. This gives a timing resolution of about 1/4 millisecond, which seems to be good enough, and we only need a 256 byte LUT.

Playing it

All these tricks resulted in the little microcontroller being able to read the state of 128 switches at a rate of about 9 kHz. It is quite playable as an instrument.

Code is on Github. Demo here: