├── .gitignore ├── LICENSE ├── README.md ├── docs ├── README_zh-CN.md ├── image.png └── key_value_log.png ├── embedded_button.c ├── embedded_button.h └── examples ├── README.md ├── example_callback.c └── example_poll.c /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Object files 5 | *.o 6 | *.ko 7 | *.obj 8 | *.elf 9 | 10 | # Linker output 11 | *.ilk 12 | *.map 13 | *.exp 14 | 15 | # Precompiled Headers 16 | *.gch 17 | *.pch 18 | 19 | # Libraries 20 | *.lib 21 | *.a 22 | *.la 23 | *.lo 24 | 25 | # Shared objects (inc. Windows DLLs) 26 | *.dll 27 | *.so 28 | *.so.* 29 | *.dylib 30 | 31 | # Executables 32 | *.exe 33 | *.out 34 | *.app 35 | *.i*86 36 | *.x86_64 37 | *.hex 38 | 39 | # Debug files 40 | *.dSYM/ 41 | *.su 42 | *.idb 43 | *.pdb 44 | 45 | # Kernel Module Compile Results 46 | *.mod* 47 | *.cmd 48 | .tmp_versions/ 49 | modules.order 50 | Module.symvers 51 | Mkfile.old 52 | dkms.conf 53 | 54 | # vscode 55 | .vscode 56 | 57 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

EmbeddedButton

2 | 3 |

4 | 5 | github-profile-readme-generator license 6 | 7 | 8 | github-profile-readme-generator stars 9 | 10 | 11 | github-profile-readme-generator issues 12 | 13 | 14 | github-profile-readme-generator pull-requests 15 | 16 |

17 | 18 |

19 | [简体中文] 20 |

21 | 22 |

👋 Introduction

