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

STM32H7开发笔记(五):GPIO-输入处理-HAL库实现 - EM

在上一篇中,我简单介绍了一下 easy_button,这一篇,我们使用 HAL 库结合 easy_button 来实现一个简单的按键输入例子。

工程导入

在这里我们依然沿用之前的工程,如果你打算新建工程,或者说没有看前面的文章,请先看 STM32H7开发笔记(二):GPIO-HAL库实现

下载 easy_button

  • Github仓库:https://github.com/bobwenstudy/easy_button

考虑到国内网络问题,部分读者可能无法访问 Github,所以我自己部署了 Gitea,将 easy_button 仓库同步到了我的 Gitea 服务器上,地址:https://git.orangetime.top/EMTime/easy_button

添加 easy_button 文件

进入下载好的 easy_button 路径,进入 ebtn 文件夹,将其中的所有文件复制到我们的工程目录中,在 MDK 软件中添加对应的源码文件,同时设置好头文件搜索路径,如下图:

代码实现

为了方便管理,我在 ebtn.c 同路径下创建了 ebtn_cb.c 和 ebtn_cb.h 文件,用于存放 easy_button 的具体实现函数。

初始化

easy_button 的初始化主要包含:

  • 按键的时间配置参数(如消抖时间、长按时间等)
  • 按键定义(区分按键)
  • 实现按键状态读取函数
  • 获取系统时间函数
  • 实现事件回调函数

时间配置

直接上代码:

static const ebtn_btn_param_t default_param = EBTN_PARAMS_INIT(20,   // 按下去抖时间(ms)20,   // 释放去抖时间(ms)20,   // 点击最短时间(ms)300,  // 点击最长时间(ms)200,  // 连击间隔最大值(ms)500,  // 长按KEEPALIVE间隔(ms)10    // 最大连续点击次数);

easy_button 可以给每一个不同的按键设置不同的时间参数,这里我就定义了一个变量 default_param,之后所有的按键都使用这个参数。

按键定义

typedef enum
{USER_BUTTON1 = 0,USER_BUTTON_MAX,
} user_button_t;static ebtn_btn_t btns[] =
{EBTN_BUTTON_INIT(USER_BUTTON1, &default_param),
};

用枚举来给我们的按键定义一个编号,实际上你不用枚举也是可以的,不过这样更方便管理。
然后使用 EBTN_BUTTON_INIT 宏来初始化我们的按键,第一个参数是按键编号,第二个参数是之前定义的时间参数。

按键状态读取函数

static uint8_t prv_btn_get_state(struct ebtn_btn* btn)
{switch(btn->key_id){case USER_BUTTON1:return HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_13) == GPIO_PIN_SET;default:return 0;}
}

我们自己实现一个参数为 struct ebtn_btn* btn 的函数,根据按键编号来读取按键状态,我的按键按下时为高电平,所以我们判断是否为高电平,然后返回 1(表示true,按下了按键) 或者 0(表示false,按键没有被按下)。

获取系统时间函数

static uint32_t ebtn_user_get_tick(void)
{return HAL_GetTick();
}

这个函数很简单,直接调用 HAL 库提供的 HAL_GetTick() 函数即可,或者也可以直接获取 uwTick这个值,看你个人的习惯。

实现事件回调函数

static void prv_btn_event(struct ebtn_btn* btn, ebtn_evt_t evt)
{switch(evt){case EBTN_EVT_ONPRESS:break;case EBTN_EVT_ONRELEASE:break;case EBTN_EVT_ONCLICK:if((ebtn_click_get_count(btn) == 2) && (btn->key_id == USER_BUTTON1)){HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);}//printf("[BTN %d] Clicked, count=%d\r\n", btn->key_id, ebtn_click_get_count(btn));break;case EBTN_EVT_KEEPALIVE:if(btn->key_id == USER_BUTTON1){HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);}//printf("[BTN %d] Keepalive, cnt=%d\r\n", btn->key_id, ebtn_keepalive_get_count(btn));break;default:break;}
}

我们自己实现一个参数为 struct ebtn_btn* btn 和 ebtn_evt_t evt 的函数,我们根据事件类型来处理不同的按键事件,在不同的事件中通过判断按键编号来执行不同的操作。

