在嵌入式体系中,用的最多的输入设备便是按键,用户的使用需求可经过相应按键传递到体系软件中,软件转而完结用户恳求,完成简略的人机交互。笔者此处就矩阵按键的完成作一个简略的介绍。
1. 按键输入概述
按键是一种常开型按钮开关,平常键的二个触点处于断开状况,按下键时它们才闭合。按键操控电路便是用来实时监督按键,当有键接下时,电路监控中的输入引脚电平发生改变,检测到这种改变后,操控电路进行按键扫描,定位按键的方位,并把相关的按键信息反馈回上一层使用中。常见的按键输入规划有独立式按键,矩阵式按键。独立式按键每个键占用一个IO口,电路装备灵敏,软件简略,但按键较多时,IO口糟蹋大。矩阵式按键适用于按键数量较多的场合,由行线和列线组成,按键坐落队伍的交叉点上。节约IO口。一般按键操控电路经过查询方法或中止方法去检测按键的输入,查询方法需占用必定的cpu资源,查询频率太低或许形成按键输入丢掉,太高糟蹋cpu资源,一般按键查询频率约50HZ较适宜。中止方法需占用cpu一路外部中止,但不会占用cpu资源,只需有按键按下时,cpu即可立刻检测到输入,进行扫描并得到按键值。
2. 硬件规划
笔者此处选用4×4的矩阵按键规划,当然,矩阵键盘可经过四个肖特基二极管构成四输入的与门(可参阅笔者这篇文章<浅谈小信号肖特基二极管在数字电路中的使用>),连接到单片机的外部中止引脚,然后完成中止方法检测按键输入。为兼容现在开发板常见的矩阵按键规划,笔者把4×4的矩阵按键接口接在P1口,经过查询方法检测按键输入。
图2-1 4×4矩阵按键
3. 驱动完成
因为咱们选用的是查询方法按键规划,因而单片机需必定的频率去扫描P1口的按键,一般这个频率约50HZ较适宜,为确保这个扫描频率,一般是经过定时器发生时标周期性进行履行扫描。P1.4~P1.7列线经过上拉电阻接到VCC上,P1.0~P1.3行线发生相应的扫描信号,无按键,列线处于高电平状况,有键按下,列线电平状况将由与此列线相连的行线电平决议。行线电平为低,则列线电平为低,行线电平为高,则列线电平为高。
按键扫描函数如下,该函数需周期履行,以扫描按键的状况。以51单片机为例,P1.0~P1.3逐行输出扫描信号,在Key.h模块头文件完成接口宏KeyOutputSelect()
#define KeyOutputSelect(Select) {P1 = ~(1<<(Select));}
输出扫描线后,需求读取对应扫描线的按键状况(P1.4~P1.7),同样在Key.h模块头文件完成引脚状况读取接口宏KeyGetPinState()
#define KeyGetPinState() (P1>> 4)
读取了对应扫描线下的按键引脚状况,就需判别哪些引脚电平为0(按下),对读到的引脚状况进行取反转化成对引脚状况变量进行搜1算法,得到键值的速度能到达最快,而且多个按键一起按下时也能够正确得到优先级最高的按键。按键有用按下会得到0~15的键值,无按键按下时得到键值16。
voidKeyScan()
{
unsigned char i;
unsigned char KeyValue;
unsigned char PinState;
if (KeyState.State == STATE_DISABLE) {
return; // 按键禁用时,不对键盘进行扫描
}
// 键值为0~15,未按键键值为16,恣意多的键按下均能
// 正确回来优先级最高的键值
KeyValue = 0;
for (i=0; i<4; i++) {
KeyOutputSelect(i); // 输出扫描线
// 得到对应扫描线时的按键状况
PinState = KeyGetPinState();
// 有键按下时,PinState中有0的方位即为键值方位
PinState = ~PinState;
// 查找Pinstate第一个为1的位
if (!(PinState & 0xf)) {
KeyValue += 4;
conTInue; // 该扫描线没有按键按下,进入下一扫描线
}
// 该扫描线有键按下,对半进行检索1的方位
if (!(PinState & 0x3)) {
KeyValue += 2; // 低2位(P1.4~P1.5)没有按下
PinState >>= 2; // 移位检索(P1.6~P1.7)
}
if (!(PinState & 0x1)){
KeyValue += 1;
}
break; // 有鍵按下,退出持续扫描
}
KeyStore(KeyValue); // 保存按键状况
}
得到了按键值后,咱们需求对按键值进行处理并依据按键状况把或许发生的按键音讯保存进缓冲区中,以便用户程序读取处理。按键一般有按下、松手、长按这几个状况,需求支撑按下检测、松手检测、长按、连击的功用,而且需求对按键进行去抖滤波。按键的状况往往会在这几种状况进行切换,因而,对按键进行状况机编程是适当明晰的思路。咱们在KeyStore()函数中完成对按键状况的搬运判别,在模块中咱们经过按键状况结构变量KeyState来盯梢记载按键的状况
typedef struct {
unsigned char State; // 按键的各个状况搬运
unsigned int TImeCount; // 用来盯梢各个状况的计时
} KEY_STATE;
staTIc KEY_STATE KeyState; // 按键状况机状况搬运
检测到相应的按键事情后(KEY_UP、KEY_DOWN、KEY_LONG),需发生相应的按键音讯保存进按键缓存区,一般能够拓荒一个按键行列缓存,以便保存多个发生的按键音讯,不会因用户代码未能及时处理按键而形成按键丢掉,笔者此处为防止杂乱,以一个按键缓冲为例,按键事情结构变量KeyBuffer用来保存按键音讯
typedef struct {
unsigned char Value;
unsigned char State;
} KEY_EVENT;
// 按键扫描得到的键值存放在KeyBuffer中,包括键值及键状况
staTIc volatile KEY_EVENT KeyBuffer;
按键消抖以及长按均是需求以时刻为判别规范,咱们在模块中界说消抖时刻以及长按时刻判定以及相应的状况宏
// 按键的扫描周期为20ms
#define WOBBLE_COUNT 1 // 按键消抖计数,1个按键扫描周期(20ms)
#define LONG_COUNT 100 // 长按100个扫描周期判别为长按(2S)
#define STATE_INIT 0x0 // 按键初始化状况
#define STATE_WOBBLE 0x1 // 按键消抖状况
#define STATE_LONG 0x2 // 按键长按检测状况
#define STATE_RELEASE 0x3 // 按键开释状况
#define STATE_DISABLE 0x4 // 按键禁用状况
完好的KeyStore()函数完成如下
static voidKeyStore(unsigned char Value)
{
static unsigned char LastValue;
switch (KeyState.State) {
case STATE_INIT: // 初始状等候按键
if (Value < KEY_NULL) {
// 记载下按下的键并进入消抖状况
LastValue = Value;
KeyState.TimeCount = WOBBLE_COUNT -1;
KeyState.State = STATE_WOBBLE;
}
break;
case STATE_WOBBLE:
if (KeyState.TimeCount) {
KeyState.TimeCount–; // 消抖计时未到
break;
}
// 消抖后再次判别为同一键值则以为键按下保存键值
// 并进入到长按检测
来历;21ic