【程序库】 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);/***********************************************************************************/