当前位置: 首页 > news >正文

STM32GPIO输入实战-key按键easy_button库移植

STM32GPIO输入实战-key按键easy_button库移植

  • 一,ebtn介绍
  • 二,ebtn移植
  • 三,组件库的思想组成
    • 1. 事件驱动 (Event-Driven) 🛎️ —— 像按门铃
    • 2. 状态机 (State Machine) 🚦 —— 像红绿灯
    • 3. 回调函数 (Callback Function) 📞 —— 留下你的电话
  • 四,建立组件库应用层
    • 1,建立应用层ebtn_app文件
    • 2,定义KEY_ID、按键参数和按键数组
        • 1. 定义按键参数defaul_ebtn_param
        • 2. 定义按键ID
        • 3,定义按键数组
    • 3, 编写回调函数,完善按键驱动初始化
            • 1,prv_btn_get_state解释
            • 2,prv_btn_event解释
        • 4,初始化 ebtn 库
        • 5,周期性调用处理函数

一,ebtn介绍

前面介绍的按键 4 行代码虽然极致简洁,但是无法区分单击、双击、长按等常用操作。这时,就需要请出easy button库了。
ebtn 库是组件库,其的目标是让你只需关注按键"事件"本身,而把底层的抖动处理、状态判断、时间计算等繁琐细节交给它来完成。
据作者介绍ebtn有多处优势
在这里插入图片描述
打开github,搜索作者

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
解压后Typora打开说明文档
在这里插入图片描述

二,ebtn移植

打开工程文件,新建组件库文件
在这里插入图片描述
复制组件库ebtn,粘贴到Component
在这里插入图片描述
打开Keil,将组件库文件添加到Keil,编译检查错误
在这里插入图片描述
在这里插入图片描述
没有错误,框架好,就不需要修改
基于组件库建立组件库应用层文件ebtn_app.c和.h,添加到APP
在这里插入图片描述

三,组件库的思想组成

1. 事件驱动 (Event-Driven) 🛎️ —— 像按门铃

想象一下你家的门铃。你不需要每隔几秒就跑去门口问:“有人吗?”。你只需要待在屋里,当有人按下门铃时(这就是一个"事件"),铃声会响起,你再根据是谁(什么事件)决定开门(执行相应的操作)。

ebtn 就是这样工作的。它不会让你一直去检查按键状态,而是当按键发生了有意义的动作(比如按下、释放、单击、长按)时,它会主动"通知"你发生了什么"事件"。这种方式比你不停地去问"按键按下了吗?"(这叫轮询 Polling,如之前的四行代码)要高效得多,也更符合人类的直觉。

2. 状态机 (State Machine) 🚦 —— 像红绿灯

建议多了解状态机,后面出一个基于状态机的按键框架
红绿灯有几种状态:红灯、绿灯、黄灯。它不会一下子从红灯跳到黄灯,必须先变成绿灯。状态之间的切换是有固定规则和顺序的。

ebtn 内部也为每个按键维护了一个"状态机"。这个状态机记录了按键当前处于哪个阶段,比如:

空闲状态: 按键没被按下。
抖动检测状态: 刚检测到按下信号,需要等待一小段时间(去抖时间)确认不是干扰。
按下状态 (Pressed): 确认是有效按下,并且持续按着。
单击判断状态: 按下后很快松开了,正在等待看是否会有下一次点击(用于判断连击)。
释放状态 (Released): 确认有效松开。
ebtn 根据你按下的时间长短、松开的时间、以及连续点击的间隔,自动地在这些状态之间切换。你不需要关心这些复杂的状态转换,只需要等待 ebtn 告诉你最终的结果(事件)。

3. 回调函数 (Callback Function) 📞 —— 留下你的电话

想象你去餐厅点餐,服务员告诉你菜好了会叫你。你留下你的桌号(这就是"回调信息"),然后就可以做自己的事了。当菜准备好时,服务员会根据桌号找到你并通知你(这就是"回调")。

在 ebtn 中,你需要提供两个重要的"联系方式":

