In this tutorial, we’ll explore how to create a button matrix using the ESP IoT Solution button component. This is particularly useful in applications requiring multiple button inputs, like keypads or control panels. We’ll use the ESP-IDF framework for programming and a Wokwi Membrane Keypad for hardware interaction.
Line by line Explanation
The provided source code for the ESP-IDF button matrix is a great example of how to handle multiple button inputs in an embedded system.
Let’s break it down into two key parts: initializing the button matrix and configuring button callbacks.
Initializing the Button Matrix
The initialization of the button matrix is done in the initialize_keypad
function. Here’s a step-by-step explanation:
-
Defining GPIO Pins:
int32_t row_gpio[4] = {14, 13, 12, 11}; int32_t col_gpio[4] = {10, 9, 46, 3};
- This code specifies the GPIO pins used for the keypad’s rows and columns. The ESP32 connects to the keypad through these GPIOs.
-
Button Configuration:
button_config_t cfg = { .type = BUTTON_TYPE_MATRIX, .long_press_time = 3000, // in ms .short_press_time = 500, // in ms ... };
- Here, a
button_config_t
struct is initialized, setting the button type to a matrix. It also defines the time durations for long and short presses.
- Here, a
-
Creating Buttons:
for (int i = 0; i < 4; i++) { cfg.matrix_button_config.row_gpio_num = row_gpio[i]; for (int j = 0; j < 4; j++) { cfg.matrix_button_config.col_gpio_num = col_gpio[j]; g_btns[i * 4 + j] = iot_button_create(&cfg); ... } }
- This nested loop iterates over rows and columns, creating buttons using
iot_button_create
and storing them ing_btns
.
- This nested loop iterates over rows and columns, creating buttons using
Configuring Button Callbacks
Callbacks are functions called in response to various button events. Each button can have multiple callbacks for different events like press down, release, etc.
-
Registering Callbacks:
iot_button_register_cb(g_btns[i * 4 + j], BUTTON_PRESS_DOWN, button_press_down_cb, NULL);
- This line registers a callback for the “press down” event. Similar lines are used for other events like “press up”, “single click”, etc.
-
Callback Functions:
- Each callback function is defined to perform a specific action. For example,
button_press_down_cb
logs the button press down event.
static void button_press_down_cb(void *arg, void *data) { ESP_LOGI(TAG, "BTN%s: BUTTON_PRESS_DOWN", get_btn_index((button_handle_t)arg)); }
- This function uses
get_btn_index
to determine which button was pressed and logs the event.
- Each callback function is defined to perform a specific action. For example,
-
Handling Different Press Types:
- The code includes various callback functions for handling single clicks, double clicks, long presses, etc. Each callback function is tailored to its specific event.
Testing in Wokwi Simulator
To test the button matrix with an ESP32 in the Wokwi Simulator, follow these steps.
Connection of the Keypad and ESP32
Connect the rows and columns of the keypad to the ESP32 using the following ASCII diagram:
Keypad ESP32
R1 ----> GPIO 14
R2 ----> GPIO 13
R3 ----> GPIO 12
R4 ----> GPIO 11
C1 ----> GPIO 10
C2 ----> GPIO 9
C3 ----> GPIO 46
C4 ----> GPIO 3
Serial Output Interpretation
When a button is pressed, the ESP32 will output different statuses to the serial monitor, indicating the type of press:
BUTTON_LONG_PRESS_HOLD
is printed repeatedly as long as a button is held down.BUTTON_PRESS_UP
appears when the button is released.BUTTON_PRESS_DOWN
is shown when a button is initially pressed.- For single and double clicks,
BUTTON_SINGLE_CLICK
andBUTTON_DOUBLE_CLICK
messages are displayed. BUTTON_PRESS_REPEAT_DONE
is printed after a single or double click is recognized.
Example output in the serial console:
(88295) BUTTON TEST: BTN6: BUTTON_LONG_PRESS_HOLD
(88315) BUTTON TEST: BTN6: BUTTON_LONG_PRESS_HOLD
(88335) BUTTON TEST: BTN6: BUTTON_LONG_PRESS_HOLD
(88355) BUTTON TEST: BTN6: BUTTON_PRESS_UP
(89685) BUTTON TEST: BTN9: BUTTON_PRESS_DOWN
(90765) BUTTON TEST: BTN9: BUTTON_PRESS_UP
(91275) BUTTON TEST: BTN9: BUTTON_SINGLE_CLICK
(91275) BUTTON TEST: BTN9: BUTTON_PRESS_REPEAT_DONE
(95185) BUTTON TEST: BTN8: BUTTON_PRESS_DOWN
(97665) BUTTON TEST: BTN8: BUTTON_PRESS_UP
(98175) BUTTON TEST: BTN8: BUTTON_SINGLE_CLICK
(98175) BUTTON TEST: BTN8: BUTTON_PRESS_REPEAT_DONE
(100815) BUTTON TEST: BTN1: BUTTON_PRESS_DOWN
(101935) BUTTON TEST: BTN1: BUTTON_PRESS_UP
(102435) BUTTON TEST: BTN1: BUTTON_SINGLE_CLICK
(102435) BUTTON TEST: BTN1: BUTTON_PRESS_REPEAT_DONE
This setup allows you to test various button interactions and observe their corresponding outputs in the Wokwi Simulator.
Full source code
The following is the complete source code of driving a matrix keypad using the button component.
#include "esp_log.h"
#include "iot_button.h"
static const char *TAG = "BUTTON TEST";
#define BUTTON_NUM 16
static button_handle_t g_btns[BUTTON_NUM] = {0};
#define NUMBER_OF_ROWS 4
#define NUMBER_OF_COLUMNS 4
#define LONG_PRESS_DURATION_MS 1500
#define SHORT_PRESS_DURATION_MS 500
const char* keys[NUMBER_OF_ROWS][NUMBER_OF_COLUMNS] = {
{"1", "2", "3", "A"},
{"4", "5", "6", "B"},
{"7", "8", "9", "C"},
{"*", "0", "#", "D"}
};
static const char* get_btn_index(button_handle_t btn)
{
for (size_t i = 0; i < BUTTON_NUM; i++) {
if (btn == g_btns[i]) {
// Calculate row and column from the linear index
size_t row = i / NUMBER_OF_ROWS; // Integer division to get the row
size_t col = i % NUMBER_OF_COLUMNS; // Modulo operation to get the column
return keys[row][col];
}
}
return NULL; // Return NULL if the button is not found
}
static void button_press_down_cb(void *arg, void *data)
{
ESP_LOGI(TAG, "BTN%s: BUTTON_PRESS_DOWN", get_btn_index((button_handle_t)arg));
}
static void button_press_up_cb(void *arg, void *data)
{
ESP_LOGI(TAG, "BTN%s: BUTTON_PRESS_UP[%d]", get_btn_index((button_handle_t)arg), iot_button_get_ticks_time((button_handle_t)arg));
}
static void button_press_repeat_cb(void *arg, void *data)
{
ESP_LOGI(TAG, "BTN%s: BUTTON_PRESS_REPEAT[%d]", get_btn_index((button_handle_t)arg), iot_button_get_repeat((button_handle_t)arg));
}
static void button_single_click_cb(void *arg, void *data)
{
ESP_LOGI(TAG, "BTN%s: BUTTON_SINGLE_CLICK", get_btn_index((button_handle_t)arg));
}
static void button_double_click_cb(void *arg, void *data)
{
ESP_LOGI(TAG, "BTN%s: BUTTON_DOUBLE_CLICK", get_btn_index((button_handle_t)arg));
}
static void button_long_press_start_cb(void *arg, void *data)
{
ESP_LOGI(TAG, "BTN%s: BUTTON_LONG_PRESS_START", get_btn_index((button_handle_t)arg));
}
static void button_long_press_hold_cb(void *arg, void *data)
{
ESP_LOGI(TAG, "BTN%s: BUTTON_LONG_PRESS_HOLD[%d],count is [%d]", get_btn_index((button_handle_t)arg), iot_button_get_ticks_time((button_handle_t)arg), iot_button_get_long_press_hold_cnt((button_handle_t)arg));
}
static void button_press_repeat_done_cb(void *arg, void *data)
{
ESP_LOGI(TAG, "BTN%s: BUTTON_PRESS_REPEAT_DONE[%d]", get_btn_index((button_handle_t)arg), iot_button_get_repeat((button_handle_t)arg));
}
void initialize_keypad() {
// Define the GPIO pins for the keypad rows and columns
int32_t row_gpio[NUMBER_OF_ROWS] = {14, 13, 12, 11}; // Example GPIOs for rows
int32_t col_gpio[NUMBER_OF_COLUMNS] = {10, 9, 46, 3}; // Example GPIOs for columns
button_config_t cfg = {
.type = BUTTON_TYPE_MATRIX,
.long_press_time = LONG_PRESS_DURATION_MS,
.short_press_time = SHORT_PRESS_DURATION_MS,
.matrix_button_config = {
.row_gpio_num = 0,
.col_gpio_num = 0,
}
};
for (int i = 0; i < NUMBER_OF_ROWS; i++) {
cfg.matrix_button_config.row_gpio_num = row_gpio[i];
for (int j = 0; j < NUMBER_OF_COLUMNS; j++) {
cfg.matrix_button_config.col_gpio_num = col_gpio[j];
g_btns[i * NUMBER_OF_ROWS + j] = iot_button_create(&cfg);
iot_button_register_cb(g_btns[i * NUMBER_OF_ROWS + j], BUTTON_PRESS_DOWN, button_press_down_cb, NULL);
iot_button_register_cb(g_btns[i * NUMBER_OF_ROWS + j], BUTTON_PRESS_UP, button_press_up_cb, NULL);
iot_button_register_cb(g_btns[i * NUMBER_OF_ROWS + j], BUTTON_PRESS_REPEAT, button_press_repeat_cb, NULL);
iot_button_register_cb(g_btns[i * NUMBER_OF_ROWS + j], BUTTON_SINGLE_CLICK, button_single_click_cb, NULL);
iot_button_register_cb(g_btns[i * NUMBER_OF_ROWS + j], BUTTON_DOUBLE_CLICK, button_double_click_cb, NULL);
iot_button_register_cb(g_btns[i * NUMBER_OF_ROWS + j], BUTTON_LONG_PRESS_START, button_long_press_start_cb, NULL);
iot_button_register_cb(g_btns[i * NUMBER_OF_ROWS + j], BUTTON_LONG_PRESS_HOLD, button_long_press_hold_cb, NULL);
iot_button_register_cb(g_btns[i * NUMBER_OF_ROWS + j], BUTTON_PRESS_REPEAT_DONE, button_press_repeat_done_cb, NULL);
}
}
}
void app_main(void) {
initialize_keypad();
}