23 | EmbeddedButton is a lightweight and user-friendly embedded button handling framework that allows for infinite expansion of buttons; 24 | 25 | - Supports multiple types of key events, including multi-tap, long press, short press followed by long press, and more; 26 | - The module implements the entire code logic based on a few simple principles; 27 | - Core processing adopts a data-driven approach, supporting bitwise operations for key value matching. It only includes basic key value definitions internally, while other key values' meanings are defined by users through **configuring key value matching rules**, without the need to modify the code intrusively, providing great flexibility.; 28 | 29 | ## 🌱 Feature 30 | 31 | > 1.Relying on just a few simple principles, it supports the entire logic for button judgment. 32 | - As long as the key value is non-zero,tick++ 33 | - Whenever the button state changes, update the key value once(**__append_bit()**),and reset the tick(to ensure the tick represents the time of press or release) 34 | - The length of the tick time and the button release are used as criteria to determine the end of a state, which enables good implementation of operations such as short press and long press.; 35 | 36 | > 2.Implemented in C language, it cleverly uses bitwise operations to represent each button's key value in binary form, where 1 indicates a press and 0 indicates a release. 37 | 38 | key value | means 39 | --- | --- 40 | 0b0 | Not pressing the button 41 | 0b010 | Single click 42 | 0b01010 | Double click 43 | 0b01010...n | Repeat n click 44 | 0b011 | Long press start 45 | 0b0111| Long press hold 46 | 0b01110|Long press finish 47 | 0b01011|Single click then long press 48 | 0b0101011 | Double click then long press 49 | 0b01010..n11 | repeat n click then long press 50 | 51 | > 3.Core processing adopts a data-driven approach, supporting bitwise operations for key value matching: 52 | - Critical data structure: the key value matching rule configuration table: 53 | ```c 54 | typedef struct { 55 | key_value_type_t operand; // operand 56 | kv_match_operator_type_t operator; // operator 57 | key_value_type_t tar_result; // tar result 58 | void (*kv_func_cb)(void*); // Callback functions called upon matching. 59 | } key_value_match_map_t; 60 | 61 | ``` 62 | - Critical algorithms: 63 | ```c 64 | key_value_type_t operand_origin = button->kv_match_map_ptr[i].operand; 65 | key_value_type_t operand_result = button->kv_match_map_ptr[i].operand; 66 | kv_match_operator_type_t operator =button->kv_match_map_ptr[i].operator; 67 | key_value_type_t tar_result = button->kv_match_map_ptr[i].tar_result; 68 | 69 | if(operator == KV_MATCH_OPERATOR_NULL) 70 | operand_result = button->key_value; 71 | else if(operator & KV_MATCH_OPERATOR_BITWISE_AND) 72 | operand_result = (operand_origin & button->key_value); 73 | else if(operator & KV_MATCH_OPERATOR_BITWISE_OR) 74 | operand_result = (operand_origin | button->key_value); 75 | else if(operator & KV_MATCH_OPERATOR_BITWISE_NOT) 76 | operand_result = ~(button->key_value); 77 | else if(operator & KV_MATCH_OPERATOR_BITWISE_XOR) 78 | operand_result = (operand_origin ^ button->key_value); 79 | 80 | if(operand_result == tar_result) 81 | { 82 | button->kv_match_map_ptr[i].kv_func_cb(button); 83 | } 84 | ``` 85 | 86 | - Supported operators: 87 | ```c 88 | #define KV_MATCH_OPERATOR_NULL (0) // null operator,only judge by(key_value == tar_result)?, this is default 89 | #define KV_MATCH_OPERATOR_BITWISE_AND (1 << 0) // Bitwise AND operator,(operand & key_value == tar_result)? 90 | #define KV_MATCH_OPERATOR_BITWISE_OR (1 << 1) // Bitwise OR operator,(operand | key_value == tar_result)? 91 | #define KV_MATCH_OPERATOR_BITWISE_NOT (1 << 2) // Bitwise NOT operator,(~ key_value == tar_result)? 92 | #define KV_MATCH_OPERATOR_BITWISE_XOR (1 << 2) // Bitwise XOR operator,(operand ^ key_value == tar_result)? 93 | ``` 94 | 95 | > 4.Designed based on an object-oriented approach, each button object is managed by its own instance of a data structure. 96 | 97 | ## 📋 Getting Started 98 | 99 | ### 1)How to use 100 |
101 | Click to expand/collapse C code 102 | 103 | - Using the callback method as an example: 104 | ```c 105 | // 1.Include header file 106 | #include "embedded_button.h" 107 | 108 | // 2.Define button entities 109 | struct button_obj_t button1; 110 | 111 | // 3.Configure the GPIO level read interface 112 | uint8_t read_button_pin(uint8_t button_id) 113 | { 114 | // you can share the GPIO read function with multiple Buttons 115 | switch(button_id) 116 | { 117 | case 0: 118 | return get_button1_value(); // User-implemented 119 | break; 120 | 121 | default: 122 | return 0; 123 | break; 124 | } 125 | 126 | return 0; 127 | } 128 | 129 | // 4. Configure key value matching rules (set up callback events) 130 | void single_click_handle(void* btn) 131 | { 132 | //do something... 133 | printf("/****single click****/\r\n"); 134 | } 135 | 136 | void double_click_handle(void* btn) 137 | { 138 | //do something... 139 | printf("/****double click****/\r\n"); 140 | } 141 | 142 | void long_press_handle(void* btn) 143 | { 144 | //do something... 145 | printf("/****long press****/\r\n"); 146 | } 147 | 148 | void single_click_then_long_press_handle(void* btn) 149 | { 150 | //do something... 151 | printf("/****single click and long press****/\r\n"); 152 | } 153 | 154 | void quintuple_click_handle(void* btn) 155 | { 156 | //do something... 157 | if(check_is_repeat_click_mode(btn)) 158 | printf("/****quintuple click****/\r\n"); 159 | } 160 | 161 | const key_value_match_map_t button1_map[] = 162 | { 163 | { 164 | .tar_result = SINGLE_CLICK_KV, 165 | .kv_func_cb = single_click_handle 166 | }, 167 | { 168 | .tar_result = DOUBLE_CLICK_KV, 169 | .kv_func_cb = double_click_handle 170 | }, 171 | { 172 | .tar_result = LONG_PRESEE_START, 173 | .kv_func_cb = long_press_handle 174 | }, 175 | { 176 | .tar_result = SINGLE_CLICK_THEN_LONG_PRESS_KV, 177 | .kv_func_cb = single_click_then_long_press_handle 178 | }, 179 | { 180 | .operand = 0b1010101010, 181 | .operator = KV_MATCH_OPERATOR_BITWISE_AND, 182 | .tar_result = 0b1010101010, 183 | .kv_func_cb = quintuple_click_handle 184 | } 185 | }; 186 | 187 | int main() 188 | { 189 | /************************************************ 190 | ****5.Initialize button objects, where the parameter means: 191 | **** 192 | ****- Button entities 193 | ****- Bind the GPIO level read interface for the button**read_button1_pin()** 194 | ****- Set the effective trigger level" 195 | ****- Button ID 196 | ****- Key value matching rule configuration table 197 | ****- Size of Key value matching rule configuration table 198 | *************************************************/ 199 | button_init(&button1, read_button_pin, 0, 0, button1_map, ARRAY_SIZE(button1_map)); 200 | // 6.Button start 201 | button_start(&button1); 202 | 203 | // 7. Set up a timer with a 5ms interval to periodically call the button background processing function **button_ticks()** 204 | __timer_start(button_ticks, 0, 5); 205 | 206 | while(1) 207 | {} 208 | } 209 | ``` 210 | ![Alt text](image.png) 211 |
212 | 213 | ### 2)Debug 214 | 215 |
216 | Click to expand/collapse 217 | 218 | - Defining the **EB_DEBUG_PRINTF** macro will enable key value printing, for example, as shown below, you need to replace printf with your own print function: 219 | ```c 220 | #define EB_DEBUG_PRINTF printf 221 | ``` 222 | ![alt text](./docs/key_value_log.png) 223 |
224 | 225 | ## ⚡ Ohter 226 | - This project was developed based on some issues I encountered with button drivers during my actual development work, drawing inspiration from another project (see reference link). Previously, I mentioned the advantages of this module. Now, let me discuss areas that need improvement: For representing combinations of multiple buttons, there is currently no elegant solution. I plan to refine this aspect when I have further ideas. Finally, I'd like to thank my colleague [shawnfeng0](https://github.com/shawnfeng0) for his help and thoughts, as well as anyone who is currently using this module. I welcome everyone to join in the development and improvement! 227 | - More advanced usage examples can be found in [examples](./examples/README.md) 228 | 229 | ## 💬 Reference links 230 | - [MultiButton](https://github.com/0x1abin/MultiButton) 231 | - [FlexibleButton](https://github.com/murphyzhao/FlexibleButton/tree/master) 232 | - [armfly](https://www.armbbs.cn/forum.php?mod=viewthread&tid=111527&highlight=%B0%B4%BC%FC) 233 | -------------------------------------------------------------------------------- /docs/README_zh-CN.md: -------------------------------------------------------------------------------- 1 |