get_state_fn (获取状态函数): 你需要告诉 ebtn 如何读取按键的物理状态(高电平还是低电平)。ebtn 在需要的时候会"打电话"给这个函数来获取信息。就像服务员问你:“你现在饿了吗?”。
evt_fn (事件通知函数): 你需要告诉 ebtn,当有按键事件发生时,应该去执行哪个函数来处理。就像你告诉服务员:“菜好了,请到这个桌号通知我。”。当 ebtn 检测到单击、长按等事件时,就会"打电话"给你提供的这个函数,并告诉你哪个按键发生了什么事件。
通过这种方式,ebtn 负责检测和判断,而你只需要在 evt_fn 这个函数里编写处理逻辑(比如开关灯、发送消息等)。

驱动事件解耦,回调函数注册API
用事件驱动代替轮询,回调函数实现保留API,不同场景实现不同的API

四,建立组件库应用层

1,建立应用层ebtn_app文件

将组件库头文件引用到ebtn_app.c中
在这里插入图片描述
编译,有一个报错说找不到ebtn.h,因为没有添加组件库.h文件路径
在这里插入图片描述

2,定义KEY_ID、按键参数和按键数组

1. 定义按键参数defaul_ebtn_param

如下图在这里插入图片描述

const是什么?
const 是 C/C++ 中的关键字,用于定义常量,表示该变量的值不可修改。(它会拒绝编译器对所定义的变量的修改)

ebtn_btn_param_t是组件库里定义的结构体类型,如下图。
在这里插入图片描述

结构体成员说明
time_debounce防抖处理,按下防抖超时,配置为0,代表不启动
time_debounce_release防抖处理,松开防抖超时,配置为0,代表不启动
time_click_pressed_min按键超时处理,按键最短时间,配置为0,代表不检查最小值
time_click_pressed_max按键超时处理,按键最长时间,配置为0xFFFF,代表不检查最大值,用于区分长按和按键事件。
time_click_multi_max多击处理,两个按键之间认为是连击的超时时间
time_keepalive_period长按处理,长按周期,每个周期增加keepalive_cnt计数
max_consecutive最大连击次数,配置为0,代表不进行连击检查。

原文链接:https://blog.csdn.net/wenbo13579/article/details/136268852

而defaul_ebtn_param是ebtn_btn_param_t类型的结构体defaul_ebtn_param
defaul:默认
ebtn:即easy_botton
param:参数
所以单从名字,我们可以知道,它是按键的默认参数
在后面需要用到,将按键与这个参数绑定

EBTN_PARAMS_INIT是组件库规定的一个宏
在这里插入图片描述
为什么使用宏定义呢?
因为宏定义可以把我们规定的参数赋值给结构体里的成员
如果不使用宏,直接在代码中对 ebtn_btn_param_t 结构体变量 defaul_ebtn_param 进行初始化,需要逐个写出成员变量的赋值语句,代码会显得较为零散。而使用宏定义,在初始化结构体变量时,能一眼看清各个关键参数的设置

2. 定义按键ID

使用枚举定义按键ID

typedef
为已有的数据类型(这里即enum枚举类型)创建一个新的别名,之后可直接用该枚举类型名称user_button_t定义

enum
代表这是枚举类型的变量,枚举具体可以看我关于宏和枚举的笔记

由于定义user_button_t时使用了typedef,
在这里插入图片描述

3,定义按键数组

在这里插入图片描述

static
关键字,设定该变量为局部静态变量
static 修饰的变量具有静态存储持续性,它在程序启动时就被创建,在整个程序的生命周期内都存在,不会因为代码块的结束而被销毁。(可持续储存)
其他文件无法通过外部链接(extern)来访问这个数组。(防止意外修改)
静态变量只会被初始化一次。(按键数据不会被多次初始化)

按键控制结构体说明-ebtn_btn_t
此结构体是组件库中定义的,用于记录按键当前状态,按键参数等信息。总之,它就像按键的身份证,包含了按键的基本信息
在这里插入图片描述

因为使用了变量别名typedef,所以struct ebtn_btn可以替换为 ebtn_btn_t

