4x4矩阵键盘扫描与按键消抖——西风模板的三态检测算法解析
·
CT107D上的键盘是4x4矩阵布局,总共16个按键(S4~S19),但实际代码里通常只用其中一部分。这一篇结合项目中的实际代码,把键盘扫描的原理、消抖策略和按键处理讲清楚。
硬件原理
CT107D的矩阵键盘布局:
P3.3 P3.2 P3.1 P3.0
| | | |
[S4] [S8] [S12] [S16] ← P4.4 控制(列1)
| | | |
[S5] [S9] [S13] [S17] ← P4.2 控制(列2)
| | | |
[S6] [S10] [S14] [S18] ← P3.5 控制(列3)
| | | |
[S7] [S11] [S15] [S19] ← P3.4 控制(列4)
扫描原理就是逐列拉低,逐行检测:
- 把某一列的IO拉低(比如P4.4=0),其余列保持高电平
- 检查P3.3/P3.2/P3.1/P3.0哪个被拉低了
- 如果P3.3=0,说明是S4被按下;P3.2=0说明是S5被按下,以此类推
有一个需要注意的地方:P3.4跟NE555频率发生器共用一个引脚。如果题目考到了NE555频率测量,就不能扫描第四列了。代码里的注释也说了:
// 若考到ne555 把P34全注释掉
P44 = 1; P42 = 1; P35 = 1; P34 = 1;
键盘扫描函数(项目实际代码)
以下是 Driver/Key.c 中的完整扫描代码(西风模板版本):
unsigned char Key_Read() {
unsigned char Temp = 0; // 一定要赋初值0!
/* 第一列扫描(P4.4=0)*/
P44 = 0; P42 = 1; P35 = 1; P34 = 1;
if(P33 == 0) Temp = 4; // S4
if(P32 == 0) Temp = 5; // S5
if(P31 == 0) Temp = 6; // S6
if(P30 == 0) Temp = 7; // S7
/* 第二列扫描(P4.2=0)*/
P44 = 1; P42 = 0; P35 = 1; P34 = 1;
if(P33 == 0) Temp = 8; // S8
if(P32 == 0) Temp = 9; // S9
if(P31 == 0) Temp = 10; // S10
if(P30 == 0) Temp = 11; // S11
/* 第三列扫描(P3.5=0)*/
P44 = 1; P42 = 1; P35 = 0; P34 = 1;
if(P33 == 0) Temp = 12; // S12
if(P32 == 0) Temp = 13; // S13
if(P31 == 0) Temp = 14; // S14
if(P30 == 0) Temp = 15; // S15
/* 第四列扫描(P3.4=0)—— NE555时需要注释掉!*/
P44 = 1; P42 = 1; P35 = 1; P34 = 0;
if(P33 == 0) Temp = 16; // S16
if(P32 == 0) Temp = 17; // S17
if(P31 == 0) Temp = 18; // S18
if(P30 == 0) Temp = 19; // S19
return Temp;
}
两个关键细节:
Temp必须初始化为0。如果没有按键按下,函数返回0。如果忘了初始化,返回值就是不确定的,按键处理会出错。- 每扫描完一列后恢复所有列为高电平(
P44=1; P42=1; P35=1; P34=1;),防止列之间互相干扰。
三态检测算法
西风模板里处理按键的核心逻辑:
idata uc Key_Val, Key_Old, Key_Down, Key_Up;
void Key_Proc() {
Key_Val = Key_Read();
Key_Down = Key_Val & (Key_Val ^ Key_Old); // 按下检测
Key_Up = ~Key_Val & (Key_Val ^ Key_Old); // 释放检测
Key_Old = Key_Val; // 更新旧值
switch(Key_Down) {
case 4: // S4按下
// ...
break;
case 8: // S8按下
// ...
break;
}
}
这个算法的好处在于:
- 一行代码检测按下和释放,不需要一堆if-else
- 天然消抖,配合10ms调度周期,按键抖动会被过滤
- 支持双按键,比如S8和S9同时按下
消抖的原理:机械按键按下时会有5~20ms的抖动期,电平会来回跳变。但因为Key_Proc在调度器里每10ms才执行一次,这10ms内不管抖动多少次都只读一次。
实际竞赛中常用的按键分配:
| 按键 | 常用功能 |
|---|---|
| S4 (case 4) | 模式切换/启动/确认 |
| S5 (case 5) | 复位/清除 |
| S8 (case 8) | 界面切换 |
| S9 (case 9) | 参数位置切换 |
| S12 (case 12) | 参数增加 |
| S13 (case 13) | 参数减少 |
| S8+S9 (case 89) | 双按键(特殊操作) |
参数调节的边界保护
按键调参数时必须加边界保护,否则调过头会导致数据溢出或功能异常。第15届国赛中的例子:
case 12: // 增加
if(Seg_Disp_Mode == 2) {
if(Param_Set_Pos == 0) { // R参数(范围10~20)
if(++Param_R_10x_Show > 20)
Param_R_10x_Show = 20; // 到上限就停
} else { // B参数(范围-90~90)
Param_B_Show += 5;
if(Param_B_Show > 90)
Param_B_Show = 90;
}
}
break;
case 13: // 减少
if(Seg_Disp_Mode == 2) {
if(Param_Set_Pos == 0) {
if(--Param_R_10x_Show < 10)
Param_R_10x_Show = 10; // 到下限就停
} else {
Param_B_Show -= 5;
if(Param_B_Show < -90)
Param_B_Show = -90;
}
}
break;
++ 在条件判断之前执行,所以用 > 20 而不是 > 19。这种"先变后判"的写法很常见。
第16届省赛的状态互锁设计
第16届省赛的按键处理有一个很好的设计——累加测量进行中时禁止某些操作:
case 8: // 界面切换
if(measure_start_flag == 0 && ++seg_display_mode == 4)
seg_display_mode = 1;
break;
// 累加测量中(measure_start_flag=1)不允许切换界面
case 12: // 清除
if(measure_start_flag == 0) {
measure_mode = 0;
seg_display_mode = 1;
quench_flag = 1;
distance = 0;
}
break;
// 累加测量中不允许清除
这种"条件限制"的写法在竞赛中非常实用,可以防止用户在不合适的时候执行操作导致系统状态混乱。
更多推荐



所有评论(0)