按键状态机(实现单击,长按,双击)的模块分享

2023-11-02

目录

一、相关说明

二、分析

三、模块代码

三、代码讲解

四、作者的话


一、相关说明

        1.需要的资源:一个定时器,一个按键。

        2.相关设置:利用定时器计时中断,10ms进行一次按键扫描。

        3.使用说明:定时器中断的优先级要设置高一点,相关的宏定义可以自行定义。

        4.实现功能:区分单个按键的单击,双击,长按。

        5.规定:双击:2次按下的间隔不超过200ms属于双击。

                      单击:第一次按下持续时间小于1s属于单击。

                      长按:第一次按下持续时间不小于1s属于长按。

        (时间长短可自己调整)

        6.目标:帮助理解按键的状态机。

二、分析

        1.时间线分析和状态概览

        2.状态分析和编程思路

         说明:一般按键有两种状态,按下和弹起,这里将按键按下的状态拆分为两种状态,以长按1s的标志触发为断点,拆分为按键按下到标志触发状态和标志触发到按键弹起的状态。而对于单击和双击可直接理解为按下状态,只是形式上作了拆分。而且按下抖动的状态实际编程中没有编写,有兴趣的小伙伴可以尝试添加。

那为什么要拆分按键按下的状态?
定时器会10ms进行实时检测。
长按标志到时会实时清零,如果不拆分状态只能等待按键弹起进入1状态,但此时长按标志已被清零,系统错乱,误判为单击。
要想长按标志不被清零,就不能在按下状态动态清零,所以只能设计为松手反馈。
而拆分状态后可将1s前要做的事和1s后要做的事拆分开,1s到时即可实时反馈。

三、模块代码

1.相关宏

    #define KEY GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_5)
   
    #define KEY_Up  1         //按键弹起
    #define KEY_DownShake  2  //按下抖动
    #define KEY_Down  3       //按键按下
    #define KEY_wait  4       //等待状态
     
    #define SHORT_KEY 1       //短按反馈
    #define LONG_KEY 2        //长按反馈
    #define DOUBLE_CLICK 3    //双击反馈
    
    #define FALSE 0
    #define TRUE 1  

2.函数主体

/**
  * @brief  KEY_Scan();// 按键检测
  * @note   按键检测,返回单击,双击,长按
  * @param  无
  * @retval 无
  * @author 常州工学院电子协会22级团体
	PS:key_return属于外部全局变量,用于接受反馈信息
  */	
void KEY_Scan(void)
{
	//反馈系统
	static uint8_t  Click_Buf = FALSE;  //第一次弹起标志,用与区分双击的第一次按下和第二次按下
	static uint8_t  KEY_flag= FALSE;//标志触发判断标志位,用于在反馈结束后统一清零
  	static uint8_t  Click   = FALSE;//单击判断标志位
	static uint8_t  Long_Press = FALSE; //长按判断标志位
  	static uint8_t  Double_Click  = FALSE;//双击判断标志位
	
	//计时系统
	//定时器10ms进入一次函数
    static uint8_t  Long_Cnt = 100;//长按计时时长1s
    static uint8_t  Twice_Cnt = 20;//双击间隔计时时长200ms
	Long_Cnt--;
    Twice_Cnt--;
	
	//状态系统
    switch(key_state)
    {
        /*状态1:空闲状态(单击)和按键弹起后(双击)*/
        case KEY_Up:
        {
    	  if(KEY == 0)
		  {
			key_state = KEY_Down;//切换到状态2
			Long_Cnt = 100;//长按计时开始
		  }
          else
          {
            //判断是否为按键弹起状态
            if(Click_Buf == TRUE)
            {
			  //弹起时间超过200ms,双击判定时间失效,且一定不为长按,直接判断为单击
              if(Twice_Cnt<=0)
              {
				KEY_flag = TRUE;
                Click = TRUE;
              }
            }
          }
        break;
    }
    
//    /*状态2:按下抖动(过渡状态)*/
//    case KEY_DownShake:
//    {
//      if(KEY == 0)
//        key_state = KEY_Down;//确认按下,切换到状态3
//      break;
//    } 
    
    /*状态3:按键按下到长按标志触发状态*/
    case KEY_Down:
    {
      if(KEY == 1)
      {
        key_state = KEY_Up;//切换到状态4
        //不是长按操作,则判断是不是双击操作
        if(Long_Press == FALSE)
        {
          //双击检测
          //前面已经单击一次,这次就判断为双击操作
          if(Click_Buf == TRUE)
          {
			KEY_flag = TRUE;
		    Double_Click  = TRUE;
          }
          else
          {
            //这是单击或双击的第一次点击,标志位置1
            Click_Buf = TRUE;
            //双击计时器开始计时
			Twice_Cnt = 20;
          } 
        }
      }
      else
      {
        //长按检测(一直在按下,第一次弹起不会触发)
        if(Long_Press == FALSE&&Click_Buf == FALSE)
        {
		  //1s时间到就判断为长按
          if(Long_Cnt<=0)
          {
			key_state = KEY_wait;//切换到状态4
			KEY_flag = TRUE;
            Long_Press = TRUE;
          }
        }
      }  
      break;  
    }
    
    /*状态4:标志触发到等待按键弹起状态*/
    case KEY_wait:
    {
      if(KEY == 1)
        key_state = KEY_Up;//完成一次按键动作,切换到状态1
      break;
    }
		
    default:
      key_state = KEY_Up;//默认情况都切换到状态1
      break;
  }
    //标志触发,反馈结果
    //PS:key_return属于外部全局变量,用于接受反馈信息
    if(KEY_flag == TRUE)
	{
		//单击动作
		if(Click == TRUE)
		    key_return = SHORT_KEY;
		//长按动作
		else if(Long_Press == TRUE)
			key_return = LONG_KEY;
		//双击动作
		else if(Double_Click == TRUE)
			key_return = DOUBLE_CLICK;
        //按键状态位清零,为下一次按下准备
        KEY_flag= FALSE;
        Click_Buf = FALSE;
        Click = FALSE;
        Long_Press = FALSE;
        Double_Click  = FALSE;
    }
}

