I bought a "dumb" 8-pin 4x4 keypad for use in a project. (This one: https://www.sparkfun.com/products/14881 It has a wire connected to each row, and a wire connected to each column. When you press a key, it connects a row pin and a column pin. You have to use GPIO outputs to send +5V down one row/column at a time, and then use inputs to look for a HIGH on a corresponding set of pins from the other dimension, which indicates a connection between a specific row/column pair.)
I found a framework, MultitapKeypad, that at first appeared perfect. It is a nice clean C++ object with a simple main method, getKey(), that returns a struct with information about the key the user pressed. It detects long presses and repeat presses, things I plan on using.
However, when I looked into it I found things were not as good on the inside. The getKey method uses a while (no key pressed) loop that loops forever. The author's solution to not locking up the Arduino any time you are waiting for a keypress is to provide a callback function that the library calls from the while loop.
The idea of turning a sketch's control flow completely upside down and having to do all your app's event handling from the callback of a keypad class just seems wrong. Then control flow from the app's main loop has to go through the keypad's callback when I'm waiting for keys, or through some other flow otherwise.
The framework also makes use of delay() in it's debouncing, another no-no for apps that manage multiple things at once.
I was therefore thinking of writing my own keypad handler.
My current project, a power controller for lights, chargers, and a humidity controlled warm air dryer will run all of those things simultaneously. It has a back-lit LCD for a display, and uses a motion sensor and photoresistor to turn lights on when it detects motion in low light.
It puts the LCD to sleep if it doesn't detect motion or any user input from it's various switches for an extended period.
When I integrate a keypad into the project I would also like to add keypresses to the list of things that keeps the display awake. I don't see an easy way to do that with the LCD library I was using.
My proposed design:
I was thinking of wiring the keypad with all of the row pins connected to a 9th pin with diodes, anodes pointed towards the 9th pin. I'd set that 9th pin up in INPUT_PULLUP mode and attach it to an interrupt, set to trigger on FALLING signal (or perhaps CHANGE. See below.) The row pins would all also be configured as INPUT_PULLUP. The column pins would all be set up as outputs, and driven LOW.
When the user pressed a key, it would connect one of the column pins to a row pin. That would pull that row pin's input LOW, and also pull the interrupt pin LOW, triggering the ISR (Interrupt service routine).
In the ISR I would use port registers to switch all the column pins to HIGH, and then toggle one column pin at a time back to LOW, looking for a row pin to also drop LOW. If one and only one column going LOW triggers one and only one row to go LOW, I know only one button is pressed, and know the row/column combo, and hence the key that was pressed. If multiple columns trigger a row, or more than one row drops to LOW, I know more than one key is pressed. (I could use that info to report multi-key presses if I need to, although >2 keys at a time gets messy fast.)
Once I have a key I could add the key code for that key to a simple ring buffer and set a "key ready" flag. (My ring buffer might contain structs with more than just a key code, like up/down events, time stamps, etc.)
The rest of my sketch could then query my keypad for "key ready" and read one or more keys as needed. I'd provide housekeeping functions for clearing the key buffer, a way to detect buffer overflows, etc.
I guess I'd actually want interrupts on both falling and rising state change, so I could detect key up events and track the duration of key presses.
I'd also need to add debouncing of some sort or another. I could use a simple RC filter on each input pin, but I don't have a lot of room left on my project breadboard and that would require 4 capacitors and 8 resistors.
From what I've read the millis() function is a little slow and best avoided in ISR routines. Is there a fast alternative to get the current clock value directly in an ISR? If so I could use software to debounce the keys.
Are there any obvious flaws in my plan? Does using an ISR like this sound reasonable? The Keypad actually doesn't short the row/column pins together. It uses variable resistances between the row/column combinations, I guess so that you can use a voltage divider and an analog input to figure out which key was pressed based on the measured voltage. The docs for the keypad say the resistance for a row-column pair varies from 10 to 625 ohms. I'm assuming that the key resistance is low enough that even with signal diodes in the circuit, the closed switch resistance should be enough to pull my interrupt pin and my row pins to LOW.
I suppose I could also tie all the columns and all the rows together, hook it to a voltage divider, and see if I could decode the key using an analog input. That would let me do all that decoding with a single pin.
Alternatively, are there other keypad frameworks that handle 3x4 and 4x4 keypads and offer things like repeated taps, long taps, and allow you to trigger events on ANY key press (So I could keep my LCD display awake.)