4x4矩阵键盘驱动代码,包含扫描、去抖、多键检测、长按短按识别等功能,适用于STM32全系列。
一、硬件连接与原理
1.1 4x4矩阵键盘连接
行线(输出):Row0-Row3
列线(输入):Col0-Col3
STM32连接示例(GPIOB):
行线:Row0-PB0, Row1-PB1, Row2-PB2, Row3-PB3
列线:Col0-PB4, Col1-PB5, Col2-PB6, Col3-PB7
内部上拉电阻:列线启用内部上拉
1.2 扫描原理
逐行扫描法:
1. 设置当前行为低电平,其他行为高电平
2. 读取所有列线状态
3. 如果有列线为低电平,说明对应交叉点的按键被按下
4. 循环扫描4行
二、核心代码实现
2.1 头文件定义
c
/**
* @file matrix_keypad.h
* @brief 4x4矩阵键盘驱动(STM32库函数版)
*/
#ifndef __MATRIX_KEYPAD_H
#define __MATRIX_KEYPAD_H
#include "stm32f1xx_hal.h"
#include <stdint.h>
#include <string.h>
/* 键盘配置 */
#define KEYPAD_ROWS 4
#define KEYPAD_COLS 4
#define KEY_BUFFER_SIZE 16
/* 按键值定义 */
typedef enum {
KEY_NONE = 0,
KEY_0 = '0', KEY_1 = '1', KEY_2 = '2', KEY_3 = '3',
KEY_4 = '4', KEY_5 = '5', KEY_6 = '6', KEY_7 = '7',
KEY_8 = '8', KEY_9 = '9',
KEY_A = 'A', KEY_B = 'B', KEY_C = 'C', KEY_D = 'D',
KEY_STAR = '*', KEY_HASH = '#',
KEY_UP = 0x80, KEY_DOWN, KEY_LEFT, KEY_RIGHT, // 方向键
KEY_OK, KEY_CANCEL, KEY_MENU, KEY_BACK
} KeyCode;
/* 按键状态 */
typedef enum {
KEY_STATE_IDLE = 0, // 空闲
KEY_STATE_PRESSED, // 按下
KEY_STATE_HOLD, // 保持
KEY_STATE_RELEASED // 释放
} KeyState;
/* 按键事件 */
typedef enum {
KEY_EVENT_NONE = 0, // 无事件
KEY_EVENT_PRESS, // 按下事件
KEY_EVENT_RELEASE, // 释放事件
KEY_EVENT_SHORT_PRESS, // 短按事件
KEY_EVENT_LONG_PRESS, // 长按事件
KEY_EVENT_REPEAT // 重复按下事件
} KeyEvent;
/* 按键结构体 */
typedef struct {
KeyCode code; // 按键编码
KeyState state; // 当前状态
KeyEvent event; // 当前事件
uint32_t press_time; // 按下时间戳
uint32_t hold_time; // 保持时间
uint8_t repeat_count; // 重复次数
} KeyInfo;
/* 键盘配置结构体 */
typedef struct {
GPIO_TypeDef* row_port[KEYPAD_ROWS]; // 行线端口
uint16_t row_pin[KEYPAD_ROWS]; // 行线引脚
GPIO_TypeDef* col_port[KEYPAD_COLS]; // 列线端口
uint16_t col_pin[KEYPAD_COLS]; // 列线引脚
uint16_t debounce_time; // 去抖时间(ms)
uint16_t long_press_time; // 长按时间(ms)
uint16_t repeat_delay; // 重复延迟(ms)
uint16_t repeat_rate; // 重复速率(ms)
} Keypad_Config;
/* 键盘句柄 */
typedef struct {
Keypad_Config config; // 配置参数
KeyInfo keys[KEYPAD_ROWS * KEYPAD_COLS]; // 按键状态数组
KeyCode key_map[KEYPAD_ROWS][KEYPAD_COLS]; // 键值映射表
uint8_t key_buffer[KEY_BUFFER_SIZE]; // 键值缓冲区
uint8_t buffer_head; // 缓冲区头
uint8_t buffer_tail; // 缓冲区尾
uint32_t last_scan_time; // 上次扫描时间
uint8_t scan_enabled; // 扫描使能
} Keypad_Handle;
/* 函数声明 */
void Keypad_Init(Keypad_Handle *hkeypad, Keypad_Config *config);
void Keypad_Scan(Keypad_Handle *hkeypad);
uint8_t Keypad_GetKey(Keypad_Handle *hkeypad, KeyCode *key);
uint8_t Keypad_GetKeyEvent(Keypad_Handle *hkeypad, KeyCode *key, KeyEvent *event);
uint8_t Keypad_IsKeyPressed(Keypad_Handle *hkeypad, KeyCode key);
uint8_t Keypad_IsKeyReleased(Keypad_Handle *hkeypad, KeyCode key);
uint8_t Keypad_GetKeyHoldTime(Keypad_Handle *hkeypad, KeyCode key, uint32_t *time);
void Keypad_ClearBuffer(Keypad_Handle *hkeypad);
uint8_t Keypad_BufferFull(Keypad_Handle *hkeypad);
uint8_t Keypad_BufferEmpty(Keypad_Handle *hkeypad);
#endif /* __MATRIX_KEYPAD_H */
2.2 键盘驱动实现
c
/**
* @file matrix_keypad.c
* @brief 4x4矩阵键盘驱动实现
*/
#include "matrix_keypad.h"
/* 默认键值映射 */
static const KeyCode default_keymap[KEYPAD_ROWS][KEYPAD_COLS] = {
{KEY_1, KEY_2, KEY_3, KEY_A},
{KEY_4, KEY_5, KEY_6, KEY_B},
{KEY_7, KEY_8, KEY_9, KEY_C},
{KEY_STAR, KEY_0, KEY_HASH, KEY_D}
};
/**
* @brief 初始化矩阵键盘
* @param hkeypad 键盘句柄
* @param config 配置参数
*/
void Keypad_Init(Keypad_Handle *hkeypad, Keypad_Config *config)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
/* 复制配置参数 */
memcpy(&hkeypad->config, config, sizeof(Keypad_Config));
/* 初始化按键状态数组 */
for(int i = 0; i < KEYPAD_ROWS * KEYPAD_COLS; i++)
{
hkeypad->keys[i].code = KEY_NONE;
hkeypad->keys[i].state = KEY_STATE_IDLE;
hkeypad->keys[i].event = KEY_EVENT_NONE;
hkeypad->keys[i].press_time = 0;
hkeypad->keys[i].hold_time = 0;
hkeypad->keys[i].repeat_count = 0;
}
/* 初始化键值映射表 */
memcpy(hkeypad->key_map, default_keymap, sizeof(default_keymap));
/* 初始化缓冲区 */
memset(hkeypad->key_buffer, 0, KEY_BUFFER_SIZE);
hkeypad->buffer_head = 0;
hkeypad->buffer_tail = 0;
/* 初始化扫描状态 */
hkeypad->last_scan_time = 0;
hkeypad->scan_enabled = 1;
/* 初始化行线为推挽输出 */
for(int i = 0; i < KEYPAD_ROWS; i++)
{
if(config->row_port[i] != NULL)
{
/* 使能GPIO时钟 */
if(config->row_port[i] == GPIOA) __HAL_RCC_GPIOA_CLK_ENABLE();
else if(config->row_port[i] == GPIOB) __HAL_RCC_GPIOB_CLK_ENABLE();
else if(config->row_port[i] == GPIOC) __HAL_RCC_GPIOC_CLK_ENABLE();
else if(config->row_port[i] == GPIOD) __HAL_RCC_GPIOD_CLK_ENABLE();
else if(config->row_port[i] == GPIOE) __HAL_RCC_GPIOE_CLK_ENABLE();
GPIO_InitStruct.Pin = config->row_pin[i];
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(config->row_port[i], &GPIO_InitStruct);
/* 默认输出高电平(按键未按下) */
HAL_GPIO_WritePin(config->row_port[i], config->row_pin[i], GPIO_PIN_SET);
}
}
/* 初始化列线为输入上拉 */
for(int i = 0; i < KEYPAD_COLS; i++)
{
if(config->col_port[i] != NULL)
{
/* 使能GPIO时钟 */
if(config->col_port[i] == GPIOA) __HAL_RCC_GPIOA_CLK_ENABLE();
else if(config->col_port[i] == GPIOB) __HAL_RCC_GPIOB_CLK_ENABLE();
else if(config->col_port[i] == GPIOC) __HAL_RCC_GPIOC_CLK_ENABLE();
else if(config->col_port[i] == GPIOD) __HAL_RCC_GPIOD_CLK_ENABLE();
else if(config->col_port[i] == GPIOE) __HAL_RCC_GPIOE_CLK_ENABLE();
GPIO_InitStruct.Pin = config->col_pin[i];
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_PULLUP;
HAL_GPIO_Init(config->col_port[i], &GPIO_InitStruct);
}
}
}
/**
* @brief 扫描矩阵键盘(需要在主循环中定期调用)
* @param hkeypad 键盘句柄
*/
void Keypad_Scan(Keypad_Handle *hkeypad)
{
uint8_t row, col;
uint32_t current_time = HAL_GetTick();
/* 检查扫描间隔 */
if((current_time - hkeypad->last_scan_time) < 10) // 10ms扫描一次
return;
hkeypad->last_scan_time = current_time;
if(!hkeypad->scan_enabled)
return;
/* 逐行扫描 */
for(row = 0; row < KEYPAD_ROWS; row++)
{
if(hkeypad->config.row_port[row] == NULL)
continue;
/* 设置当前行为低电平,其他行为高电平 */
for(uint8_t r = 0; r < KEYPAD_ROWS; r++)
{
if(hkeypad->config.row_port[r] != NULL)
{
if(r == row)
HAL_GPIO_WritePin(hkeypad->config.row_port[r],
hkeypad->config.row_pin[r], GPIO_PIN_RESET);
else
HAL_GPIO_WritePin(hkeypad->config.row_port[r],
hkeypad->config.row_pin[r], GPIO_PIN_SET);
}
}
/* 延时等待稳定(可选) */
// for(volatile int i = 0; i < 10; i++);
/* 读取列线状态 */
for(col = 0; col < KEYPAD_COLS; col++)
{
if(hkeypad->config.col_port[col] == NULL)
continue;
/* 计算按键索引 */
uint8_t key_index = row * KEYPAD_COLS + col;
KeyCode key_code = hkeypad->key_map[row][col];
/* 读取列线状态(低电平表示按下) */
GPIO_PinState pin_state = HAL_GPIO_ReadPin(hkeypad->config.col_port[col],
hkeypad->config.col_pin[col]);
/* 更新按键状态 */
KeyInfo *key = &hkeypad->keys[key_index];
if(pin_state == GPIO_PIN_RESET) // 按键按下
{
switch(key->state)
{
case KEY_STATE_IDLE:
/* 首次检测到按下 */
key->state = KEY_STATE_PRESSED;
key->event = KEY_EVENT_PRESS;
key->press_time = current_time;
key->hold_time = 0;
key->repeat_count = 0;
/* 存入缓冲区 */
if(!Keypad_BufferFull(hkeypad))
{
hkeypad->key_buffer[hkeypad->buffer_head] = key_code;
hkeypad->buffer_head = (hkeypad->buffer_head + 1) % KEY_BUFFER_SIZE;
}
break;
case KEY_STATE_PRESSED:
/* 去抖处理 */
if((current_time - key->press_time) > hkeypad->config.debounce_time)
{
key->state = KEY_STATE_HOLD;
key->event = KEY_EVENT_NONE;
/* 计算保持时间 */
key->hold_time = current_time - key->press_time;
/* 检查长按 */
if(key->hold_time > hkeypad->config.long_press_time)
{
key->event = KEY_EVENT_LONG_PRESS;
/* 检查重复按下 */
if(key->repeat_count == 0 ||
(current_time - key->press_time) >
(hkeypad->config.repeat_delay +
key->repeat_count * hkeypad->config.repeat_rate))
{
key->repeat_count++;
key->event = KEY_EVENT_REPEAT;
/* 存入缓冲区 */
if(!Keypad_BufferFull(hkeypad))
{
hkeypad->key_buffer[hkeypad->buffer_head] = key_code;
hkeypad->buffer_head = (hkeypad->buffer_head + 1) % KEY_BUFFER_SIZE;
}
}
}
}
break;
case KEY_STATE_HOLD:
key->hold_time = current_time - key->press_time;
key->event = KEY_EVENT_NONE;
break;
case KEY_STATE_RELEASED:
key->state = KEY_STATE_PRESSED;
key->event = KEY_EVENT_PRESS;
key->press_time = current_time;
break;
}
}
else // 按键释放
{
switch(key->state)
{
case KEY_STATE_PRESSED:
case KEY_STATE_HOLD:
/* 检查是否为短按 */
if((current_time - key->press_time) < hkeypad->config.long_press_time)
{
key->event = KEY_EVENT_SHORT_PRESS;
}
else
{
key->event = KEY_EVENT_RELEASE;
}
key->state = KEY_STATE_RELEASED;
key->hold_time = 0;
break;
case KEY_STATE_RELEASED:
/* 释放后回到空闲状态 */
if((current_time - key->press_time) > 50) // 50ms防抖
{
key->state = KEY_STATE_IDLE;
key->event = KEY_EVENT_NONE;
}
break;
}
}
}
}
/* 所有行恢复高电平 */
for(row = 0; row < KEYPAD_ROWS; row++)
{
if(hkeypad->config.row_port[row] != NULL)
{
HAL_GPIO_WritePin(hkeypad->config.row_port[row],
hkeypad->config.row_pin[row], GPIO_PIN_SET);
}
}
}
/**
* @brief 从缓冲区获取按键值
* @param hkeypad 键盘句柄
* @param key 返回的按键值
* @return 1:成功, 0:缓冲区为空
*/
uint8_t Keypad_GetKey(Keypad_Handle *hkeypad, KeyCode *key)
{
if(Keypad_BufferEmpty(hkeypad))
return 0;
*key = hkeypad->key_buffer[hkeypad->buffer_tail];
hkeypad->buffer_tail = (hkeypad->buffer_tail + 1) % KEY_BUFFER_SIZE;
return 1;
}
/**
* @brief 获取按键事件
* @param hkeypad 键盘句柄
* @param key 按键编码
* @param event 按键事件
* @return 1:有事件, 0:无事件
*/
uint8_t Keypad_GetKeyEvent(Keypad_Handle *hkeypad, KeyCode *key, KeyEvent *event)
{
/* 查找有事件的按键 */
for(int i = 0; i < KEYPAD_ROWS * KEYPAD_COLS; i++)
{
if(hkeypad->keys[i].event != KEY_EVENT_NONE)
{
*key = hkeypad->keys[i].code;
*event = hkeypad->keys[i].event;
/* 清除事件标志 */
hkeypad->keys[i].event = KEY_EVENT_NONE;
return 1;
}
}
return 0;
}
/**
* @brief 检查指定按键是否按下
* @param hkeypad 键盘句柄
* @param key 要检查的按键
* @return 1:按下, 0:未按下
*/
uint8_t Keypad_IsKeyPressed(Keypad_Handle *hkeypad, KeyCode key)
{
for(int i = 0; i < KEYPAD_ROWS * KEYPAD_COLS; i++)
{
if(hkeypad->keys[i].code == key &&
(hkeypad->keys[i].state == KEY_STATE_PRESSED ||
hkeypad->keys[i].state == KEY_STATE_HOLD))
{
return 1;
}
}
return 0;
}
/**
* @brief 检查指定按键是否释放
* @param hkeypad 键盘句柄
* @param key 要检查的按键
* @return 1:释放, 0:未释放
*/
uint8_t Keypad_IsKeyReleased(Keypad_Handle *hkeypad, KeyCode key)
{
for(int i = 0; i < KEYPAD_ROWS * KEYPAD_COLS; i++)
{
if(hkeypad->keys[i].code == key &&
hkeypad->keys[i].state == KEY_STATE_RELEASED)
{
return 1;
}
}
return 0;
}
/**
* @brief 获取按键保持时间
* @param hkeypad 键盘句柄
* @param key 按键编码
* @param time 返回保持时间(ms)
* @return 1:成功, 0:按键未按下
*/
uint8_t Keypad_GetKeyHoldTime(Keypad_Handle *hkeypad, KeyCode key, uint32_t *time)
{
for(int i = 0; i < KEYPAD_ROWS * KEYPAD_COLS; i++)
{
if(hkeypad->keys[i].code == key &&
hkeypad->keys[i].state == KEY_STATE_HOLD)
{
*time = hkeypad->keys[i].hold_time;
return 1;
}
}
return 0;
}
/**
* @brief 清空按键缓冲区
* @param hkeypad 键盘句柄
*/
void Keypad_ClearBuffer(Keypad_Handle *hkeypad)
{
hkeypad->buffer_head = 0;
hkeypad->buffer_tail = 0;
memset(hkeypad->key_buffer, 0, KEY_BUFFER_SIZE);
}
/**
* @brief 检查缓冲区是否已满
* @param hkeypad 键盘句柄
* @return 1:已满, 0:未满
*/
uint8_t Keypad_BufferFull(Keypad_Handle *hkeypad)
{
return ((hkeypad->buffer_head + 1) % KEY_BUFFER_SIZE) == hkeypad->buffer_tail;
}
/**
* @brief 检查缓冲区是否为空
* @param hkeypad 键盘句柄
* @return 1:为空, 0:非空
*/
uint8_t Keypad_BufferEmpty(Keypad_Handle *hkeypad)
{
return hkeypad->buffer_head == hkeypad->buffer_tail;
}
三、高级功能扩展
3.1 多键组合检测
c
/**
* @file keypad_combo.c
* @brief 键盘组合键检测
*/
#include "matrix_keypad.h"
#define MAX_COMBO_KEYS 3
#define COMBO_TIMEOUT 1000 // 组合键超时时间(ms)
/* 组合键定义 */
typedef struct {
KeyCode keys[MAX_COMBO_KEYS]; // 按键序列
uint8_t key_count; // 按键数量
uint32_t last_time; // 上次按键时间
uint8_t matched; // 已匹配数量
} ComboDetector;
/* 全局组合键检测器 */
ComboDetector combo_detector = {0};
/* 预定义组合键 */
typedef enum {
COMBO_NONE = 0,
COMBO_RESET, // *#0
COMBO_MENU, #123#
COMBO_DEBUG, #789*
} ComboType;
/**
* @brief 初始化组合键检测
*/
void Combo_Init(void)
{
memset(&combo_detector, 0, sizeof(combo_detector));
}
/**
* @brief 检测组合键
* @param hkeypad 键盘句柄
* @return 检测到的组合键类型
*/
ComboType Combo_Detect(Keypad_Handle *hkeypad)
{
KeyCode key;
uint32_t current_time = HAL_GetTick();
/* 获取新按键 */
if(Keypad_GetKey(hkeypad, &key))
{
/* 检查超时 */
if((current_time - combo_detector.last_time) > COMBO_TIMEOUT)
{
/* 超时重置 */
memset(&combo_detector, 0, sizeof(combo_detector));
}
/* 记录按键 */
if(combo_detector.matched < MAX_COMBO_KEYS)
{
combo_detector.keys[combo_detector.matched++] = key;
combo_detector.last_time = current_time;
/* 检查预定义组合 */
if(combo_detector.matched == 3)
{
/* 检查组合 *#0 */
if(combo_detector.keys[0] == KEY_STAR &&
combo_detector.keys[1] == KEY_HASH &&
combo_detector.keys[2] == KEY_0)
{
memset(&combo_detector, 0, sizeof(combo_detector));
return COMBO_RESET;
}
/* 检查组合 123# */
if(combo_detector.keys[0] == KEY_1 &&
combo_detector.keys[1] == KEY_2 &&
combo_detector.keys[2] == KEY_3 &&
key == KEY_HASH) // 第4个键
{
memset(&combo_detector, 0, sizeof(combo_detector));
return COMBO_MENU;
}
}
}
}
return COMBO_NONE;
}
3.2 按键宏定义
c
/**
* @file keypad_macro.c
* @brief 键盘宏功能
*/
#include "matrix_keypad.h"
#define MAX_MACROS 10
#define MACRO_BUFFER_SIZE 32
/* 宏定义结构体 */
typedef struct {
KeyCode trigger; // 触发键
KeyCode sequence[MACRO_BUFFER_SIZE]; // 宏序列
uint8_t seq_len; // 序列长度
uint8_t enabled; // 启用标志
} KeyMacro;
/* 宏定义数组 */
KeyMacro key_macros[MAX_MACROS] = {0};
uint8_t macro_count = 0;
/**
* @brief 注册宏定义
* @param trigger 触发键
* @param sequence 宏序列
* @param seq_len 序列长度
* @return 宏ID,0xFF表示失败
*/
uint8_t Macro_Register(KeyCode trigger, KeyCode *sequence, uint8_t seq_len)
{
if(macro_count >= MAX_MACROS || seq_len > MACRO_BUFFER_SIZE)
return 0xFF;
key_macros[macro_count].trigger = trigger;
memcpy(key_macros[macro_count].sequence, sequence, seq_len);
key_macros[macro_count].seq_len = seq_len;
key_macros[macro_count].enabled = 1;
return macro_count++;
}
/**
* @brief 执行宏
* @param hkeypad 键盘句柄
* @param key 按下的键
* @return 1:执行了宏, 0:未执行
*/
uint8_t Macro_Execute(Keypad_Handle *hkeypad, KeyCode key)
{
for(int i = 0; i < macro_count; i++)
{
if(key_macros[i].enabled && key_macros[i].trigger == key)
{
/* 将宏序列存入缓冲区 */
for(int j = 0; j < key_macros[i].seq_len; j++)
{
if(!Keypad_BufferFull(hkeypad))
{
hkeypad->key_buffer[hkeypad->buffer_head] = key_macros[i].sequence[j];
hkeypad->buffer_head = (hkeypad->buffer_head + 1) % KEY_BUFFER_SIZE;
}
}
return 1;
}
}
return 0;
}
四、应用示例
4.1 简单密码锁示例
c
/**
* @file keypad_example.c
* @brief 矩阵键盘应用示例 - 密码锁
*/
#include "main.h"
#include "matrix_keypad.h"
#include "lcd.h"
/* 硬件定义 */
#define KEYPAD_ROW0_PORT GPIOB
#define KEYPAD_ROW0_PIN GPIO_PIN_0
#define KEYPAD_ROW1_PORT GPIOB
#define KEYPAD_ROW1_PIN GPIO_PIN_1
#define KEYPAD_ROW2_PORT GPIOB
#define KEYPAD_ROW2_PIN GPIO_PIN_2
#define KEYPAD_ROW3_PORT GPIOB
#define KEYPAD_ROW3_PIN GPIO_PIN_3
#define KEYPAD_COL0_PORT GPIOB
#define KEYPAD_COL0_PIN GPIO_PIN_4
#define KEYPAD_COL1_PORT GPIOB
#define KEYPAD_COL1_PIN GPIO_PIN_5
#define KEYPAD_COL2_PORT GPIOB
#define KEYPAD_COL2_PIN GPIO_PIN_6
#define KEYPAD_COL3_PORT GPIOB
#define KEYPAD_COL3_PIN GPIO_PIN_7
/* 密码设置 */
#define PASSWORD_LENGTH 6
const char correct_password[PASSWORD_LENGTH + 1] = "123456";
char input_password[PASSWORD_LENGTH + 1] = {0};
uint8_t password_index = 0;
/* 键盘句柄 */
Keypad_Handle hkeypad;
Keypad_Config keypad_config;
/**
* @brief 初始化键盘配置
*/
void Keypad_Config_Init(void)
{
/* 配置行线 */
keypad_config.row_port[0] = KEYPAD_ROW0_PORT;
keypad_config.row_pin[0] = KEYPAD_ROW0_PIN;
keypad_config.row_port[1] = KEYPAD_ROW1_PORT;
keypad_config.row_pin[1] = KEYPAD_ROW1_PIN;
keypad_config.row_port[2] = KEYPAD_ROW2_PORT;
keypad_config.row_pin[2] = KEYPAD_ROW2_PIN;
keypad_config.row_port[3] = KEYPAD_ROW3_PORT;
keypad_config.row_pin[3] = KEYPAD_ROW3_PIN;
/* 配置列线 */
keypad_config.col_port[0] = KEYPAD_COL0_PORT;
keypad_config.col_pin[0] = KEYPAD_COL0_PIN;
keypad_config.col_port[1] = KEYPAD_COL1_PORT;
keypad_config.col_pin[1] = KEYPAD_COL1_PIN;
keypad_config.col_port[2] = KEYPAD_COL2_PORT;
keypad_config.col_pin[2] = KEYPAD_COL2_PIN;
keypad_config.col_port[3] = KEYPAD_COL3_PORT;
keypad_config.col_pin[3] = KEYPAD_COL3_PIN;
/* 配置参数 */
keypad_config.debounce_time = 20; // 20ms去抖
keypad_config.long_press_time = 1000; // 1秒长按
keypad_config.repeat_delay = 500; // 500ms重复延迟
keypad_config.repeat_rate = 200; // 200ms重复速率
}
/**
* @brief 密码锁主程序
*/
void PasswordLock_Example(void)
{
KeyCode key;
KeyEvent event;
uint8_t authenticated = 0;
/* 初始化LCD */
LCD_Init();
LCD_Clear();
/* 初始化键盘 */
Keypad_Config_Init();
Keypad_Init(&hkeypad, &keypad_config);
/* 显示欢迎信息 */
LCD_SetCursor(0, 0);
LCD_PrintString("Password Lock");
LCD_SetCursor(1, 0);
LCD_PrintString("Enter Code: ");
while(1)
{
/* 扫描键盘 */
Keypad_Scan(&hkeypad);
/* 处理按键事件 */
if(Keypad_GetKeyEvent(&hkeypad, &key, &event))
{
if(event == KEY_EVENT_SHORT_PRESS)
{
/* 数字键处理 */
if(key >= '0' && key <= '9')
{
if(password_index < PASSWORD_LENGTH)
{
input_password[password_index] = (char)key;
password_index++;
/* 显示星号 */
LCD_SetCursor(1, 12 + password_index - 1);
LCD_PrintChar('*');
}
}
/* 确认键 (#) */
else if(key == KEY_HASH)
{
if(password_index == PASSWORD_LENGTH)
{
input_password[password_index] = '\0';
/* 验证密码 */
if(strcmp(input_password, correct_password) == 0)
{
LCD_Clear();
LCD_SetCursor(0, 0);
LCD_PrintString("Access Granted!");
authenticated = 1;
/* 控制门锁等 */
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_SET);
HAL_Delay(2000);
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET);
}
else
{
LCD_Clear();
LCD_SetCursor(0, 0);
LCD_PrintString("Access Denied!");
HAL_Delay(2000);
}
/* 重置输入 */
password_index = 0;
memset(input_password, 0, sizeof(input_password));
/* 重新显示界面 */
if(!authenticated)
{
LCD_Clear();
LCD_SetCursor(0, 0);
LCD_PrintString("Password Lock");
LCD_SetCursor(1, 0);
LCD_PrintString("Enter Code: ");
}
}
}
/* 清除键 (*) */
else if(key == KEY_STAR)
{
if(password_index > 0)
{
password_index--;
input_password[password_index] = '\0';
/* 清除显示 */
LCD_SetCursor(1, 12 + password_index);
LCD_PrintChar(' ');
}
}
}
/* 长按清除键清除所有输入 */
else if(event == KEY_EVENT_LONG_PRESS && key == KEY_STAR)
{
password_index = 0;
memset(input_password, 0, sizeof(input_password));
LCD_SetCursor(1, 12);
LCD_PrintString(" ");
LCD_SetCursor(1, 12);
}
}
HAL_Delay(10);
}
}
4.2 菜单导航示例
c
/**
* @brief 菜单系统示例
*/
void MenuSystem_Example(void)
{
KeyCode key;
KeyEvent event;
uint8_t menu_index = 0;
const char *menu_items[] = {
"1. System Info",
"2. Settings",
"3. Calibration",
"4. Test Mode",
"5. Exit"
};
uint8_t menu_count = sizeof(menu_items) / sizeof(menu_items[0]);
/* 初始化LCD */
LCD_Init();
LCD_Clear();
/* 初始化键盘 */
Keypad_Config_Init();
Keypad_Init(&hkeypad, &keypad_config);
/* 显示菜单 */
LCD_SetCursor(0, 0);
LCD_PrintString("Main Menu:");
LCD_SetCursor(1, 0);
LCD_PrintString(menu_items[menu_index]);
while(1)
{
Keypad_Scan(&hkeypad);
if(Keypad_GetKeyEvent(&hkeypad, &key, &event))
{
if(event == KEY_EVENT_SHORT_PRESS)
{
switch(key)
{
case KEY_2: // 下方向键
case KEY_8: // 上方向键
if(key == KEY_2) // 下
{
menu_index = (menu_index + 1) % menu_count;
}
else // 上
{
menu_index = (menu_index == 0) ? menu_count - 1 : menu_index - 1;
}
LCD_SetCursor(1, 0);
LCD_PrintString(" "); // 清空行
LCD_SetCursor(1, 0);
LCD_PrintString(menu_items[menu_index]);
break;
case KEY_5: // 确认键
ExecuteMenuItem(menu_index);
break;
case KEY_0: // 返回键
LCD_Clear();
LCD_SetCursor(0, 0);
LCD_PrintString("Return to menu");
HAL_Delay(1000);
LCD_Clear();
LCD_SetCursor(0, 0);
LCD_PrintString("Main Menu:");
LCD_SetCursor(1, 0);
LCD_PrintString(menu_items[menu_index]);
break;
}
}
}
HAL_Delay(10);
}
}
/**
* @brief 执行菜单项
*/
void ExecuteMenuItem(uint8_t index)
{
LCD_Clear();
switch(index)
{
case 0: // System Info
LCD_SetCursor(0, 0);
LCD_PrintString("System Info:");
LCD_SetCursor(1, 0);
LCD_PrintString("Ver 1.0 2024");
break;
case 1: // Settings
LCD_SetCursor(0, 0);
LCD_PrintString("Settings Menu");
break;
case 2: // Calibration
LCD_SetCursor(0, 0);
LCD_PrintString("Calibration");
break;
case 3: // Test Mode
LCD_SetCursor(0, 0);
LCD_PrintString("Test Mode");
break;
case 4: // Exit
LCD_SetCursor(0, 0);
LCD_PrintString("Exiting...");
HAL_Delay(1000);
break;
}
HAL_Delay(2000);
LCD_Clear();
}
参考代码 Stm32矩阵键盘程序(库函数) www.youwenfan.com/contentcsv/70644.html
五、中断扫描版本
5.1 定时器中断扫描
c
/**
* @file keypad_timer.c
* @brief 定时器中断扫描键盘
*/
#include "main.h"
#include "matrix_keypad.h"
/* 定时器句柄 */
TIM_HandleTypeDef htim2;
Keypad_Handle hkeypad;
/**
* @brief 初始化定时器扫描
*/
void Keypad_Timer_Init(void)
{
TIM_ClockConfigTypeDef sClockSourceConfig = {0};
TIM_MasterConfigTypeDef sMasterConfig = {0};
/* 使能定时器时钟 */
__HAL_RCC_TIM2_CLK_ENABLE();
/* 配置定时器 */
htim2.Instance = TIM2;
htim2.Init.Prescaler = 7200 - 1; // 72MHz/7200 = 10kHz
htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
htim2.Init.Period = 100 - 1; // 10kHz/100 = 100Hz (10ms)
htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
HAL_TIM_Base_Init(&htim2);
/* 配置时钟源 */
sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
HAL_TIM_ConfigClockSource(&htim2, &sClockSourceConfig);
/* 主输出使能 */
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig);
/* 启动定时器中断 */
HAL_TIM_Base_Start_IT(&htim2);
/* 配置NVIC */
HAL_NVIC_SetPriority(TIM2_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(TIM2_IRQn);
}
/**
* @brief 定时器中断处理
*/
void TIM2_IRQHandler(void)
{
if(__HAL_TIM_GET_FLAG(&htim2, TIM_FLAG_UPDATE) != RESET)
{
if(__HAL_TIM_GET_IT_SOURCE(&htim2, TIM_IT_UPDATE) != RESET)
{
__HAL_TIM_CLEAR_IT(&htim2, TIM_IT_UPDATE);
/* 扫描键盘 */
Keypad_Scan(&hkeypad);
}
}
}
六、调试与测试
6.1 键盘测试程序
c
/**
* @file keypad_test.c
* @brief 键盘测试程序
*/
void Keypad_Test(void)
{
KeyCode key;
KeyEvent event;
char display_str[20];
/* 初始化串口 */
UART_Init(115200);
printf("Matrix Keypad Test\n");
/* 初始化键盘 */
Keypad_Config_Init();
Keypad_Init(&hkeypad, &keypad_config);
printf("Press any key...\n");
while(1)
{
/* 扫描键盘 */
Keypad_Scan(&hkeypad);
/* 获取按键事件 */
if(Keypad_GetKeyEvent(&hkeypad, &key, &event))
{
switch(event)
{
case KEY_EVENT_PRESS:
printf("Key 0x%02X Pressed\n", key);
break;
case KEY_EVENT_SHORT_PRESS:
printf("Key 0x%02X Short Press\n", key);
break;
case KEY_EVENT_LONG_PRESS:
printf("Key 0x%02X Long Press\n", key);
break;
case KEY_EVENT_RELEASE:
printf("Key 0x%02X Released\n", key);
break;
case KEY_EVENT_REPEAT:
printf("Key 0x%02X Repeat\n", key);
break;
}
}
/* 检查特定按键 */
if(Keypad_IsKeyPressed(&hkeypad, KEY_1))
{
printf("Key 1 is holding\n");
}
HAL_Delay(10);
}
}
七、性能优化
7.1 硬件优化
c
/* 使用位带操作提高扫描速度 */
#ifdef USE_BITBAND
/* 定义位带地址 */
#define BITBAND(addr, bit) ((addr & 0xF0000000) + 0x02000000 + ((addr & 0xFFFFF) << 5) + (bit << 2))
/* 快速读写函数 */
#define GPIO_READ_BIT(gpio, pin) (*(volatile uint32_t*)BITBAND((uint32_t)&gpio->IDR, __builtin_ctz(pin)))
#define GPIO_WRITE_BIT(gpio, pin, value) *(volatile uint32_t*)BITBAND((uint32_t)&gpio->ODR, __builtin_ctz(pin)) = value
/* 快速扫描函数 */
void Keypad_FastScan(Keypad_Handle *hkeypad)
{
for(uint8_t row = 0; row < KEYPAD_ROWS; row++)
{
/* 设置当前行为低 */
GPIO_WRITE_BIT(hkeypad->config.row_port[row], hkeypad->config.row_pin[row], 0);
/* 读取列线 */
for(uint8_t col = 0; col < KEYPAD_COLS; col++)
{
if(GPIO_READ_BIT(hkeypad->config.col_port[col], hkeypad->config.col_pin[col]) == 0)
{
/* 按键按下处理 */
}
}
/* 恢复行线 */
GPIO_WRITE_BIT(hkeypad->config.row_port[row], hkeypad->config.row_pin[row], 1);
}
}
#endif
八、常见问题解决
| 问题 | 可能原因 | 解决方案 |
|---|---|---|
| 按键无响应 | 1. GPIO配置错误 2. 上拉电阻未启用 3. 扫描频率过低 | 1. 检查GPIO模式 2. 启用内部上拉 3. 提高扫描频率 |
| 按键抖动 | 1. 机械抖动 2. 接触不良 | 1. 增加去抖时间 2. 检查硬件连接 |
| 多键同时按下错误 | 1. 行线冲突 2. 扫描逻辑错误 | 1. 使用二极管隔离 2. 改进扫描算法 |
| 耗电过高 | 1. 上拉电阻过小 2. 扫描过频繁 | 1. 使用内部上拉 2. 降低扫描频率或使用中断 |