# handy_button **Repository Path**: Bryan_He/handy_button ## Basic Information - **Project Name**: handy_button - **Description**: 基于嵌入式MCU应用开发的按键事件处理软件框架,她是一个精简的按键处理软件模块,极致的资源占用率,利用极简的按键事件去覆盖大部分的应用需求。 - **Primary Language**: C - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-04-14 - **Last Updated**: 2026-04-14 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Handy Button - 简洁易移植的MCU按键处理模块 (优化版) ## 概述 Handy Button 是一个专为MCU设计的按键处理软件模块,具有代码简洁、易于移植的特点。该模块通过状态机实现按键事件的检测,支持多种按键事件类型,并通过回调函数通知应用程序。优化版针对MCU系统进行了专门优化。 ## 功能特性 1. **多种按键事件支持**: - `HANDY_BTN_PRESSED` - 按键按下事件 - `HANDY_BTN_RELEASED` - 按键释放事件 - `HANDY_BTN_CLICK` - 点击事件(支持多击:单击、双击、三击等) - `HANDY_BTN_PRESSED_ACTIVE` - 持续按下事件(可配置上报间隔) 2. **灵活的按键状态读取**: - 支持GPIO按键(高电平/低电平有效) - 支持ADC按键(通过ADC值判断) - 支持I2C/SPI扩展设备按键 - 用户只需实现状态读取函数即可 3. **可配置的按键参数**: - 消抖时间(5-20ms可配置,符合MCU要求) - 点击超时时间(多击间隔时间) - 持续按下事件上报间隔 - 高/低电平有效配置 4. **MCU优化特性**: - 无动态内存分配,使用静态数组 - RAM占用小,适合资源受限系统 - 代码结构简洁,模块化编程 5. **简洁的API接口**: - 初始化、注册、处理、回调等函数 - 易于集成到现有项目中 ## 文件结构 ``` handy_button/ ├── handy_button.h # 模块头文件(API接口) ├── handy_button.c # 模块源文件(实现) ├── handy_button_config.h # 模块配置文件 ├── demo_windows.c # Windows演示程序 └── README.md # 本文档 ``` ## 实现原理 ### 状态机设计 模块使用5种状态的状态机来处理按键事件: 1. **BTN_STATE_IDLE** - 空闲状态 - 等待按键按下 - 检测到按下时进入消抖状态 2. **BTN_STATE_DEBOUNCE_PRESS** - 按下消抖状态 - 消除按键抖动 - 消抖完成后确认按下,发送按下事件 3. **BTN_STATE_PRESSED** - 已按下状态 - 处理持续按下事件(按配置间隔上报) - 检测按键释放时进入释放消抖状态 4. **BTN_STATE_DEBOUNCE_RELEASE** - 释放消抖状态 - 消除释放抖动 - 消抖完成后确认释放,开始点击计数 5. **BTN_STATE_WAIT_CLICK** - 等待点击状态 - 多击检测(等待下一次按下) - 超时后发送点击事件 ### 事件处理流程 ``` 按键按下 → 消抖处理 → 确认按下 → 发送PRESSED事件 ↓ 持续按下 → 按间隔发送PRESSED_ACTIVE事件 ↓ 按键释放 → 消抖处理 → 确认释放 → 发送RELEASED事件 ↓ 多击检测 → 超时或达到最大次数 → 发送CLICK事件 ``` ## 移植指南 ### 1. 添加文件到项目 将以下文件添加到您的MCU项目中: - `handy_button.h` - `handy_button.c` - `handy_button_config.h`(可选,可自定义配置) ### 2. 配置模块参数 在 `handy_button_config.h` 中根据需求修改配置: ```c /* 最大按键数量 */ #define HANDY_BUTTON_MAX_COUNT 10 /* 默认消抖时间(毫秒) */ #define HANDY_BUTTON_DEFAULT_DEBOUNCE_MS 10 /* 默认点击超时时间(毫秒) */ #define HANDY_BUTTON_DEFAULT_CLICK_TIMEOUT_MS 300 /* 默认持续按下事件上报间隔(毫秒) */ #define HANDY_BUTTON_DEFAULT_PRESSED_ACTIVE_MS 200 /* 默认高/低电平有效 */ #define HANDY_BUTTON_DEFAULT_ACTIVE_LEVEL true /* 消抖时间最小值(毫秒) */ #define HANDY_BUTTON_MIN_DEBOUNCE_MS 5 /* 消抖时间最大值(毫秒) */ #define HANDY_BUTTON_MAX_DEBOUNCE_MS 20 ``` ### 3. 实现按键状态读取函数 根据您的硬件实现按键状态读取函数: ```c handy_button_state_t my_read_button_state(uint8_t btn_id) { /* 示例1: GPIO按键 */ if (btn_id == 0) { return (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0) == Bit_SET) ? HANDY_BTN_STATE_PRESSED : HANDY_BTN_STATE_RELEASED; } /* 示例2: ADC按键 */ if (btn_id == 1) { uint16_t adc_value = ADC_GetConversionValue(ADC1); return (adc_value > 1000) ? HANDY_BTN_STATE_PRESSED : HANDY_BTN_STATE_RELEASED; } /* 示例3: I2C扩展按键 */ if (btn_id == 2) { uint8_t key_status = I2C_ReadRegister(0x20, 0x00); return ((key_status & 0x01) != 0) ? HANDY_BTN_STATE_PRESSED : HANDY_BTN_STATE_RELEASED; } return HANDY_BTN_STATE_RELEASED; } ``` ### 4. 初始化模块 在系统初始化时调用: ```c /* 初始化按键模块 */ if (!handy_button_init(my_read_button_state)) { /* 初始化失败处理 */ } /* 设置事件回调函数 */ handy_button_set_callback(my_button_callback); ``` ### 5. 注册按键 为每个按键配置参数并注册: ```c handy_button_config_t config = { .id = 0, /* 按键ID */ .debounce_time_ms = 10, /* 消抖时间10ms(5-20ms范围内) */ .click_timeout_ms = 300, /* 点击超时300ms(多击间隔) */ .pressed_active_ms = 200, /* 持续按下事件上报间隔200ms */ .active_level = true /* 高电平有效 */ }; if (!handy_button_register(&config)) { /* 注册失败处理 */ } ``` ### 6. 定期处理 在主循环或定时器中断中定期调用处理函数: ```c /* 在主循环中,假设每次循环间隔约10ms */ while (1) { /* 其他任务... */ /* 处理按键状态,传递从上一次调用到现在的间隔时间 */ handy_button_process(10); /* 延时或其他任务... */ Delay_ms(10); } ``` ## API参考 ### 初始化函数 ```c bool handy_button_init(handy_button_read_state_t read_state_func); ``` 初始化按键处理模块。 **参数**: - `read_state_func`: 按键状态读取函数指针 **返回值**: - `true`: 初始化成功 - `false`: 初始化失败 **注意**:使用静态内存分配,最大按键数量在`handy_button_config.h`中配置 ### 注册函数 ```c bool handy_button_register(const handy_button_config_t *config); ``` 注册一个按键。 **参数**: - `config`: 按键配置结构体指针 **返回值**: - `true`: 注册成功 - `false`: 注册失败 ### 回调设置函数 ```c void handy_button_set_callback(handy_button_callback_t callback); ``` 设置按键事件回调函数。 **参数**: - `callback`: 回调函数指针 ### 处理函数 ```c void handy_button_process(uint32_t elapsed_ms); ``` 按键处理函数,需要定期调用。 **参数**: - `elapsed_ms`: 从上一次调用到本次调用的时间间隔(毫秒) ### 其他函数 ```c const handy_button_config_t* handy_button_get_info(uint8_t btn_id); void handy_button_reset_all(void); const char* handy_button_get_version(void); uint8_t handy_button_get_registered_count(void); void handy_button_deinit(void); ``` ## 使用示例 ### 基本使用 ```c #include "handy_button.h" /* 按键事件回调函数 */ void button_callback(uint8_t btn_id, handy_button_event_t event, uint32_t param) { switch (event) { case HANDY_BTN_PRESSED: printf("按键 %d 按下\n", btn_id); break; case HANDY_BTN_RELEASED: printf("按键 %d 释放\n", btn_id); break; case HANDY_BTN_CLICK: printf("按键 %d 点击 %u 次\n", btn_id, param); break; case HANDY_BTN_PRESSED_ACTIVE: printf("按键 %d 持续按下 %ums\n", btn_id, param); break; } } /* 按键状态读取函数 */ handy_button_state_t read_button_state(uint8_t btn_id) { /* 根据实际硬件实现 */ switch (btn_id) { case 0: return (read_gpio(KEY0_PIN) == KEY_PRESSED_LEVEL) ? HANDY_BTN_STATE_PRESSED : HANDY_BTN_STATE_RELEASED; case 1: return (read_gpio(KEY1_PIN) == KEY_PRESSED_LEVEL) ? HANDY_BTN_STATE_PRESSED : HANDY_BTN_STATE_RELEASED; default: return HANDY_BTN_STATE_RELEASED; } } int main(void) { /* 系统初始化 */ system_init(); /* 初始化按键模块 */ if (!handy_button_init(read_button_state)) { printf("按键模块初始化失败\n"); return -1; } /* 设置回调函数 */ handy_button_set_callback(button_callback); /* 注册按键0 */ handy_button_config_t btn0_config = { .id = 0, .debounce_time_ms = 10, /* 消抖时间10ms(5-20ms范围内) */ .click_timeout_ms = 300, /* 点击超时300ms(多击间隔) */ .pressed_active_ms = 200, /* 持续按下事件上报间隔200ms */ .active_level = true /* 高电平有效 */ }; handy_button_register(&btn0_config); /* 注册按键1 */ handy_button_config_t btn1_config = { .id = 1, .debounce_time_ms = 15, /* 消抖时间15ms(5-20ms范围内) */ .click_timeout_ms = 300, /* 点击超时300ms(多击间隔) */ .pressed_active_ms = 0, /* 不启用持续按下事件 */ .active_level = false /* 低电平有效 */ }; handy_button_register(&btn1_config); /* 主循环 */ uint32_t last_time = get_system_time_ms(); while (1) { uint32_t current_time = get_system_time_ms(); uint32_t elapsed = current_time - last_time; last_time = current_time; /* 处理按键 */ handy_button_process(elapsed); /* 其他任务 */ delay_ms(10); } return 0; } ``` ### Windows演示程序 项目中包含一个Windows演示程序 (`demo_windows.c`),可以通过以下步骤编译和运行: ```bash # 使用MinGW GCC编译 gcc -o demo.exe handy_button.c demo_windows.c -I. # 运行演示程序 ./demo.exe ``` 演示程序功能: - 虚拟10个按键(0-9) - 支持单击、双击、三击检测 - 支持长按(持续按下事件) - 实时显示按键状态和事件统计 ## 配置说明 ### 按键配置结构体 ```c typedef struct { uint8_t id; /* 按键ID(0~255,必须唯一) */ uint16_t debounce_time_ms; /* 消抖时间(单位:ms,范围5~20ms) */ uint16_t click_timeout_ms; /* 点击超时时间(单位:ms,多击间隔) */ uint16_t pressed_active_ms; /* 持续按下事件上报间隔(单位:ms,0表示不启用) */ bool active_level; /* 高/低电平有效:true=高电平有效,false=低电平有效 */ } handy_button_config_t; ``` ### 配置建议 1. **消抖时间**:5-20ms,根据按键硬件特性调整,符合MCU系统要求 2. **点击超时时间**:通常200-500ms,影响多击检测的灵敏度 3. **持续按下事件间隔**:根据应用需求设置,如音量调节可设置100-200ms 4. **电平有效配置**:根据硬件连接方式设置,true=高电平有效,false=低电平有效 ## 移植注意事项 1. **时间基准**:模块需要毫秒级的时间基准,确保`handy_button_process()`函数按正确的时间间隔调用 2. **内存管理**:使用静态内存分配,无需动态内存,适合资源受限的MCU 3. **中断处理**:如果按键状态读取函数可能在中断中调用,确保其可重入性 4. **平台差异**:不同MCU的GPIO、ADC、I2C等外设操作不同,需要适配状态读取函数 ## 性能优化 1. **减少处理频率**:如果系统资源紧张,可适当降低`handy_button_process()`的调用频率 2. **静态内存分配**:已使用静态数组,无动态内存分配,RAM占用小 3. **简化状态读取**:优化状态读取函数的实现,减少不必要的计算 ## 常见问题 ### Q1: 按键响应不灵敏 - 检查消抖时间是否设置过长 - 检查`handy_button_process()`调用频率是否足够(推荐1-10ms) - 检查状态读取函数是否正确实现 ### Q2: 多击检测不准确 - 调整`click_timeout_ms`参数,增大或减小点击超时时间 - 检查按键释放后是否立即读取到释放状态 ### Q3: 持续按下事件不触发 - 检查`pressed_active_ms`是否设置为非零值 - 检查按键是否真的持续按下足够长时间 ### Q4: 内存占用过大 - 减少最大按键数量 - 禁用动态内存分配,使用静态内存 ## 版本历史 - v1.0.0 (2025-04-14): 初始版本发布 - 支持基本按键事件(按下、释放、点击、持续按下) - 支持多击检测 - 提供Windows演示程序 - 完整的API文档和移植指南 - v2.0.0 (2025-04-14): 优化版本发布 - 去除了动态内存分配,使用静态数组 - 支持高/低电平有效配置 - 消抖时间限制在5-20ms范围内 - 多击不做限制 - RAM占用小,适合MCU系统 - 代码结构优化,模块化编程 ## 许可证 本项目采用MIT许可证,详情请参阅LICENSE文件。 ## 贡献 欢迎提交Issue和Pull Request来改进本项目。 ## 联系方式 如有问题或建议,请通过项目仓库的Issue页面提交。