EmbeddedButton

2 | 3 |

4 | 5 | github-profile-readme-generator license 6 | 7 | 8 | github-profile-readme-generator stars 9 | 10 | 11 | github-profile-readme-generator issues 12 | 13 | 14 | github-profile-readme-generator pull-requests 15 | 16 |

17 | 18 |

👋 简介

19 | EmbeddedButton是一个轻量级简单易用的嵌入式按键处理框架,可无限拓展按键; 20 | 21 | - 支持多连击、长按、短按长按等多种按键事件; 22 | - 模块通过几个简单原则完成了整个代码逻辑的支撑; 23 | - 核心处理采取数据驱动方式,支持位运算键值匹配,仅内置基本键值定义,其他键值含义由用户通过**配置键值匹配规则**自行定义,而不用侵入式修改代码,灵活性极强; 24 | 25 | ## 🌱 特性 26 | 27 | > 1.依靠简单几个原则,支持起整个按键判断逻辑 28 | - 只要键值非零,时间tick++ 29 | - 只要按键状态发生变化,改变一次键值(**__append_bit()**),tick时间清零(确保tick为按下或抬起的时间) 30 | - 以tick时间的长短及按键抬起作为一次状态结束的判断依据,可以很好的实现短按长按等操作; 31 | 32 | > 2.使用C语言实现,巧妙利用位运算来实现每个按键键值的二进制记录表示,1代表按下,0代表松开 33 | 34 | 键值 | 说明 35 | --- | --- 36 | 0b0 | 未按下 37 | 0b010 | 单击 38 | 0b01010 | 双击 39 | 0b01010...n | n连击 40 | 0b011 | 长按开始 41 | 0b0111| 长按保持 42 | 0b01110|长按结束 43 | 0b01011|短按然后长按 44 | 0b0101011 | 双击然后长按 45 | 0b01010..n11 | n连击然后长按 46 | 47 | > 3.核心处理采取数据驱动方式,支持位运算键值匹配: 48 | - 关键数据结构,键值匹配规则配置表: 49 | ```c 50 | typedef struct { 51 | key_value_type_t operand; // 操作数 52 | kv_match_operator_type_t operator; // 操作符 53 | key_value_type_t tar_result; // 目标结果 54 | void (*kv_func_cb)(void*); // 符合匹配后调用的回调函数 55 | } key_value_match_map_t; 56 | 57 | ``` 58 | - 关键算法: 59 | ```c 60 | key_value_type_t operand_origin = button->kv_match_map_ptr[i].operand; 61 | key_value_type_t operand_result = button->kv_match_map_ptr[i].operand; 62 | kv_match_operator_type_t operator =button->kv_match_map_ptr[i].operator; 63 | key_value_type_t tar_result = button->kv_match_map_ptr[i].tar_result; 64 | 65 | if(operator == KV_MATCH_OPERATOR_NULL) 66 | operand_result = button->key_value; 67 | else if(operator & KV_MATCH_OPERATOR_BITWISE_AND) 68 | operand_result = (operand_origin & button->key_value); 69 | else if(operator & KV_MATCH_OPERATOR_BITWISE_OR) 70 | operand_result = (operand_origin | button->key_value); 71 | else if(operator & KV_MATCH_OPERATOR_BITWISE_NOT) 72 | operand_result = ~(button->key_value); 73 | else if(operator & KV_MATCH_OPERATOR_BITWISE_XOR) 74 | operand_result = (operand_origin ^ button->key_value); 75 | 76 | if(operand_result == tar_result) 77 | { 78 | button->kv_match_map_ptr[i].kv_func_cb(button); 79 | } 80 | ``` 81 | 82 | - 支持的操作符: 83 | ```c 84 | #define KV_MATCH_OPERATOR_NULL (0) // 无操作符,仅通过(key_value == tar_result)?判断, 默认是这个 85 | #define KV_MATCH_OPERATOR_BITWISE_AND (1 << 0) // 按位与操作符,(operand & key_value == tar_result)? 86 | #define KV_MATCH_OPERATOR_BITWISE_OR (1 << 1) // 按位或操作符,(operand | key_value == tar_result)? 87 | #define KV_MATCH_OPERATOR_BITWISE_NOT (1 << 2) // 按位取反操作符,(~ key_value == tar_result)? 88 | #define KV_MATCH_OPERATOR_BITWISE_XOR (1 << 2) // 按位异或操作符,(operand ^ key_value == tar_result)? 89 | ``` 90 | 91 | > 4.基于面向对象方式设计思路,每个按键对象单独用一份数据结构管理 92 | 93 | ## 📋 如何食用 94 | 95 | ### 1)使用 96 |
97 | 点击展开/折叠C代码 98 | 99 | - 以使用callback方式为例: 100 | ```c 101 | // 1.包含头文件 102 | #include "embedded_button.h" 103 | 104 | // 2.定义按键实体 105 | struct button_obj_t button1; 106 | 107 | // 3.GPIO电平读取接口设置 108 | uint8_t read_button_pin(uint8_t button_id) 109 | { 110 | // you can share the GPIO read function with multiple Buttons 111 | switch(button_id) 112 | { 113 | case 0: 114 | return get_button1_value(); // 用户自行实现 115 | break; 116 | 117 | default: 118 | return 0; 119 | break; 120 | } 121 | 122 | return 0; 123 | } 124 | 125 | // 4. 配置键值匹配规则(设置回调事件) 126 | void single_click_handle(void* btn) 127 | { 128 | //do something... 129 | printf("/****single click****/\r\n"); 130 | } 131 | 132 | void double_click_handle(void* btn) 133 | { 134 | //do something... 135 | printf("/****double click****/\r\n"); 136 | } 137 | 138 | void long_press_handle(void* btn) 139 | { 140 | //do something... 141 | printf("/****long press****/\r\n"); 142 | } 143 | 144 | void single_click_then_long_press_handle(void* btn) 145 | { 146 | //do something... 147 | printf("/****single click and long press****/\r\n"); 148 | } 149 | 150 | void quintuple_click_handle(void* btn) 151 | { 152 | //do something... 153 | if(check_is_repeat_click_mode(btn)) 154 | printf("/****quintuple click****/\r\n"); 155 | } 156 | 157 | const key_value_match_map_t button1_map[] = 158 | { 159 | { 160 | .tar_result = SINGLE_CLICK_KV, 161 | .kv_func_cb = single_click_handle 162 | }, 163 | { 164 | .tar_result = DOUBLE_CLICK_KV, 165 | .kv_func_cb = double_click_handle 166 | }, 167 | { 168 | .tar_result = LONG_PRESEE_START, 169 | .kv_func_cb = long_press_handle 170 | }, 171 | { 172 | .tar_result = SINGLE_CLICK_THEN_LONG_PRESS_KV, 173 | .kv_func_cb = single_click_then_long_press_handle 174 | }, 175 | { 176 | .operand = 0b1010101010, 177 | .operator = KV_MATCH_OPERATOR_BITWISE_AND, 178 | .tar_result = 0b1010101010, 179 | .kv_func_cb = quintuple_click_handle 180 | } 181 | }; 182 | 183 | int main() 184 | { 185 | /************************************************ 186 | ****5.初始化按键对象,参数含义分别为 187 | **** 188 | ****- 按键实体 189 | ****- 绑定按键的GPIO电平读取接口**read_button1_pin()** 190 | ****- 设置有效触发电平 191 | ****- 按键ID 192 | ****- 键值匹配规则配置表 193 | ****- 键值匹配规则配置表大小 194 | *************************************************/ 195 | button_init(&button1, read_button_pin, 0, 0, button1_map, ARRAY_SIZE(button1_map)); 196 | // 6.启动按键 197 | button_start(&button1); 198 | 199 | // 7. 设置一个5ms间隔的定时器循环调用按键后台处理函数 button_ticks() 200 | __timer_start(button_ticks, 0, 5); 201 | 202 | while(1) 203 | {} 204 | } 205 | ``` 206 | ![Alt text](image.png) 207 |
208 | 209 | ### 2)调试 210 | 211 |
212 | 点击展开/折叠 213 | 214 | - 定义EB_DEBUG_PRINTF宏后将会开启键值打印,例如下面,需要将printf换成你的打印函数: 215 | ```c 216 | #define EB_DEBUG_PRINTF printf 217 | ``` 218 | ![alt text](key_value_log.png) 219 |
220 | 221 | ## ⚡ 其他 222 | - 本项目基于本人实际开发中遇到的一些按键驱动使用体验问题,在他人项目(见参考链接)的思想基础上,开发的此按键驱动模块,之前提到了本模块的优势,下面说下有待改进的地方:对于多按键时组合按键的表示方式,目前还没有想到比较优雅的实现方式,后续有头绪后会进一步改进,补齐这一环。最后,感谢帮助思考我的小伙伴[shawnfeng0](https://github.com/shawnfeng0)以及正在使用此模块的小伙伴,欢迎一起开发改进! 223 | - 更多高级用法见 [examples](../examples/README.md) 224 | 225 | ## 💬 参考链接 226 | - [MultiButton](https://github.com/0x1abin/MultiButton) 227 | - [FlexibleButton](https://github.com/murphyzhao/FlexibleButton/tree/master) 228 | - [安富莱按键FIFO思想](https://www.armbbs.cn/forum.php?mod=viewthread&tid=111527&highlight=%B0%B4%BC%FC) 229 | -------------------------------------------------------------------------------- /docs/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/530china/EmbeddedButton/f4c7a6c8aa1e7ae38f26c3d6f374741f9c3f3048/docs/image.png -------------------------------------------------------------------------------- /docs/key_value_log.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/530china/EmbeddedButton/f4c7a6c8aa1e7ae38f26c3d6f374741f9c3f3048/docs/key_value_log.png -------------------------------------------------------------------------------- /embedded_button.c: -------------------------------------------------------------------------------- 1 | 2 | #include "embedded_button.h" 3 | 4 | static struct button_obj_t* button_list_head = NULL; 5 | 6 | #ifdef EB_DEBUG_PRINTF 7 | static void debug_print_binary(uint32_t num); 8 | #endif 9 | /** 10 | * @brief Initializes the button struct. 11 | * @param button: the button strcut. 12 | * @param read_button_func_ptr: read button level function ptr. 13 | * @param active_level: GPIO level when button is pressed . 14 | * @param button_id: the button id. 15 | * @param map_ptr: key-value map 16 | * @param map_size: map size 17 | * @retval None 18 | */ 19 | void button_init(struct button_obj_t* button , \ 20 | uint8_t(*read_button_func_ptr)(uint8_t), \ 21 | uint8_t active_level , \ 22 | uint8_t button_id , \ 23 | const key_value_match_map_t *kv_match_map_ptr, \ 24 | size_t map_size \ 25 | ) 26 | { 27 | memset(button, 0, sizeof(struct button_obj_t)); 28 | button->key_value = (key_value_type_t)NONE_PRESS_KV; 29 | button->_read_button_func_ptr = read_button_func_ptr; 30 | button->read_level = button->_read_button_func_ptr(button_id); 31 | button->active_level = active_level; 32 | button->id = button_id; 33 | button->kv_match_map_ptr = kv_match_map_ptr; 34 | button->map_size = map_size; 35 | } 36 | 37 | /** 38 | * @brief Inquire the button event happen. 39 | * @param button: the button button strcut. 40 | * @retval button key value. 41 | */ 42 | key_value_type_t get_button_key_value(struct button_obj_t* button) 43 | { 44 | return (key_value_type_t)(button->key_value); 45 | } 46 | 47 | /** 48 | * @brief Add a bit to the end of the number 49 | * @param state_bits: src number point. 50 | * @param bit: tartget bit 51 | * @retval none. 52 | */ 53 | static void __append_bit(state_bits_type_t* state_bits, uint8_t bit) 54 | { 55 | *state_bits = (*state_bits << 1) | bit; 56 | } 57 | 58 | /** 59 | * @brief check number if match the target bits 60 | * @param state_bits: src number point. 61 | * @param target: tartget bits 62 | * @param target_bits_number: target bits number 63 | * @retval 1 is match, 0 is not match. 64 | */ 65 | static uint8_t __check_if_the_bits_match(key_value_type_t *state_bits, key_value_type_t target, uint8_t target_bits_number) 66 | { 67 | key_value_type_t mask = (1 << target_bits_number) - 1; 68 | 69 | return (((*state_bits) & mask) == target? 1 : 0); 70 | } 71 | 72 | uint8_t check_is_repeat_click_mode(struct button_obj_t* button) 73 | { 74 | key_value_type_t kv_input = button->key_value; 75 | 76 | /* Check if the two least significant bits form 0b10 */ 77 | if((kv_input & 0b11) != 0b10) 78 | return 0; 79 | 80 | /* Calculate the XOR result */ 81 | key_value_type_t xor_result = kv_input ^ (kv_input >> 1); 82 | 83 | /* Check if xor_result + 1 is a power of 2 84 | This means all bits except the least significant one are 1 */ 85 | return (xor_result != 0) && (((xor_result + 1) & (xor_result - 1)) == 0); 86 | } 87 | 88 | /** 89 | * @brief Button driver core function, driven by 2 principles 90 | * 1. As long as the key value is not zero, the time tick++ 91 | * 2. As long as the button status changes, 92 | add a bit (1: press, 0: release) and 93 | reset tick time (ensure that the tick is the time it was pressed or raised) 94 | * @param handle: the button handle strcut. 95 | * @retval None 96 | */ 97 | void button_handler(struct button_obj_t* button) 98 | { 99 | uint8_t read_gpio_level = button->_read_button_func_ptr(button->id); 100 | 101 | if((button->state_bits) > 0) button->ticks++; 102 | 103 | /*------------button debounce button---------------*/ 104 | if(read_gpio_level != button->read_level) { //not equal to prev one 105 | //continue read DEBOUNCE_TICKS times same new level change 106 | if(++(button->debounce_cnt) >= DEBOUNCE_TICKS) { 107 | button->read_level = read_gpio_level; 108 | button->read_level_update = 1; 109 | button->debounce_cnt = 0; 110 | } 111 | } else { //leved not change ,counter reset. 112 | button->debounce_cnt = 0; 113 | } 114 | 115 | if(button->read_level_update) { 116 | if(button->read_level == button->active_level) { 117 | __append_bit(&button->state_bits, 1); 118 | } 119 | else { 120 | __append_bit(&button->state_bits, 0); 121 | } 122 | 123 | button->read_level_update = 0; 124 | button->ticks = 0; 125 | } 126 | 127 | if(button->ticks > SHORT_TICKS) { 128 | if(button->read_level != button->active_level) { 129 | button->event_analyze_en = 1; 130 | } 131 | } 132 | 133 | if(button->ticks > LONG_TICKS) { 134 | if((button->read_level == button->active_level) \ 135 | && __check_if_the_bits_match(&button->state_bits, 0b01, 2)) // long press start 136 | { 137 | __append_bit(&button->state_bits, 1); 138 | button->event_analyze_en = 1; 139 | } 140 | else if((button->read_level == button->active_level) \ 141 | && __check_if_the_bits_match(&button->state_bits, 0b011, 3)) // long press hold 142 | { 143 | __append_bit(&button->state_bits, 1); 144 | button->event_analyze_en = 1; 145 | } 146 | } 147 | 148 | if((button->state_bits) && (button->event_analyze_en)) { 149 | // button event processing 150 | button->key_value = button->state_bits; 151 | #ifdef EB_DEBUG_PRINTF 152 | debug_print_binary(button->key_value); 153 | #endif 154 | 155 | for(size_t i = 0; i < button->map_size; i++) { 156 | if(button->kv_match_map_ptr[i].kv_func_cb == NULL) 157 | continue; 158 | 159 | key_value_type_t operand_origin = button->kv_match_map_ptr[i].operand; 160 | key_value_type_t operand_result = button->kv_match_map_ptr[i].operand; 161 | kv_match_operator_type_t operator =button->kv_match_map_ptr[i].operator; 162 | key_value_type_t tar_result = button->kv_match_map_ptr[i].tar_result; 163 | 164 | if(operator == KV_MATCH_OPERATOR_NULL) 165 | operand_result = button->key_value; 166 | else if(operator & KV_MATCH_OPERATOR_BITWISE_AND) 167 | operand_result = (operand_origin & button->key_value); 168 | else if(operator & KV_MATCH_OPERATOR_BITWISE_OR) 169 | operand_result = (operand_origin | button->key_value); 170 | else if(operator & KV_MATCH_OPERATOR_BITWISE_NOT) 171 | operand_result = ~(button->key_value); 172 | else if(operator & KV_MATCH_OPERATOR_BITWISE_XOR) 173 | operand_result = (operand_origin ^ button->key_value); 174 | 175 | if(operand_result == tar_result) 176 | { 177 | button->kv_match_map_ptr[i].kv_func_cb(button); 178 | } 179 | } 180 | 181 | // button is released state 182 | if(button->read_level != button->active_level) { 183 | button->event_analyze_en = 0; 184 | button->state_bits = 0; 185 | button->ticks = 0; 186 | } 187 | 188 | } 189 | } 190 | 191 | /** 192 | * @brief Start the button work, add the button struct into work list. 193 | * @param button: target button strcut. 194 | * @retval 0: succeed. -1: already exist. 195 | */ 196 | int button_start(struct button_obj_t* button) 197 | { 198 | struct button_obj_t* target = button_list_head; 199 | while(target) { 200 | if(target == button) return -1; //already exist. 201 | target = target->next; 202 | } 203 | button->next = button_list_head; 204 | button_list_head = button; 205 | 206 | return 0; 207 | } 208 | 209 | /** 210 | * @brief Stop the button work, remove the button struct off work list. 211 | * @param button: target button strcut. 212 | * @retval None 213 | */ 214 | void button_stop(struct button_obj_t* button) 215 | { 216 | struct button_obj_t** curr; 217 | for(curr = &button_list_head; *curr; ) { 218 | struct button_obj_t* entry = *curr; 219 | if (entry == button) { 220 | *curr = entry->next; 221 | return; 222 | } else 223 | curr = &entry->next; 224 | } 225 | } 226 | 227 | /** 228 | * @brief Background ticks, timer repeat invoking interval 5ms. 229 | * @param None. 230 | * @retval None 231 | */ 232 | void button_ticks() 233 | { 234 | struct button_obj_t* target; 235 | for(target = button_list_head; target; target = target->next) { 236 | button_handler(target); 237 | } 238 | } 239 | 240 | /** 241 | * @brief Debugging function, print the input decimal number in binary format. 242 | * @param None. 243 | * @retval None 244 | */ 245 | #ifdef EB_DEBUG_PRINTF 246 | static void debug_print_binary(uint32_t num) { 247 | if (num == 0) { 248 | EB_DEBUG_PRINTF("0b0 \r\n"); 249 | return; 250 | } 251 | 252 | EB_DEBUG_PRINTF("0b"); 253 | int printed = 0; 254 | for (int i = 31; i >= 0; i--) { 255 | if ((num >> i) & 1) { 256 | EB_DEBUG_PRINTF("1"); 257 | printed = 1; 258 | } else if (printed) { 259 | EB_DEBUG_PRINTF("0"); 260 | } 261 | } 262 | if (!printed) { 263 | EB_DEBUG_PRINTF("0"); 264 | } 265 | 266 | EB_DEBUG_PRINTF("\r\n"); 267 | } 268 | #endif 269 | -------------------------------------------------------------------------------- /embedded_button.h: -------------------------------------------------------------------------------- 1 | #ifndef __EMBEDDED_BUTTON_H__ 2 | #define __EMBEDDED_BUTTON_H__ 3 | 4 | #include "stdint.h" 5 | #include "string.h" 6 | 7 | typedef uint32_t key_value_type_t; 8 | typedef uint32_t state_bits_type_t; 9 | typedef uint8_t kv_match_operator_type_t; 10 | 11 | //According to your need to modify the constants. 12 | #define TICKS_INTERVAL 5 //ms 13 | #define DEBOUNCE_TICKS 3 //MAX 16 14 | #define SHORT_TICKS (350 /TICKS_INTERVAL) 15 | #define LONG_TICKS (1000 /TICKS_INTERVAL) 16 | 17 | #define NONE_PRESS_KV 0 18 | #define SINGLE_CLICK_KV 0b010 19 | #define DOUBLE_CLICK_KV 0b01010 20 | 21 | #define SINGLE_CLICK_THEN_LONG_PRESS_KV 0b01011 22 | #define DOUBLE_CLICK_THEN_LONG_PRESS_KV 0b0101011 23 | 24 | #define LONG_PRESEE_START 0b011 25 | #define LONG_PRESEE_HOLD 0b0111 26 | #define LONG_PRESEE_HOLD_END 0b01110 27 | 28 | #define KV_MATCH_OPERATOR_NULL (0) 29 | #define KV_MATCH_OPERATOR_BITWISE_AND (1 << 0) 30 | #define KV_MATCH_OPERATOR_BITWISE_OR (1 << 1) 31 | #define KV_MATCH_OPERATOR_BITWISE_NOT (1 << 2) 32 | #define KV_MATCH_OPERATOR_BITWISE_XOR (1 << 2) 33 | 34 | #ifndef ARRAY_SIZE 35 | #define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) 36 | #endif 37 | 38 | typedef struct { 39 | key_value_type_t operand; 40 | kv_match_operator_type_t operator; 41 | key_value_type_t tar_result; 42 | void (*kv_func_cb)(void*); 43 | } key_value_match_map_t; 44 | 45 | typedef struct button_obj_t { 46 | uint8_t debounce_cnt : 4; 47 | uint8_t active_level : 1; 48 | uint8_t read_level : 1; 49 | uint8_t read_level_update : 1; 50 | uint8_t event_analyze_en : 1; 51 | uint8_t id; 52 | uint16_t ticks; 53 | state_bits_type_t state_bits; 54 | key_value_type_t key_value; 55 | uint8_t (*_read_button_func_ptr)(uint8_t button_id_); 56 | const key_value_match_map_t *kv_match_map_ptr; 57 | size_t map_size; 58 | struct button_obj_t* next; 59 | }button_obj_t; 60 | 61 | void button_init(struct button_obj_t* button , \ 62 | uint8_t(*read_button_func_ptr)(uint8_t), \ 63 | uint8_t active_level , \ 64 | uint8_t button_id , \ 65 | const key_value_match_map_t *kv_match_map_ptr, \ 66 | size_t map_size \ 67 | ); 68 | int button_start(struct button_obj_t* handle); 69 | void button_stop(struct button_obj_t* handle); 70 | void button_ticks(void); 71 | key_value_type_t get_button_key_value(struct button_obj_t* button); 72 | uint8_t check_is_repeat_click_mode(struct button_obj_t* button); 73 | 74 | #endif 75 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # 高级用法示例 2 | 3 | - 大于等于n连击按n连击处理,见[example_callback.c](./example_callback.c)中的``quintuple_click_handle``函数和其匹配规则; 4 | - 连击事件如果只想通过一个回调处理,不想每一个连接都注册一个回调,见[example_callback.c](./example_callback.c)中``repeat_click_handle``函数和其匹配规则; 5 | - 键值以长按结束就会触发的事件(包括短按长按、单纯长按),见[example_callback.c](./example_callback.c)中``filter_ending_with_long_press_handle``函数和其匹配规则; 6 | -------------------------------------------------------------------------------- /examples/example_callback.c: -------------------------------------------------------------------------------- 1 | #include "embedded_button.h" 2 | 3 | enum button_id_e { 4 | btn1_id = 0, 5 | btn2_id, 6 | }; 7 | 8 | struct button_obj_t button1; 9 | struct button_obj_t button2; 10 | 11 | uint8_t read_button_pin(uint8_t button_id) 12 | { 13 | // you can share the GPIO read function with multiple Buttons 14 | switch(button_id) 15 | { 16 | case btn1_id: 17 | return get_button1_value(); //Require self implementation 18 | break; 19 | case btn2_id: 20 | return get_button2_value(); //Require self implementation 21 | break; 22 | 23 | default: 24 | return 0; 25 | break; 26 | } 27 | 28 | return 0; 29 | } 30 | 31 | void single_click_handle(void* btn) 32 | { 33 | //do something... 34 | printf("/****single click****/\r\n"); 35 | } 36 | 37 | void double_click_handle(void* btn) 38 | { 39 | //do something... 40 | printf("/****double click****/\r\n"); 41 | } 42 | 43 | void long_press_handle(void* btn) 44 | { 45 | //do something... 46 | printf("/****long press****/\r\n"); 47 | } 48 | 49 | void single_click_then_long_press_handle(void* btn) 50 | { 51 | //do something... 52 | printf("/****single click and long press****/\r\n"); 53 | } 54 | 55 | void quintuple_click_handle(void* btn) 56 | { 57 | //do something... 58 | if(check_is_repeat_click_mode(btn)) 59 | {// To implement the logic where any key press that occurs five or more times in succession is treated as a five-hit press. 60 | printf("/****quintuple click****/\r\n"); 61 | } 62 | 63 | } 64 | 65 | void repeat_click_handle(void* btn) 66 | { 67 | //do something... 68 | if(check_is_repeat_click_mode(btn)) 69 | { 70 | printf("/****repeat click****/\r\n"); 71 | 72 | if(get_button_key_value(&button2) == 0b1010) 73 | { 74 | printf("/****button2 double click****/\r\n"); 75 | } 76 | 77 | if(get_button_key_value(&button2) == 0b101010) 78 | { 79 | printf("/****button2 triple click****/\r\n"); 80 | } 81 | 82 | if(get_button_key_value(&button2) == 0b10101010) 83 | { 84 | printf("/****button2 quadruple click****/\r\n"); 85 | } 86 | } 87 | } 88 | 89 | void filter_ending_with_long_press_handle(void* btn) 90 | { 91 | printf("/****an event ending with a long press on a key****/\r\n"); 92 | } 93 | 94 | const key_value_match_map_t button1_map[] = 95 | { 96 | { 97 | .tar_result = SINGLE_CLICK_KV, 98 | .kv_func_cb = single_click_handle 99 | }, 100 | { 101 | .tar_result = DOUBLE_CLICK_KV, 102 | .kv_func_cb = double_click_handle 103 | }, 104 | { 105 | .tar_result = LONG_PRESEE_START, 106 | .kv_func_cb = long_press_handle 107 | }, 108 | { 109 | .tar_result = SINGLE_CLICK_THEN_LONG_PRESS_KV, 110 | .kv_func_cb = single_click_then_long_press_handle 111 | }, 112 | { 113 | .operand = 0b1010101010, 114 | .operator = KV_MATCH_OPERATOR_BITWISE_AND, 115 | .tar_result = 0b1010101010, 116 | .kv_func_cb = quintuple_click_handle 117 | } 118 | }; 119 | 120 | const key_value_match_map_t button2_map[] = 121 | { 122 | { 123 | .operand = 0b1010, 124 | .operator = KV_MATCH_OPERATOR_BITWISE_AND, 125 | .tar_result = 0b1010, 126 | .kv_func_cb = repeat_click_handle 127 | }, 128 | { 129 | .operand = 0b1111, 130 | .operator = KV_MATCH_OPERATOR_BITWISE_AND, 131 | .tar_result = 0b1110, 132 | .kv_func_cb = filter_ending_with_long_press_handle 133 | } 134 | }; 135 | 136 | 137 | int main() 138 | { 139 | button_init(&button1, read_button_pin, 0, btn1_id, button1_map, ARRAY_SIZE(button1_map)); 140 | button_start(&button1); 141 | 142 | button_init(&button2, read_button_pin, 0, btn2_id, button2_map, ARRAY_SIZE(button2_map)); 143 | button_start(&button2); 144 | 145 | //make the timer invoking the button_ticks() interval 5ms. 146 | //This function is implemented by yourself. 147 | __timer_start(button_ticks, 0, 5); 148 | 149 | while(1) 150 | {} 151 | } -------------------------------------------------------------------------------- /examples/example_poll.c: -------------------------------------------------------------------------------- 1 | #include "embedded_button.h" 2 | 3 | struct button_obj_t button1; 4 | 5 | uint8_t read_button_pin(uint8_t button_id) 6 | { 7 | // you can share the GPIO read function with multiple Buttons 8 | switch(button_id) 9 | { 10 | case 0: 11 | return get_button1_value(); //Require self implementation 12 | break; 13 | 14 | default: 15 | return 0; 16 | break; 17 | } 18 | 19 | return 0; 20 | } 21 | 22 | 23 | int main() 24 | { 25 | static uint8_t btn1_event_val; 26 | 27 | button_init(&button1, read_button_pin, 0, 0, NULL, 0); 28 | button_start(&button1); 29 | 30 | //make the timer invoking the button_ticks() interval 5ms. 31 | //This function is implemented by yourself. 32 | __timer_start(button_ticks, 0, 5); 33 | 34 | while(1) 35 | { 36 | if(btn1_event_val != get_button_key_value(&button1)) { 37 | btn1_event_val = get_button_key_value(&button1); 38 | 39 | if(btn1_event_val == SINGLE_CLICK_KV) { 40 | //do something 41 | } else if(btn1_event_val == DOUBLE_CLICK_KV) { 42 | //do something 43 | } else if(btn1_event_val == SINGLE_CLICK_THEN_LONG_PRESS_KV) { 44 | //do something 45 | } 46 | } 47 | } 48 | } 49 | --------------------------------------------------------------------------------