尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

【程序库】 MutiButton 按键库

【程序库】 MutiButton 按键库 目录1.1 MutiButton 库1.1.0 功能特性1.1.1 编译和构建1.1.2 构建输出1.1.3 快速开始1.1.3.1 包含头文件1.1.3.2 定义按键实例1.1.3.3 实现 GPIO 读取函数1.1.3.4 初始化按键1.1.3.5 注册事件回调1.1.3.6 启动按键处理1.1.3.7 定时调用处理函数1.1.4 API 参考1.1.4.1 按键事件类型1.1.4.2 核心函数1.1.4.3 工具函数1.1.5 user_data 上下文指针1.1.6 实现三击 (N-Click)1.1.7 配置选项1.1.8 重要注意事项1.1.9 回调执行上下文1.1.10 线程安全1.1.11 状态机说明1.1.12 示例程序1.2 FAQ1.3 项目结构1.4 应用案例移植到 GD32F10x1.1 MutiButton 库一个高效、灵活的多按键状态机库支持多种按键事件检测。兼容性C99 标准适用于各种微控制器平台 (STM32, Arduino, ESP32, etc.)支持裸机和 RTOS 环境内存占用小适合资源受限的系统按键库MutiButton【原作者】1.1.0 功能特性多种按键事件 :按下、抬起、单击、双击、长按开始、长按保持、重复按下多按键支持 :支持无限数量的按键实例状态机驱动 :清晰的状态转换逻辑可靠性高硬件 去抖 :内置数字滤波消除按键抖动回调 机制 :灵活的事件回调函数注册支持 void* user_data 上下文指针内存 优化 :紧凑的数据结构低内存占用配置 灵活 :可自定义时间参数和功能选项参数 验证 :完善的错误检查和边界条件处理线程 安全 :可选的 RTOS 锁支持裸机零开销1.1.1 编译和构建使用 Makefile (推荐)# 编译所有内容 (库 示例)make# 只编译库makelibrary# 只编译示例makeexamples# 编译特定示例makebasic_examplemakeadvanced_examplemakepoll_example# 运行测试maketest# 清理构建文件makeclean# 查看帮助makehelp1.1.2 构建输出编译完成后文件结构如下build/ -- lib/ | -- libmultibutton.a # 静态库 -- bin/ | -- basic_example # 基础示例 | -- advanced_example # 高级示例 | -- poll_example # 轮询示例 -- obj/ # 目标文件1.1.3 快速开始1.1.3.1 包含头文件#includemulti_button.h1.1.3.2 定义按键实例staticButton btn1;1.1.3.3 实现 GPIO 读取函数uint8_tread_button_gpio(uint8_tbutton_id){switch(button_id){case1:returnHAL_GPIO_ReadPin(BUTTON1_GPIO_Port,BUTTON1_Pin);default:return0;}}1.1.3.4 初始化按键// 初始化按键 (active_level: 0低电平有效, 1高电平有效)button_init(btn1,read_button_gpio,0,1);1.1.3.5 注册事件回调voidbtn1_single_click_handler(Button*btn,void*user_data){printf(Button 1: Single Click\n);}button_attach(btn1,BTN_SINGLE_CLICK,btn1_single_click_handler,NULL);1.1.3.6 启动按键处理button_start(btn1);1.1.3.7 定时调用处理函数// 在 5ms 定时器中断中调用voidtimer_5ms_interrupt_handler(void){button_ticks();}1.1.4 API 参考1.1.4.1 按键事件类型typedefenum{BTN_PRESS_DOWN0,// 按键按下BTN_PRESS_UP,// 按键抬起BTN_PRESS_REPEAT,// 重复按下检测BTN_SINGLE_CLICK,// 单击完成BTN_DOUBLE_CLICK,// 双击完成BTN_LONG_PRESS_START,// 长按开始BTN_LONG_PRESS_HOLD,// 长按保持 (每个 tick 触发)BTN_NONE_PRESS// 无事件}ButtonEvent;1.1.4.2 核心函数voidbutton_init(Button*handle,uint8_t(*pin_level)(uint8_t),uint8_tactive_level,uint8_tbutton_id)功能初始化按键实例 参数handle按键句柄pin_levelGPIO 读取函数指针active_level有效电平 (0 或 1)button_id按键 IDvoidbutton_attach(Button*handle,ButtonEvent event,BtnCallback cb,void*user_data)功能注册事件回调函数 参数:handle按键句柄event事件类型cb回调函数user_data用户上下文指针同一按键的所有回调共享voidbutton_detach(Button*handle,ButtonEvent event)功能: 移除事件回调函数intbutton_start(Button*handle)功能: 启动按键处理 返回值: 0成功, -1已存在, -2参数错误voidbutton_stop(Button*handle)功能: 停止按键处理可在回调中安全调用voidbutton_ticks(void)功能: 后台处理函数 (每 5ms 调用一次)1.1.4.3 工具函数ButtonEventbutton_get_event(Button*handle)功能: 获取当前按键事件uint8_tbutton_get_repeat_count(Button*handle)功能: 获取重复按下次数voidbutton_reset(Button*handle)功能: 重置按键状态intbutton_is_pressed(Button*handle)功能: 查询按键是否按下 返回值: 1按下, 0未按下, -1错误1.1.5 user_data 上下文指针每个回调函数都会收到一个void* user_data指针通过button_attach()设置typedefstruct{intled_pin;intcount;}MyContext;MyContext ctx{.led_pin13,.count0};voidon_click(Button*btn,void*user_data){MyContext*ctx(MyContext*)user_data;toggle_led(ctx-led_pin);ctx-count;}button_attach(btn1,BTN_SINGLE_CLICK,on_click,ctx);同一按键的所有回调共享同一个user_data按键级别存储非事件级别。1.1.6 实现三击 (N-Click)库原生支持单击和双击事件。三击及以上可通过BTN_PRESS_REPEATbutton_get_repeat_count()实现voidon_repeat(Button*btn,void*user_data){uint8_tcountbutton_get_repeat_count(btn);if(count3){// 三击!}}button_attach(btn,BTN_PRESS_REPEAT,on_repeat,NULL);说明:BTN_SINGLE_CLICK在 repeat1 时触发BTN_DOUBLE_CLICK在 repeat2 时触发。repeat3 时仅BTN_PRESS_REPEAT在按下过程中触发。1.1.7 配置选项在multi_button.h中可以自定义以下参数:#defineTICKS_INTERVAL5// 定时器中断间隔 (ms)#defineDEBOUNCE_TICKS3// 去抖深度 (最大 7)#defineSHORT_TICKS(300/TICKS_INTERVAL)// 短按阈值#defineLONG_TICKS(1000/TICKS_INTERVAL)// 长按阈值#definePRESS_REPEAT_MAX_NUM15// 最大重复计数1.1.8 重要注意事项BTN_LONG_PRESS_HOLD 每 tick 触发BTN_LONG_PRESS_HOLD在长按保持期间每个 tick默认 5ms 200Hz触发一次。如果回调中有耗时操作请自行节流voidon_long_hold(Button*btn,void*user_data){staticuint16_tthrottle0;if(throttle20)return;// 每 100ms 触发一次throttle0;// ... 执行操作 ...}1.1.9 回调执行上下文如果button_ticks()在定时器中断ISR中调用所有回调都在中断上下文中执行。回调应尽量简短避免阻塞操作。建议在回调中设置标志位在主循环中处理。如果button_ticks()在主循环或 RTOS 任务中调用回调在该上下文中执行无 ISR 限制。1.1.10 线程安全#defineMULTIBUTTON_THREAD_SAFE#defineMULTIBUTTON_LOCK()osMutexAcquire(btn_mutex,osWaitForever)#defineMULTIBUTTON_UNLOCK()osMutexRelease(btn_mutex)#includemulti_button.h回调在锁外执行因此可以在回调中安全调用button_stop()/button_start()不会死锁。使用普通互斥量即可无需递归锁。1.1.11 状态机说明-- 长按 -- [LONG_HOLD] | | [IDLE] -- 按下 -- [PRESS] 抬起 ^ | | | 抬起 | | v | | [RELEASE] ----------------- | | ^ | 超时| | 快速按下 | | | ------------- [REPEAT] -- 按住太久 -- [PRESS]1.1.12 示例程序examples/basic_example.c- 基础示例单击、双击、长按检测examples/advanced_example.c- 高级示例多按键管理、动态回调examples/poll_example.c- 轮询模式示例1.2 FAQQ: 如何检测三击A: 注册BTN_PRESS_REPEAT回调在回调中用button_get_repeat_count()检查次数。Q: 在回调中调用button_stop()安全吗A: 安全。库在调用回调前缓存了 next 指针遍历过程中移除按键不会崩溃。Q: ticks 计数器会溢出吗A: 不会。计数器在UINT16_MAX(65535) 处饱和不会回绕。在 5ms 间隔下可覆盖约 327 秒的持续按住。Q: 能在多线程 RTOS 中使用吗A: 可以。定义MULTIBUTTON_THREAD_SAFE并提供锁宏。回调在锁外执行普通互斥量即可。1.3 项目结构MultiButton/ -- multi_button.h # 主头文件 -- multi_button.c # 主源文件 -- Makefile # 构建脚本 -- examples/ # 示例目录 | -- basic_example.c # 基础示例 | -- advanced_example.c # 高级示例 | -- poll_example.c # 轮询示例 -- tests/ | -- test_button.c # 单元测试 -- build/ # 构建输出目录 -- README.md # 英文文档 -- README_CN.md # 中文文档1.4 应用案例移植到 GD32F10xGD32F103C8T6main.c 中的头文件根据需要进行注释掉。延时函数可以采用 systick 或者 timer 。main.c文件#includebsp_led.h//#include oled.h#includekernel_dwt.h#includesystick.h#includegd32f10x.h#includegd32f10x_libopt.h#includemulti_button.h/********************************************************************************** 功能描述 ①、按键1按下 LED灯亮 ②、按键2按下 LED灯灭 ③、按键3按下 LED灯亮 ④、按键4按下 LED灯灭 **********************************************************************************/// 申请2个按键结构体staticButton btn1;staticButton btn2;staticButton btn3;staticButton btn4;// 硬件抽象层功能读取IO电平状态uint8_tread_button_gpio(uint8_tbutton_id){switch(button_id){case1:returnKEY1_Read_IO_Level;case2:returnKEY2_Read_IO_Level;case3:returnKEY3_Read_IO_Level;case4:returnKEY4_Read_IO_Level;default:return0;}}// 按键1 被单击的回调函数//void btn1_single_click_handler(Button* btn, void* user_data)//{// LED_ON;//}// 按键1 被按下的回调函数voidbtn1_press_down_handler(Button*btn,void*user_data){LED_ON;}// 按键2 被单击的回调函数//void btn2_single_click_handler(Button* btn, void* user_data)//{// LED_OFF;//}// 按键2 被按下的回调函数voidbtn2_press_down_handler(Button*btn,void*user_data){LED_OFF;}// 按键3 被单击的回调函数//void btn3_single_click_handler(Button* btn, void* user_data)//{// LED_ON;//}// 按键3 被按下的回调函数voidbtn3_press_down_handler(Button*btn,void*user_data){LED_ON;}// 按键4 被单击的回调函数//void btn4_single_click_handler(Button* btn, void* user_data)//{// LED_OFF;//}// 按键4 被按下的回调函数voidbtn4_press_down_handler(Button*btn,void*user_data){LED_OFF;}// 按键1、按键2、按键3、按键4 注册函数voidbuttons_init(void){// 按键结构体/按键回调函数/低电平有效/按键IDbutton_init(btn1,read_button_gpio,0,1);button_init(btn2,read_button_gpio,0,2);button_init(btn3,read_button_gpio,0,3);button_init(btn4,read_button_gpio,0,4);// 添加按键事件处理程序button_attach(btn1,BTN_PRESS_DOWN,btn1_press_down_handler,NULL);button_attach(btn2,BTN_PRESS_DOWN,btn2_press_down_handler,NULL);button_attach(btn3,BTN_PRESS_DOWN,btn3_press_down_handler,NULL);button_attach(btn4,BTN_PRESS_DOWN,btn4_press_down_handler,NULL);// 启动按键button_start(btn1);button_start(btn2);button_start(btn3);button_start(btn4);}// 所有硬件初始化函数voidHardware_Init(void){DWT_Init();// DWT初始化LED_Init();// LED初始化// OLED_Init(); // OLED初始化Key_Init();// 按键硬件初始化buttons_init();// 按键抽象层初始化}// 功能函数voidHardware_Loop(void){button_ticks();DWT_DelayMs(5);}intmain(void){Hardware_Init();while(1){Hardware_Loop();}}multi_button.c在该文件中添加硬件初始化接口或者另起文件。/********************************************************************************** 按键初始化 **********************************************************************************/voidKey_Init(void){//打开GPIOB时钟rcu_periph_clock_enable(KEY1_GPIO_CLK_PORT);//将PB10配置为上拉输入2MHzgpio_init(KEY1_GPIO_PORT,GPIO_MODE_IPU,KEY1_GPIO_SPEED,KEY1_GPIO_PIN);//打开GPIOB时钟rcu_periph_clock_enable(KEY2_GPIO_CLK_PORT);//将PB11配置为上拉输入2MHzgpio_init(KEY2_GPIO_PORT,GPIO_MODE_IPU,KEY2_GPIO_SPEED,KEY2_GPIO_PIN);//打开GPIOB时钟rcu_periph_clock_enable(KEY3_GPIO_CLK_PORT);//将PB12配置为上拉输入2MHzgpio_init(KEY3_GPIO_PORT,GPIO_MODE_IPU,KEY3_GPIO_SPEED,KEY3_GPIO_PIN);//打开GPIOB时钟rcu_periph_clock_enable(KEY4_GPIO_CLK_PORT);//将PB13配置为上拉输入2MHzgpio_init(KEY4_GPIO_PORT,GPIO_MODE_IPU,KEY4_GPIO_SPEED,KEY4_GPIO_PIN);}multi_button.h在该文件中添加硬件初始化接口或者另起文件。/********************************************************************************** 用户自定义按键接口 **********************************************************************************///PB10#defineKEY1_GPIO_CLK_PORTRCU_GPIOB/* 对应GPIO端口时钟位 */#defineKEY1_GPIO_PORTGPIOB/* 对应GPIO端口 */#defineKEY1_GPIO_PINGPIO_PIN_10/* 对应PIN脚 */#defineKEY1_GPIO_SPEEDGPIO_OSPEED_2MHZ/* 对应PIN脚输出速度 *///PB11#defineKEY2_GPIO_CLK_PORTRCU_GPIOB/* 对应GPIO端口时钟位 */#defineKEY2_GPIO_PORTGPIOB/* 对应GPIO端口 */#defineKEY2_GPIO_PINGPIO_PIN_11/* 对应PIN脚 */#defineKEY2_GPIO_SPEEDGPIO_OSPEED_2MHZ/* 对应PIN脚输出速度 *///PB12#defineKEY3_GPIO_CLK_PORTRCU_GPIOB/* 对应GPIO端口时钟位 */#defineKEY3_GPIO_PORTGPIOB/* 对应GPIO端口 */#defineKEY3_GPIO_PINGPIO_PIN_12/* 对应PIN脚 */#defineKEY3_GPIO_SPEEDGPIO_OSPEED_2MHZ/* 对应PIN脚输出速度 *///PB13#defineKEY4_GPIO_CLK_PORTRCU_GPIOB/* 对应GPIO端口时钟位 */#defineKEY4_GPIO_PORTGPIOB/* 对应GPIO端口 */#defineKEY4_GPIO_PINGPIO_PIN_13/* 对应PIN脚 */#defineKEY4_GPIO_SPEEDGPIO_OSPEED_2MHZ/* 对应PIN脚输出速度 *///PB10#defineKEY1_Read_IO_Levelgpio_input_bit_get(KEY1_GPIO_PORT,KEY1_GPIO_PIN);//PB11#defineKEY2_Read_IO_Levelgpio_input_bit_get(KEY2_GPIO_PORT,KEY2_GPIO_PIN);//PB12#defineKEY3_Read_IO_Levelgpio_input_bit_get(KEY3_GPIO_PORT,KEY3_GPIO_PIN);//PB13#defineKEY4_Read_IO_Levelgpio_input_bit_get(KEY4_GPIO_PORT,KEY4_GPIO_PIN);/***********************************************************************************/
返回列表