结构体成员说明
key_id用户定义的key_id信息,该值建议唯一
flags用于记录一些状态,目前只支持EBTN_FLAG_ONPRESS_SENT和EBTN_FLAG_IN_PROCESS
time_change记录按键按下或者松开状态的时间点
time_state_change记录按键状态切换时间点(并不考虑防抖,单纯记录状态切换时间点)
keepalive_last_time长按最后一次上报长按时间的时间点,用于管理keepalive_cnt
click_last_time点击最后一次松开状态的时间点,用于管理click_cnt
keepalive_cnt长按的KEEP_ALIVE次数
click_cnt多击的次数
param按键时间参数,指向ebtn_btn_param_t,方便节省RAM,并且多个按键可公用一组参数

原文链接:https://blog.csdn.net/wenbo13579/article/details/136268852

EBTN_BUTTON_INIT宏有什么用?
前面提到过,按键参数需要与按键ID关联,此宏可以实现
其作用是关联按键 ID 与参数:
将 user_button_t 枚举类型中定义的按键 ID(比如 USER_BUTTON_0 、USER_BUTTON_1 等 )与按键参数 defaul_ebtn_param 关联起来。在按键处理相关逻辑中,通过这种关联,程序能识别不同按键并应用对应的参数配置在这里插入图片描述
在这里插入图片描述在这里插入图片描述

结构体数组ebtns[]
以EBTN_BUTTON_INIT宏为成员关联按键ID与按键参数

3, 编写回调函数,完善按键驱动初始化

在ebtn.h中,有一个按键驱动初始化函数,ebtn_evt_fn 和 ebtn_get_state_fn 这两个回调函数作为其形参

回调函数指的是将函数指针当作参数传递给另一个函数,并且在合适的时机由接收到该指针的函数来调用

在这里插入图片描述
右键跳转到它们的定义

红框翻译:
转到 “ebtn_get_state_fn” 的定义
转到 “ebtn_get_state_fn” 的下一处引用
转到 “ebtn_get_state_fn” 的上一处引用

在这里插入图片描述
在这里插入图片描述
发现这两个函数定义了,但是没有具体内容
这需要我们在应用层再次定义一下

1,prv_btn_get_state解释

ebtn_get_state_fn定义为prv_btn_get_state
在这里插入图片描述

词段含义嵌入式领域惯例
prvprivate (私有)表示该函数是模块内部使用,不对外暴露
btnbutton (按钮)缩写,指代按键相关功能
get获取常见的操作动词
state状态指按键的当前状态(按下/释放等)

所以显而易见,这个函数名字代表它是用来获取按键实际状态的

怎么实现呢?
1,btn 是函数形参,它是一个指向 ebtn_btn 结构体的指针,通过这个指针,函数可以访问和操作 ebtn_btn 结构体中的成员(看上图,其实就是操作成员key_id)
2,prv_btn_get_state 函数会通过传入的指针 btn 来访问 ebtn_btn_t 结构体的成员
在函数中switch (btn->key_id):btn->key_id则是代表我们想访问结构体的某一个具体内容,也就是key_id
所以case是选择哪一个按键。毕竟是按键的id

之前讲到ebtn_btn是存储了按键参数的结构体,组件库定义时为其加了别名:struct ebtn_btn可以用ebtn_btn_t替代

所以函数prv_btn_get_state 的功能就很明确了:扫描按键ID,获取按键的高低电平并返回

按键高低电平的两种写法:

2,prv_btn_event解释

ebtn_evt_fn定义为prv_btn_event
在这里插入图片描述
第一个形参是ebtn_btn_t/struct ebtn_btn类型的结构体,和上一个函数一样,传入key_id
第二个形参是ebtn_evt_t 类型的枚举变量evt,代表按键事件
此函数接收组件库整理好的按键事件,留给我们去写事件对应的逻辑代码:

typedef enum
{EBTN_EVT_ONPRESS = 0x00,//按键被按下EBTN_EVT_ONRELEASE,//按键从按下变为释放      EBTN_EVT_ONCLICK,   //按键被单击     EBTN_EVT_KEEPALIVE,  //按键被长按
} ebtn_evt_t;
4,初始化 ebtn 库