其中:

  • EBTN_EVT_ONPRESS:按键按下事件,当按键按下时触发。
  • EBTN_EVT_ONRELEASE:按键释放事件,当按键释放时触发。
  • EBTN_EVT_ONCLICK:按键点击事件,当按键被点击时触发,easy_button 将单击和多击进行了合并,统一都叫 EBTN_EVT_ONCLICK,在代码中可以看到,我们可以通过使用 ebtn_click_get_count() 函数来获取连续点击的次数,从而区分单击和双击以及更多的点击次数。
  • EBTN_EVT_KEEPALIVE:按键长按事件,当按键被长按时持续触发,执行的周期和时间参数中的 KEEPALIVE 间隔一致,我千面写的是500,那么长按期间,每隔500ms就会触发一次 EBTN_EVT_KEEPALIVE 事件。
    • 那么如果想实现长按后只执行一次操作,应该怎么处理呢?在代码中其实你也看到了,注释掉的部分有一个函数 ebtn_keepalive_get_count,通过这个函数,我们可以获取到长按期间,keepalive 事件触发的次数,当这个次数为1时,表示长按后第一次触发 keepalive 事件,那么我们就可以执行我们想要执行的操作,之后值为2,3,4...,表示 keepalive 事件被多次触发,那么我们简单的通过 if 判断就可以不再执行长按的操作了。

加入到代码逻辑中

上面的代码,只是分别定义了按键的时间参数、按键定义、按键状态读取函数、获取系统时间函数、事件回调函数,但是并没有将他们关联起来,更没有实现按键事件的执行,所以我们还需要一些函数,将他们关联起来,并且实现真正的逻辑执行,如下:

