IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 嵌入式 -> 【stm32单片机基础】按键状态机 -> 正文阅读

[嵌入式]【stm32单片机基础】按键状态机

【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 

/**
 * 单个按键检测事件
 * 功能:使用状态机方式,扫描单个按键;扫描周期为10ms,10ms刚好跳过抖动;
 * 状态机使用switch case语句实现状态之间的跳转
 * 
 */
void Key_Scan(void)
{
    static u8 Key_status = Key_ON;   //设置局部静态变量 keystatus 记录状态
    switch (Key_status)
    {
        //按键未按下状态,此时判断Key的值
        case   Key_ON:    
            if(!Key)   
            {
                Key_status =  Key_Pressed;  //如果按键Key值为0,说明按键开始按下,进入下一个状态
                Key_Result = No_Key;        //Key_Result =0;
            }
            break;
         //按键按下状态:
        case   Key_Pressed:
            if(!Key)                     //查看当前Key是否还是0,再次确认是否按下
            {
                Key_status =  Key_Release;  //进入下一个释放状态
                Key_Result = Get_Key;        //按下已经按下,Key_Result = 1;        
            }   
            else                         //当前Key值为1,确认为抖动,则返回上一个状态
            {
                Key_status =  Key_ON;    //返回上一个状态
                Key_Result = No_Key;
            } 
            break;
         //按键释放状态
         case  Key_Release:
             if(Key)                     //当前Key值为1,说明按键已经释放,返回开始状态
             { 
                 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 

/**
 * 单个按键检测短按和长按事件
 * 短按:时间 10ms < T < 30 ms, 长按:时间 T > 500ms
 * 功能:使用状态机方式,扫描单个按键;扫描周期为10ms,10ms刚好跳过抖动;
 * 状态机使用switch case语句实现状态之间的跳转
 * 使用长按u8 LongFlag和短按u8 ShortFlag标志位以及计时变量 u8 TimeCnt;
 * 在单个按键长按和短按事件中Key_Result: 0:没有按下,1:表示短按,2:表示长按
 */
void Key_Scan(void)
{
    static u8 Key_status = Key_ON;   //设置局部静态数据 keystatus 记录状态
    static u8 TimeCnt = 0;
    switch (Key_status)
    {
        //按键未按下状态,此时判断Key的值
        case   Key_ON:    
            if(!Key)   
            {
                Key_status =  Key_Pressed;  //如果按键Key值为0,说明按键开始按下,进入下一个状态
                TimeCnt = 0;
            }
            break;
        case   Key_Pressed:
            if(!Key)                     //查看当前Key是否还是0,再次确认是否按下
            {
                TimeCnt++;               //开始记录按下时长
                if(TimeCnt > S_KeyTime)          //短按时长,大于30ms
                {
                    Key_status =  Key_LongPress;    //需要进入长按状态确定
                }
            }   
            else                         //当前Key值为1,确认为抖动,则返回上一个状态
            {
                Key_status =  Key_ON;    //返回上一个状态
            } 
            break;
        case  Key_LongPress:
            if(!Key)
            {
                TimeCnt++;               //继续记录按下时长
                if(TimeCnt > L_KeyTime)         //大于500ms
                {
                    Key_Result = Long_Key;  
                    Key_status =  Key_Release;  //进入按键释放状态                   
                }
            
            }
            else                         //Key值变为了1,说明此处动作为短按
            {
                Key_Result = Short_Key;          //短按
                TimeCnt = 0;             //清空计时器
                Key_status =  Key_ON;    //返回初状态
            }
            
            break;
            
         case  Key_Release:
             if(Key)                     //当前Key值为1,说明按键已经释放,返回开始状态
             { 
                 Key_status =  Key_ON;    
             }
             break;
             
         default: break;
    }    
}
#endif


三、长按和短按测试示例

头文件

/**
  * 使用定时器来轮询Key_Scan()函数,定时节拍为2ms,
  * 状态转换时间为10ms,即每次进入switch case语句的时间差为10ms
  * 利用该10ms的间隙跳过按键抖动
  */
#ifndef __BUTTON_H
#define __BUTTON_H

#include "stm32f10x.h"

//按键对应的IO管脚 KEY1  PA.15
#define KEY_IO_RCC        RCC_APB2Periph_GPIOA      
#define KEY_IO_PORT       GPIOA
#define KEY_IO_PIN        GPIO_Pin_15

//Key的值 0:没有按下,1:表示短按,2:表示长按
#define No_Key      0x00
#define Get_Key     0x01

#define Short_Key   0x01
#define Long_Key    0x02

#define S_KeyTime    3
#define L_KeyTime    50

//Key: 1:高电平,按键未按下, 0:低电平,按键按下
#define Key  GPIO_ReadInputDataBit(KEY_IO_PORT,KEY_IO_PIN)

//一个按键的状态,Key_ON:未按下,Key_Down:按下,Key_Release:按键释放,Key_LongPress :长按
#define Key_ON              0x00
#define Key_Pressed         0x01
#define Key_Release         0x02
#define Key_LongPress       0x03

//单个按键事件
//#define SingleKeyEvent

//单个按键实现长按和短按
#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);//10Khz的计数频率,计数到20为2ms
    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)   //TIM3 每 2ms 中断一次
{   
    static u8 cnt;
	if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET) //检查指定的TIM中断发生与否:TIM 中断源 
		{
		cnt++;
        if(cnt>4)           // 每10ms 执行一次按键扫描程序
        {
            Key_Scan();
            cnt = 0;
        }
        TIM_ClearITPendingBit(TIM3, TIM_IT_Update);  //清除TIMx的中断待处理位:TIM 中断源 
		}   
}

总结

使用状态机时,需要对事件的状态进行分析,可适当的增加中间状态,便于处理。在上文中,增加了长按的状态S3,S2为第一次按下状态,S2状态可以消抖判断,也用于处理短按处理,S3为长按状态,增加S3状态之后,程序逻辑更清楚,不至于混乱。

  嵌入式 最新文章
基于高精度单片机开发红外测温仪方案
89C51单片机与DAC0832
基于51单片机宠物自动投料喂食器控制系统仿
《痞子衡嵌入式半月刊》 第 68 期
多思计组实验实验七 简单模型机实验
CSC7720
启明智显分享| ESP32学习笔记参考--PWM(脉冲
STM32初探
STM32 总结
【STM32】CubeMX例程四---定时器中断(附工
上一篇文章      下一篇文章      查看所有文章
加:2021-08-17 15:33:32  更:2021-08-17 15:35:03 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/25 21:19:38-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码