九层妖塔 起于垒土
蓝桥杯模块矩阵键盘Part_1
矩阵键盘
一、理论
1、矩阵键盘的识别与编码:
step1:判断键盘中有无按键按下
因为矩阵键盘的接口中只有四个行线是在一组I/O口上的,为了便于识别,所以将行拉高,列拉低 若四个行线不全为高电平,则表示有按键按下。
step2:判断闭合按键的所在的位置
在确认有按键按下后,即可进入确定具体闭合键的过程。
①扫描法:依次将行线置为低电平即在置某根行线为低电平时,其他线全为高电平。再逐个检查列线的状态。
②反转法:行全扫描,读取列码;列全扫描,读取行码。将行、列码组合在一起,得到按键的键码。
step3:根据闭合键的键码,采用查表法或者计算法得到自定义的键值。
2、矩阵键盘的工作方式:
①查询扫描
把键盘扫描子程序和其他子程序并列在一起,单片机循环分时运行各个子程序。当按键按下并且单片机查询到的时候立即响应键盘输入操作。
②定时扫描
定时器,灵活多样,方法较多。
③中断扫描
依赖于硬件(如四路与门等),利用外部中断。
二、原理图
三、Template1
Template1使用状态机的方法进行按键的扫描识别,用定时器来替代软件延时,没必要纠结于反转法还是行扫描法。
无法实现长按短按以及多个同时按下的识别,但写法较为简单,适用于要求较少的情况。
来自国信长天官方电子版例程的改编。
1、矩阵键盘扫描函数:
//--------------------------矩阵键盘扫描--读取矩阵键盘键值--------------//
void keyscan16(void) //每隔10ms扫描一次
{
static uchar hang; //行
static uchar key_state=0;
if(key_flag==1)key_flag = 0;
else return;
switch(key_state)
{
case 0:
{
P44=0;P42=0;P3=0X0F; //行拉高,列拉低
if(P3 != 0X0F) //有按键按下
key_state=1;
}break;
case 1:
{
P44=0;P42=0;P3=0X0F; //行拉高,列拉低
if(P3 != 0X0F) //有按键按下
{
if(P30 == 0) hang = 1;
if(P31 == 0) hang = 2;
if(P32 == 0) hang = 3;
if(P33 == 0) hang = 4;//确定列
switch(hang)
{
case 1:
{
P44=1;P42=1;P3=0XF0; //列拉高,行拉低
if(P44 == 0) {key_value=7;key_state=2;} //S7
else if(P42 == 0) {key_value=11;key_state=2;} //S11
else if(P35 == 0) {key_value=15;key_state=2;} //S15
else if(P34 == 0) {key_value=19;key_state=2;} //S19
}break;
case 2:
{
P44=1;P42=1;P3=0XF0; //列拉高,行拉低
if(P44 == 0) {key_value=6;key_state=2;} //S6
else if(P42 == 0) {key_value=10;key_state=2;} //S10
else if(P35 == 0) {key_value=14;key_state=2;} //S14
else if(P34 == 0) {key_value=18;key_state=2;} //S18
}break;
case 3:
{
P44=1;P42=1;P3=0XF0; //列拉高,行拉低
if(P44 == 0) {key_value=5;key_state=2;} //S5
else if(P42 == 0) {key_value=9;key_state=2;} //S9
else if(P35 == 0) {key_value=13;key_state=2;} //S13
else if(P34 == 0) {key_value=17;key_state=2;} //S17
}break;
case 4:
{
P44=1;P42=1;P3=0XF0; //列拉高,行拉低
if(P44 == 0) {key_value=4;key_state=2;} //S4
else if(P42 == 0) {key_value=8;key_state=2;} //S8
else if(P35 == 0) {key_value=12;key_state=2;} //S12
else if(P34== 0) {key_value=16;key_state=2;} //S16
} break;
}
}
else
{
key_state=0;
}
}break;
case 2:
{
P44=0;P42=0;P3=0X0F; //行拉高,列拉低
if(P3 == 0X0F) //按键放开
key_state=0;
}break;
}
}
2、定时器初始化及定时器服务函数:
//----------------------------定时器1服务函数,数码管显示&按键消抖----------------//
void Timer1_Service()
{
static uchar in_T1=0; //1ms定时器T1计满溢出的次数
TR1 = 1; //定时器1开始计时
if(TF1 == 1) //TF1为1,1ms定时时间已到
{
Timer1Init(); //关定时器;重装初值;清除TF1标志
SEG_Disp(SEG_Code,SEG_Position); //动态数码管显示,1ms执行一次
if(++SEG_Position == 8) SEG_Position=0;
if(++in_T1 == 10)
{
in_T1=0;
key_flag = 1; //10ms按键扫描标志位置1
}
TR1 = 1; //定时器1开始计时
}
}
//------------------------------------定时器1初始化,------------//
void Timer1Init(void) //1毫秒@12.000MHz
{
TR1 = 0; //关定时器1
AUXR |= 0x40; //定时器时钟1T模式
TMOD &= 0x0F; //设置定时器模式
TL1 = 0x20; //设置定时初值
TH1 = 0xD1; //设置定时初值
TF1 = 0; //清除TF1标志
//TR1 = 1; //定时器1开始计时
}
3、部分主程序:
bit key_flag = 0; //按键10ms扫描标志位
uchar key_value=0; //按键值
void main(void)
{
All_Init();
Timer1Init(); //1毫秒@12.000MHz定时器1初始化
while(1)
{
keyscan16();
sprintf(SEG_Buf,"%04u",(uint)key_value);
SEG_Tran(SEG_Buf,SEG_Code); //动态数码管显示转换
Timer1_Service(); //定时器1服务函数,数码管显示
}
}
4、Notes:
●key_flag
按键扫描标志位,初始值为0。
○在定时器服务函数Timer1_Service()
中定时10ms到key_flag
被置1;
○进入矩阵键盘扫描函数keyscan16()
中先判断key_flag
是否为1,若为1则先将key_flag
置0再继续执行,若为0则跳出。
○key_flag
按键扫描标志位保证了10ms进行一次按键扫描
●矩阵键盘扫描函数keyscan16()
每10ms执行一次按键扫描,子函数里的静态局部变量按键状态 key_state
有0,1,2三个值,初始值为0。
○一次按键识别要进行三次按键扫描。
○ key_state
为0表示本次按键识别还未开始。10ms到进行第一次按键扫描,有无按键按下,若检测到低电平,将key_state
置1,表示又可能有按键按下;若无低电平,则继续保持为0,等待下一个10ms。
○ key_state
为1表示已经扫描到一次低电平。10ms到进行第二次按键扫描,先检测有无按键按下,若检测到低电平就继续进行按键值的判断,并将key_state
置为2;若没检测到低电平,则将 key_state
置零,本次按键识别结束。
○ key_state
为2表示已经进行完按键是否按下和具体按键键值的判断,进行松手检测,防止重入。若按键以放开,将key_state
置0,本次按键识别结束。
●定时器溢出响应依旧使用的是查询法,担心如果用中断法响应定时器溢出,会对其他部分产生影响。自拟了一个定时器服务子函数Timer1_Service()
,通过查询定时器1的溢出标志位TF1
来决定是否进入定时器服务子函数Timer1_Service()
。使用静态局部变量in_T1
来判断计时溢出的次数,10次时,10ms时间到。实现数码管和按键公用一个定时器。
●采用状态法进行按键识别扫描,系统实时性好,且每10ms扫描可以防止抖动。
●无法实现长按短按以及多个同时按下的识别,但写法较为简单。
●来源于国信长天的电子版例程,原为中断法响应定时器溢出,改为查询标志位法。还有其他细节和习惯的修改。