在系统启动的初始化阶段(例如 main 函数开始处,或专门的初始化函数中),调用 ebtn_init 函数,将之前准备好的按键列表和回调函数通过ebtn_init 函数"注册"给 ebtn 库。
暂时没有用到组合按键
在这里插入图片描述

代码运行流程
[ System Start ]

main()
├─ HAL_Init()
├─ SystemClock_Config()
├─ MX_GPIO_Init()
└─ ebtn_app_init() ← 注册 btns[ ]、prv_btn_get_state、prv_btn_event
[ SysTick 每 1ms ]

HAL_SYSTICK_Callback()
└─ ebtn_task() ← 调用 ebtn_process(now_ms)

ebtn_process(now_ms)
├─ 对每个按键 btn:
│ ├─ 调 prv_btn_get_state(btn) → 读 GPIO
│ ├─ 去抖 / 状态机推进
│ ├─ 若检测到单击/双击/长按 →
│ │ 调 prv_btn_event(btn, evt)
│ └─ 继续下一个 btn
└─ 结束
prv_btn_event(btn, EBTN_EVT_ONCLICK)
└─ switch(btn->key_id):
case USER_BUTTON_0:
if click_cnt=1 → ucLed[0]=1;
else if click_cnt=2 → ucLed[0]=0;
总结
prv_btn_get_state的职责:把 GPIO 读成 “0/1” 通知给组件库。
prv_btn_event的职责:接收组件库识别好的“单击/双击”等事件,做你的应用逻辑。
而ebtn_init的职责:btns[] 数组和prv_btn_get_state / prv_btn_event 注册到组件库里

5,周期性调用处理函数

在这里插入图片描述
将任务加入调度器
在这里插入图片描述
将初始化放在调度器之前
在这里插入图片描述

为什么周期性调用如此重要? ebtn 库内部的所有计时(去抖、单击超时、长按周期)都依赖于你通过 ebtn_process 传入的当前时间。如果调用间隔不规律或太长,会导致时间判断错误,按键事件也就无法被正确识别了。

http://www.xdnf.cn/news/359911.html

相关文章:

  • flex 还是 inline-flex?实际开发中应该怎么选?
  • 【Python 列表(List)】
  • 传统数据展示 vs 可视化:谁更打动人心?
  • 第十七节:图像梯度与边缘检测-Sobel 算子
  • Python函数:从基础到进阶的完整指南
  • 2006-2023年各省研发投入强度数据/研究与试验发展(RD)经费投入强度数据(无缺失)
  • 【大语言模型ChatGPT4/4o 】“AI大模型+”多技术融合:赋能自然科学暨ChatGPT在地学、GIS、气象、农业、生态与环境领域中的应用
  • Python基础学习-Day20
  • Transformer编码器+SHAP分析,模型可解释创新表达!
  • 星云智控:物联网时代的设备守护者——卓伊凡详解物联网监控革命-优雅草卓伊凡
  • 2021-11-15 C++下一个生日天数
  • 【计算机视觉】OpenCV实战项目: opencv-text-deskew:实时文本图像校正
  • Bitcoin跨链协议Clementine的技术解析:重构DeFi生态的信任边界
  • .Net HttpClient 概述
  • CTF-DAY11
  • ClickHouse多表join的性能优化:原理与源码详解
  • WebSocket:实时通信的新时代
  • List<T>中每次取固定长度的数据
  • 报错 | vitest中,vue中使用jsx语法,报错:ReferenceError: React is not defined
  • 图上思维:基于知识图的大型语言模型的深层可靠推理
  • YOLOv8 优化:基于 Damo-YOLO 与 DyHead 检测头融合的创新研究
  • Android Framework学习四:init进程实现
  • 矩阵分解——Cholesky分解,LU分解,LDLT分解
  • 华为5.7机考第一题充电桩问题Java代码实现
  • Sourcetree安装使用的详细教程
  • 深入解析网络联通性检测:ping 与 tracert 的原理、用法及实战应用
  • 范式之殇-关系代数与参照完整性在 Web 后台的落寞
  • Linux基础篇命令整合表(大全)
  • Cjson格式解析与接入AI大模型
  • Git标签删除脚本解析与实践:轻松管理本地与远程标签