Skip to main content

STM32 Matrix Keypad Interface

Introduction

Matrix keypads are an efficient way to add multiple button inputs to your microcontroller projects while using a minimal number of I/O pins. Instead of connecting each button to a separate input pin, matrix keypads arrange buttons in rows and columns, significantly reducing the required pin count. For example, a 4×4 keypad with 16 buttons requires only 8 pins (4 for rows and 4 for columns) instead of 16 individual pins.

In this tutorial, we'll learn how to interface a matrix keypad with an STM32 microcontroller, read key presses, and implement debouncing techniques to ensure reliable input detection.

How Matrix Keypads Work

A matrix keypad consists of buttons arranged in rows and columns. Each button acts as a switch that connects a row line to a column line when pressed. By scanning the rows and columns, we can determine which button is pressed.

Hardware Requirements

To follow this tutorial, you'll need:

  1. An STM32 development board (STM32F4 Discovery, Nucleo, or similar)
  2. A 4×4 matrix keypad (12 or 16 buttons)
  3. Jumper wires
  4. Optional: Breadboard for easy connections

Hardware Connection

Connect your matrix keypad to the STM32 board as follows:

  1. Connect the row pins of the keypad to GPIO pins configured as outputs (PA0-PA3)
  2. Connect the column pins of the keypad to GPIO pins configured as inputs with pull-up resistors (PA4-PA7)

Software Implementation

Let's implement a complete example for reading a 4×4 matrix keypad with an STM32 microcontroller using the STM32 HAL library.

Step 1: Pin Configuration

First, we'll define the pins for our rows and columns:

c
// Row pins (outputs)
#define ROW_1_PIN GPIO_PIN_0
#define ROW_2_PIN GPIO_PIN_1
#define ROW_3_PIN GPIO_PIN_2
#define ROW_4_PIN GPIO_PIN_3
#define ROW_PORT GPIOA

// Column pins (inputs)
#define COL_1_PIN GPIO_PIN_4
#define COL_2_PIN GPIO_PIN_5
#define COL_3_PIN GPIO_PIN_6
#define COL_4_PIN GPIO_PIN_7
#define COL_PORT GPIOA

Step 2: Define Keypad Layout

Next, let's define the layout of our keypad:

c
const char keyMap[4][4] = {
{'1', '2', '3', 'A'},
{'4', '5', '6', 'B'},
{'7', '8', '9', 'C'},
{'*', '0', '#', 'D'}
};

// Row and column pin arrays for easier iteration
uint16_t rowPins[4] = {ROW_1_PIN, ROW_2_PIN, ROW_3_PIN, ROW_4_PIN};
uint16_t colPins[4] = {COL_1_PIN, COL_2_PIN, COL_3_PIN, COL_4_PIN};

Step 3: Initialize GPIO Pins

Now, let's initialize the GPIO pins for our keypad:

c
void KeyPad_Init(void)
{
// Enable clock for GPIOA
__HAL_RCC_GPIOA_CLK_ENABLE();

GPIO_InitTypeDef GPIO_InitStruct = {0};

// Configure row pins as outputs
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;

GPIO_InitStruct.Pin = ROW_1_PIN | ROW_2_PIN | ROW_3_PIN | ROW_4_PIN;
HAL_GPIO_Init(ROW_PORT, &GPIO_InitStruct);

// Set all rows high initially
HAL_GPIO_WritePin(ROW_PORT, ROW_1_PIN | ROW_2_PIN | ROW_3_PIN | ROW_4_PIN, GPIO_PIN_SET);

// Configure column pins as inputs with pull-up resistors
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_PULLUP;

GPIO_InitStruct.Pin = COL_1_PIN | COL_2_PIN | COL_3_PIN | COL_4_PIN;
HAL_GPIO_Init(COL_PORT, &GPIO_InitStruct);
}

Step 4: Read the Keypad

Now we'll implement the scanning algorithm to detect keypresses:

c
char KeyPad_ReadKey(void)
{
char key = 0;

// Scan each row
for (int row = 0; row < 4; row++)
{
// Set the current row to LOW (active)
HAL_GPIO_WritePin(ROW_PORT, rowPins[row], GPIO_PIN_RESET);

// Add a small delay to stabilize
HAL_Delay(1);

// Check each column
for (int col = 0; col < 4; col++)
{
// If a key is pressed, the column will read LOW
if (HAL_GPIO_ReadPin(COL_PORT, colPins[col]) == GPIO_PIN_RESET)
{
// Debounce: wait for key to stabilize
HAL_Delay(20);

// Check again to confirm keypress
if (HAL_GPIO_ReadPin(COL_PORT, colPins[col]) == GPIO_PIN_RESET)
{
key = keyMap[row][col];

// Wait for key release
while (HAL_GPIO_ReadPin(COL_PORT, colPins[col]) == GPIO_PIN_RESET) {}

// Add a small delay after release
HAL_Delay(20);
}
}
}

// Set the current row back to HIGH (inactive)
HAL_GPIO_WritePin(ROW_PORT, rowPins[row], GPIO_PIN_SET);
}

return key;
}

Step 5: Main Program

Let's tie everything together in a simple main program:

c
#include "stm32f4xx_hal.h"
#include <stdio.h>

// Include previous keypad code here...

void SystemClock_Config(void);

