Sunday, 25th October 2020
Using the rppal library and the Raspberry Pi's GPIO pins, I've added support for the six Cherry ML switches on the back of the Bramley.
Reading from the pins is easy enough, but to correctly detect a key press, I've got some more work to do.
Without a pull-up or pull-down resistor, the value read from a GPIO pin will be floating. As a digital input, it will hover between 0 and 1, depending on background interference, and, if I'm not careful, produce unwanted key presses.
Normally, I rely on the Raspberry Pi's internal pull-up resistors to deal with this because they're easy to enable in software, and that's my comfort zone.
But, this time, despite enabling the internal resistor I was still experiencing 'phantom' key presses. Determined to exorcise them using hardware, I wired another 10K pull-up resistor to each switch:
That seemed to block out the noise. I'm now ready to try and make sense of the signal.
An actuated switch doesn't produce a signal that's immedately high or low. Before stabalising, it will bounce between each state.
If I watch only for transitions from high to low (1 to 0), I would incorrectly count three key presses in the example above. This could be avoided in hardware by using a capacitor to smooth the transition, but I'm going to do it in software where the solution is to take several readings and wait for the signal to stablise.
Normally, I make use of interrupts and timeouts to wait for a stable signal, but the code is always more complicated than I'd like. So this time I've decided to treat the Pi more like a microcontroller and just poll the pins at a regular iterval.
Every 3 milliseconds, I read a digital high/low from the six GPIO pins and emit new button events according to these rules:
If both of those are true, I can emit a new event. The code works something like this:
For the full context see the source code.
By waiting for 8 stable reads at 3ms intervals, I give the signal 24ms to stablise. And by storing the high/low bits in an unsigned 8-bit integer, I can easily compare it to 0 or 255 to determine if the signal is stable.
Compared to some other debouncing techniques (like using a capacitor), my approach has the drawback that it won't protect against brief background inteference during an otherwise stable signal - the hope being that this has been safely avoided by those 10K resistors.
This approach does, however, have a side-effect I like: provided the signal was stable beforehand, button presses are detected immediately. My aim is to reduce input latency wherever possible, because I know some screen updates might be slow.
What I'm unsure about is whether polling every 3ms is significantly more CPU hungry than using interrupts. Using interrupts I don't see any CPU usage because it happens inside the kernel, but for all I know it might be doing the same polling I'm doing. If anyone knows how Linux handles this I'd be curious to learn more. Anyway, I think the CPU use will be negligable either way.
Next time, I'll get a Rust program to automatically launch when the Pi is booted.