基于有限状态机与STM32实现按键扫描

一开始学习单片机编程时,都是用十分简单的软件延时实现按键检测。以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);
		}
	}
}

 

  • 4
    点赞
  • 38
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值