int main(void)
{
HAL_Init();
SystemClock_Config();

// Initialize keypad
KeyPad_Init();

// Buffer for key presses
char lastKey = 0;

while (1)
{
// Read keypad
char key = KeyPad_ReadKey();

// Process key press if a key was pressed
if (key != 0 && key != lastKey)
{
lastKey = key;

// Process the key (in a real application, you would do something with the key)
// For example, you might send it over UART, store it in a buffer, etc.

// For debugging, print via SWO or similar
printf("Key pressed: %c
", key);
}
else if (key == 0)
{
lastKey = 0;
}

// A short delay to prevent excessive CPU usage
HAL_Delay(10);
}
}

Advanced Implementation: Interrupt-Based Keypad Scanning

The polling-based approach above works well for simple applications, but consumes CPU time. For more efficient operation, we can use an interrupt-based approach:

c
// Global variables
volatile uint8_t currentRow = 0;
volatile char detectedKey = 0;
volatile uint8_t keypadScanning = 0;

// Timer callback for keypad scanning
void KeyPad_TimerCallback(void)
{
if (!keypadScanning) return;

// Set all rows high
HAL_GPIO_WritePin(ROW_PORT, ROW_1_PIN | ROW_2_PIN | ROW_3_PIN | ROW_4_PIN, GPIO_PIN_SET);

// Set current row low
HAL_GPIO_WritePin(ROW_PORT, rowPins[currentRow], GPIO_PIN_RESET);

// Check columns
for (int col = 0; col < 4; col++)
{
if (HAL_GPIO_ReadPin(COL_PORT, colPins[col]) == GPIO_PIN_RESET)
{
detectedKey = keyMap[currentRow][col];
}
}

// Move to next row
currentRow = (currentRow + 1) % 4;
}

// Start keypad scanning
void KeyPad_StartScanning(void)
{
keypadScanning = 1;
currentRow = 0;
detectedKey = 0;

// In a real application, you would configure a timer to call KeyPad_TimerCallback
// periodically, e.g., every 10ms
}

// Get last detected key and reset
char KeyPad_GetKey(void)
{
char key = detectedKey;
detectedKey = 0;
return key;
}

Practical Example: Simple Calculator

Here's a practical example implementing a simple calculator using the keypad:

c
#include "stm32f4xx_hal.h"
#include <stdio.h>
#include <stdlib.h>

// Include keypad code here...

// Calculator state
typedef enum {
STATE_FIRST_OPERAND,
STATE_OPERATOR,
STATE_SECOND_OPERAND,
STATE_RESULT
} CalcState;

int main(void)
{
HAL_Init();
SystemClock_Config();

// Initialize keypad
KeyPad_Init();

// Calculator variables
CalcState state = STATE_FIRST_OPERAND;
int firstOperand = 0;
int secondOperand = 0;
char operator = 0;
int result = 0;

while (1)
{
char key = KeyPad_ReadKey();

if (key != 0)
{
// Process the key based on calculator state
switch (state)
{
case STATE_FIRST_OPERAND:
if (key >= '0' && key <= '9')
{
firstOperand = firstOperand * 10 + (key - '0');
printf("First operand: %d
", firstOperand);
}
else if (key == '+' || key == '-' || key == '*' || key == '/')
{
operator = key;
state = STATE_OPERATOR;
printf("Operator: %c
", operator);
}
break;

case STATE_OPERATOR:
if (key >= '0' && key <= '9')
{
secondOperand = key - '0';
state = STATE_SECOND_OPERAND;
printf("Second operand: %d
", secondOperand);
}
break;

case STATE_SECOND_OPERAND:
if (key >= '0' && key <= '9')
{
secondOperand = secondOperand * 10 + (key - '0');
printf("Second operand: %d
", secondOperand);
}
else if (key == '=')
{
state = STATE_RESULT;

// Calculate result
switch (operator)
{
case '+': result = firstOperand + secondOperand; break;
case '-': result = firstOperand - secondOperand; break;
case '*': result = firstOperand * secondOperand; break;
case '/':
if (secondOperand != 0)
result = firstOperand / secondOperand;
else
printf("Error: Division by zero
");
break;
}

printf("Result: %d
", result);
}
break;

case STATE_RESULT:
if (key == 'C')
{
// Clear and reset
state = STATE_FIRST_OPERAND;
firstOperand = 0;
secondOperand = 0;
operator = 0;
result = 0;
printf("Calculator reset
");
}
break;
}

// Wait for key release
while (KeyPad_ReadKey() != 0) {
HAL_Delay(10);
}
}

HAL_Delay(10);
}
}

Common Issues and Troubleshooting

Keypad Bouncing

Key bouncing occurs when a single keypress is detected as multiple presses due to mechanical contacts bouncing. Our code includes basic debouncing, but you might need to adjust the delay values based on your specific keypad's characteristics.

Multiple Key Presses

The basic implementation doesn't handle multiple simultaneous key presses. For most applications, this is acceptable, but if you need to detect multiple keys, you'll need a more sophisticated algorithm.

Scanning Speed

If your application is performing other time-critical tasks, the delays in our keypad scanning might be too long. Consider using the interrupt-based approach and adjusting the scanning frequency.

Summary

In this tutorial, we've learned:

  1. How matrix keypads work and their advantage in reducing I/O pin requirements
  2. How to connect a matrix keypad to an STM32 microcontroller
  3. How to initialize GPIO pins for rows and columns
  4. How to implement a scanning algorithm to detect keypresses
  5. Advanced techniques like interrupt-based scanning
  6. A practical example of using a keypad for a simple calculator

Matrix keypads are versatile input devices that can be used in a variety of applications, from security systems to simple user interfaces. With the knowledge from this tutorial, you can now incorporate keypads into your STM32 projects!

Additional Exercises

  1. Password Lock: Implement a simple password lock system using the keypad. Store a password and compare user input against it.
  2. Menu Navigation: Create a menu system that lets users navigate through options using the keypad.
  3. Data Entry Form: Design a simple data entry form where users can input numeric values.
  4. Game Controller: Use the keypad as a controller for a simple game (like Snake) displayed on an LCD.
  5. DTMF Tone Generator: Implement a DTMF tone generator using the keypad for input and a speaker for output.

Further Resources



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)