在使用按键时,常见的使用方法是识别按键是否按下—消抖(delay 延时)—再次确认按键是否按下,但是这样进行判断时会经常使用 delay 延时函数,大大降低 CPU 的效率。利用状态机的思想可以很好的解决 CPU 等待时间过长的缺陷,提高 CPU 的效率。
状态机是在进行程序编写时的一个重要概念,比如在进行按键按下与否的判断时,可以看成一个状态机,其中共有四个要素:现在的状态、触发的条件、触发后的动作及新的状态。
现在的状态指的是目前的状态,通常为按键未按下的初始状态。
触发的条件指的是当有满足的条件后,将会触发一个新的动作,或者进行一次状态的迁移。
触发后的动作指的是当条件满足后执行的动作。动作执行完毕后,可以迁移到新的状态、保持现在的状态或者回到原来的状态。
新的状态指的是条件满足后要迁移的新状态。
以按键为例,可以将识别按键的过程设定为三个状态:
(1)按键未按下的初始状态,设定为 S1
(2)确认有按键按下的状态,设定为 S2
(3)按键按下后释放的状态,设定为 S3
以 Nebula Pi 开发板上的按键为例(按下时 IO 口的电平为 0,释放时 IO 口的电平为 1),采用状态机的方法实现识别按键的过程如下:
当开发板上电时,按键处于初始状态 S1,当检测到 IO 口的电平值为 1 时,表明按键没有被按下,保持 S1 状态。当检测到 IO 口的电平值为 0 时,表明有按键按下,此时立即转入状态 S2。
在状态 S2 时,如果检测到 IO 口的电平为 1 时,表明刚才的按键按下为干扰,此时状态立即转回 S1;如果检测到 IO 口的电平为 0 时,表明确实有按键被按下,此时可以保存按键的键值,也可以转入新的状态 S3,也可以同时进行。
在状态 S3 时,如果检测到 IO 口的电平值为 0,表明按键一直被按下,此时可以进行按键按下的时长计算,也可以进行其他的操作;如果检测到 IO 口的电平值为 1,表明按键被释放了,此时可以保存按键的键值,也可以转回状态 S0。具体的过程可以用下面的状态迁移图来表示:
具体的代码实现如下(按下 K1 时,D7 点亮;释放 K1 时,K1 熄灭。长按 K1 1 秒时,D6 点亮,再次长按 K1 1 秒时,D6 熄灭):
KeyState.h:
#ifndef _KEYSTATE_H
#define _KEYSTATE_H
#include "reg52.h"
unsigned char ReadKeyState(void);
void Timer0_Init(void);
extern unsigned char Times; //按下的次数
extern bit Flag; //1s长按标志位
#endif
KeyState.c:
#include "KeyState.h"
// 位定义 Key1 按键
sbit Key1 = P1 ^0;
// 保存按键的返回值
unsigned char KeyReturn;
unsigned char Times;
bit Flag;
/****宏定义按键的初始、按下和抬起状态****/
#define Key_State_Init 0
#define Key_State_Down 1
#define Key_State_Up 2
unsigned char ReadKeyState(void) {
// 按键的状态值,初始值为0
static char KeyState = 0;
// 保存按键的电平值
unsigned char KeyPress;
// 保存P1.0口电平
KeyPress = Key1;
switch (KeyState) {
// 初始状态
case Key_State_Init:
// 读取 IO 电平
if (!KeyPress) {
// 按键返回值为0
KeyReturn = 0;
// 按键的状态转为按键按下的状态
KeyState = Key_State_Down;
}
break;
// 初始状态按下后转为按下状态
case Key_State_Down:
// 读取IO电平确认按键是否真的被按下
if (!KeyPress) {
// 按键返回值为1
KeyReturn = 1;
// 按键的状态转为按键释放的状态
KeyState = Key_State_Up;
} else {
// 如果IO电平为0,证明按键已抬起,回到初始状态
KeyState = Key_State_Init;
}
break;
// 按下以后转为抬起状态
case Key_State_Up:
// 如果按键一直按下,即没有抬起
if (!KeyPress) {
// 只要一直按下按键,Times就会10ms增加1
Times++;
// Times=100时,1s定时时间到,标志位取反并且Times值清零
if (Times == 100) {
Flag = ~Flag;
Times = 0;
}
}
// 如果按键抬起了
if (KeyPress) {
// 按键返回值为 2
KeyReturn = 2;
// 读取 IO 电平,如果是 1,证明按键已释放,回到初始状态
KeyState = Key_State_Init;
}
break;
}
return KeyReturn;
}
void Timer0_Init(void) {
TMOD = 0x01;
TH0 = 0xDC;
// 10ms 定时初值
TL0 = 0x00;
// 开定时器 0 中断
ET0 = 1;
// 开总中断
EA = 1;
// 打开定时器 0
TR0 = 1;
}
/****在中断服务函数中调用按键读取函数,然后主函数中判断键值****/
main.c:
#include "reg52.h"
#include "KeyState.h"
sbit Led = P1^7;
sbit Led2 = P1^6;
unsigned char KeyNumber = 0; //按键值保存变量
void main(void)
{
Timer0_Init();
while(1)
{
if( KeyNumber == 1 )
{
Led = 0;
}
if( KeyNumber == 2 )
{
Led = 1;
}
if( Flag == 1 )
{
Led2 = 0;
}
else Led2 = 1;
}
}
void Timer0(void) interrupt 1
{
TH0 = 0xDC;
TL0 = 0x00; //重新赋初值
KeyNumber = ReadKeyState(); //读取按键值
}