由上图可以看到若J5跳线帽接地,就S4~S7就可以当做四路独立按键,若接到P44,则就是4*4的矩阵键盘。
相对传统的按键延时消抖方案,这里我采用更高效,更经典,更偏向产品级应用的状态机方案:
u8 Trg=0,Cont=0;
//独立按键
void Key_Scan()
{
//状态机
u8 ReadData = P3^0XFF;
Trg = ReadData&(ReadData^Cont); //记录短按键值
Cont = ReadData; //记录长按键值
Key_Read(); //判断哪个按键按下
}
void Key_Read()
{
if(Trg == 0x08){ //S4短按
//按键任务区
Set_Leds(1,1);//打开LED1
}
else if(Trg == 0x04){ //S5短按
Set_Leds(2,1); //打开LED2
}
else if(Trg == 0x02){ //S6短按
Set_Leds(3,1);
}
else if(Trg == 0x01){ //S7短按
Set_Leds(4,1);
}
else;
}
在应用的时候我们只需要知道三个状态对应的按键情况就行:
1.按键未按下或者松手时:Trg=0,Cont=0
2.按键刚按下,且Key_Scan只运行一遍时:Trg=键值,Cont=键值
3.按键持续按下状态时:Trg=0,Cont=键值
然后我们就能根据这三个状态来判断长按,短按,未按下,以及后续扩展双击等功能
扩展长按功能:由于按键持续按下状态Cont=键值,且Trg=0;因此我们可以在此条件基础上增加一个计时变量,当变量大于一定值则判定为长按。
u8 Trg=0,Cont=0,Num=0;
bit LONG_CLICKED=0;
//独立按键
void Key_Scan() //20ms扫描一次
{
//状态机
u8 ReadData = P3^0XFF;
Trg = ReadData&(ReadData^Cont); //记录短按键值
Cont = ReadData; //记录长按键值
if((Trg==0)&&(Cont!=0)) //记录按下时长
{
if(++Num==100) //满足长按2S
{
LONG_CLICKED=1;
Num++;
}
}
if((Trg==0)&&(Cont==0)) //松手或者未按下
{
//此处可扩展双击
Num=0;
}
Key_Read(); //判断哪个按键按下
}
void Key_Read()
{
if(Trg == 0x08){ //S4短按
//按键任务区
Set_Leds(1,1);
}
else if(Trg == 0x04){ //S5短按
Set_Leds(2,1);
}
else if(Trg == 0x02){ //S6短按
Set_Leds(3,1);
}
else if(Trg == 0x01){ //S7短按
Set_Leds(4,1);
}
else;
if(!LONG_CLICKED) return;
if(Cont==0x08){ //S4长按
Set_Leds(1,0);
}
else if(Cont==0x04){ //S5长按
Set_Leds(2,0);
}
else if(Cont==0x02){ //S6长按
Set_Leds(3,0);
}
else if(Cont==0x01){ //S7长按
Set_Leds(4,0);
}
else;
LONG_CLICKED=0;
}
扩展双击功能:双击功能则是在松手状态下去判断长按功能的计时变量,小于一定值则可认为按键短按一次,此时再创建一个计时变量,当此计时变量大于一定值而小于一定值时按键再次按下则可判定为双击。好像往年都未曾考查过此功能,所以我就不写了,有兴趣的同学可以试试。
矩阵键盘的处理方案同独立按键,我就不细讲了,理解记忆即可
u8 Trg=0,Cont=0,Num=0;
bit LONG_CLICKED=0;
//矩阵键盘
void Keys_Scan()
{
//状态机
u8 ReadData,ColumnData;
P3 = 0XF0; //行低列高
P42 = 1;
P44 = 1;
P36 = P42; //IO映射,便于数据操作
P37 = P44;
ColumnData = P3; //暂存列数据
P3 = 0X0F; //行高列低
P42 = 0;
P44 = 0;
ReadData=(P3 | ColumnData)^0XFF;
Trg = ReadData&(ReadData^Cont); //记录短按键值
Cont = ReadData; //记录长按键值
if((Trg==0)&&(Cont!=0)) //记录按下时长
{
if(++Num==100) //满足长按2S
{
LONG_CLICKED=1;
Num++;
}
}
if((Trg==0)&&(Cont==0)) //松手或者未按下
{
//此处可扩展双击
Num=0;
}
Keys_Read(); //判断哪个按键按下
}
/****************矩阵键码表******************
S7-0X81 S11-0X41 S15-0X21 S19-0X11
S6-0X82 S10-0X42 S14-0X22 S18-0X12
S5-0X84 S9-0X44 S13-0X24 S17-0X14
S4-0X88 S8-0X48 S12-0X28 S16-0X18
*******************************************/
void Keys_Read()
{
//用到再加,一一列举程序运行慢
if(Trg == 0x81){ //S7按下
Set_Leds(1,1);
}
else if(Trg == 0x48){ //S8按下
Set_Leds(2,1);
}
else;
if(!LONG_CLICKED) return;
if(Cont == 0x81){ //S7长按
Set_Leds(1,0);
}
else if(Cont == 0X48){ //S8按下
Set_Leds(2,0);
}
else;
LONG_CLICKED=0;
}
#include "main.h"
void System_Init(void);
void Delay20ms() //@12.000MHz
{
unsigned char data i, j;
i = 234;
j = 115;
do
{
while (--j);
} while (--i);
}
void main()
{
System_Init();
while(1)
{
Delay20ms();
//Key_Scan();
Keys_Scan();
}
}
void System_Init()//系统上电初始化
{
//先锁存蜂鸣器,继电器所在573输出低电平,防止上电乱叫
P25=1;P26=0;P27=1; //74HC138-->Y5=0,else=1-->Y5C=1,else=0
P0=0X00; //ULN2003输入经过非门送入达林顿管,低电平有效
P25=0;P26=0;P27=0;//锁存数据
//关闭所有LED灯
P25=0;P26=0;P27=1; //74HC138-->Y4=0,else=1-->Y4C=1,else=0
P0=0XFF;
P25=0;P26=0;P27=0;//锁存数据
}
如果有用到长按功能,20ms运行一次最好是通过定时器去扫描实现,后续笔记中我会提供示例。