RYMCU

Nebula Pi 开发板应用(一)—利用状态机的思想实现按键检测

ii11nnocent 2 月前
# 状态机 # cpu时间

在使用按键时,常见的使用方法是识别按键是否按下—消抖(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。具体的过程可以用下面的状态迁移图来表示:
状态迁移图.png

具体的代码实现如下(按下 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();		//读取按键值
}
后发布评论