void ebtn_user_init(void)
{ebtn_init(btns,EBTN_ARRAY_SIZE(btns),NULL, 0,                    // 无组合键prv_btn_get_state,prv_btn_event);
}void ebtn_user_process(void)
{ebtn_process(ebtn_user_get_tick());
}

ebtn_user_init 就是 easy_button 的初始化函数,第一个参数是之前定义的按键数组,包含了按键 ID和按键时间参数,第二个参数是按键数组的长度,第三个参数是组合键数组,第四个参数是组合键数组的长度,这里我们不需要组合键,所以都设置为 NULL 和 0,第五个参数是按键状态读取函数,第六个参数是事件回调函数。

ebtn_user_process 是用于周期性调用的函数,进行按键的状态读取、消抖、状态判断、事件执行等,它可以在定时器中断中调用,也可以在主循环中调用,如果你使用 RTOS,那么还可以单独开一个线程,调用这个函数,我这里就放在主循环中调用了。

预期效果

对于已经定义的 USER_BUTTON1,我通过按键状态读取函数将 PC11 与其关联起来,然后在事件回调函数中,实现了点击事件和长按事件,当双击按钮时,LED 灯会闪烁,当长按按钮时,LED 灯会每隔500ms闪烁一次,直到松开按键。

完整代码

ebtn_cb.c

include "ebtn_cb.h"/* ---------------- 按钮参数配置 ---------------- */
static const ebtn_btn_param_t default_param = EBTN_PARAMS_INIT(20,   // 按下去抖时间(ms)20,   // 释放去抖时间(ms)20,   // 点击最短时间(ms)300,  // 点击最长时间(ms)200,  // 连击间隔最大值(ms)500,  // 长按KEEPALIVE间隔(ms)10    // 最大连续点击次数);/* ---------------- 按钮ID定义 ---------------- */
typedef enum
{USER_BUTTON1 = 0,USER_BUTTON_MAX,
} user_button_t;/* ---------------- 按钮对象 ---------------- */
static ebtn_btn_t btns[] =
{EBTN_BUTTON_INIT(USER_BUTTON1, &default_param),
};/* ---------------- 获取按键状态 ---------------- */
static uint8_t prv_btn_get_state(struct ebtn_btn* btn)
{switch(btn->key_id){case USER_BUTTON1:// 如果按下时为高电平return HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_13) == GPIO_PIN_SET;default:return 0;}
}/* ---------------- 事件回调函数 ---------------- */
static void prv_btn_event(struct ebtn_btn* btn, ebtn_evt_t evt)
{switch(evt){case EBTN_EVT_ONPRESS:break;case EBTN_EVT_ONRELEASE:break;case EBTN_EVT_ONCLICK:if((ebtn_click_get_count(btn) == 2) && (btn->key_id == USER_BUTTON1)){HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);}//printf("[BTN %d] Clicked, count=%d\r\n", btn->key_id, ebtn_click_get_count(btn));break;case EBTN_EVT_KEEPALIVE:if(btn->key_id == USER_BUTTON1){HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);}//printf("[BTN %d] Keepalive, cnt=%d\r\n", btn->key_id, ebtn_keepalive_get_count(btn));break;default:break;}
}/* ---------------- 系统时间 ---------------- */
static uint32_t ebtn_user_get_tick(void)
{return HAL_GetTick();
}/* ---------------- 初始化函数 ---------------- */
void ebtn_user_init(void)
{ebtn_init(btns,EBTN_ARRAY_SIZE(btns),NULL, 0,                    // 无组合键prv_btn_get_state,prv_btn_event);
}/* ---------------- 周期处理函数 ---------------- */
void ebtn_user_process(void)
{ebtn_process(ebtn_user_get_tick());
}

ebtn_cb.h

#ifndef __EBTN_CB_H_
#define __EBTN_CB_H_#include "gpio.h"
#include "ebtn.h"#ifdef __cplusplus
extern "C" {
#endifvoid ebtn_user_init(void);
void ebtn_user_process(void);#ifdef __cplusplus
}
#endif#endif

main.c


...
#include "ebtn_cb.h"
...int main(void)
{...ebtn_user_init();...while (1){...ebtn_user_process();HAL_Delay(5);}
}

总结

通过使用 easy_button 库,我们可以很方便的实现按键的点击、长按、连击等功能,并且可以很方便的扩展到多个按键,只需要在 ebtn_btn_t 数组中添加按键对象即可,非常方便。

阅读原文:STM32H7开发笔记(五):GPIO-输入处理-HAL库实现

http://www.jsqmd.com/news/752399/

相关文章:

  • 2026年5月阿里云快速流程:怎么搭建OpenClaw?Coding Plan配置及大模型API Key设置
  • 宁奋斗不躺平,海棠山铁哥以《第一大道》坚守本心,不屑《灵魂摆渡・浮生梦》资本套路
  • 基于Ollama与Supabase构建本地私有RAG知识库:从原理到实践
  • PUBG罗技鼠标宏压枪脚本:让普通玩家也能打出职业选手的精准度
  • 从RS-485接线到终端电阻:手把手教你搞定PROFIBUS物理层配置(附常见故障排查)
  • 关于接口相关知识
  • 去水印不破坏原图,哪些方法真的有效?2026最新实测去水印工具推荐 - 爱上科技热点
  • 别再只跑Demo了!用YOLOv5s训练你自己的水果检测模型(附数据集和PyQt界面代码)
  • 深入AMD Ryzen硬件底层:SMU Debug Tool完全指南与实战应用
  • 炉石传说脚本:终极自动化卡组配置与智能对战完全指南
  • ESP32S3+Arduino搞定0.96寸OLED屏:从SPI接线到显示‘Hello World’的保姆级避坑指南
  • 如何在Windows上使用FlicFlac:终极音频格式转换完全指南
  • 2026年3月野外求生技能培训挑战营口碑推荐,速来了解,2026夏令营,野外求生技能培训特训营哪家靠谱 - 品牌推荐师
  • 体验Taotoken在多模型间智能路由与容灾带来的稳定性
  • 如何用AntiDupl.NET解决图片重复问题:5步释放硬盘空间的完整方案
  • LinkSwift:九大网盘直链解析工具,免费高速下载的终极解决方案
  • 5分钟搞定Windows 11安装限制:MediaCreationTool.bat终极解决方案
  • 用Python和NumPy手把手实现你的第一个多臂老虎机(附完整代码和可视化)
  • 不为流量折腰,海棠山铁哥《第一大道》凭初心抗衡营销至上的《灵魂摆渡・浮生梦》
  • 新手开发者首次使用 Taotoken 模型广场完成模型选型的步骤
  • VuePress/Webpack项目构建时内存爆了?手把手教你配置`--max-old-space-size`和`increase-memory-limit`插件
  • 快手号水印去除教程:去掉快手号水印的方法有哪些?2026最新实测全攻略 - 爱上科技热点
  • 5分钟掌握GPU显存稳定性测试:memtest_vulkan完整实战教程
  • STM32H7开发笔记(一):前言 - EM
  • 抖音去水印提取工具哪个好用?抖音去水印提取操作方法2026最新实测汇总 - 爱上科技热点
  • 基于Next.js的全栈CRM系统架构设计与工程实践
  • 2026年云端保姆级流程:如何搭建OpenClaw?Coding Plan配置及大模型API Key接入
  • 3分钟快速上手BetterNCM:一键解锁网易云音乐隐藏功能的终极指南
  • FreeRTOS消息队列实验中的按键“失灵”谜案:一次调用引发的后果
  • 从驱动安装到流控配置:一份给单片机新手的CH9101与FT232R实战避坑指南