├── LICENSE ├── comKey.h ├── README.md └── comKey.c /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 ZheWana 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /comKey.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file comKey.h 3 | * @author ZheWana 4 | * @date 2022/3/6 006 5 | */ 6 | 7 | #ifndef COMKEY_H 8 | #define COMKEY_H 9 | 10 | #include "Debug.h" 11 | #include "stdint.h" 12 | 13 | #ifdef USE_HAL_DRIVER 14 | 15 | #include "gpio.h" 16 | 17 | #endif // USE_HAL_DRIVER 18 | 19 | #define COMKEY_ClickThreshold 20 20 | #define COMKEY_HoldThreshold 800 21 | #define COMKEY_IntervalVal 200 22 | #define COMKEY_HoldTriggerThreshold 300 23 | 24 | typedef struct comKeyTypedef { 25 | uint8_t preVal : 1; 26 | uint8_t val : 1; 27 | 28 | uint32_t preTimer; // 按下计时 29 | uint32_t intervalTimer; // 放开计时 30 | uint16_t triggerTimer; // 长按触发计时 31 | 32 | struct comKeyTypedef* next; 33 | 34 | enum { 35 | Release = 0, 36 | PrePress, 37 | Prelong, 38 | LongHold, 39 | MultiClick 40 | } state; // 状态枚举 41 | 42 | uint32_t holdTime; // 长按计时 43 | uint8_t clickCnt; // 按下计数 44 | } comkey_t, *pcomkey_t; 45 | 46 | // TODO:Rewrite the "ComKey_SyncValue" function by user. 47 | void ComKey_SyncValue(comkey_t* key); 48 | 49 | void ComKey_Init(comkey_t* key, int pollingPeriod); 50 | 51 | void ComKey_Handler(); 52 | 53 | void ComKey_FirstLongTriggerCallback(comkey_t* key); 54 | 55 | void ComKey_LongHoldCallback(comkey_t* key); 56 | 57 | void ComKey_HoldTriggerCallback(comkey_t* key); 58 | 59 | void ComKey_MultipleClickCallback(comkey_t* key); 60 | 61 | void ComKey_KeyReleaseCallback(comkey_t* key); 62 | 63 | void ComKey_KeyPressCallback(comkey_t* key); 64 | 65 | #endif // COMKEY_H 66 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CommonKey 2 | 3 | 基础按键处理库,实现了一般按键的单点、连点、长按检测。可进行多按键的封装,采用回调的方式与主程序通讯。 4 | 5 | ## 使用说明 6 | 7 | 要使用[CommonKey][2]按键检测封装,你需要在你的工程中做出如下处理: 8 | 9 | * 将文件加入你的工程路径中,按需修改时间阈值宏: 10 | 11 | > `COMKEY_ClickThreshold`:单击时间阈值,短于此时间视为抖动 12 | > 13 | > `COMKEY_HoldThreshold`:长按时间阈值,长于此时间视为长按 14 | > 15 | > `COMKEY_HoldTriggerThreshold`:长按触发阈值,长按时每隔该事件触发一次按键 16 | > 17 | > `COMKEY_IntervalVal`:间隔时间阈值,短于此时间视为连按 18 | 19 | * 为您的每个按键定义一个`comkey_t`句柄 20 | 21 | * 为**每个按键**调用`ComKey_Init`初始化函数,参数为**按键句柄**和**检测时间** 22 | 23 | * 改写`ComKey_SyncValue`函数 24 | 25 | * 根据需要重写回调函数: 26 | 27 | > `ComKey_FirstLongTriggerCallback`:**首次长按触发回调函数**,参数为**按键句柄** 28 | > 29 | > `ComKey_LongHoldCallback`:**长按回调函数**,参数为**按键句柄** 30 | > 31 | > `ComKey_HoldTriggerCallback`:**长按触发回调函数**,参数为**按键句柄** 32 | > 33 | > `ComKey_MultipleClickCallback`:**连点回调函数**,参数为**按键句柄** 34 | > 35 | > `ComKey_KeyReleaseCallback`:**按键松开回调函数**,参数为**按键句柄** 36 | > 37 | > `ComKey_KeyPressCallback`:**按键按下回调函数**,参数为**按键句柄** 38 | 39 | * 以一个固定的间隔调用`ComKey_Handler`函数 40 | 41 | * 测试实际效果 42 | 43 | *** 44 | 45 | ## 一些说明与示例 46 | 47 | 此部分提供了一些对于接口函数、回调函数等的说明,以及简单的使用示例。 48 | 49 | ### 接口函数的移植 50 | 51 | 库内部不提供对按键触发电平的检测,移植时**请务必**保证按键触发时传递到`key->val`的值为1; 52 | 53 | 如果您有多个按键,请通过**您自己定义的按键句柄**来区分您的触发按键; 54 | 55 | 此外**请不要删除**最后一句代码,这将影响程序的正常运行。 56 | 57 | ```C 58 | void ComKey_SyncValue(comkey_t *key) { 59 | //if your key was pressed,"key->val" should be 1. 60 | if (key == &key0) { 61 | key->val = !HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0); 62 | } 63 | if (key == &key1) { 64 | key->val = !HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1); 65 | } 66 | 67 | //IMPORTANT!!!DO NOT MODIFIED!!! 68 | key->preVal = key->val; 69 | } 70 | ``` 71 | 72 | ### 首次长按触发回调函数 73 | 74 | 如果您有多个按键,请通过**您自己定义的按键句柄**来区分您的触发按键; 75 | 76 | 该回调函数为进入长按状态的信号,此函数被调用意味着您的按键**进入长按状态**。 77 | 78 | ```C 79 | __attribute__((weak)) void ComKey_FirstLongTriggerCallback(comkey_t* key) 80 | { 81 | if (key == &key0) 82 | printf("key0: "); 83 | if (key == &key1) { 84 | printf("key1: "); 85 | } 86 | printf("was triggerred!\n", key->holdTime); 87 | } 88 | ``` 89 | 90 | ### 长按回调函数 91 | 92 | 如果您有多个按键,请通过**您自己定义的按键句柄**来区分您的触发按键; 93 | 94 | 此外,您可以通过按键句柄的`holdTime`成员来获取**当次长按的持续时间**(ms)供您使用. 95 | 96 | ```C 97 | void ComKey_LongHoldCallback(comkey_t *key) { 98 | if (key == &key0) 99 | printf("key0: "); 100 | if (key == &key1) { 101 | printf("key1: "); 102 | } 103 | printf("hold %ldms\n", key->holdTime); 104 | } 105 | ``` 106 | 107 | ### 长按触发回调函数 108 | 109 | 如果您有多个按键,请通过**您自己定义的按键句柄**来区分您的触发按键; 110 | 111 | 当您采用按键单击进行类似数据加减的任务时,若想**通过长按来实现多次单击的效果**,请使用此回调函数; 112 | 113 | 该函数将会以`COMKEY_HoldTriggerThreshold`的间隔被调用。 114 | 115 | ```C 116 | void ComKey_HoldTriggerCallback(comkey_t *key) { 117 | if (key == &key0) 118 | printf("key0: HoldTrigger\n"); 119 | if (key == &key1) { 120 | printf("key1: HoldTrigger\n"); 121 | } 122 | } 123 | ``` 124 | 125 | ### 连续点击回调函数 126 | 127 | 如果您有多个按键,请通过**您自己定义的按键句柄**来区分您的触发按键; 128 | 129 | 当您多次连续进行点击**后**,会触发该回调函数,连续点击间隔时间阈值可通过`COMKEY_IntervalVal`宏来进行修改; 130 | 131 | 此外,您可以通过按键句柄的`clickCnt`成员来获得当次**连按的次数**以供您使用。 132 | 133 | ```C 134 | void ComKey_MultipleClickCallback(comkey_t *key) { 135 | if (key == &key0) 136 | printf("key0: "); 137 | if (key == &key1) { 138 | printf("key1: "); 139 | } 140 | printf("click: %d\n", key->clickCnt); 141 | } 142 | ``` 143 | 144 | ### 按键按下回调函数 145 | 146 | 如果您有多个按键,请通过**您自己定义的按键句柄**来区分您的触发按键; 147 | 148 | 任何按键按下的事件都会触发该回调函数。 149 | 150 | ```C 151 | void ComKey_KeyPressCallback(comkey_t *key) { 152 | if (key == &key0) { 153 | printf("key0: "); 154 | } 155 | if (key == &key1) { 156 | printf("key1: "); 157 | } 158 | printf("keyPress!\n"); 159 | } 160 | ``` 161 | 162 | ### 按键释放回调函数 163 | 164 | 如果您有多个按键,请通过**您自己定义的按键句柄**来区分您的触发按键; 165 | 166 | 此外,您可以通过按键句柄的`state`成员的两个枚举值: 167 | 168 | `LongHold`和`MultiClick` 169 | 170 | 来区分当次释放按键的事件是**长按**还是**连击**(单击)。 171 | 172 | ```C 173 | void ComKey_KeyReleaseCallback(comkey_t *key) { 174 | if (key == &key0) { 175 | printf("key0: "); 176 | } 177 | if (key == &key1) { 178 | printf("key1: "); 179 | } 180 | 181 | if (key->state == LongHold) { 182 | printf("LongHold"); 183 | } else if (key->state == MultiClick) { 184 | printf("Click"); 185 | } 186 | printf(" Release!\n"); 187 | } 188 | ``` 189 | 190 | 191 | 192 | *** 193 | 194 | ## 实现思路 195 | 196 | 思路其实很简单,核心部分是两个计时器:**按下计时器**和**间隔计时器**。通过有限状态机的方式来实现对各个功能的检测,状态转移图大概如下: 197 | 198 | ![按键状态转移][3] 199 | 200 | 仅作分享,欢迎提出优化意见以及建议🥰 201 | 202 | 203 | [1]: https://zhewana.cn/usr/wp-content/2022/03/2707392082.png 204 | [2]: https://github.com/Zhewana/CommonKey 205 | [3]: https://zhewana.cn/wp-content/uploads/2022/03/3781171879.png 206 | -------------------------------------------------------------------------------- /comKey.c: -------------------------------------------------------------------------------- 1 | /** 2 | * @file comKey.c 3 | * @author ZheWana 4 | * @date 2022/3/6 006 5 | */ 6 | 7 | #include "comKey.h" 8 | 9 | comkey_t key0, key1; 10 | 11 | __attribute__((weak)) void ComKey_SyncValue(comkey_t* key) 12 | { 13 | // if your key was pressed,"key->val" should be 1. 14 | if (key == &key0) { 15 | key->val = !HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0); 16 | } 17 | if (key == &key1) { 18 | key->val = !HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1); 19 | } 20 | 21 | // IMPORTANT!!!DO NOT MODIFIED!!! 22 | key->preVal = key->val; 23 | } 24 | 25 | __attribute__((weak)) void ComKey_FirstLongTriggerCallback(comkey_t* key) 26 | { 27 | if (key == &key0) 28 | printf("key0: "); 29 | if (key == &key1) { 30 | printf("key1: "); 31 | } 32 | printf("was triggerred!\n", key->holdTime); 33 | } 34 | 35 | __attribute__((weak)) void ComKey_LongHoldCallback(comkey_t* key) 36 | { 37 | if (key == &key0) 38 | printf("key0: "); 39 | if (key == &key1) { 40 | printf("key1: "); 41 | } 42 | printf("hold %ldms\n", key->holdTime); 43 | } 44 | 45 | __attribute__((weak)) void ComKey_HoldTriggerCallback(comkey_t* key) 46 | { 47 | if (key == &key0) 48 | printf("key0: HoldTrigger\n"); 49 | if (key == &key1) { 50 | printf("key1: HoldTrigger\n"); 51 | } 52 | } 53 | 54 | __attribute__((weak)) void ComKey_MultipleClickCallback(comkey_t* key) 55 | { 56 | if (key == &key0) 57 | printf("key0: "); 58 | if (key == &key1) { 59 | printf("key1: "); 60 | } 61 | printf("click: %d\n", key->clickCnt); 62 | } 63 | 64 | __attribute__((weak)) void ComKey_KeyReleaseCallback(comkey_t* key) 65 | { 66 | if (key == &key0) { 67 | printf("key0: "); 68 | } 69 | if (key == &key1) { 70 | printf("key1: "); 71 | } 72 | 73 | if (key->state == LongHold) { 74 | printf("LongHold"); 75 | } else if (key->state == MultiClick) { 76 | printf("Click"); 77 | } 78 | printf(" Release!\n"); 79 | } 80 | 81 | __attribute__((weak)) void ComKey_KeyPressCallback(comkey_t* key) 82 | { 83 | if (key == &key0) { 84 | printf("key0: "); 85 | } 86 | if (key == &key1) { 87 | printf("key1: "); 88 | } 89 | printf("keyPress!\n"); 90 | } 91 | 92 | /*************** DO NOT MODIFY THE FOLLOWING CODE *******************/ 93 | 94 | static pcomkey_t head = NULL, tail = NULL; 95 | static uint8_t ITPeriod = 1; 96 | 97 | void ComKey_Init(comkey_t* key, int pollingPeriod) 98 | { 99 | ITPeriod = pollingPeriod; 100 | key->state = Release; 101 | key->next = NULL; 102 | 103 | if (head == NULL) { 104 | head = key; 105 | tail = key; 106 | } else { 107 | tail->next = key; 108 | tail = tail->next; 109 | } 110 | } 111 | 112 | void ComKey_Handler() 113 | { 114 | 115 | for (pcomkey_t key = head; key != NULL; key = key->next) { 116 | //键值同步 117 | ComKey_SyncValue(key); 118 | 119 | //按下计时 120 | if (!key->val) { 121 | if (key->state == LongHold) { 122 | key->holdTime = key->preTimer; 123 | } 124 | key->preTimer = 0; 125 | } 126 | if (key->preVal & key->val) { 127 | key->preTimer += ITPeriod; 128 | } 129 | //间隔计时 130 | if (key->state == MultiClick) { 131 | key->intervalTimer += ITPeriod; 132 | } else { 133 | key->intervalTimer = 0; 134 | } 135 | 136 | //事件生成 137 | switch (key->state) { 138 | case Release: 139 | key->clickCnt = 0; 140 | 141 | if (key->val) { 142 | key->state = PrePress; 143 | } 144 | break; 145 | case PrePress: 146 | 147 | if (!key->val) { 148 | key->state = Release; 149 | } else if (key->preTimer > COMKEY_ClickThreshold) { 150 | key->state = Prelong; 151 | ComKey_KeyPressCallback(key); 152 | } 153 | break; 154 | case Prelong: 155 | 156 | if (!key->val) { 157 | key->state = MultiClick; 158 | key->clickCnt++; 159 | } else if (key->preTimer > COMKEY_HoldThreshold) { 160 | key->state = LongHold; 161 | key->triggerTimer = COMKEY_HoldTriggerThreshold; 162 | ComKey_FirstLongTriggerCallback(key); 163 | } 164 | break; 165 | case LongHold: { 166 | if (key->triggerTimer > 0) 167 | key->triggerTimer -= ITPeriod; 168 | else { 169 | key->triggerTimer = COMKEY_HoldTriggerThreshold; 170 | ComKey_HoldTriggerCallback(key); 171 | } 172 | 173 | if (!key->val) { 174 | ComKey_LongHoldCallback(key); 175 | ComKey_KeyReleaseCallback(key); 176 | key->state = Release; 177 | } 178 | } break; 179 | case MultiClick: 180 | 181 | if (key->intervalTimer > COMKEY_IntervalVal) { 182 | ComKey_MultipleClickCallback(key); 183 | ComKey_KeyReleaseCallback(key); 184 | key->state = Release; 185 | } else if (key->preTimer > COMKEY_ClickThreshold) { 186 | key->state = Prelong; 187 | } 188 | break; 189 | 190 | default: 191 | #ifdef USE_HAL_DRIVER 192 | Error_Handler(); 193 | #endif // USE_HAL_DRIVER 194 | break; 195 | } 196 | } 197 | } --------------------------------------------------------------------------------