从开始学51就接触到按键扫描,起初接触到郭天祥的delay滤波,方法虽然简陋,但是确实有效。
用了一段时间后,偶然接触到状态机扫描按键。那会儿没有啥数电知识懂不起状态机,硬啃啃懂了,顿时觉得怎么又这种机智的想法!
持续使用此方法将近一年半,期间自己也做了几种扩展,也能正常表达出算法使用,但是唯一的缺点就是代码比较长。
先贴我写过的状态机扫描按键的程序,包括独立按键,以及矩阵式的按键。
注意:所有扫描均用定时器定时实现,通常是20ms调用一次扫描且检测到低电平认为触发按键
(补)程序的结构是这样的:
void main(){ //some initialization while (1){ if (key_flag){//这个key_flag由定时器置1 大概20ms一次 key_flag=0; keyscan(); //按键判断及相应动作 } //other task } }
矩阵式(一):
1 sbit r1=P3^0; 2 sbit r2=P3^1; 3 sbit r3=P3^2; 4 sbit r4=P3^3; 5 sbit c1=P4^4; 6 sbit c2=P4^2; 7 sbit c3=P3^5; 8 sbit c4=P3^4; 9 10 unsigned char matrix_scan() 11 { 12 static unsigned char state=0x00; 13 unsigned char keyval=16; 14 15 switch (state) 16 { 17 case 0x00: 18 r1=r2=r3=r4=0; 19 c1=c2=c3=c4=1; 20 if (!(c1&c2&c3&c4)) 21 state=0x01; 22 break; 23 case 0x01: 24 r1=r2=r3=r4=0; 25 c1=c2=c3=c4=1; 26 if (!(c1&c2&c3&c4)) 27 { 28 r1=0; 29 r2=r3=r4=c1=c2=c3=c4=1; 30 if (!c1) keyval=0; 31 else if (!c2) keyval=1; 32 else if (!c3) keyval=2; 33 else if (!c4) keyval=3; 34 35 r2=0; 36 r1=r3=r4=c1=c2=c3=c4=1; 37 if (!c1) keyval=4; 38 else if (!c2) keyval=5; 39 else if (!c3) keyval=6; 40 else if (!c4) keyval=7; 41 42 r3=0; 43 r2=r1=r4=c1=c2=c3=c4=1; 44 if (!c1) keyval=8; 45 else if (!c2) keyval=9; 46 else if (!c3) keyval=10; 47 else if (!c4) keyval=11; 48 49 r4=0; 50 r2=r3=r1=c1=c2=c3=c4=1; 51 if (!c1) keyval=12; 52 else if (!c2) keyval=13; 53 else if (!c3) keyval=14; 54 else if (!c4) keyval=15; 55 56 state=0x02; 57 r1=r2=r3=r4=0; 58 c1=c2=c3=c4=1; 59 } 60 break; 61 case 0x02: 62 if (c1&c2&c3&c4) 63 state=0x00; 64 break; 65 } 66 return keyval; 67 }
特点:行列式的按键它的引脚不一定需要连续,如上的p42p44。实际应用中可能不常见,但是蓝桥杯的板子上是这样的
矩阵式(二):
1 unsigned char matrix_scan() 2 { 3 static signed char state_cnt = 0x00;//状态记录 4 unsigned char key_state=0;//键值记录 5 6 switch(state_cnt) 7 { 8 case 0x00://闲置 9 P3 = 0xf0; //高四位高电平低四位低电平 10 if (P3 != 0xf0) 11 { 12 state_cnt = 0x01;//进入触发状态 13 break; 14 } 15 case 0x01://触发 16 if (P3 != 0xf0)//确实有按键触发 17 { 18 state_cnt = 0x02;//进入释放状态 19 key_state = P3&0xf0; 20 P3 = 0x0f; 21 key_state |= P3&0x0f; 22 P3 = 0xf0; 23 break; 24 } 25 else//毛刺等干扰 26 { 27 state_cnt = 0x00; 28 break; 29 } 30 case 0x02://释放 31 if (P3 == 0xf0)//上次置的电平 32 { 33 state_cnt = 0x00;//完成一次按键动作 34 break; 35 } 36 default :break; 37 } 38 return key_state;//返回键值 39 }
特点:代码相对上一个较短,但是需要io连续
接下来是独立式:
第一种:
void key_scan() {/*定义静态变量state_cnt记住状态*/ static unsigned char state_cnt = 0x00; //初始化为闲置状态 switch (state_cnt) { case 0x00:/*闲置状态*/ if (key==0)//低电平可能有按键按下 进入状态1 { state_cnt = 0x01; break; } case 0x01:/*触发状态*/ if (key==0)//低电平确实有按键按下 执行动作并进入状态2 { state_cnt = 0x02; //->此处放置要执行的函数等<-// led=!led; break; } else //高电平为毛刺等 还原状态0 { state_cnt = 0x00; break; } case 0x02:/*待释放状态*/ if (key==1)//高电平为一个按键动作结束 { state_cnt = 0x00; break; } default :break; } }
特点:和第二个矩阵扫描的原理相同, 一次仅能扫描一个按键,也可以更改代码实现扫描一行或者一列,但是肯定必我接下来介绍的这种刚刚发现的代码多!!
重头戏:
1 unsigned char trg,cont; 2 void keyscan(void) 3 { 4 unsigned char tmp=P2^0xff; 5 trg=tmp&(tmp^cont); 6 cont=tmp; 7 }
你没有看错!!就是这三行代码,实现了扫描连续的独立按键。
下面简单的分析一下:
第一行定义两个静态变量,trg是trigger,cont是continue,字面意思理解。先说:trig只会在第一次按下的时候置一,cont只要当前次扫描到某一个按键触发,那么对应的cont里的那一位就为1
第四行用数电解释一波:P2和0xff的抑或,用公式展开可以得到:tmp=(P2 AND 0) OR [(NOT P2) AND 0xff] (NOT P2是非P2的意思,可能不标准,就是按位取反)
进一步化简就是tmp=NOT P2 也就是将读取的P2所有io取反。
第五行:直接贴出化简结果 (NOT P2)AND(NOT cont)
第六行:cont更新值
合起来就是trg的某一位只会在那一位对应的按键第一次检测到按下的时候为1,然后cont会在那个按键检测到按下的时候一直为1。
一旦第二次检测到该按键还是按下,trg为0,cont仍为1。大致的状况就是这样, 然后就随意扩展使用了。
trg可以检测按下一次,取某一位相与就可以了,如:
if (trg & 0x01) { //1号按键对应的功能 }
cont可以检测长按甚至检测按下多久,如:
if (cont & 0x01) { if (++hold >= 150)//按下20ms*150=3s 因为程序20ms调用一次 { hold=0; //func } }
相应的按键释放则检测trg和cont那一位皆为0。如:
if (trg&cont&0x01) { //relax func }
date:21点22分2018年4月27日
相应的结构:
void main(){ //some initialization while (1){ if (key_flag){//这个key_flag由定时器置1 大概20ms一次 key_flag=0; keyscan(); //按键判断及相应动作 if (trg & 0x01) { //1号按键单次按下对应的功能 } if (cont & 0x01) { if (++hold >= 150)//按下20ms*150=3s 因为程序20ms调用一次 { hold=0; //1号按键长按对应的功能 } } if (trg&cont&0x01) { //1号按键释放对应的功能 } } //other task } }
date:2018-11-25 13:49:42