Previous Tutorial | Tutorial 35 | Next Tutorial | |||||
STM32 KeyPad Interfacing Driver (Library) | |||||||
STM32 Course Home Page ???? |
In this tutorial, we’ll be discussing the usage of STM32 GPIO pins to interface and read a keypad matrix, It can be 3×3, 4×4, or whatever. I’ll also show you the Keypad library (driver) that I’ve developed for STM32 microcontrollers and discuss how it works and how it’s been built in this way.
We’ll do some tests, measurements, and spot the light on potential improvements and features that you can make and add to this driver code. But first of all, we’ll discuss how the keypad matrix works and the general procedure for reading buttons in a keypad.
Without further ado, let’s get right into it.
[toc]
Required Components For LABs
All the example code/LABs/projects in the course are going to be done using those boards below.
- Nucleo32-L432KC (ARM Cortex-M4 @ 80MHz) or (eBay)
- Blue Pill STM32-F103 (ARM Cortex-M3 @ 72MHz) or (eBay)
- ST-Link v2 Debugger or (eBay)
QTY | Component Name | ???? Amazon.com | ???? eBay.com |
2 | BreadBoard | Amazon | eBay |
1 | LEDs Kit | Amazon Amazon | eBay |
1 | Resistors Kit | Amazon Amazon | eBay |
1 | Capacitors Kit | Amazon Amazon | eBay & eBay |
2 | Jumper Wires Pack | Amazon Amazon | eBay & eBay |
1 | 9v Battery or DC Power Supply | Amazon Amazon Amazon | eBay |
1 | Micro USB Cable | Amazon | eBay |
1 | Push Buttons | Amazon Amazon | eBay |
1 | KeyPAD 4×4 | Amazon | eBay |
Alphanumeric LCD Module | Amazon | eBay |
★ Check The Full Course Complete Kit List
Some Extremely Useful Test Equipment For Troubleshooting:
- My Digital Storage Oscilloscope (DSO): Siglent SDS1104 (on Amazon.com) (on eBay)
- FeelTech DDS Function Generator: KKMoon FY6900 (on Amazon.com) (on eBay)
- Logic Analyzer (on Amazon.com) (on eBay)
Affiliate Disclosure: When you click on links in this section and make a purchase, this can result in this site earning a commission. Affiliate programs and affiliations include, but are not limited to, the eBay Partner Network (EPN) and Amazon.com.
Procedure For Reading a Keypad Matrix
Before getting into how to read a keypad matrix, let’s first have a look at how it’s constructed internally and how the buttons are connected together. Here is a diagram down below for the internal structure of a typical 4×4 keypad matrix.
There exist a few different ways for interfacing such a keypad matrix to a microcontroller. While some of them are interrupt-driven, there are others that work by polling the pins and scanning through all the keys one by one. And we’re going to discuss how to implement a keypad scanning routine that doesn’t utilize the pin interrupt feature of the microcontroller.
Obviously, it’s going to be a little bit slower compared to the interrupt-based method in terms of reaction time, and also it’s time-consuming, however, it’s easily portable and almost guaranteed to work with any microcontroller not only STM32 families. Just keep that in mind.
Keypad Matrix Scanning Procedure
1- Configuration
First of all, we need to configure 8 GPIO pins. Four of which will be inputs and the other 4 are going to be output pins. Then you can hook the column pins to either the input or output pins, but for convenience, I’ll connect them to the 4 output pins. And last but not least, the 4-row pins will be connected to the 4 input pins of the microcontroller. As you can see in the diagram down below.
2- Steady-state
The first state we’ve got to start with each scan cycle is the steady-state in which we output four ones (1’s) to the 4 output pins (columns). So, all the keypad buttons have a 1 on their right-side terminal. Therefore, if any button is pressed, the corresponding row input pin will read a 1, otherwise, it’ll read a zero.
If more than one button in the same row is pressed at the same time, the row input pin will still read a 1. And we’ll see in the next step how to identify which button is pressed in each row.
3- Read & Check Activated Rows
After reading the 4-row input pins, we now know the pressed button/buttons are located in which row/rows. So, we’ll cycle through those specific row buttons.
4- Cycle Through Activated Row Buttons
First, we’ll check the left-most button by outputting this [1-0-0-0] bit-pattern to the 4 output column pins. If that first button is pressed, the row input pin should still read a 1, otherwise, it’s going to be a 0.
Then, we’ll move to the next button and will use this bit pattern to check for it [0-1-0-0]. Next [0-0-1-0] and the last button will be checked with [0-0-0-1]. And repeat this step #4 for all rows that found to be activated in step #3.
STM32 Keypad ECUAL Driver
The ECUAL Keypad driver is built for STM32 microcontrollers using some GPIO pins. You’ll have to configure an instance of it and use the APIs to read your keypad and that’s all. The code should be easily ported to any other STM32 microcontroller or reconfigured to use any GPIO pins you want just as we’ll see in this section. And here is a link for the course’s repo, and you’ll find the KEYPAD driver in the ECUAL directory as usual.
Keypad Driver Code Files
The Keypad driver consists of the following files:
|
You’ll need only to modify the configuration files. The source code for this driver is found in (KEYPAD.c) and to use it you’ll include the header (KEYPAD.h). Nothing in the source code needs to be changed at all unless you need to add any extra features or customize the driver for your application’s needs. For today’s labs, we’ll only be changing the configuration files to build some test applications.
Therefore, I’ll write here the code listing for the KEYPAD.h & KEYPAD_cfg.c files.
KEYPAD.h File
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 |
#define HAL_GPIO_MODULE_ENABLED #include "stm32f1xx_hal.h" // The Number OF KeyPAD Units To Be Used In The Project #define KEYPAD_UNITS 1 #define ROWS 4 #define COLS 4 #define KEYS 16 #define KEY_PRESSED 1 #define KEY_RELEASED 0 typedef struct { GPIO_TypeDef * ROW_GPIO[ROWS]; uint16_t ROW_PIN[ROWS]; GPIO_TypeDef * COL_GPIO[COLS]; uint16_t COL_PIN[COLS]; }KEYPAD_CfgType; /*-----[ Prototypes For All Functions ]-----*/ void KEYPAD_Init(uint16_t au16_Instance, uint8_t* au8_KeyStates); void KEYPAD_Scan(uint16_t au16_Instance); |
If you’re willing to use multiple keypad matrices, just adjust that number definition (KEYPAD_UNITS).
KEYPAD Driver APIs
As you’ve seen in the KEYPAD.h file, the provided APIs do all the basic functionalities that you may need from a Keypad driver library. It initializes the required GPIO pins and reads the raw state of the buttons matrix at any instance of time. And it also gives you the main scanning logic in the function KEYPAD_Scan() that you can call periodically within your SysTick ISR or in a task if you’re using an RTOS or whatever.
The structure of the files and the provided APIs look very intuitive and self-explanatory to me. And the example LABs will put everything under test and you’ll see how and when to use each of these functions.
KEYPAD_cfg.c File
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
#include "KEYPAD.h" const KEYPAD_CfgType KEYPAD_CfgParam[KEYPAD_UNITS] = { // KeyPAD Unit 1 Configurations { /* ROWs Pins Info */ {GPIOA, GPIOA, GPIOA, GPIOA}, {GPIO_PIN_0, GPIO_PIN_1, GPIO_PIN_2, GPIO_PIN_3}, /* COLs Pins */ {GPIOA, GPIOA, GPIOA, GPIOA}, {GPIO_PIN_4, GPIO_PIN_5, GPIO_PIN_6, GPIO_PIN_7} } }; |
I’ll discuss those configuration parameters in the next section down below.
Available Configurations For ECUAL KEYPAD Driver
From the code above in KEYPAD.h & KEYPAD_cfg.c you can see that there are not many parameters at all in the configuration structure. The config structure will be used to assign the pin you want to the 4-row pins and the 4-column pins, and that’s all.
Typical Usage Application Example
Here is a typical usage application for this driver code in order to initialize a keypad matrix and read a couple of buttons to drive ON or OFF two output LEDs.
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 |
#include "main.h" #include "../ECUAL/KEYPAD/KEYPAD.h" #include "../ECUAL/LEDS/LEDS.h" #define LED_BLUE 0 #define LED_YELLOW 1 #define KEY_1 0 #define KEY_2 1 uint8_t gu8_KeyStatesArr[KEYS] = {0}; uint8_t SysTicks = 0; void SystemClock_Config(void); static void MX_GPIO_Init(void); void SysTick_CallBack(void); int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); LEDs_Init(); KEYPAD_Init(0, gu8_KeyStatesArr); while (1) { if(gu8_KeyStatesArr[KEY_1] == KEY_PRESSED) { LED_ON(LED_YELLOW); } else { LED_OFF(LED_YELLOW); } if(gu8_KeyStatesArr[KEY_2] == KEY_PRESSED) { LED_ON(LED_BLUE); } else { LED_OFF(LED_BLUE); } } } void SysTick_CallBack(void) { SysTicks++; if(SysTicks == 5) // Each 5msec { KEYPAD_Scan(0); SysTicks = 0; } } |
Keypad Scan Function
In the KEYPAD.c source code file, you’ll find the implementation of this function “KEYPAD_Scan()“. I highly recommend that you take the time to figure it out. This function is written in this way in order to be called asynchronously from the main application or the OS dispatcher if you have got a task for the keypad scanning. It does handle all the logical operations for scanning all buttons in that specific keypad unit in your application and writes out the buttons states to the buffer array that you’ve got to check in the main application.
And yes, it has to be called repeatedly at a steady rate as we’ll see in one of the LAB examples hereafter.
Keypad Buttons Read Example With LEDs – LAB
LAB Number | 35 |
LAB Title | STM32 4×4 Keypad Buttons Read & LEDs Control |
- Set up a new project as usual with a system clock @ 72MHz or whatever your uC board supports
- Add the ECUAL / LEDS driver files to our project. As shown here
- Add the ECUAL / KEYPAD driver files to our project.
- Configure 2 LEDs instances in LEDS_cfg.c file
- Configure 1 Keypad instance in KEYPAD_cfg.c file
- Initialize the LEDs & Keypad in the main application, read the buttons, turn ON or OFF the LEDs correspondingly.
Keypad buttons functions:
Key 1 | Switch ON The Yellow LED | ||
Key 2 | Switch ON The Blue LED |
And now, let’s build this system step-by-step
Step1: Open CubeMX & Create New Project
Step2: Choose The Target MCU & Double-Click Its Name
STM32F103C8 (the one I’ll be using) or any other STM32 part you’ve got
Step3: Go To The RCC Clock Configuration
Step4: Set The System Clock To Be 72MHz or whatever your uC board supports
Step5: Generate The Initialization Code & Open The Project In Your IDE
Step6: Add the ECUAL LEDS driver files to your project
Follow This Tutorial which shows you How To Add Any ECUAL Driver To An STM32 Project step-by-step.
Step7: Add the ECUAL KEYPAD driver files to your project, same as always
Now, we can start developing our application in the main.c source file.
Here is The Application Code For This LAB (main.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 |
#include "main.h" #include "../ECUAL/KEYPAD/KEYPAD.h" #include "../ECUAL/LEDS/LEDS.h" #define LED_BLUE 0 #define LED_YELLOW 1 #define KEY_1 0 #define KEY_2 1 uint8_t gu8_KeyStatesArr[KEYS] = {0}; uint8_t SysTicks = 0; void SystemClock_Config(void); static void MX_GPIO_Init(void); void SysTick_CallBack(void); int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); LEDs_Init(); KEYPAD_Init(0, gu8_KeyStatesArr); while (1) { if(gu8_KeyStatesArr[KEY_1] == KEY_PRESSED) { LED_ON(LED_YELLOW); } else { LED_OFF(LED_YELLOW); } if(gu8_KeyStatesArr[KEY_2] == KEY_PRESSED) { LED_ON(LED_BLUE); } else { LED_OFF(LED_BLUE); } } } void SysTick_CallBack(void) { SysTicks++; if(SysTicks == 5) // Each 5msec { KEYPAD_Scan(0); SysTicks = 0; } } |
Note that the KEYPAD_Scan function is being called from the SysTick callback function once each 5 SysTicks or once/5msec. This callback function needs to be called inside the ISR handler for the SysTick timer. The HAL_Init initializes the SysTick timer (tick time = 1msec) and the HAL_Delay uses it to work as well. So, let’s now open this file (stm32f1xx_it.c) and search for the SysTick handler and call our SysTick_Callback from there as shown below.
1 2 3 4 5 |
void SysTick_Handler(void) { HAL_IncTick(); SysTick_CallBack(); } |
Download The STM32 Keypad With LEDs LAB35
The Result For This LAB Testing (Video)
STM32 With Keypad & LCD Example – LAB
LAB Number | 36 |
LAB Title | STM32 With 4×4 Keypad And LCD 16×2 |
- Set up a new project as usual with a system clock @ 72MHz or whatever your uC board supports
- Add the ECUAL / LCD driver files to our project. As shown here
- Add the ECUAL / KEYPAD driver files to our project.
- Create a source folder name it util & Add the util files to our project.
- Configure 1 LCD instance in LCD_cfg.c file
- Configure 1 Keypad instance in KEYPAD_cfg.c file
- Initialize the LCD & Keypad in the main application, read the buttons, write to the LCD correspondingly.
Keypad buttons functions:
Keys [0 -> 9] | Writes “0->9” to the LCD | ||
Keys [ # and *] | Writes “#” and “*” to the LCD | ||
Key F1 | Right-Shift the entire LCD display | ||
Key F2 | Left-Shift the entire LCD display | ||
Key F4 | Clear the LCD screen |
And now, let’s build this system step-by-step
Step1: Open CubeMX & Create New Project
Step2: Choose The Target MCU & Double-Click Its Name
STM32F103C8 (the one I’ll be using) or any other STM32 part you’ve got
Step3: Go To The RCC Clock Configuration
Step4: Set The System Clock To Be 72MHz or whatever your uC board supports
Step5: Generate The Initialization Code & Open The Project In Your IDE
Step6: Add the ECUAL LCD driver files to your project
Follow This Tutorial which shows you How To Add Any ECUAL Driver To An STM32 Project step-by-step.
Step7: Add the ECUAL KEYPAD driver files to your project, same as always
Step8: Add the util files to your project, as shown in the guide above
Now, we can start developing our application in the main.c source file.
Here is The Application Code For This LAB (main.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 |
#include "main.h" #include "../ECUAL/KEYPAD/KEYPAD.h" #include "../ECUAL/LCD16x2/LCD16x2.h" #define KEY_1 0 #define KEY_2 1 #define KEY_3 2 #define KEY_F1 3 #define KEY_4 4 #define KEY_5 5 #define KEY_6 6 #define KEY_F2 7 #define KEY_7 8 #define KEY_8 9 #define KEY_9 10 #define KEY_F3 11 #define KEY_A 12 #define KEY_0 13 #define KEY_H 14 #define KEY_F4 15 uint8_t gu8_KeyStatesArr[KEYS] = {0}; uint8_t SysTicks = 0; void SystemClock_Config(void); static void MX_GPIO_Init(void); void SysTick_CallBack(void); void Display_Handler(void); int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); KEYPAD_Init(0, gu8_KeyStatesArr); LCD_Init(); LCD_Clear(); LCD_Set_Cursor(1, 1); while (1) { Display_Handler(); HAL_Delay(10); } } void SysTick_CallBack(void) { SysTicks++; if(SysTicks == 5) // Each 5msec { KEYPAD_Scan(0); SysTicks = 0; } } void Display_Handler(void) { if(gu8_KeyStatesArr[KEY_1] == KEY_PRESSED) { LCD_Write_Char('1'); while(gu8_KeyStatesArr[KEY_1] == KEY_PRESSED); } if(gu8_KeyStatesArr[KEY_2] == KEY_PRESSED) { LCD_Write_Char('2'); while(gu8_KeyStatesArr[KEY_2] == KEY_PRESSED); } if(gu8_KeyStatesArr[KEY_3] == KEY_PRESSED) { LCD_Write_Char('3'); while(gu8_KeyStatesArr[KEY_3] == KEY_PRESSED); } if(gu8_KeyStatesArr[KEY_4] == KEY_PRESSED) { LCD_Write_Char('4'); while(gu8_KeyStatesArr[KEY_4] == KEY_PRESSED); } if(gu8_KeyStatesArr[KEY_5] == KEY_PRESSED) { LCD_Write_Char('5'); while(gu8_KeyStatesArr[KEY_5] == KEY_PRESSED); } if(gu8_KeyStatesArr[KEY_6] == KEY_PRESSED) { LCD_Write_Char('6'); while(gu8_KeyStatesArr[KEY_6] == KEY_PRESSED); } if(gu8_KeyStatesArr[KEY_7] == KEY_PRESSED) { LCD_Write_Char('7'); while(gu8_KeyStatesArr[KEY_7] == KEY_PRESSED); } if(gu8_KeyStatesArr[KEY_8] == KEY_PRESSED) { LCD_Write_Char('8'); while(gu8_KeyStatesArr[KEY_8] == KEY_PRESSED); } if(gu8_KeyStatesArr[KEY_9] == KEY_PRESSED) { LCD_Write_Char('9'); while(gu8_KeyStatesArr[KEY_9] == KEY_PRESSED); } if(gu8_KeyStatesArr[KEY_0] == KEY_PRESSED) { LCD_Write_Char('0'); while(gu8_KeyStatesArr[KEY_0] == KEY_PRESSED); } if(gu8_KeyStatesArr[KEY_A] == KEY_PRESSED) { LCD_Write_Char('*'); while(gu8_KeyStatesArr[KEY_A] == KEY_PRESSED); } if(gu8_KeyStatesArr[KEY_H] == KEY_PRESSED) { LCD_Write_Char('#'); while(gu8_KeyStatesArr[KEY_H] == KEY_PRESSED); } if(gu8_KeyStatesArr[KEY_F1] == KEY_PRESSED) { LCD_SR(); while(gu8_KeyStatesArr[KEY_F1] == KEY_PRESSED); } if(gu8_KeyStatesArr[KEY_F2] == KEY_PRESSED) { LCD_SL(); while(gu8_KeyStatesArr[KEY_F2] == KEY_PRESSED); } if(gu8_KeyStatesArr[KEY_F4] == KEY_PRESSED) { LCD_Clear(); while(gu8_KeyStatesArr[KEY_F4] == KEY_PRESSED); } } |
Note that the KEYPAD_Scan function is being called from the SysTick callback function once each 5 SysTicks or once/5msec. This callback function needs to be called inside the ISR handler for the SysTick timer. The HAL_Init initializes the SysTick timer (tick time = 1msec) and the HAL_Delay uses it to work as well. So, let’s now open this file (stm32f1xx_it.c) and search for the SysTick handler and call our SysTick_Callback from there as shown below.
1 2 3 4 5 |
void SysTick_Handler(void) { HAL_IncTick(); SysTick_CallBack(); } |
Download The STM32 Keypad With LCD LAB36
The Result For This LAB Testing (Video)
Some Interesting Tests For Keypad Driver Code
I’ve also done some final tests that I’d like to share with you. As we’re going to build a very basic operating system in the future and also make some RTOS applications, it’s important to know the execution time of the keypad scanning routine and also have a rough estimate for the response time given that the KEYPAD_Scan() function is called once/5msec.
1- Response Time
The time it takes the system to respond to a keypad button press or release event is said to be the response time. And this is what I’ve decided to test out, using 2 channels of my DSO and measure the timing between button & LED signals.
The keypad scan cycle is set to once/5msec as you’ve seen in the code. Hence, you can press/release any button almost anywhere in time between each consecutive scan cycles, therefore, the expected response time can be anywhere from ZERO sec up to 5msec at maximum with an equal probability distribution.
Here in this test, I did find the readings to be exactly as expected ranging from 0 up to 5msec. And you can improve the response time by shortening the measurement cycle, maybe make it 1msec instead of 5msec. And guarantee that the response time will be always shorter than 1msec.
2- Keypad Scan Execution Time & CPU Load
I’ve used a GPIO pin configured in fast mode push-pull driver output for fast edges switching on the output pin and driven it high before calling the KEYPAD_Scan function and bring it low when it returns. And now for the interesting part!
If you’ve taken the time to check the source code for my KEYPAD driver, you’ll notice that the scanning logic skips over any row that has no button pressed which is obviously a logical thing to do. But the result of that is variance in execution time depending on the current situation,
In other words, when no button is pressed, the execution time is found to be minimal which is obvious. Because the scanning logic will skip over to the end of the function. However, when one or more buttons in the same row is pressed, the execution time increases. It doesn’t increase if more than one button are pressed while they are in the same row. This is also an obvious fact if you’ve checked how the code works.
When 2 buttons of different rows are pressed, the execution time increases a little bit, and it gets to maximum when 4 buttons, one of each row are pressed at the same time.
Note that, if you’re designing a digital lock password system or watch timer or whatever, the user is not expected to click more than one button at the same time. But I did want to test that out and have this piece of info ready if we needed it in the future or just for the sake of curiosity!
Condition | The Measured Execution Time | CPU Load |
No Button is pressed at all | 24µs | 0.48% |
One or all buttons of the 1st row are pressed at once | 42µs | 0.84% |
One or all buttons of the first 2 rows are pressed at once | 66µs | 1.3% |
One or all buttons of the first 3 row are pressed at once | 88µs | 1.76% |
One button of each row or all keypad 16 buttons are pressed at the same time | 110µs | 2.2% |
Generally speaking, you should only care for the first test result at it’s going to be the case for almost any keypad-based project. And it looks ok to me in the meantime we don’t need further optimizations but you can, of course, make it run much quicker as I can clearly see some repeated logic pieces in my code that maybe get reduced in one way or another.
3- Keypad Button Bouncing
I did also notice on the DSO signal that the keypad buttons are bouncing just like any other mechanical switchs. But there is some sort of inherent immunity in our system against this because of the repetitive scanning cycle for the keypad each 5msec, it turns out to be de-coupling the input bouncing from appearing at the LED output almost perfectly.
However and if you’re using a shorter scanning cycle with a fast reaction time to a key press event, the system will be more likely to falsely detect a button press/release event. And it becomes a necessity to implement a debouncing technique. And I’d prefer the one I did previously demonstrate in this article. By including MATH/FIR there you’ll find a simple digital filter that can be used with the keypad button states array to knock-down any high-frequency artifacts caused by the key bouncing.
All in all, that’s it for this tutorial! I’d like to revisit this topic again in the future after I collect some questions, suggestions, and corrections from many readers.
Did you find this helpful? If yes, please consider supporting this work and sharing these tutorials!
Stay tuned for the upcoming tutorials and don’t forget to SHARE these tutorials. And consider SUPPORTING this work to keep publishing free content just like this!
Previous Tutorial | Tutorial 35 | Next Tutorial |