【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing @163.com】
有一种输入是数字输入,在现实生活中用的特别多。如果还需要计算的话,一般要加上+-*/,这样就更好了。当然除了0~9,以及四位运算,剩下就是点和等于,这样基本上就把常用的计算都包含在内了。不过,如果16个按键全部用gpio表示,则需要16个gpio,看上去有一点浪费。因此,人们想出了薄膜键盘的方法,也就是矩阵键盘,一般按下去的时候,对应的行和列就会拉低,通过这个办法就可以知道哪个按键被按下去了。

1、8个输出
如果是4*4的薄膜键盘,一般就是4+4=8个输出信号。推广一下,如果是3*4,也就是12个按键的键盘,就是3+4=7个输出信号。
2、准备8个gpio
前面谈到了需要8个gpio,所以只需要找到8个引脚就可以了。不失一般性,不妨找到gpio12~gpio19这8个引脚。
3、连线
矩阵键盘本身没有电源线和地线,这和之前的传感器不太一样。所以,我们需要做的就是就是把esp32对应的引脚和矩阵键盘连接起来即可。一般连接的时候,采用公对母的连线,公口连接键盘,母口连接esp32。
4、开始ai编程
连接好线路之后,就可以准备ai编程了。比如告诉ai,用esp32的8个pin脚连接薄膜键盘,8个引脚是pin12到pin19。没什么大问题的话,ai就可以帮助我们生成的答案。
#include <stdio.h>
#include "driver/gpio.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "esp_rom_sys.h"
static const char *TAG = "KEYPAD_SCAN";
// ==================== Configuration ====================
#define ROWS 4
#define COLS 4
// GPIO pins for rows (outputs)
const gpio_num_t row_pins[ROWS] = {12, 13, 14, 15};
// GPIO pins for columns (inputs with pull-up)
const gpio_num_t col_pins[COLS] = {16, 17, 18, 19};
// Key mapping table (4x4 layout)
const char key_map[ROWS][COLS] = {
{'1', '2', '3', 'A'},
{'4', '5', '6', 'B'},
{'7', '8', '9', 'C'},
{'*', '0', '#', 'D'}
};
// Debounce configuration
#define DEBOUNCE_THRESHOLD 3 // Number of consistent reads required
#define SCAN_INTERVAL_MS 10 // Scan interval in milliseconds
#define STABLE_DELAY_US 50 // Delay for signal stabilization
// ========================================================
// Debounce state variables
static uint8_t last_stable_state[ROWS][COLS] = {0}; // Last debounced key state (0=pressed, 1=released)
static uint8_t debounce_counter[ROWS][COLS] = {0}; // Counter for debounce stability
// Key event tracking
static char last_reported_key = 0; // Last key that was reported
static uint32_t last_key_press_time = 0; // Timestamp of last key press
/**
* @brief Initialize GPIO pins for matrix keypad
*
* Row pins: Configured as push-pull outputs, initial state HIGH
* Column pins: Configured as inputs with internal pull-up resistors
*/
void keypad_gpio_init(void)
{
// Initialize row pins as outputs, default HIGH
for (int i = 0; i < ROWS; i++) {
gpio_config_t io_conf = {
.pin_bit_mask = (1ULL << row_pins[i]),
.mode = GPIO_MODE_OUTPUT,
.pull_up_en = GPIO_PULLUP_DISABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE,
};
gpio_config(&io_conf);
gpio_set_level(row_pins[i], 1); // Set initial state HIGH
}
// Initialize column pins as inputs with internal pull-up
for (int i = 0; i < COLS; i++) {
gpio_config_t io_conf = {
.pin_bit_mask = (1ULL << col_pins[i]),
.mode = GPIO_MODE_INPUT,
.pull_up_en = GPIO_PULLUP_ENABLE, // Enable internal pull-up
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE,
};
gpio_config(&io_conf);
}
ESP_LOGI(TAG, "GPIO initialized: %d rows, %d columns", ROWS, COLS);
}
/**
* @brief Scan the matrix keypad and return the currently pressed key
*
* Scanning principle:
* 1. Drive each row LOW one at a time
* 2. Read all column pins
* 3. If a column reads LOW, the key at that row/column intersection is pressed
* 4. Uses debouncing to filter out mechanical bouncing
*
* @return char Pressed key character, or 0 if no key is pressed
*/
char keypad_scan(void)
{
// Iterate through each row
for (int row = 0; row < ROWS; row++) {
// Drive current row LOW
gpio_set_level(row_pins[row], 0);
// Small delay to allow signal to stabilize
esp_rom_delay_us(STABLE_DELAY_US);
// Read all columns for this row
for (int col = 0; col < COLS; col++) {
// Read column state (0 = pressed, 1 = released due to pull-up)
int current_state = gpio_get_level(col_pins[col]);
// Debouncing logic: require multiple consistent reads
if (current_state != last_stable_state[row][col]) {
// State changed, increment counter
debounce_counter[row][col]++;
if (debounce_counter[row][col] >= DEBOUNCE_THRESHOLD) {
// State is stable, update last stable state
last_stable_state[row][col] = current_state;
debounce_counter[row][col] = 0;
// Log state change for debugging
if (current_state == 0) {
ESP_LOGD(TAG, "Key %c stabilized (pressed)", key_map[row][col]);
} else {
ESP_LOGD(TAG, "Key %c stabilized (released)", key_map[row][col]);
}
}
} else {
// State consistent, reset counter
debounce_counter[row][col] = 0;
}
// Check if key is pressed (active LOW)
if (last_stable_state[row][col] == 0) {
// Restore row HIGH before returning
gpio_set_level(row_pins[row], 1);
return key_map[row][col];
}
}
// Restore row HIGH before moving to next row
gpio_set_level(row_pins[row], 1);
}
return 0; // No key pressed
}
/**
* @brief Task for continuous keypad scanning with event detection
*
* This task continuously scans the keypad and reports key press/release events
* It only reports when key state changes to avoid spamming the log
*/
void keypad_scan_task(void *pvParameters)
{
char current_key;
TickType_t last_scan_time = 0;
ESP_LOGI(TAG, "Keypad scanning task started");
while (1) {
// Scan for currently pressed key
current_key = keypad_scan();
// Report events only when state changes
if (current_key != last_reported_key) {
if (current_key != 0) {
// Key pressed event
ESP_LOGI(TAG, "Key pressed: %c", current_key);
last_key_press_time = xTaskGetTickCount();
} else if (last_reported_key != 0) {
// Key released event
ESP_LOGI(TAG, "Key released: %c", last_reported_key);
}
last_reported_key = current_key;
}
// Maintain consistent scan interval
vTaskDelay(pdMS_TO_TICKS(SCAN_INTERVAL_MS));
}
}
/**
* @brief Alternative: Simple blocking scan for single key detection
*
* This function blocks until a key is pressed and returns it
* Useful for menu navigation or PIN entry applications
*
* @return char The key that was pressed
*/
char keypad_wait_for_key(void)
{
char key;
while (1) {
key = keypad_scan();
if (key != 0) {
// Wait for key release to avoid multiple detections
while (keypad_scan() != 0) {
vTaskDelay(pdMS_TO_TICKS(SCAN_INTERVAL_MS));
}
return key;
}
vTaskDelay(pdMS_TO_TICKS(SCAN_INTERVAL_MS));
}
}
/**
* @brief Get the current key state without event logging
*
* @return char Currently pressed key, or 0 if none
*/
char keypad_get_key(void)
{
return keypad_scan();
}
/**
* @brief Check if a specific key is currently pressed
*
* @param key The key character to check
* @return true if the key is pressed, false otherwise
*/
bool keypad_is_key_pressed(char key)
{
char current_key = keypad_scan();
return (current_key == key);
}
/**
* @brief Reset debounce states (useful after deep sleep)
*/
void keypad_reset_debounce(void)
{
for (int i = 0; i < ROWS; i++) {
for (int j = 0; j < COLS; j++) {
last_stable_state[i][j] = 1; // Default to released state
debounce_counter[i][j] = 0;
}
}
last_reported_key = 0;
ESP_LOGI(TAG, "Debounce states reset");
}
/**
* @brief Main application entry point
*/
void app_main(void)
{
// Initialize GPIO pins
keypad_gpio_init();
// Reset debounce states
keypad_reset_debounce();
ESP_LOGI(TAG, "4x4 Matrix Keypad Scanner Started");
ESP_LOGI(TAG, "Scan interval: %d ms, Debounce threshold: %d",
SCAN_INTERVAL_MS, DEBOUNCE_THRESHOLD);
// Option 1: Continuous scanning with event detection (recommended)
xTaskCreate(keypad_scan_task, "keypad_scan", 4096, NULL, 5, NULL);
// Option 2: Simple blocking scan (uncomment to use)
while (1) {
char key = keypad_wait_for_key();
ESP_LOGI(TAG, "User entered: %c", key);
// Example: Simple calculator or menu logic
switch(key) {
case 'A':
ESP_LOGI(TAG, "Function A triggered");
break;
case 'B':
ESP_LOGI(TAG, "Function B triggered");
break;
case '#':
ESP_LOGI(TAG, "Enter key pressed");
break;
case '*':
ESP_LOGI(TAG, "Back/Cancel pressed");
break;
}
}
// Main loop can do other tasks while keypad is scanned in background
while (1) {
// Your main application code here
// Key events will be printed by the scanning task
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
5、验证确认
代码ok之后,就可以编译烧入看一下,确认下是不是我们想要的结果。等到结果ok之后,再回头看看代码的流程,做到心中有数即可。等到真正有需要的时候,再来详细分析也不迟。