7—按键扫描修正
在之前的按键扫描程序中,使用Delay进行消抖,同时按键一直按下时会进入死循环并占用机器时间。此时MCU无法处理其他事务,会造成单片机与外设的通信出现问题,因此考虑修改程序并加入长按功能。
传统扫描
传统的扫描程序存在无意义占用系统资源的问题,对于实时性要求较高的系统不建议使用。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15void Scan_Key()
{
if(S7==0)
{
Delay(200); //消抖
if(S7==0) //再次判断按键是否按下
{
count++;
while(S7==0) //避免按下按键不松开造成多次判断
{
Show_Count(count); //需要在死循环中加入数码管扫描程序
}
}
}
}
状态机
按键按下的全过程分为四个连续的状态:
- 按键未按下:
S7=1,S7_Down=0;
- 按键按下的前沿抖动:
S7=1,S7_Down=0;
—->S7=0,S7_Down=0;
- 按键按下时:
S7=0,S7_Down=0;
—>S7=0,S7_Down=1;
- 按键松开:
S7=0,S7_Down=1;
—>S7=1,S7_Down=1;
—>S7=1,S7_Down=0;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15void Scan_Key()
{
if(S7==0&&S7_Down==0) //按键按下但标志位为0
{
Delay(200);
if(S7==0) //判断前沿抖动
{
S7_Down=1; //如果非抖动则S7_Down判断为1,即按下
}
}
if(S7==1&&S7_Down==1) //按键松开并当S7_Down标志按下时,标志为松开按键
{
S7_Down=0;
}
}
长短按扫描
优点是简短且高效,缺点是缺少消抖(可以通过增加扫描延迟到50ms解决),且程序可读性很差,不便于维护。
核心算法
1 | uchar trg=0; |
解释
在核心算法中,trg(Triger)代表触发,cont(Continue)代表连续按下。该程序能做到按下按键时trg对应位立刻变化为1,当按住按键时trg在第二次扫描时翻转为0,即仅在单次触发时响应。当按住按键时,cont为1,松开为0。
- 假定单次按下P3^0对应按键,扫描时KEY=0xfe,read_data=0x01,trg=0x01,cont=0x01。
- 假定长按P3^0对应按键,第一次扫描KEY=0xfe,read_data=0x01,trg=0x01,cont=0x01;第二次扫描时KEY=0xfe,read_data=0x01,trg=0x00,cont=0x01。当按键不松开时trg恒等于0,cont恒等于1,即验证前文所述:仅在单次触发时响应。
- 假定按下的是其他按键,例如P3^1,当trg=0x02,cont=0x02时即代表响应。
即:cont与按键状态相同,trg仅在按下时变化为1。
应用
将按键扫描函数放入中断服务函数,定时扫描,如果需要长按的话Key_Fun();
也需要放入中断以便计数。
- 短按键触发
1
2
3
4if(trg&0x01)
{
Fun();
} - 仅按下时触发
1
2
3
4if(cont&0x01)
{
Fun();
} - 长按2s触发
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17void Key_Fun()
{
static uchar count = 0;
if(cont&0x01) //当S7长按时开始计数count,50次(1s)后运行Fun()
{
count++;
if(count==50)
{
Fun();
count=0;
}
}
else //当S7非长按时清零count,避免运行时计数混乱
{
count=0;
}
}
状态转化
定时器定时2ms,每次中断扫描一次,并将按键的扫描值写入keybuff。8次扫描过后,如果keybuff全为1,则认为在16ms内按键状态均为松开,判断按键未按下;反之,当keybuff全为0则判断为按下,其他状态判断为抖动。
将按键的过程判断(按下,松开,抖动)转化为状态判断,即将时间点判断转化为时间段判断,判断时间段内的高低电平来去除某个时间点上抖动的影响,参考自此。
独立按键
1 | void Scan_Key_4(void) |
矩阵按键
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41sbit KEY_IN_1 = P4^4;
sbit KEY_IN_2 = P4^2;
sbit KEY_IN_3 = P3^5;
sbit KEY_IN_4 = P3^4;
sbit KEY_OUT_1 = P3^0;
sbit KEY_OUT_2 = P3^1;
sbit KEY_OUT_3 = P3^2;
sbit KEY_OUT_4 = P3^3;
void Scan_Key_16(void)
{
uchar i;
static uchar keyout = 0; //矩阵按键扫描输出行索引
static uchar keybuff[4][4] = {{0xff,0xff,0xff,0xff},{0xff,0xff,0xff,0xff},{0xff,0xff,0xff,0xff},{0xff,0xff,0xff,0xff}}; //矩阵按键扫描缓存区
keybuff[keyout][0] = (keybuff[keyout][0] << 1) | KEY_IN_1; //将每一行的4个按键值移入缓存区
keybuff[keyout][1] = (keybuff[keyout][1] << 1) | KEY_IN_2;
keybuff[keyout][2] = (keybuff[keyout][2] << 1) | KEY_IN_3;
keybuff[keyout][3] = (keybuff[keyout][3] << 1) | KEY_IN_4;
//消抖后更新按键状态
for(i = 0;i < 4;i ++)
{
if((keybuff[keyout][i] & 0x0f) == 0x00)
KeySta[keyout][i] = 0; //连续4次扫描值都是0,即4×4ms内都是按下状态,认为按键已平稳按下
else if((keybuff[keyout][i] & 0x0f) == 0x0f)
KeySta[keyout][i] = 1; //连续4次扫描值都是1,即4×4ms内都是松开状态,认为按键已稳定弹起
}
//执行下一次的扫描输出
keyout ++;
keyout = keyout & 0x03; //索引加到4就归零
switch(keyout) //根据索引,释放当前输出引脚,拉低下次的输出引脚
{
case 0:KEY_OUT_4 = 1;KEY_OUT_1 = 0;break;
case 1:KEY_OUT_1 = 1;KEY_OUT_2 = 0;break;
case 2:KEY_OUT_2 = 1;KEY_OUT_3 = 0;break;
case 3:KEY_OUT_3 = 1;KEY_OUT_4 = 0;break;
default:break;
}
}
本文标题:7—按键扫描修正
文章作者:Raincorn
发布时间:2020-07-31
最后更新:2020-08-01
原始链接:https://blog.raincorn.top/2020/07/31/CT107D_7_Scan_Keys/
版权声明:本文采用知识共享署名-非商业性使用 4.0 国际许可协议进行许可