三、代码讲解

为了使代码更加清晰,可读性更强,我将按键状态机的代码做了分区,分成了三个系统,分别是反馈系统,计时系统,状态系统。

理解要点:梳理好状态之间的切换关系,特别关注按键按下和弹起在状态机中是怎么检测的。

难点:1.Click_Buf的实际意义不是按键按下的标志,是按键弹起的标志。

           2.要区分状态和动作,按下既可以是状态也可以是动作,空闲是一种状态,弹起是一种动作。

在理解代码的时候可以把单击,双击,长按的路线走几遍,有助于理解。

四、作者的话

我们团队在学习状态机很痛苦,因为比较抽象难以理解,所以我们将学习经验分享,希望能对刚学习这方面内容的人有所帮助。

本人自学小白,如果有纰漏和错误,希望大佬们多多指教,欢迎大家一起交流。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

按键状态机(实现单击,长按,双击)的模块分享 的相关文章

  • 从一路高歌到遭多国“封杀”,ChatGPT未来将是什么样子?

    IT有得聊 是机械工业出版社旗下IT专业资讯和服务平台 致力于帮助读者在广义的IT领域里 掌握更专业 更实用的知识与技能 快速提升职场竞争力 点击蓝色微信名可快速关注我们 人工智能技术的发展已经逐渐改变了我们的生活和工作方式 其中 语言模型
  • 每日一题:子串的最大差

    子串的最大差 题目 Daimayuan Online Judge 枚举每个子串不现实 肯定会超时 子串1的最大值 子串1的最小值 子串2的最大值 子串2的最小值 即为 子串1的最大值 子串2的最大值 子串1的最小值 子串2的最小值 可以从贡
  • Linux环境下Selenium截图乱码及字体安装及与字符集区别

    概述 参考Java实现HTML页面截图功能 在使用Selenium对HTML页面进行截图时 一段没有任何问题的代码 在Windows环境下执行成功 但放到使用很久的测试环境Linux服务器也没有问题 但是部署到刚申请不久的阿里云生产Linu
  • yolov7 mask 使用学习笔记

    目录 yolov7 mask trt安装笔记 安装detectron方法1 OK 安装detectron方法2 pip install regex 4 1 无法找到头文件 math h fatal error C1083 安装cocoapi

