# key_board **Repository Path**: jindizhiwa/key_board ## Basic Information - **Project Name**: key_board - **Description**: 用于单片机中的小巧多功能按键支持;最强功能:支持不限数量、任意按键、任意按键的任意状态之间的随意组合!!! - **Primary Language**: C - **License**: AGPL-3.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 109 - **Created**: 2022-12-03 - **Last Updated**: 2022-12-03 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # key_board #### 介绍 用于单片机中的小巧多功能按键支持,软件采用了分层的思想,并且做到了与平台无关,用户只需要提供按键的基本信息和读写io电平的函数即可,非常方便移植,同时支持多个矩阵键盘及多个单io控制键盘,目前已实现按下触发、弹起触发、长按自动触发、长按弹起触发、多击触发、连续触发等功能,并且能够随意组合(支持状态的同一时间轴和非同一时间轴),后续还会添加更多的功能。 #### 使用说明 1. 初始化相关的硬件资源。 2. 提供一个1ms的定时器,用于周期性的调用'key_check'函数。 3. 提供按键的描述及读写io的函数。 4. 将键盘注册到系统。 5. 具体的操作参考提供的stm32例程。 6. 因为程序默认使用了堆内存,当发现程序运行结果不正常时,尝试增大你的程序堆空间,或者注册调试接口查看原因。 7. 更详细的使用教程见详细使用说明或者提供的stm32例程。 #### 后续功能 1. 添加一些参数的配置接口(比如可以不使用默认的各种超时时间配置)。 #### 更新日志 1. 2021-01-15 解决了长按、多击等功能检测需要多一轮扫描的BUG。 2. 2021-01-18 增加统计当前按下键的计数接口,一般用于在某些情况需要检测一定超时时间无按键按下就执行特定操作的需求。 3. 2021-01-30 增加任意组合状态的检测,增加新的检测示例程序,将配置项移到单独的配置头文件,优化部分代码细节。 4. 2021-02-01 修改编码格式为utf-8(之前是GB2312)。 5. 2021-02-02 优化示例代码,增加调试信息输出函数注册接口。 6. 2021-05-19 增加消抖处理,并且去掉一个无用配置参数。 7. 2021-05-21 解决了扫描间隔时间远大于按键读取间隔时间产生的状态重复BUG,其它细节优化。 8. 2021-06-13 经过考虑,去掉了扫描时间的配置项(固定为1ms),以减少配置步骤。 9. 2021-06-13 去掉了消抖功能使能的配置项,当消抖时间设置为0时,消抖功能自动失能。 10. 2021-06-13 优化时间值溢出时的解决方案。 11. 2021-06-13 优化部分细节。 12. 2021-10-20 优化部分堆内存的使用 #### 已支持的键盘 1. 矩阵键盘 ![矩阵键盘](./矩阵键盘.jpeg) 2. 单io按键 ![单io按键](./单io按键.jpeg) #### 详细使用说明 将`key_board.c`,`key_board.h`,`key_board_config.h`放进`key_board`文件夹中并包含进你的工程,添加头文件路径。 ##### 基础功能移植(以`stm32`矩阵键盘为例) 首先需要一个可使用的定时器(如果不想使用定时器也可直接放到主循环中,但不推荐,会导致时基不准确),固定为`1ms`触发一次; 准备待检测的按键的基本信息,可参考`key_board_sample.c`文件中的`struct key_pin_t`结构体,如: ```c struct key_pin_t { GPIO_TypeDef *port; //按键端口号 uint16_t pin; //按键的引脚号 GPIO_PinState valid; //按键的有效电平(即按键按下时的电平) GPIO_PinState invalid; //按键的无效电平(即按键空闲时的电平) /* 可添加你的其它参数 */ }; ``` 定义待检测的按键信息,可参考`key_board_sample.c`文件中的`const struct key_pin_t key_pin_sig[]`结构体数组,对应头文件为`key_board_sample.h`,如: ```c //全局变量 const struct key_pin_t key_pin_sig[] = { { .port = KEY_PORT_J12, .pin = KEY_PIN_J12, .valid = KEY_PRESS_LEVEL_J12, .invalid = KEY_RELEASE_LEVEL_J12 }, { .port = KEY_PORT_J34, .pin = KEY_PIN_J34, .valid = KEY_PRESS_LEVEL_J34, .invalid = KEY_RELEASE_LEVEL_J34 }, { .port = KEY_PORT_J56, .pin = KEY_PIN_J56, .valid = KEY_PRESS_LEVEL_J56, .invalid = KEY_RELEASE_LEVEL_J56 }, }; ``` ***如果为矩阵键盘***还需要定义控制io的相关信息,可参考`key_board_sample.c`文件中的`const struct key_pin_t key_pin_ctrl[]`结构体数组,对应头文件为`key_board_sample.h`,如: ```c const struct key_pin_t key_pin_ctrl[] = { { .port = KEY_PORT_J135, .pin = KEY_PIN_J135, .valid = KEY_CTL_LINE_ENABLE, .invalid = KEY_CTL_LINE_DISABLE }, { .port = KEY_PORT_J246, .pin = KEY_PIN_J246, .valid = KEY_CTL_LINE_ENABLE, .invalid = KEY_CTL_LINE_DISABLE }, }; ``` 实现按键io的电平读取函数,可参考`key_board_sample.c`文件中的`pin_level_get`函数,如: ```c static inline bool pin_level_get(const void *desc) { struct key_pin_t *pdesc; pdesc = (struct key_pin_t *)desc; return HAL_GPIO_ReadPin(pdesc->port, pdesc->pin) == pdesc->valid; } ``` ***如果为矩阵键盘***还需要实现按键io的电平写入函数,可参考`key_board_sample.c`文件中的`pin_level_set`函数,如: ```c static inline void pin_level_set(const void *desc, bool flag) { struct key_pin_t *pdesc; pdesc = (struct key_pin_t *)desc; HAL_GPIO_WritePin(pdesc->port, pdesc->pin, flag ? pdesc->valid : pdesc->invalid); } ``` 定义按键的id及功能结构体`struct key_public_sig_t`,可参考`key_board_sample.c`文件中的`const struct key_public_sig_t key_public_sig[]`结构体数组,对应头文件`key_board.h`,如: ```c const struct key_public_sig_t key_public_sig[] = { KEY_PUBLIC_SIG_DEF(KEY_UP, &key_pin_sig[0], pin_level_get, KEY_FLAG_NONE), KEY_PUBLIC_SIG_DEF(KEY_LEFT, &key_pin_sig[1], pin_level_get, KEY_FLAG_NONE), KEY_PUBLIC_SIG_DEF(KEY_DOWN, &key_pin_sig[2], pin_level_get, KEY_FLAG_NONE), //下面的是因为使用的矩阵键盘而扩展出来的三个按键 KEY_PUBLIC_SIG_DEF(KEY_ENTER, &key_pin_sig[0], pin_level_get, KEY_FLAG_NONE), KEY_PUBLIC_SIG_DEF(KEY_RIGHT, &key_pin_sig[1], pin_level_get, KEY_FLAG_NONE), KEY_PUBLIC_SIG_DEF(KEY_EXIT, &key_pin_sig[2], pin_level_get, KEY_FLAG_NONE), }; ``` ***如果为矩阵键盘***还需要定义控制io的id及功能结构体`struct key_public_ctrl_t`,可参考`key_board_sample.c`文件中的`const struct key_public_ctrl_t key_public_ctrl[]`结构体数组,对应头文件`key_board.h`,如: ```c const struct key_public_ctrl_t key_public_ctrl[] = { KEY_PUBLIC_CTRL_DEF(&key_pin_ctrl[0], pin_level_set), KEY_PUBLIC_CTRL_DEF(&key_pin_ctrl[1], pin_level_set), }; ``` 初始化键盘,可参考`key_board_sample.c`文件中的`GPIO_Key_Board_Init`函数,如: ```c void GPIO_Key_Board_Init(void) { //硬件io的初始化 GPIO_InitTypeDef GPIO_InitStruct; unsigned int i; RCC_KEY_BOARD_CLK_ENABLE(); GPIO_InitStruct.Pull = GPIO_PULLUP; GPIO_InitStruct.Mode = GPIO_MODE_INPUT; for(i = 0;i < ARRAY_SIZE(key_pin_sig);i++) { GPIO_InitStruct.Pin = key_pin_sig[i].pin; HAL_GPIO_Init(key_pin_sig[i].port, &GPIO_InitStruct); } GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; for(i = 0;i < ARRAY_SIZE(key_pin_ctrl);i++) { GPIO_InitStruct.Pin = key_pin_ctrl[i].pin; HAL_GPIO_Init(key_pin_ctrl[i].port, &GPIO_InitStruct); } //初始化键盘 key_board_init(); //注册键盘到系统中(矩阵键盘) key_board_register(KEY_BOARD_MATRIX, key_public_sig, ARRAY_SIZE(key_public_sig), key_public_ctrl, ARRAY_SIZE(key_public_ctrl)); } ``` 主流程伪代码框架,更多例子参考`main_test.c`文件: ```c int main(void) { //初始化硬件io,并注册键盘 GPIO_Key_Board_Init(); //初始化定时器,用于按键扫描(1ms) init_tmr(); for(;;) { if(key_check_state(KEY_UP, KEY_RELEASE)) { PRINTF("KEY_UP KEY_RELEASE\r\n"); } if(key_check_state(KEY_UP, KEY_PRESS)) { PRINTF("KEY_UP KEY_PRESS\r\n"); } } } //定时器到期回调处理函数 void tmr_irq_callback(void) { //调用按键扫描核心函数 key_check(); } ``` ##### 扩展功能长按的使用 首先确保`key_board_config.h`文件中宏`KEY_LONG_SUPPORT`已处于使能状态,并且正确设置了宏`KEY_DEFAULT_LONG_TRRIGER_TIME`的值; 设置按键功能需要对长按进行检测,如: ```c KEY_PUBLIC_SIG_DEF(KEY_UP, &key_pin_sig[0], pin_level_get, KEY_FLAG_PRESS_LONG | KEY_FLAG_RELEASE_LONG) ``` 使用例程: ```c if(key_check_state(KEY_UP, KEY_PRESS_LONG)) { PRINTF("KEY_UP KEY_PRESS_LONG\r\n"); } if(key_check_state(KEY_UP, KEY_RELEASE_LONG)) { PRINTF("KEY_UP KEY_RELEASE_LONG\r\n"); } ``` ##### 扩展功能连按的使用 首先确保`key_board_config.h`文件中宏`KEY_CONTINUOUS_SUPPORT`已处于使能状态,并且正确设置了宏`KEY_DEFAULT_CONTINUOUS_INIT_TRRIGER_TIME`和`KEY_DEFAULT_CONTINUOUS_PERIOD_TRRIGER_TIME`的值; 设置按键功能需要对连按进行检测,如: ```c KEY_PUBLIC_SIG_DEF(KEY_UP, &key_pin_sig[0], pin_level_get, KEY_FLAG_PRESS_CONTINUOUS) ``` 使用例程: ```c if(key_check_state(KEY_UP, KEY_PRESS_CONTINUOUS)) { PRINTF("KEY_UP KEY_PRESS_CONTINUOUS\r\n"); } ``` ##### 扩展功能多击的使用 首先确保`key_board_config.h`文件中宏`KEY_MULTI_SUPPORT`已处于使能状态,并且正确设置了宏`KEY_DEFAULT_MULTI_INTERVAL_TIME`的值; 设置按键功能需要多击进行检测,如: ```c KEY_PUBLIC_SIG_DEF(KEY_UP, &key_pin_sig[0], pin_level_get, KEY_FLAG_PRESS_MULTI | KEY_FLAG_RELEASE_MULTI) ``` 使用例程: ```c unsigned int res; res = key_check_state(KEY_UP, KEY_PRESS_MULTI); if(res) { PRINTF("KEY_UP KEY_PRESS_MULTI:%d\r\n", res); } res = key_check_state(KEY_UP, KEY_RELEASE_MULTI); if(res) { PRINTF("KEY_UP KEY_RELEASE_MULTI:%d\r\n", res); } ``` ##### 扩展功能组合状态(同一时间轴) 感谢网友:石玉虎[@shi-yuhu]的反馈,已更正之前错误的使用案例。 使用例程: ```c unsigned int key_down_release_long, key_up_release_long; key_down_release_long = key_check_state(KEY_DOWN, KEY_RELEASE_LONG); key_up_release_long = key_check_state(KEY_UP, KEY_RELEASE_LONG); if(key_down_release_long && key_up_release_long) { PRINTF("KEY_DOWN KEY_RELEASE_LONG && KEY_UP KEY_RELEASE_LONG\n"); } ``` ##### 扩展功能组合状态(非同一时间轴) 首先确保`key_board_config.h`文件中宏`KEY_COMBINE_SUPPORT`已处于使能状态,并且正确设置了宏`KEY_DEFAULT_COMBINE_INTERVAL_TIME`的值; 使用例程: ```c //用于保存注册后的组合状态id static unsigned int test_id1, test_id2; //定义要检测的状态 const struct key_combine_t test_combine1[] = { { .id = KEY_UP, .state = KEY_PRESS }, { .id = KEY_DOWN, .state = KEY_PRESS_LONG }, { .id = KEY_UP, .state = KEY_PRESS }, }; //注册组合状态 test_id1 = key_combine_register(test_combine1, ARRAY_SIZE(test_combine1)); const struct key_combine_t test_combine2[] = { { .id = KEY_UP, .state = KEY_PRESS }, { .id = KEY_DOWN, .state = KEY_PRESS }, { .id = KEY_UP, .state = KEY_PRESS }, { .id = KEY_DOWN, .state = KEY_PRESS }, }; test_id2 = key_combine_register(test_combine2, ARRAY_SIZE(test_combine2)); if(key_check_combine_state(test_id1)) { PRINTF("combine test_id1\r\n"); } if(key_check_combine_state(test_id2)) { PRINTF("combine test_id2\r\n"); } ```