【stm32单片机基础】按键状态机
前言
在单片机的教学例程中,常使用delay延迟的方式消除按键抖动,而delay延迟的方式使CPU处于空等的状态,不能进行其他任务,直到结束delay延时函数,这种阻塞的方式不便 利于多任务的情形。本文将使用非阻塞的方式消抖,并采用状态机的模式编写按键处理函数。
一、按键的消抖
按键消抖:通常的按键所用开关为机械弹性开关,当机械触点断开、闭合时,由于机械触点的弹性作用,一个按键开关在闭合时不会马上稳定地接通,在断开时也不会一下子断开,因而在闭合及断开的瞬间均伴随有一连串的抖动,按键抖动会引起一次按键被误读多次。
抖动时间的长短由按键的机械特性决定,一般为5ms~10ms。
软件消抖:硬件方法将导致系统硬件电路设计复杂化,常采用软件方法进行消抖。
软件方法去抖,即检测出键闭合后执行一个延时程序,5ms~10ms的延时,让前沿抖动消失后再一次检测键的状态,如果仍保持闭合状态电平,则确认为真正有键按下。
二、按键状态机实现
0.状态机模式
简单理解为:将一个事件划分为有限个状态,满足相应的条件,在有限个状态之间跳转;可以使用状态图来描述事件处理过程,这种方式使得程序逻辑思路更加清晰严谨。以按键为例,按键检测的过程可以分为三个状态:未按下状态、按下状态、按键释放;而在这三个状态之间跳转的条件为当前状态下按键的值。 在单片机中实现状态机最常用的语句便是switch case语句。
【状态机中如何非阻塞消抖】:使用定时器中断,定时每10ms执行一次switch case语句,即两个状态之间跳转的时间为10ms,这样便代替了delay延时。当定时中断发生时,才跳转到中断服务函数执行。
1.单个按键检测
独立按键电路 单个按键的状态转移图如下: S1状态为按键未按下,S2为按键按下,S3为释放按键;状态跳转条件为当前状态下读取到的按键高低电平Key,当Result为1时,表示按键已经成功按下。
单个按键检测的代码实现:
#ifdef SingleKeyEvent
void Key_Scan(void)
{
static u8 Key_status = Key_ON;
switch (Key_status)
{
case Key_ON:
if(!Key)
{
Key_status = Key_Pressed;
Key_Result = No_Key;
}
break;
case Key_Pressed:
if(!Key)
{
Key_status = Key_Release;
Key_Result = Get_Key;
}
else
{
Key_status = Key_ON;
Key_Result = No_Key;
}
break;
case Key_Release:
if(Key)
{
Key_status = Key_ON;
}
Key_Result = No_Key;
break;
default: break;
}
}
#endif
2.单个按键实现长按和短按
单个按键实现短按和长按是个很常用的方式,许多电子设计中都这么用。区别单个按键是否是长按和短按依靠检测按键按下的持续时间。
一般将短按时间T设为 10ms < T < 30ms;长按时间的T设置为:T > 500ms.
按键长按和短按的状态转移图如下: S1状态为按键未按下,S2为第一次按键稳定按下,S3为按键长按状态,S4为释放按键;状态跳转条件为当前状态下读取到的按键高低电平Key,对于短按和长按的判断在S2和S3状态中处理。
代码如下(示例):
#ifdef SingleKey_LongShort_Event
void Key_Scan(void)
{
static u8 Key_status = Key_ON;
static u8 TimeCnt = 0;
switch (Key_status)
{
case Key_ON:
if(!Key)
{
Key_status = Key_Pressed;
TimeCnt = 0;
}
break;
case Key_Pressed:
if(!Key)
{
TimeCnt++;
if(TimeCnt > S_KeyTime)
{
Key_status = Key_LongPress;
}
}
else
{
Key_status = Key_ON;
}
break;
case Key_LongPress:
if(!Key)
{
TimeCnt++;
if(TimeCnt > L_KeyTime)
{
Key_Result = Long_Key;
Key_status = Key_Release;
}
}
else
{
Key_Result = Short_Key;
TimeCnt = 0;
Key_status = Key_ON;
}
break;
case Key_Release:
if(Key)
{
Key_status = Key_ON;
}
break;
default: break;
}
}
#endif
三、长按和短按测试示例
头文件
#ifndef __BUTTON_H
#define __BUTTON_H
#include "stm32f10x.h"
#define KEY_IO_RCC RCC_APB2Periph_GPIOA
#define KEY_IO_PORT GPIOA
#define KEY_IO_PIN GPIO_Pin_15
#define No_Key 0x00
#define Get_Key 0x01
#define Short_Key 0x01
#define Long_Key 0x02
#define S_KeyTime 3
#define L_KeyTime 50
#define Key GPIO_ReadInputDataBit(KEY_IO_PORT,KEY_IO_PIN)
#define Key_ON 0x00
#define Key_Pressed 0x01
#define Key_Release 0x02
#define Key_LongPress 0x03
#define SingleKey_LongShort_Event
void Key_Init(void);
void Key_Scan(void);
#endif
main函数
extern u8 Key_Result;
int main()
{
Key_Init();
led_init();
timer_init(19,7199);
while(1)
{
switch(Key_Result)
{
case Short_Key:
LED0 = 0;
Delay_ms(1000);
break;
case Long_Key:
LED1 = 0;
Delay_ms(1000);
break;
case No_Key:
LED0 = 1;
LED1 = 1;
break;
default: break;
}
}
}
#endif
void TIM3_IRQHandler(void)
{
static u8 cnt;
if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET)
{
cnt++;
if(cnt>4)
{
Key_Scan();
cnt = 0;
}
TIM_ClearITPendingBit(TIM3, TIM_IT_Update);
}
}
总结
使用状态机时,需要对事件的状态进行分析,可适当的增加中间状态,便于处理。在上文中,增加了长按的状态S3,S2为第一次按下状态,S2状态可以消抖判断,也用于处理短按处理,S3为长按状态,增加S3状态之后,程序逻辑更清楚,不至于混乱。
|