Convenient Way of Reading Keypad Buttons
Most of microcontroller coding tutorials’ second page is dedicated to reading pin or buttons. But for sure, that never means this is a trivial task and a keypad reader is easy to implement for a well designed MCU program that you should never underestimate. With this C keypad reader, you don’t have to struggle.
The Best Practice
The very first problem that you will face when you try to implement a general purpose keypad reader library is mechanical and electrical spikes that come from the buttons, so called bouncing effect. Although you already implemented hardware filters to reduce, it is still out there and can be prevented only with a good software filter.
Another reason that drives me to write this post is that reading multiple buttons at once is a very complex task. Even when you try to make the most basic user interface, you will need multiple buttons. Let’s imagine we have a no-touch display with a settings menu. In this scenario ENTER, UP, DOWN and EXIT look like the most common buttons. What if you want to have a secret button combination to enter hidden service menu? Then you need to sense multiple button press, i.e. ENTER-while-UP-and-DOWN for 3 seconds. I bet you would find the following piece of code a trash.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
int timeSinceButtonPress = 0; while(true){ if((ENTER == 1) && (UP == 1) && (DOWN == 1)){ timeSinceButtonPress++; if(timeSinceButtonPress > 3000){ timeSinceButtonPress = 0; enterHiddenMenu(); }else{ delay_ms(1); } }else{ timeSinceButtonPress = 0; } } |
And I am pretty sure that you would desire such a clean code instead:
1 |
isKeyHold(ENTER|UP|DOWN); |
Good news: this is possible with “PushME” keypad reader library ?
Features of PushME
The fancy name doesn’t only for show but it has several features that you will find useful. Let’s talk about technical details. PushME is implemented in C language and aimed to real-time applications. The library supports up to 15 buttons (KEY00 to KEY14) by default. it doesn’t mean that you can’t extend it for more buttons or compact it for less resource consumption.
Key Definitions
To add a button to be polled by the library, just assign it to one of the library-specific defines. Respectively; the pin definition (architecture specific) and active state of the button.
1 2 |
#define KEY00_pin GPIOA,0 #define KEY00_active 1 |
Here, GPIOA,0 is the pin definition that i use in Porty project if you haven’t read it yet. But it is always possible to use with any microcontroller like Microchip for example PORTAbits.RB0. If PIN access is not available, defining the pin as (PORTC & 0x80) and the active state as 0x80 will do the work.
What is better than pooling a button? To be able to pool any signal of course. I used capacitive touch buttons on a Cypress PSoC (which has its own API calls to read the buttons) by replacing the pin definitions with these functions like the following and the keypad library worked seamlessly.
1 2 |
#define KEY00_pin CapSense_IsWidgetActive(CapSense_BUTTON0) #define KEY00_active true |
Debounce Filter
The most important one is debouncing of all buttons with no need for extra filtering. There is a specific minimum time limit KEY_DEBOUNCE_TIME for continuous key press in milliseconds defined in keypad.h. It simply keeps reading the PIN value for the given amount of time and if detects a bouncing, resets the time and keeps reading again until it catches a clear active signal. The library uses a timeout framework that I call Punctual to implement debouncing filter and other time events. This framework is already embedded in the attached project below.
LongPress or KeyHold
Long press is useful for many applications and PushME supports longpress events. Just assign a value to KEY_LONGPRESS_TIME in milliseconds that you wish to count as a long press and that’s all.
Repeated Events
PushME allows you to hold a button longer than KEY_LONGPRESS_TIME to create repeated events. You can imagine this as increasing the volume of your TV by holding VOL+ button on remote. Those repeated events start when KEY_LONGPRESS_TIME ends and repeats in every KEY_LONGPRESS_REPEAT_TIME milliseconds which can be set in keypad.h. It can be set to 0 to disable repeating.
Boosted Repeats
Another feature is boosted repeated events. Let’s say you have a menu and you want to increase a number field from 0 to 100. This takes a long time by pressing 100 times and still takes time with repeated long press (that has been mentioned above). To solve this, PushME sends repeated events in the beginning and changes the repetition interval to something smaller after some number of repetitions. Settings for this 2 are KEY_LONGPRESS_BOOST_TIME and KEY_LONGPRESS_BOOST_THRESHOLD. To disable boost feature, just set KEY_LONGPRESS_BOOST_TIME equal to KEY_LONGPRESS_TIME.
In other words, when KEY_LONGPRESS_TIME is over, 1 signal is sent in every KEY_LONGPRESS_REPEAT_TIME milliseconds for KEY_LONGPRESS_BOOST_THRESHOLD times, then it sends the rest of the signals in every KEY_LONGPRESS_BOOST_TIME milliseconds.
Key Labels
As explained above, every button label is created individually using KEYnn definitions and this is the low-level name of the button. User friendly case is that you define your own labels using or combining them. Available low-level labels are KEYnn and special KEYLONG that are used in the following examples.
1 2 3 4 5 6 |
//-- Label definitions (OPTIONAL) #define KEY_NONE 0 #define KEY_ON (KEY00) #define KEY_OFF (KEY00|KEYLONG) #define KEY_ESC (KEY01) #define KEY_RESTART (KEY00|KEY01|KEYLONG) |
Events Explained
Following 3 events are possible with the provided keypad library.
KeyPress
KeyPress is the combination of a KeyDown event is followed by KeyUp event on a specific key or a combination of keys. This event is polled using isKeyPress(...) method and returns true if the given key had a KeyPress event recently. Supports repetition and boost features and returns true repeatedly.
KeyHold (LongPress)
KeyHold is the combination of a KeyDown event is followed LongPress w.r.t. KEY_LONGPRESS_TIME. KeyUp event on a specific key or a combination of keys. This event is polled using isKeyHold(...) method and returns true on KeyHold event. Supports repetition and boost features and returns true repeatedly.
This event is actually an overloading of isKeyPress(...) with and addition of KEYLONG to the given key combination. The following lines do exactly the same job.
1 2 |
isKeyPress(KEY00|KEYLONG); isKeyHold(KEY00); |
KeyDown
This event is polled using isKeyDown(...) method and it simply returns if the button is hold-down at the time being, regardless of debounce filter. This method is implemented for instantenaous response that requires fast action rather than regular button events. Use with caution.
Usage
To poll the keys keypadRead() method has to be called in a fast infinite loop. The following code turns an LED on when KEY00 is pressed and turns off (or toggles) by longpress of the same key. When the key is not released, it starts toggling the LED in low frequency. Finally after 8 repeated events, toggling occurs at a high frequency which is the boost.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
/** * @file main.c * @author Atakan S. * @date 01/01/2018 * @version 1.3 * @brief Keypad project, MCU independent embedded code template. * * @copyright Copyright (c) 2018 Atakan SARIOGLU ~ www.atakansarioglu.com * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #include "porty.h" //-- Label definitions (OPTIONAL) #define KEY_NONE 0 #define KEY_ON (KEY00) #define KEY_OFF (KEY00|KEYLONG) // The main routine. int main(void){ // Init cpu. Board_Init(); // Init Ticker. TickerInit(); // Init the scheduler. PunctualInit(); // Enable global interrupts. mIntEnable(); // The main loop. while (true) { keypadRead(); if(isKeyPress(KEY_ON)){// ON mPinWrite(LED_Green, LED_Green_ON); }else if(isKeyPress(KEY_OFF)){// OFF mPinWrite(LED_Green, !mPinRead(LED_Green)); } } } |
PushME Files
keypad.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
/** * @file keypad.h * @author Atakan S. * @date 01/01/2018 * @version 1.0 * @brief "PushME" Event based keypad reader module with debouncing. * * @copyright Copyright (c) 2018 Atakan SARIOGLU ~ www.atakansarioglu.com * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #ifndef _H_KEYPAD_H #define _H_KEYPAD_H #include "porty.h" //-- Settings. #define KEY_DEBOUNCE_TIME 50UL #define KEY_LONGPRESS_TIME 1000UL #define KEY_LONGPRESS_REPEAT_TIME 1000UL #define KEY_LONGPRESS_BOOST_TIME 50UL #define KEY_LONGPRESS_BOOST_THRESHOLD 8 //-- Definitions (DONT TOUCH) #define KEY00 0x0001 #define KEY01 0x0002 #define KEY02 0x0004 #define KEY03 0x0008 #define KEY04 0x0010 #define KEY05 0x0020 #define KEY06 0x0040 #define KEY07 0x0080 #define KEY08 0x0100 #define KEY09 0x0200 #define KEY10 0x0400 #define KEY11 0x0800 #define KEY12 0x1000 #define KEY13 0x2000 #define KEY14 0x4000 #define KEYLONG 0x8000 //-- Prototypes. void keypadRead(void); bool isKeyDown(uint16_t m); // Key(s) is just down. bool isKeyPress(uint16_t m); // Key(s) is pressed. bool isKeyHold(uint16_t m); // Key(s) is held down. #endif |
keypad.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 |
/** * @file keypad.c * @author Atakan S. * @date 01/01/2018 * @version 1.0 * @brief "PushME" Event based keypad reader module with debouncing. * * @copyright Copyright (c) 2018 Atakan SARIOGLU ~ www.atakansarioglu.com * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #include "keypad.h" // Struct to keep the variables of the keypad module. typedef struct { //-- Key events. uint16_t keysPressed; // PORT POLLING register uint16_t keysDown; // uint16_t keysReleased; // tTimeout timerDebounce; tTimeout timerLong; uint8_t counterBoost; } tKeypad; // Initialize the variables. tKeypad keypad = { .keysPressed = 0, .keysDown = 0, .keysReleased = 0, .counterBoost = 0, }; // This function reads the pin values and writes to bits of a variable. uint16_t keypadSerialize(void) { uint16_t result = 0; #ifdef KEY00_pin result |= (mPinRead(KEY00_pin) == KEY00_active) ? KEY00 : 0; #endif #ifdef KEY01_pin result |= (mPinRead(KEY01_pin) == KEY01_active) ? KEY01 : 0; #endif #ifdef KEY02_pin result |= (mPinRead(KEY02_pin) == KEY02_active) ? KEY02 : 0; #endif #ifdef KEY03_pin result |= (mPinRead(KEY03_pin) == KEY03_active) ? KEY03 : 0; #endif #ifdef KEY04_pin result |= (mPinRead(KEY04_pin) == KEY04_active) ? KEY04 : 0; #endif #ifdef KEY05_pin result |= (mPinRead(KEY05_pin) == KEY05_active) ? KEY05 : 0; #endif #ifdef KEY06_pin result |= (mPinRead(KEY06_pin) == KEY06_active) ? KEY06 : 0; #endif #ifdef KEY07_pin result |= (mPinRead(KEY07_pin) == KEY07_active) ? KEY07 : 0; #endif #ifdef KEY08_pin result |= (mPinRead(KEY08_pin) == KEY08_active) ? KEY08 : 0; #endif #ifdef KEY09_pin result |= (mPinRead(KEY09_pin) == KEY09_active) ? KEY09 : 0; #endif #ifdef KEY10_pin result |= (mPinRead(KEY10_pin) == KEY10_active) ? KEY10 : 0; #endif #ifdef KEY11_pin result |= (mPinRead(KEY11_pin) == KEY11_active) ? KEY11 : 0; #endif #ifdef KEY12_pin result |= (mPinRead(KEY12_pin) == KEY12_active) ? KEY12 : 0; #endif #ifdef KEY13_pin result |= (mPinRead(KEY13_pin) == KEY13_active) ? KEY13 : 0; #endif #ifdef KEY14_pin result |= (mPinRead(KEY14_pin) == KEY14_active) ? KEY14 : 0; #endif return result; } //-- Read keypad. void keypadRead() { //-- All release events processed in the last cycle are cleared. keypad.keysReleased &= KEYLONG; //Release=0 // Read all the keys to variable. keypad.keysPressed = keypadSerialize(); //-- If pressed. if (keypad.keysPressed) { //-- If pressed and seen for the first time. if (keypad.keysPressed != keypad.keysDown) { keypad.keysReleased &= ~KEYLONG; //Long=0 keypad.counterBoost = 0; PunctualTimeoutSet(&keypad.timerDebounce, KEY_DEBOUNCE_TIME); PunctualTimeoutSet(&keypad.timerLong, KEY_LONGPRESS_TIME); } //-- Remember what is just seen. keypad.keysDown |= keypad.keysPressed; //-- Longpress timeout check. //-- If this is the first Longpress event or REPEAT is enabled (KEY_LONGPRESS_REPEAT_TIME > 0). if (PunctualTimeoutCheck(&keypad.timerLong) && (((keypad.keysReleased & KEYLONG) == 0) || KEY_LONGPRESS_REPEAT_TIME)) { keypad.keysReleased = keypad.keysDown | KEYLONG; //Release=Down, Long=1 // If number of REPEAT events are greater than KEY_LONGPRESS_BOOST_THRESHOLD and BOOST is enabled. if (KEY_LONGPRESS_BOOST_THRESHOLD && (++keypad.counterBoost > KEY_LONGPRESS_BOOST_THRESHOLD)) { keypad.counterBoost = KEY_LONGPRESS_BOOST_THRESHOLD; PunctualTimeoutSet(&keypad.timerLong, KEY_LONGPRESS_BOOST_TIME); } else { PunctualTimeoutSet(&keypad.timerLong, KEY_LONGPRESS_REPEAT_TIME); } } } else { //-- Keypress timeout check. if (PunctualTimeoutCheck(&keypad.timerDebounce)) { if ((keypad.keysReleased & KEYLONG) == 0) { keypad.keysReleased = keypad.keysDown; //Release=Down } } //-- Reset all. keypad.keysDown = 0; keypad.keysReleased &= ~KEYLONG; //Long=0 keypad.counterBoost = 0; //-- Reset timeouts. PunctualTimeoutSet(&keypad.timerLong, KEY_LONGPRESS_TIME); PunctualTimeoutSet(&keypad.timerDebounce, KEY_DEBOUNCE_TIME); } } bool isKeyDown(uint16_t m) { return ((keypad.keysDown == (m)) && (keypad.keysReleased == 0)); } bool isKeyPress(uint16_t m) { return ((keypad.keysReleased == (m))); } bool isKeyHold(uint16_t m) { return ((keypad.keysReleased == (m | KEYLONG))); } |
Download
GitHub: PushME Keypad Library. A complete example project that runs on STM32F100 is here: PushME demo project (Or here PushME-0.1.0). For compilation check here.
Conclusion
This project successfully reads digital keypads and every kind of digital signals from any sources. My plan is to add functionality for reading analog keypads since multiple keys on a single line is very advantageous in tightly constrained application. Don’t hesitate to share your comments ?