1. 矩阵键盘扫描:从“是什么”到“为什么需要优化”
大家好,我是老张,在嵌入式这行摸爬滚打十几年,从早期的51单片机到现在的各种ARM核MCU,矩阵键盘几乎是每个项目都绕不开的“老朋友”。你可能在计算器、密码锁、工控面板甚至一些智能家电的遥控器上都见过它。简单来说,矩阵键盘就是一种用最少的I/O口控制最多按键的聪明办法。想象一下,16个独立按键需要16个I/O口,但一个4x4的矩阵键盘,只需要8个口(4行+4列)就能搞定,这对于引脚资源宝贵的单片机来说,简直是“救命稻草”。
但问题来了,按键多了,怎么知道是哪个被按下了呢?这就是“扫描”要干的事。你可以把矩阵键盘想象成一个由行线和列线交叉组成的网格,每个交叉点放一个按键。扫描,就是单片机这个“指挥官”,按一定策略去“点名”,检查每个交叉点上的“士兵”(按键)有没有“应答”(被按下)。这个过程的核心目标就两个:准和快。准,不能按了“1”出来“A”;快,不能按下去半天才有反应,尤其是在需要快速输入或者实时性要求高的场合。
所以,我们今天不聊那些干巴巴的理论,直接上干货。我会结合我这些年踩过的坑和积累的经验,重点对比两种最主流的扫描“兵法”——行列式扫描法和线翻转扫描法,并深入聊聊在不同实战场景下,如何对它们进行“调校”和“优化”,让你的键盘响应又快又稳。无论你是刚入门的新手,还是正在为项目性能发愁的老鸟,相信都能找到有用的东西。
2. 行列式扫描法:稳扎稳打的“步兵方阵”
行列式扫描法,也有人叫它“逐列扫描法”,是最好理解、最直观的一种方法。它的策略很像古代打仗的步兵方阵,一列一列地向前推进检查。
2.1 工作原理与代码逐行解析
它的工作步骤非常清晰,我把它总结为“三步走”:
- 定点清除:选中一列,把它拉低到低电平(比如0V),同时把其他所有列都置为高电平(比如3.3V或5V)。这就相当于只给当前这一列的“士兵”发了“请回答”的指令。
- 全员监听:单片机立刻去读取所有行线的电平状态。如果某一行线读到了低电平,那就意味着,这一行和当前我们选中的那一列交叉点上的按键被按下了!因为按键按下会把行线和列线接通,我们给列线低电平,这个低电平就“传导”到了对应的行线上。
- 轮询推进:完成一列的检查后,就切换到下一列,重复上述过程,直到所有列都被检查一遍。
听起来很简单对吧?我们来看一段经典的4x4矩阵键盘行列扫描代码,我边写边给你加注释,把每个细节都掰开讲明白:
// 假设我们的8位端口P1直接连接矩阵键盘:高4位(P1.4-P1.7)接4行,低4位(P1.0-P1.3)接4列。
#define KEY_MATRIX_PORT P1
// 按键消抖的简单延时函数,单位约为10微秒
void delay_10us(uint16_t us) {
while(us--);
}
uint8_t key_matrix_row_col_scan(void) {
uint8_t key_value = 0; // 用于存储最终识别到的按键值,0表示无按键
// 第一轮:扫描第一列(假设P1.0是第一列)
KEY_MATRIX_PORT = 0xF7; // 二进制 1111 0111, 只有P1.0(第一列)为0,其他列(P1.1-P1.3)和所有行(P1.4-P1.7)为1
if (KEY_MATRIX_PORT != 0xF7) { // 如果端口值变了,说明有按键按下影响了某行
delay_10us(1000); // 延时大约10ms进行消抖,避开机械触点抖动期
if (KEY_MATRIX_PORT != 0xF7) { // 再次确认,不是干扰
// 根据行线的状态,判断是第一列的哪个按键
switch (KEY_MATRIX_PORT) {
case 0x77: // 0111 0111, 第一行(P1.4)变0了 -> 按键(1,1),我们映射为键值1
key_value = 1;
break;
case 0xB7: // 1011 0111, 第二行(P1.5)变0了 -> 按键(2,1),映射为键值5
key_value = 5;
break;
case 0xD7: // 1101 0111, 第三行(P1.6)变0 -> 按键(3,1),映射为键值9
key_value = 9;
break;
case 0xE7: // 1110 0111, 第四行(P1.7)变0 -> 按键(4,1),映射为键值13
key_value = 13;
break;
}
// 等待按键释放,防止一次按下被重复识别
while (KEY_MATRIX_PORT != 0xF7);
}
}
// 第二轮:扫描第二列(P1.1)
KEY_MATRIX_PORT = 0xFB; // 1111 1011
if (KEY_MATRIX_PORT != 0xFB) {
delay_10us(1000);
if (KEY_MATRIX_PORT != 0xFB) {
switch (KEY_MATRIX_PORT) {
case 0x7B: key_value = 2; break; // (1,2)
case 0xBB: key_value = 6; break; // (2,2)
case 0xDB: key_value = 10; break; // (3,2)

341

被折叠的 条评论
为什么被折叠?