随机推荐

  • React 16入门教程

    React 16入门教程 技术胖 讲的一般 React16 8版本 https jspang com posts 2019 05 04 new react base html E7 AC AC01 E8 8A 82 EF BC 9Areac
  • dinky报错:Communications link failure The last packet sent successfully to the server was 0 millisecon

    dinky链接MySQL报错 说明 MySQL版本是5 x版本 driver版本是 8 x版本 com mysql cj jdbc exceptions CommunicationsException Communications link
  • java对象序列化流设置类中的某个成员变量不参与序列化和反序列化操作

    我们用对象反序列化流输出类的name变量 然后我们给这个name变量定义一个transient关键字修饰 private transient String name 然后我们再次用反序列化读取 此时我们就不再能拿到name的值了 因为他已经
  • 项目部署到tomcat里css,js等文件失效

    项目在IDEA里面跑的好好的 到了tomcat里面样式就失效了 如何处理 其实这是我们开发中忽略了一个细节导致的 就是如果您的JavaWeb的模板使用的是thymeleaf模板的话 那么您在引入css或者js等文件的时候 一定要用下面的格式
  • h3c虚拟服务器无效,请教:H3C模拟器中防火墙无法PING通PC(虚拟主机) - H3C技术论坛 - 51CTO技术论坛_中国领先的IT技术社区...

    H3C网络模拟器版本 HCL 2 0 2 1 防火墙F1060 使用虚拟主机 PC 连接防火墙G1 0 1端口 防火墙G1 0 1端口IP地址 192 168 0 1 24 PC机端口IP地址 192 168 0 2 24 配置命令 sec
  • ros的入门知识

    ros的入门教程链接 linux ros安装 学前小案例 键盘控制小海龟的移动 创建工作空间与功能包 发布者publisher 订阅者subscriber ros的基础知识点 不同的linux系统安装的ros版本是不一样的 linux18
  • nmake简介

    提示 文章写完后 目录可以自动生成 如何生成可参考右边的帮助文档 nmake学习初步 前言 一 nmake在哪里 二 Makefile编写 1 hello c实例代码 2 Makefile 3 扩展 前言 一直使用vs的IDE 最近编译sq
  • tomcat守护进程

    如何写一个linux系统下的tomcat守护进程呢 上菜 1 编写守护进程shell脚本 下面这个脚本可以直接拿过来用 只需要改URL 和 tomcat启动目录即可 bin bash echo Start URL http 127 0 0
  • 六、线性队列

    序言 结构图 队列结构 队列常用操作 队列的实现 序言 线性队列是用数组实现的队列 队列遵循的原则FIFO first in first out 通常我们说的线性队列 为了节省数组的空间使用 都是循环队列 结构图 队列结构 typedef
  • 【函数(上)(双重for循环)】

    1 循环 1 双重for循环 概述 循环嵌套是指在一个循环语句中再定义一个循环语法结构 例如 嵌套一个for循环 这样for循环语句我们称之为双重for循环 双重for循环语法 for 外循环的初始 外循环的条件 外循环的表达式 for 内
  • LoadOfTheRoot提权学习

    端口碰撞 端口试探 port knocking 是一种通过连接尝试 从外部打开原先关闭端口的方法 一旦收到正确顺序的连接尝试 防火墙就会动态打开一些特定的端口给允许尝试连接的主机 端口试探的主要目的是防治攻击者通过端口扫描的方式对主机进行攻
  • swagger3的配置和使用(一)

    目录 Swagger3简介 Swagger的组成 Swagger的Springboot配置 maven添加依赖 创建swagger的配置类 访问路径 application yml环境配置 API分组 Swagger常用注解 注解说明 用于
  • URLSearchParams快速解析URL查询参数

    浏览器 Window 内置的 URLSearchParams 接口定义了一些实用的方法来处理 URL 的查询字符串 再也不用 字符串分割的方式去解析 url query 参数了 一 URLSearchParams 构造函数 URLSearc
  • 百度APP视频播放中的解码优化

    背景 在全民视频的时代 百度APP中视频播放是十分重要的业务 随着 5G 的到来 视频播放已经不满足以前的标清 高清 超清乃至于 4K 已经是旧时王谢堂前燕飞入寻常百姓家 越来越清晰的视频源 越来越复杂的视频编码 对 APP 的视频解码能力
  • Android设备接入阿里云IoT物联网平台——设备接入类

    1 准备工作 1 1 注册阿里云账号 使用个人淘宝账号或手机号 开通阿里云账号 并通过 实名认证 可以用支付宝认证 1 2 免费开通IoT物联网套件 产品官网 https www aliyun com product iot 1 3 软件环
  • 微信小程序发布迭代版本后如何提示用户强制更新新版本

    在点击小程序发布的时候选择 升级选项 之前用户使用过的再打开小程序页面就会弹出升级弹窗modal
  • esp32 CMT130-V1.0 PS 240*240屏幕显示一张图片的实验

    1 使用ImageConverter565软件 将图片转为120 120大小 保存为后缀名为 c的文件 保存 2 具体pic c代码如下 3 改为如下格式的文件pic h 复制到路径 Arduino hardware espressif e
  • python 结合 Flask 的html页面嵌入for 语句

    近期有个项目 使用python和Flask框架 渲染页面后 需要使用循环显示不定长的数据 由于Flask是基于python的web框架 因此可以在html页面中直接使用 嵌套python语法 官方示例如下 https dormousehol
  • linux rpm包的编译

    有些软件包的特性是编译者选定的 如果编译未选定此特性 将无法使用 rpm包的版本落后于源码包 因此需要定制安装 也就是手动编译安装 编译需要编译环境 编译的过程如下 1 下载源码 2 执行 tar xf 3 cd到源码文件夹内 config
  • 按键状态机(实现单击,长按,双击)的模块分享

    目录 一 相关说明 二 分析 三 模块代码 三 代码讲解 四 作者的话 一 相关说明 1 需要的资源 一个定时器 一个按键 2 相关设置 利用定时器计时中断 10ms进行一次按键扫描 3 使用说明 定时器中断的优先级要设置高一点 相关的宏定