一开始学习单片机编程时,都是用十分简单的软件延时实现按键检测。以stm32为例,
uint8_t key_scan(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
{
if( GPIO_ReadInputDataBit(GPIOx,GPIO_Pin) == ON )
{
while(GPIO_ReadInputDataBit(GPIOx,GPIO_Pin) == ON);
return ON;
}
else
return OFF;
}
但是当真正开始做项目的时候,就会发现这种方法其实不够圆润。因为有可能会用到两个按键来实现十几种功能,再加上其他的模块,ADC,PWM还有需要用到通信协议的模块,这样写对CPU的资源是个极大的浪费,而且效率很低。因此基于有限状态机的按键检测就出现了。那么什么是状态机呢?
有限状态机其实是一种概念性机器,表示有限个状态以及在这些状态之间的转移和动作等行为的框图(在程序上)。
以我的程序绘成的图为例:
以高电平作为标志,在S1时检测输入电平是否为高,是高电平则运行至S2,否则保持在S1;
在S2时再次判断判断输入电平是否为高,若为低则说明刚刚的改变是干扰,回到S1,若为高电平则说明按键被按下,运行至S3;
在S3可判断这次对按键的操作是长按还是短按,具体方法是判断电平是否还是为高,若为高则令状态机保持在S3,同时定义一个变量(建议用指针,具体见代码),使其一直自加。若为低则运行至S4;
在S4时判断那个变量是否大于某个数值,大于则为长按,小于则为短按,并执行相关的操作,执行完之后令状态机回到S1
这种写法是可以利用时间片的形式,每隔10ms运行一次状态机,即可实现消抖的目的,还可以对长按的时间精确控制。
运用状态机+时间片,大幅提升CPU的资源的利用率,而且效率高了不少。
时间片的话举个例子,比如我一个周期30ms,10ms需要执行一次按键扫描,20ms执行一次ADC,那么利用时间片的话程序就是:
注:Time是在定时器中断里每隔1ms自加一次。
if( Time %10 == 0 ){
KeyScan();
}
if( Time %20 == 0 ){
ADC();
}
if( Time > 30 ){
Time = 1;
}
贴上我写的完整代码:
Key.h:
#ifndef _BSP_KEY_H
#define _BSP_KEY_H
#include "stm32f10x.h"
#define KEY0_GPIO_PORT GPIOE
#define KEY0_GPIO_PIN GPIO_Pin_0
#define KEY0_GPIO_CLOCK RCC_APB2Periph_GPIOE
#define KEY1_GPIO_PORT GPIOE
#define KEY1_GPIO_PIN GPIO_Pin_1
#define KEY1_GPIO_CLOCK RCC_APB2Periph_GPIOE
#define KEY2_GPIO_PORT GPIOE
#define KEY2_GPIO_PIN GPIO_Pin_2
#define KEY2_GPIO_CLOCK RCC_APB2Periph_GPIOE
#define ON 1
#define OFF 0
#define LONGTIME 8000
//---------定义状态Sx的枚举类型
typedef enum {
FsmState_1 = 1,
FsmState_2 = 2,
FsmState_3 = 3,
FsmState_4 = 4,
FsmState_5 = 5,
}FsmState_x;
//---------定义按键的结构体
typedef struct FsmTable_s{
uint8_t event; /* 触发事件 */
uint8_t CurState; /* 当前状态 */
void (*EventFunction)(void); /* 动作函数 */
uint16_t Time; /* 时间计数 */
uint16_t GpioPin; /* 按键引脚 */
GPIO_TypeDef * GpioPort; /* 引脚GPIO */
}Key;
void key_GPIO_Init(void);
void Key_1_EventFunction(void);
void Key_1_LongEventFunction(void);
void Key_2_EventFunction(void);
void Key_2_LongEventFunction(void);
void Key_Fsm(Key *key);
#endif
Key.c:
/**********************************************
*
* 状态机相关
*
**********************************************/
/**********************************************
*
* 按键1短按时的事件函数
*
*********************************************/
void Key_1_EventFunction()
{
}
/**********************************************
*
* 按键1长按时的事件函数
*
*********************************************/
void Key_1_LongEventFunction()
{
}
/**********************************************
*
* 按键2短按时的事件函数
*
*********************************************/
void Key_2_EventFunction()
{
}
/**********************************************
*
* 按键2长按时的事件函数
*
*********************************************/
void Key_2_LongEventFunction()
{
}
/**********************************************
*
* 基于状态机的按键扫描
* 输入:Key类型的指针变量
* 输出:无
*
*********************************************/
void Key_Fsm(Key *key)
{
void (*EventFunction)(void);
switch( key->CurState )
{
case FsmState_1:{//--------状态1
if( GPIO_ReadInputDataBit(key->GpioPort,key->GpioPin) == ON ){
key->CurState = FsmState_2;
}
}break;
case FsmState_2:{//--------状态2
if( GPIO_ReadInputDataBit(key->GpioPort,key->GpioPin) == ON ){
key->CurState = FsmState_3;
}
else{//--------干扰返回状态1
key->CurState = FsmState_1;
}
}break;
case FsmState_3:{
if( GPIO_ReadInputDataBit(key->GpioPort,key->GpioPin) == ON ){
key->Time++;//--------变量自加
key->CurState = FsmState_3;//--------保持在状态3
}
else{
key->CurState = FsmState_4;
}
}break;
case FsmState_4:{
EventFunction = key->EventFunction;//--------短按函数
if( (key->Time > LONGTIME) && (key->GpioPin == KEY0_GPIO_PIN) ){
key->Time = 0;
EventFunction = Key_1_LongEventFunction;//--------Key1的长按函数
}
if( (key->Time > LONGTIME) && (key->GpioPin == KEY1_GPIO_PIN) ){
key->Time = 0;
EventFunction = Key_2_LongEventFunction;//--------Key2的长按函数
}
key->CurState = FsmState_1;//--------返回状态1
EventFunction();//--------执行对应的函数
}break;
}
}
main.c:
#include "bsp_key.h"
extern uint16_t TimeSlice;
int main()
{
Key *key_1,*key_2;
Key key1,key2;
key_1 = &key1;
key_2 = &key2;
TIM_TimeBase_Init();
key_GPIO_Init();
key_1->GpioPin = KEY0_GPIO_PIN;
key_1->GpioPort = KEY0_GPIO_PORT;
key_1->CurState = FsmState_2;
key_1->EventFunction = Key_1_EventFunction;
key_2->GpioPin = KEY1_GPIO_PIN;
key_2->GpioPort = KEY1_GPIO_PORT;
key_2->CurState = FsmState_1;
key_2->EventFunction = Key_2_EventFunction;
while(1)
{
//--------------按键扫描,10ms执行一次--------------//
if( TimeSlice %10==0 ){
Key_Fsm(key_1);
Key_Fsm(key_2);
}
}
}