一:文章概述
本文章基于我上篇博客有关独立按键单击、长按以及双击状态的检测思路进行分析改善实现了对蓝桥杯单片机4*4矩阵键盘的单击、长按、双击状态的检测。整个代码实现思路主要由两部分组成:1、在定时器中每隔2ms进行一次按键键值的读取判断矩阵键盘中哪个按键被按下(矩阵键盘按键键值规定为0-15)。2、Key_Scan_Board()函数读取到键值代表按键第一次已经被按下后执行KeyState1()判断该键值所代表按键的状态。具体实现思路见代码注释及下文分析。
二:键值获取原理(Key_Scan_Board()函数分析)
step1:依次将其中一行置为低电平,其他行为高电平
由图可知矩阵键盘4行都连接在单片机P3口上,因此可以直接对P3口进行操作不再单独进行位操作,使用for循环轮流将每一行置低电平。
for(i=0;i<=3;i++)
{
P3=(P3|0xFF)&(~(1<<i));
}
step2:依次判断矩阵键盘每一列是否读出低电平
如果在第i行为低电平时第j列读出低电平表示KEY[i,j]按下,无按键按下时键值为0xFF。
step3:返回按键键值
矩阵键盘每行四个,键值设置为0-15,第i行第j列的键值即为value=i*4+j;
三:代码解析
3.1 board_key.c
#include "board_key.h"
sbit L0=P4^4; //矩阵键盘每列引脚定义
sbit L1=P4^2;
sbit L2=P3^5;
sbit L3=P3^4;
volatile bit KeyTimerState=0; //控制定时器是否开始计时按键按下时间
volatile bit KeyDoubleTimerState=0; //控制定时器是否开始计时双击间隔时间
volatile u16 PressTime=0; //按键按下时间
volatile u16 PressDoubleTime=0; //双击间隔时间
volatile bit PressState=1; //判断是否为误触
volatile bit KeyDoubleCount=0; //双击标志位
volatile bit flag=0; //第一次进入函数标志,只有第一次进入KeyState1()函数时需要将键值保存到buf中
unsigned char buf;//储存按键值
unsigned char key;
unsigned char keystate[16][3]; //16行3列的数组表示16个按键每个按键有3种状态(keystate[i][0]:单击 keystate[i][1]:长按 keystate[i][2]:双击)
unsigned char *keyp=0; //指向keystate[i][j]的地址,用于main函数中判断当前处于哪个按键的哪个状态
extern void SmgDisplay(void);
void Delay2ms() //@12.000MHz
{
unsigned char i, j;
i = 24;
j = 85;
do
{
while (--j);
} while (--i);
}
void Timer0Init(void) //2毫秒@12.000MHz
{
AUXR |= 0x80; //定时器时钟1T模式
TMOD &= 0xF0; //设置定时器模式
TL0 = 0x40; //设置定时初始值
TH0 = 0xA2; //设置定时初始值
TF0 = 0; //清除TF0标志
TR0 = 1; //定时器0开始计时
ET0=1;
EA=1;
}
void Timer0(void) interrupt 1
{
if(KeyTimerState==1)
PressTime++;
else
PressTime=0;
if(KeyDoubleTimerState==1)
PressDoubleTime++;
else
PressDoubleTime=0;
key=Key_Scan_Board(); //2ms获取一次按键键值,提高按键检测效率
SmgDisplay();
}
unsigned char Key_Scan_Board(void) //键值获取函数,无按键按下时返回值为0xFF,有按键按下时返回其对应键值
{
unsigned char i,j,value;
value=0xFF;
for(i=0;i<=3;i++)
{
P3=(P3|0xFF)&(~(1<<i));
if(L0==0)
{
Delay2ms(); //消抖
if(L0==0)
{
j=0;
value=i*4+j;
}
}
else if(L1==0)
{
Delay2ms();
if(L1==0)
{
j=1;
value=i*4+j;
}
}
else if(L2==0)
{
Delay2ms();
if(L2==0)
{
j=2;
value=i*4+j;
}
}
else if(L3==0)
{
Delay2ms();
if(L3==0)
{
j=3;
value=i*4+j;
}
}
}
return value;
}
unsigned char * KeyState1(void) //状态判断函数,返回值为二维数组某个元素的首地址
{
if(key!=0xFF&&KeyTimerState==0&&KeyDoubleCount==0&&flag==0) //表示有按键刚按下
{
KeyTimerState=1; //开始计算按下时长
PressState=0; //按键按下标志
buf=key; //储存键值,程序循环执行时键值在不断改变,储存键值可用于在判断完状态后方便返回地址
flag=1;
}
if(PressState==0&&KeyDoubleCount==0&&key==0xFF) //按键松开
{
if(PressTime<250) //500ms内松开为短按
{
KeyDoubleCount=1; //进入双击检测
PressState=1;
KeyTimerState=0;
KeyDoubleTimerState=1;
}
if(PressTime>500)
{
KeyTimerState=0;
PressState=1;
keyp=&keystate[buf][1];
flag=0;
return keyp; //返回按键buf的长按状态
}
}
if((KeyDoubleCount==1)&&(PressDoubleTime<115)&&(key!=0xFF)) //判断230ms内是否有按键再次被按下
{
KeyDoubleCount=0;
KeyDoubleTimerState=0;
while(key!=0xFF); //按键松开返回双击,若想按键按下生效可去掉该语句
keyp=&keystate[buf][2];
flag=0;
return keyp; //返回按键buf的双击状态
}
if(PressDoubleTime>150)
{
KeyDoubleCount=0;
KeyDoubleTimerState=0;
keyp=&keystate[buf][0];
flag=0;
return keyp; 返回按键buf的单击状态
}
return 0;
}
3.2 board_key.h
#ifndef BOARD_KEY_H
#define BOARD_KEY_H
typedef unsigned char u8;
typedef unsigned int u16;
#include "stc15.h"
#include "intrins.h"
extern unsigned char keystate[16][3];
extern unsigned char *keyp;
void Timer0Init(void);
unsigned char * KeyState1(void);
unsigned char Key_Scan_Board(void);
#endif
3.3 main.c
#include "stc15.h"
#include "intrins.h"
#include "board_key.h"
#define HC138(x) P2=(P2&0x1F)|(x<<5)
u8 code smg_dm[]={0xc0, 0xf9, 0xa4, 0xb0,
0x99, 0x92, 0x82,0xf8,
0x80, 0x90,0xff};
u8 smg_num[]={10,10,10,10,10,10,10,10};
u8 com=0;
void SmgDisplay(void);
void Init(u8 i)
{
P2&=0x1F;
switch(i)
{
case 4:P0=0xFF;HC138(i);P2&=0x1F;P0=0x00;break;
case 5:HC138(i);break;
case 6:HC138(i);break;
case 7:P2&=0x1F;P0=0xFF;HC138(i);break;
}
P2&=0x1F;
}
void main(void)
{
Init(4);
Init(5);
Init(6);
Init(7);
Timer0Init();
while(1)
{
keyp=KeyState1();
if(keyp==&keystate[5][0]) //当keyp指向&keystate[5][0]时表示键值为5的按键是单击状态,以下判断相同
{
smg_num[0]=0;
}
else if(keyp==&keystate[5][1])
{
smg_num[0]=1;
}
else if(keyp==&keystate[5][2])
{
smg_num[0]=2;
}
else if(keyp==&keystate[4][0])
{
smg_num[0]=4;
}
}
}
void SmgDisplay(void)
{
P0=0xFF;
HC138(6);
P0=(1<<com);
HC138(7);
P0=0xFF;
P0=smg_dm[smg_num[com]];
(com==7)?(com=0):(com++);
}
四:总结
鉴于矩阵键盘按键数目较多不适用上次独立按键检测时采用的列举按键状态的情况,因此采用一个二维数组表示按键的各种状态,然后使用指针指向该数组的某个首地址来判断矩阵键盘按键状态的方式。