├── .clang-format ├── .gitignore ├── .vscode ├── c_cpp_properties.json ├── launch.json ├── settings.json └── tasks.json ├── LICENSE ├── Makefile ├── README.md ├── README.vsdx ├── build.mk ├── code_format.py ├── ebtn ├── bit_array.h ├── ebtn.c └── ebtn.h ├── example_test.c ├── example_user.c └── main.c /.clang-format: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: Apache-2.0 2 | # 3 | # Note: The list of ForEachMacros can be obtained using: 4 | # 5 | # git grep -h '^#define [^[:space:]]*FOR_EACH[^[:space:]]*(' include/ \ 6 | # | sed "s,^#define \([^[:space:]]*FOR_EACH[^[:space:]]*\)(.*$, - '\1'," \ 7 | # | sort | uniq 8 | # 9 | # References: 10 | # - https://clang.llvm.org/docs/ClangFormatStyleOptions.html 11 | 12 | --- 13 | BasedOnStyle: LLVM 14 | AlignConsecutiveMacros: AcrossComments 15 | AllowShortBlocksOnASingleLine: false 16 | AllowShortCaseLabelsOnASingleLine: false 17 | AllowShortEnumsOnASingleLine: false 18 | AllowShortFunctionsOnASingleLine: None 19 | AllowShortIfStatementsOnASingleLine: false 20 | AllowShortLoopsOnASingleLine: false 21 | AttributeMacros: 22 | - __aligned 23 | - __deprecated 24 | - __packed 25 | - __printf_like 26 | - __syscall 27 | - __subsystem 28 | ColumnLimit: 160 29 | ConstructorInitializerIndentWidth: 8 30 | ContinuationIndentWidth: 8 31 | ForEachMacros: 32 | - 'FOR_EACH' 33 | - 'FOR_EACH_FIXED_ARG' 34 | - 'FOR_EACH_IDX' 35 | - 'FOR_EACH_IDX_FIXED_ARG' 36 | - 'FOR_EACH_NONEMPTY_TERM' 37 | - 'RB_FOR_EACH' 38 | - 'RB_FOR_EACH_CONTAINER' 39 | - 'SYS_DLIST_FOR_EACH_CONTAINER' 40 | - 'SYS_DLIST_FOR_EACH_CONTAINER_SAFE' 41 | - 'SYS_DLIST_FOR_EACH_NODE' 42 | - 'SYS_DLIST_FOR_EACH_NODE_SAFE' 43 | - 'SYS_SFLIST_FOR_EACH_CONTAINER' 44 | - 'SYS_SFLIST_FOR_EACH_CONTAINER_SAFE' 45 | - 'SYS_SFLIST_FOR_EACH_NODE' 46 | - 'SYS_SFLIST_FOR_EACH_NODE_SAFE' 47 | - 'SYS_SLIST_FOR_EACH_CONTAINER' 48 | - 'SYS_SLIST_FOR_EACH_CONTAINER_SAFE' 49 | - 'SYS_SLIST_FOR_EACH_NODE' 50 | - 'SYS_SLIST_FOR_EACH_NODE_SAFE' 51 | - '_WAIT_Q_FOR_EACH' 52 | - 'Z_FOR_EACH' 53 | - 'Z_FOR_EACH_ENGINE' 54 | - 'Z_FOR_EACH_EXEC' 55 | - 'Z_FOR_EACH_FIXED_ARG' 56 | - 'Z_FOR_EACH_FIXED_ARG_EXEC' 57 | - 'Z_FOR_EACH_IDX' 58 | - 'Z_FOR_EACH_IDX_EXEC' 59 | - 'Z_FOR_EACH_IDX_FIXED_ARG' 60 | - 'Z_FOR_EACH_IDX_FIXED_ARG_EXEC' 61 | - 'Z_GENLIST_FOR_EACH_CONTAINER' 62 | - 'Z_GENLIST_FOR_EACH_CONTAINER_SAFE' 63 | - 'Z_GENLIST_FOR_EACH_NODE' 64 | - 'Z_GENLIST_FOR_EACH_NODE_SAFE' 65 | SortIncludes: Never 66 | IncludeBlocks: Regroup 67 | IncludeCategories: 68 | - Regex: '^<(assert|complex|ctype|errno|fenv|float|inttypes|limits|locale|math|setjmp|signal|stdarg|stdbool|stddef|stdint|stdio|stdlib|string|tgmath|time|wchar|wctype)\.h>$' 69 | Priority: 0 70 | - Regex: '.*' 71 | Priority: 1 72 | IndentCaseLabels: false 73 | IndentWidth: 4 74 | # SpaceBeforeParens: ControlStatementsExceptControlMacros # clang-format >= 13.0 75 | UseTab: Never 76 | WhitespaceSensitiveMacros: 77 | - STRINGIFY 78 | - Z_STRINGIFY 79 | 80 | AlignAfterOpenBracket: Align 81 | BraceWrapping: 82 | AfterClass: true 83 | AfterControlStatement: true 84 | AfterEnum: true 85 | AfterFunction: true 86 | AfterNamespace: true 87 | AfterObjCDeclaration: true 88 | AfterStruct: true 89 | AfterUnion: true 90 | AfterCaseLabel: true 91 | AfterExternBlock: false # Fix "extern "C"" 92 | BeforeCatch: true 93 | BeforeElse: true 94 | BeforeWhile: false 95 | IndentBraces: false 96 | SplitEmptyFunction: true 97 | SplitEmptyRecord: true 98 | SplitEmptyNamespace: true 99 | BreakBeforeBraces: Custom # Fix "extern "C"" 100 | IndentExternBlock: AfterExternBlock # Not useful for "extern "C"" 101 | 102 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /output 2 | -------------------------------------------------------------------------------- /.vscode/c_cpp_properties.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "name": "Win32", 5 | "includePath": [ 6 | "${workspaceFolder}/**" 7 | ], 8 | "defines": [ 9 | "_DEBUG", 10 | "UNICODE", 11 | "_UNICODE" 12 | ], 13 | "compilerPath": "C:\\msys64\\mingw64\\bin\\gcc.exe", 14 | "cStandard": "c99", 15 | "cppStandard": "gnu++03", 16 | "intelliSenseMode": "windows-gcc-x64", 17 | "configurationProvider": "ms-vscode.makefile-tools" 18 | } 19 | ], 20 | "version": 4 21 | } -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Debug", 6 | "type": "cppdbg", 7 | "request": "launch", 8 | "args": [], 9 | "stopAtEntry": false, 10 | "cwd": "${workspaceFolder}", 11 | "environment": [], 12 | "externalConsole": false, 13 | "MIMode": "gdb", 14 | "miDebuggerPath": "gdb.exe", 15 | "program": "${workspaceFolder}/output/main.exe", 16 | "preLaunchTask": "build" 17 | } 18 | ] 19 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.associations": { 3 | "bare_heap.h": "c", 4 | "bare_task.h": "c", 5 | "bare_list.h": "c", 6 | "stddef.h": "c", 7 | "bare_common.h": "c", 8 | "stdlib.h": "c", 9 | "windows.h": "c" 10 | } 11 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "build", 6 | "type": "shell", 7 | "group": { 8 | "kind": "build", 9 | "isDefault": true 10 | }, 11 | "windows": { 12 | "command": "powershell", 13 | "args": [ 14 | "-c", 15 | "make" 16 | ] 17 | }, 18 | "linux": { 19 | "command": "bash", 20 | "args": [ 21 | "-c", 22 | "make" 23 | ] 24 | }, 25 | "osx": { 26 | "command": "bash", 27 | "args": [ 28 | "-c", 29 | "make" 30 | ] 31 | } 32 | }, 33 | { 34 | "label": "build & run", 35 | "type": "shell", 36 | "windows": { 37 | "command": "powershell", 38 | "args": [ 39 | "-c", 40 | "make run'" 41 | ] 42 | }, 43 | "linux": { 44 | "command": "bash", 45 | "args": [ 46 | "-c", 47 | "'make run'" 48 | ] 49 | }, 50 | "osx": { 51 | "command": "bash", 52 | "args": [ 53 | "-c", 54 | "'make run'" 55 | ] 56 | } 57 | }, 58 | { 59 | "label": "clean", 60 | "type": "shell", 61 | "windows": { 62 | "command": "powershell", 63 | "args": [ 64 | "-c", 65 | "'make clean'" 66 | ] 67 | }, 68 | "linux": { 69 | "command": "bash", 70 | "args": [ 71 | "-c", 72 | "'make clean'" 73 | ] 74 | }, 75 | "osx": { 76 | "command": "bash", 77 | "args": [ 78 | "-c", 79 | "'make clean'" 80 | ] 81 | } 82 | } 83 | ] 84 | } -------------------------------------------------------------------------------- /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 | 203 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # 'make' build executable file 'main' 3 | # 'make clean' removes all .o and executable files 4 | # 5 | .DEFAULT_GOAL := all 6 | 7 | # CROSS_COMPILE ?= arm-linux-gnueabihf- 8 | CROSS_COMPILE ?= 9 | TARGET ?= main 10 | 11 | # define the C compiler to use 12 | CC := $(CROSS_COMPILE)gcc 13 | LD := $(CROSS_COMPILE)ld 14 | OBJCOPY := $(CROSS_COMPILE)objcopy 15 | OBJDUMP := $(CROSS_COMPILE)objdump 16 | SIZE := $(CROSS_COMPILE)size 17 | 18 | # define any compile-time flags 19 | CFLAGS ?= 20 | CFLAGS += -O0 21 | CFLAGS += -g 22 | 23 | # warning param setting 24 | CFLAGS += -Wall 25 | # think use -Os. 26 | CFLAGS += -Wno-unused-function 27 | CFLAGS += -Wno-unused-variable 28 | 29 | CFLAGS += -Wstrict-prototypes 30 | CFLAGS += -Wshadow 31 | # CFLAGS += -Werror 32 | 33 | # spec c version 34 | CFLAGS += -std=c99 35 | # CFLAGS += -Wno-format 36 | 37 | # for makefile depend tree create 38 | CFLAGS += -MMD -MP 39 | 40 | # for weak. 41 | CFLAGS += -fno-common 42 | 43 | ## MAKEFILE COMPILE MESSAGE CONTROL ## 44 | ifeq ($(V),1) 45 | Q= 46 | else 47 | Q=@ 48 | endif 49 | 50 | # Put functions and data in their own binary sections so that ld can 51 | # garbage collect them 52 | ifeq ($(NOGC),1) 53 | GC_CFLAGS = 54 | GC_LDFLAGS = 55 | else 56 | GC_CFLAGS = -ffunction-sections -fdata-sections 57 | GC_LDFLAGS = -Wl,--gc-sections -Wl,--check-sections 58 | endif 59 | 60 | 61 | CFLAGS += $(GC_CFLAGS) 62 | 63 | # define ld params 64 | LDFLAGS := 65 | LDFLAGS += $(GC_LDFLAGS) 66 | 67 | 68 | # define library paths in addition to /usr/lib 69 | # if I wanted to include libraries not in /usr/lib I'd specify 70 | # their path using -Lpath, something like: 71 | LFLAGS := 72 | 73 | # define output directory 74 | OUTPUT_PATH := output 75 | 76 | # define source directory 77 | SRC := 78 | 79 | # define include directory 80 | INCLUDE := 81 | 82 | # define lib directory 83 | LIB := 84 | 85 | 86 | OUTPUT_TARGET := $(OUTPUT_PATH)/$(TARGET) 87 | 88 | OBJDIR = $(OUTPUT_PATH)/obj 89 | 90 | 91 | # include user build.mk 92 | include build.mk 93 | 94 | 95 | ifeq ($(OS),Windows_NT) 96 | ifdef ComSpec 97 | WINCMD:=$(ComSpec) 98 | endif 99 | ifdef COMSPEC 100 | WINCMD:=$(COMSPEC) 101 | endif 102 | 103 | SHELL:=$(WINCMD) 104 | 105 | MAIN := $(TARGET).exe 106 | ECHO=echo 107 | SOURCEDIRS := $(SRC) 108 | INCLUDEDIRS := $(INCLUDE) 109 | LIBDIRS := $(LIB) 110 | FIXPATH = $(subst /,\,$1) 111 | RM := del /q /s 112 | MD := mkdir 113 | MD_CHECK := $(Q)if not exist "$@" 114 | else 115 | MAIN := $(TARGET) 116 | ECHO=echo 117 | SOURCEDIRS := $(shell find $(SRC) -type d) 118 | INCLUDEDIRS := $(shell find $(INCLUDE) -type d) 119 | LIBDIRS := $(shell find $(LIB) -type d) 120 | FIXPATH = $1 121 | RM = rm -rf 122 | MD := mkdir -p 123 | MD_CHECK := 124 | endif 125 | 126 | # define any directories containing header files other than /usr/include 127 | INCLUDES := $(patsubst %,-I%, $(INCLUDEDIRS:%/=%)) 128 | @echo INCLUDES: $(INCLUDES) 129 | 130 | # define the C libs 131 | LIBS := $(patsubst %,-L%, $(LIBDIRS:%/=%)) 132 | 133 | # define the C source files 134 | SOURCES := $(wildcard $(patsubst %,%/*.c, $(SOURCEDIRS))) 135 | 136 | # define the C object files 137 | OBJECTS := $(patsubst %, $(OBJDIR)/%, $(SOURCES:.c=.o)) 138 | OBJ_MD := $(addprefix $(OBJDIR)/, $(SOURCEDIRS)) 139 | 140 | 141 | 142 | ALL_DEPS := $(OBJECTS:.o=.d) 143 | 144 | # include dependency files of application 145 | ifneq ($(MAKECMDGOALS),clean) 146 | -include $(ALL_DEPS) 147 | endif 148 | 149 | # 150 | # The following part of the makefile is generic; it can be used to 151 | # build any executable just by changing the definitions above and by 152 | # deleting dependencies appended to the file from 'make depend' 153 | # 154 | 155 | OUTPUT_MAIN := $(OUTPUT_PATH)/$(MAIN) 156 | 157 | # Fix path error. 158 | #OUTPUT_MAIN := $(call FIXPATH,$(OUTPUT_MAIN)) 159 | 160 | .PHONY: all clean 161 | 162 | all: main 163 | @$(ECHO) Start Build Image. 164 | $(OBJCOPY) -v -O binary $(OUTPUT_MAIN) $(OUTPUT_TARGET).bin 165 | $(OBJDUMP) --source --all-headers --demangle --line-numbers --wide $(OUTPUT_MAIN) > $(OUTPUT_TARGET).lst 166 | @$(ECHO) Print Size 167 | $(Q)@$(SIZE) --format=berkeley $(OUTPUT_MAIN) 168 | # @$(ECHO) Executing 'all' complete! 169 | 170 | # mk path for object. 171 | $(OBJ_MD): 172 | $(MD_CHECK) $(Q)$(MD) $(call FIXPATH, $@) 173 | 174 | # mk output path. 175 | $(OUTPUT_PATH): 176 | $(MD_CHECK) $(Q)$(MD) $(call FIXPATH, $@) 177 | 178 | $(OBJDIR): 179 | $(MD_CHECK) $(Q)$(MD) $(call FIXPATH, $@) 180 | 181 | $(OUTPUT_MAIN): $(OBJECTS) 182 | @$(ECHO) Linking : "$@" 183 | $(Q)$(CC) $(CFLAGS) $(LDFLAGS) $(INCLUDES) -Wl,-Map,$(OUTPUT_TARGET).map -o $(OUTPUT_MAIN) $(OBJECTS) $(LFLAGS) $(LIBS) $(OUTPUT_BT_LIB) 184 | 185 | main: | $(OUTPUT_PATH) $(OBJDIR) $(OBJ_MD) $(OUTPUT_MAIN) 186 | @$(ECHO) Building : "$(OUTPUT_MAIN)" 187 | 188 | # Static Pattern Rules [targets ...: target-pattern: prereq-patterns ...] 189 | $(OBJECTS): $(OBJDIR)/%.o : %.c 190 | @$(ECHO) Compiling : "$<" 191 | $(Q)$(CC) $(CFLAGS) $(INCLUDES) -c $< -o $@ 192 | 193 | 194 | clean: 195 | # $(RM) $(OUTPUT_MAIN) 196 | # $(RM) $(OBJECTS) 197 | # $(RM) $(OBJDIR) 198 | $(Q)$(RM) $(call FIXPATH, $(OUTPUT_PATH)) 199 | @$(ECHO) Cleanup complete! 200 | 201 | run: all 202 | ./$(OUTPUT_MAIN) 203 | @$(ECHO) Executing 'run: all' complete! 204 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 简介 2 | 3 | 在嵌入式裸机开发中,经常有按键的管理需求,GitHub上已经有蛮多成熟的按键驱动了,但是由于这样那样的问题,最终还是自己实现了一套。本项目地址:[bobwenstudy/easy_button (github.com)](https://github.com/bobwenstudy/easy_button)。 4 | 一个网友的分享,MCU应用实践可以参考:[easy_button-Application](https://github.com/Sighthesia/easy_button-Application/tree/main) 5 | 6 | 项目开发过程中参考了如下几个项目[murphyzhao/FlexibleButton: 灵活的按键处理库(Flexible Button)| 按键驱动 | 支持单击、双击、连击、长按、自动消抖 | 灵活适配中断和低功耗 | 按需实现组合按键 (github.com)](https://github.com/murphyzhao/FlexibleButton),[0x1abin/MultiButton: Button driver for embedded system (github.com)](https://github.com/0x1abin/MultiButton)和[MaJerle/lwbtn: Lightweight button handler for embedded systems (github.com)](https://github.com/MaJerle/lwbtn)。 7 | 8 | 其中核心的按键管理机制借鉴的是[lwbtn](https://github.com/MaJerle/lwbtn),并在其基础上做了比较多的改动,部分事件上报行为和原本处理有些不同。 9 | 10 | ## 对比分析 11 | 12 | 下面从几个维度来对比几个开源库的差异。 13 | 14 | 注意:分析纯属个人观点,如有异议请随时与我沟通。 15 | 16 | | | [easy_button](https://github.com/bobwenstudy/easy_button) | [FlexibleButton](https://github.com/murphyzhao/FlexibleButton) | [MultiButton](https://github.com/0x1abin/MultiButton) | [lwbtn](https://github.com/MaJerle/lwbtn) | 17 | | ------------------------------- | --------------------------------------------------------- | ------------------------------------------------------------ | ----------------------------------------------------- | ----------------------------------------- | 18 | | 最大支持按键数 | 无限 | 32 | 无限 | 无限 | 19 | | 按键时间参数独立配置 | 支持 | 支持 | 部分支持 | 支持 | 20 | | 单个按键RAM Size(Bytes) | 20(ebtn_btn_t) | 28(flex_button_t) | 44(Button) | 48(lwbtn_btn_t) | 21 | | 支持组合按键 | 支持 | 不支持 | 不支持 | 不支持 | 22 | | 支持静态注册(可以省Code Size) | 支持 | 不支持 | 不支持 | 支持 | 23 | | 支持动态注册 | 支持 | 支持 | 支持 | 不支持 | 24 | | 单击最大次数 | 无限 | 无限 | 2 | 无限 | 25 | | 长按种类 | 无限 | 1 | 1 | 无限 | 26 | | 批量扫描支持 | 支持 | 不支持 | 不支持 | 不支持 | 27 | 28 | 可以看出easy_button功能是最全的,并且使用的RAM Size也是最小的,这个在键盘之类有很多按键场景下非常有意义。 29 | 30 | 31 | 32 | ## 组合按键支持 33 | 34 | 现有的项目基本都不支持组合按键,基本都是要求用户根据ID在应用层将多个按键作为一个ID来实现,虽然这样也能实现组合按键的功能需要。 35 | 36 | 但是这样的实现逻辑不够优雅,并且扫描按键行为的逻辑不可避免有重复的部分,增加了mips。 37 | 38 | 本项目基于[bit_array_static](https://github.com/bobwenstudy/bit_array_static)实现了优雅的组合按键处理机制,无需重复定义按键扫描逻辑,驱动会利用已经读取到的按键状态来实现组合按键的功能逻辑。 39 | 40 | 41 | 42 | ## 长按支持 43 | 44 | 实际项目中会遇到各种功能需求,如长按3s是功能A,长按5s是功能B,长按30s是功能C。通过`keepalive_cnt`和`time_keepalive_period`的设计,能够支持各种场景的长按功能需要。 45 | 46 | 如定义`time_keepalive_period=1000`,那么每隔1s会上报一个`KEEPALIVE(EBTN_EVT_KEEPALIVE)`事件,应用层在收到上报事件后,当`keepalive_cnt==3`时,执行功能A;当`keepalive_cnt==5`时,执行功能B;当`keepalive_cnt==30`时,执行功能C。 47 | 48 | 49 | 50 | ## 批量扫描支持 51 | 52 | 现有的按键库都是一个个按键扫描再单独处理,这个在按键比较少的时候,比较好管理,但是在多按键场景下,尤其是矩阵键盘下,这个会大大增加扫描延迟,通过批量扫描支持,可以先在用户层将所有按键状态记录好(用户层根据具体应用优化获取速度),而后一次性将当前状态传给(`ebtn_process_with_curr_state`)驱动。 53 | 54 | 嵌入式按键处理驱动,支持单击、双击、多击、自动消抖、长按、长长按、超长按 | 低功耗支持 | 组合按键支持 | 静态/动态注册支持 55 | 56 | 57 | 58 | ## 简易但灵活的事件类型 59 | 60 | 参考[lwbtn](https://github.com/MaJerle/lwbtn)实现,当有按键事件发生时,所上报的事件类型只有4种,通过`click_cnt`和`keepalive_cnt`来支持灵活的按键点击和长按功能需要,这样的设计大大简化了代码行为,也大大降低了后续维护成本。 61 | 62 | 如果用户觉得不好用,也可以在该驱动基础上再封装出自己所需的驱动。 63 | 64 | ```c 65 | typedef enum 66 | { 67 | EBTN_EVT_ONPRESS = 0x00, /*!< On press event - sent when valid press is detected */ 68 | EBTN_EVT_ONRELEASE, /*!< On release event - sent when valid release event is detected (from 69 | active to inactive) */ 70 | EBTN_EVT_ONCLICK, /*!< On Click event - sent when valid sequence of on-press and on-release 71 | events occurs */ 72 | EBTN_EVT_KEEPALIVE, /*!< Keep alive event - sent periodically when button is active */ 73 | } ebtn_evt_t; 74 | ``` 75 | 76 | 77 | 78 | ## 关于低功耗 79 | 80 | 参考[Flexible Button](https://github.com/murphyzhao/FlexibleButton),本按键库是通过不间断扫描的方式来检查按键状态,因此会一直占用 CPU 资源,这对低功耗应用场景是不友好的。为了降低正常工作模式下的功耗,建议合理配置扫描周期(5ms - 20ms),扫描间隙里 CPU 可以进入轻度睡眠。 81 | 82 | 一般MCU都有深度睡眠模式,这是CPU只能被IO切换唤醒,所以驱动为大家提供了`int ebtn_is_in_process(void)`接口来判断是否可以进入深度睡眠模式。 83 | 84 | 85 | 86 | 87 | 88 | 89 | # 代码结构 90 | 91 | 代码结构如下所示: 92 | 93 | - **ebtn**:驱动库,主要包含BitArray管理和EasyButton管理。 94 | - **example_user.c**:捕获windows的0-9作为按键输入,测试用户交互的例程。 95 | - **example_test.c**:模拟一些场景的按键事件,对驱动进行测试。 96 | - **main.c**:程序主入口,配置进行测试模式函数用户交互模式。 97 | - **build.mk**和**Makefile**:Makefile编译环境。 98 | - **README.md**:说明文档 99 | 100 | ```shell 101 | easy_button 102 | ├── ebtn 103 | │ ├── bit_array.h 104 | │ ├── ebtn.c 105 | │ └── ebtn.h 106 | ├── build.mk 107 | ├── example_user.c 108 | └── example_test.c 109 | ├── main.c 110 | ├── Makefile 111 | └── README.md 112 | ``` 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | # 使用说明 121 | 122 | ## 使用简易步骤 123 | 124 | Step1:定义KEY_ID、按键参数和按键数组和组合按键数组。 125 | 126 | ```c 127 | 128 | typedef enum 129 | { 130 | USER_BUTTON_0 = 0, 131 | USER_BUTTON_1, 132 | USER_BUTTON_2, 133 | USER_BUTTON_3, 134 | USER_BUTTON_4, 135 | USER_BUTTON_5, 136 | USER_BUTTON_6, 137 | USER_BUTTON_7, 138 | USER_BUTTON_8, 139 | USER_BUTTON_9, 140 | USER_BUTTON_MAX, 141 | 142 | USER_BUTTON_COMBO_0 = 0x100, 143 | USER_BUTTON_COMBO_1, 144 | USER_BUTTON_COMBO_2, 145 | USER_BUTTON_COMBO_3, 146 | USER_BUTTON_COMBO_MAX, 147 | } user_button_t; 148 | 149 | /* User defined settings */ 150 | static const ebtn_btn_param_t defaul_ebtn_param = EBTN_PARAMS_INIT(20, 0, 20, 300, 200, 500, 10); 151 | 152 | static ebtn_btn_t btns[] = { 153 | EBTN_BUTTON_INIT(USER_BUTTON_0, &defaul_ebtn_param), 154 | EBTN_BUTTON_INIT(USER_BUTTON_1, &defaul_ebtn_param), 155 | EBTN_BUTTON_INIT(USER_BUTTON_2, &defaul_ebtn_param), 156 | EBTN_BUTTON_INIT(USER_BUTTON_3, &defaul_ebtn_param), 157 | EBTN_BUTTON_INIT(USER_BUTTON_4, &defaul_ebtn_param), 158 | EBTN_BUTTON_INIT(USER_BUTTON_5, &defaul_ebtn_param), 159 | }; 160 | 161 | 162 | static ebtn_btn_combo_t btns_combo[] = { 163 | EBTN_BUTTON_COMBO_INIT(USER_BUTTON_COMBO_0, &defaul_ebtn_param), 164 | EBTN_BUTTON_COMBO_INIT(USER_BUTTON_COMBO_1, &defaul_ebtn_param), 165 | }; 166 | ``` 167 | 168 | Step2:初始化按键驱动 169 | 170 | ```c 171 | ebtn_init(btns, EBTN_ARRAY_SIZE(btns), btns_combo, EBTN_ARRAY_SIZE(btns_combo), 172 | prv_btn_get_state, prv_btn_event); 173 | ``` 174 | 175 | Step3:配置组合按键comb_key,必须在按键注册完毕后再配置,不然需要`ebtn_combo_btn_add_btn_by_idx`用这个接口。 176 | 177 | ```c 178 | ebtn_combo_btn_add_btn(&btns_combo[0], USER_BUTTON_0); 179 | ebtn_combo_btn_add_btn(&btns_combo[0], USER_BUTTON_1); 180 | 181 | ebtn_combo_btn_add_btn(&btns_combo[1], USER_BUTTON_2); 182 | ebtn_combo_btn_add_btn(&btns_combo[1], USER_BUTTON_3); 183 | ``` 184 | 185 | Step4:动态注册所需按键,并配置comb_key。 186 | 187 | ```c 188 | // dynamic register 189 | for (int i = 0; i < (EBTN_ARRAY_SIZE(btns_dyn)); i++) 190 | { 191 | ebtn_register(&btns_dyn[i]); 192 | } 193 | 194 | ebtn_combo_btn_add_btn(&btns_combo_dyn[0].btn, USER_BUTTON_4); 195 | ebtn_combo_btn_add_btn(&btns_combo_dyn[0].btn, USER_BUTTON_5); 196 | 197 | ebtn_combo_btn_add_btn(&btns_combo_dyn[1].btn, USER_BUTTON_6); 198 | ebtn_combo_btn_add_btn(&btns_combo_dyn[1].btn, USER_BUTTON_7); 199 | 200 | for (int i = 0; i < (EBTN_ARRAY_SIZE(btns_combo_dyn)); i++) 201 | { 202 | ebtn_combo_register(&btns_combo_dyn[i]); 203 | } 204 | ``` 205 | 206 | Step5:启动按键扫描,具体实现可以用定时器做,也可以启任务或者轮询处理。需要注意需要将当前系统时钟`get_tick()`传给驱动接口`ebtn_process`。 207 | 208 | ```c 209 | while (1) 210 | { 211 | /* Process forever */ 212 | ebtn_process(get_tick()); 213 | 214 | /* Artificial sleep to offload win process */ 215 | Sleep(5); 216 | } 217 | ``` 218 | 219 | 具体可以参考`example_user.c`和`example_test.c`的实现。 220 | 221 | 222 | 223 | 224 | 225 | ## key_id和key_idx的说明 226 | 227 | 为了更好的实现**组合按键**以及**批量扫描**的支持,驱动引入了BitArray来管理按键的历史状态和组合按键信息。这样就间接引入了key_index的概念,其代表独立按键在驱动的位置,该值不可直接设置,是按照一定规则隐式定义的。 228 | 229 | key_id是用户定义的,用于标识按键的,该值可以随意更改,但是尽量保证该值独立。 230 | 231 | 如下图所示,驱动有2个静态注册的按键,还有3个动态注册的按键。每个按键的key_id是随意定义的,但是key_idx却是驱动内部隐式定义的,先是静态数组,而后按照动态数组顺先依次定义。 232 | 233 | **注意**:由于组合按键也会用到key_idx的信息,所以动态按键目前并不提供删除按键的行为,这个可能引发一些风险。 234 | 235 | ![image-20240223103317921](https://markdown-1306347444.cos.ap-shanghai.myqcloud.com/img/image-20240223103317921.png) 236 | 237 | 238 | 239 | ## 结构体说明 240 | 241 | ### 按键配置参数结构体说明-ebtn_btn_param_t 242 | 243 | 按键根据不同的时间触发不同的事件,目前每个按键可以配置的参数如下。 244 | 245 | | 名称 | 说明 | 246 | | ---------------------- | ------------------------------------------------------------ | 247 | | time_debounce | 防抖处理,按下防抖超时,配置为0,代表不启动 | 248 | | time_debounce_release | 防抖处理,松开防抖超时,配置为0,代表不启动 | 249 | | time_click_pressed_min | 按键超时处理,按键最短时间,配置为0,代表不检查最小值 | 250 | | time_click_pressed_max | 按键超时处理,按键最长时间,配置为0xFFFF,代表不检查最大值,用于区分长按和按键事件。 | 251 | | time_click_multi_max | 多击处理,两个按键之间认为是连击的超时时间 | 252 | | time_keepalive_period | 长按处理,长按周期,每个周期增加keepalive_cnt计数 | 253 | | max_consecutive | 最大连击次数,配置为0,代表不进行连击检查。 | 254 | 255 | 256 | 257 | ```c 258 | typedef struct ebtn_btn_param 259 | { 260 | /** 261 | * \brief Minimum debounce time for press event in units of milliseconds 262 | * 263 | * This is the time when the input shall have stable active level to detect valid *onpress* 264 | * event. 265 | * 266 | * When value is set to `> 0`, input must be in active state for at least 267 | * minimum milliseconds time, before valid *onpress* event is detected. 268 | * 269 | * \note If value is set to `0`, debounce is not used and *press* event will be 270 | * triggered immediately when input states goes to *inactive* state. 271 | * 272 | * To be safe not using this feature, external logic must ensure stable 273 | * transition at input level. 274 | * 275 | */ 276 | uint16_t time_debounce; /*!< Debounce time in milliseconds */ 277 | 278 | /** 279 | * \brief Minimum debounce time for release event in units of milliseconds 280 | * 281 | * This is the time when the input shall have minimum stable released level to detect valid 282 | * *onrelease* event. 283 | * 284 | * This setting can be useful if application wants to protect against 285 | * unwanted glitches on the line when input is considered "active". 286 | * 287 | * When value is set to `> 0`, input must be in inactive low for at least 288 | * minimum milliseconds time, before valid *onrelease* event is detected 289 | * 290 | * \note If value is set to `0`, debounce is not used and *release* event will be 291 | * triggered immediately when input states goes to *inactive* state 292 | * 293 | */ 294 | uint16_t time_debounce_release; /*!< Debounce time in milliseconds for release event */ 295 | 296 | /** 297 | * \brief Minimum active input time for valid click event, in milliseconds 298 | * 299 | * Input shall be in active state (after debounce) at least this amount of time to even consider 300 | * the potential valid click event. Set the value to `0` to disable this feature 301 | * 302 | */ 303 | uint16_t time_click_pressed_min; /*!< Minimum pressed time for valid click event */ 304 | 305 | /** 306 | * \brief Maximum active input time for valid click event, in milliseconds 307 | * 308 | * Input shall be pressed at most this amount of time to still trigger valid click. 309 | * Set to `-1` to allow any time triggering click event. 310 | * 311 | * When input is active for more than the configured time, click even is not detected and is 312 | * ignored. 313 | * 314 | */ 315 | uint16_t time_click_pressed_max; /*!< Maximum pressed time for valid click event*/ 316 | 317 | /** 318 | * \brief Maximum allowed time between last on-release and next valid on-press, 319 | * to still allow multi-click events, in milliseconds 320 | * 321 | * This value is also used as a timeout length to send the *onclick* event to application from 322 | * previously detected valid click events. 323 | * 324 | * If application relies on multi consecutive clicks, this is the max time to allow user 325 | * to trigger potential new click, or structure will get reset (before sent to user if any 326 | * clicks have been detected so far) 327 | * 328 | */ 329 | uint16_t time_click_multi_max; /*!< Maximum time between 2 clicks to be considered consecutive 330 | click */ 331 | 332 | /** 333 | * \brief Keep-alive event period, in milliseconds 334 | * 335 | * When input is active, keep alive events will be sent through this period of time. 336 | * First keep alive will be sent after input being considered 337 | * active. 338 | * 339 | */ 340 | uint16_t time_keepalive_period; /*!< Time in ms for periodic keep alive event */ 341 | 342 | /** 343 | * \brief Maximum number of allowed consecutive click events, 344 | * before structure gets reset to default value. 345 | * 346 | * \note When consecutive value is reached, application will get notification of 347 | * clicks. This can be executed immediately after last click has been detected, or after 348 | * standard timeout (unless next on-press has already been detected, then it is send to 349 | * application just before valid next press event). 350 | * 351 | */ 352 | uint16_t max_consecutive; /*!< Max number of consecutive clicks */ 353 | } ebtn_btn_param_t; 354 | ``` 355 | 356 | 357 | 358 | 359 | 360 | ### 按键控制结构体说明-ebtn_btn_t 361 | 362 | 每个按键有一个管理结构体,用于记录按键当前状态,按键参数等信息。 363 | 364 | | 名称 | 说明 | 365 | | ------------------- | ------------------------------------------------------------ | 366 | | key_id | 用户定义的key_id信息,该值建议唯一 | 367 | | flags | 用于记录一些状态,目前只支持`EBTN_FLAG_ONPRESS_SENT`和`EBTN_FLAG_IN_PROCESS` | 368 | | time_change | 记录按键按下或者松开状态的时间点 | 369 | | time_state_change | 记录按键状态切换时间点(并不考虑防抖,单纯记录状态切换时间点) | 370 | | keepalive_last_time | 长按最后一次上报长按时间的时间点,用于管理keepalive_cnt | 371 | | click_last_time | 点击最后一次松开状态的时间点,用于管理click_cnt | 372 | | keepalive_cnt | 长按的KEEP_ALIVE次数 | 373 | | click_cnt | 多击的次数 | 374 | | param | 按键时间参数,指向ebtn_btn_param_t,方便节省RAM,并且多个按键可公用一组参数 | 375 | 376 | 377 | 378 | 379 | 380 | ```c 381 | typedef struct ebtn_btn 382 | { 383 | uint16_t key_id; /*!< User defined custom argument for callback function purpose */ 384 | uint16_t flags; /*!< Private button flags management */ 385 | ebtn_time_t time_change; /*!< Time in ms when button state got changed last time after valid 386 | debounce */ 387 | ebtn_time_t time_state_change; /*!< Time in ms when button state got changed last time */ 388 | 389 | ebtn_time_t keepalive_last_time; /*!< Time in ms of last send keep alive event */ 390 | ebtn_time_t 391 | click_last_time; /*!< Time in ms of last successfully detected (not sent!) click event 392 | */ 393 | 394 | uint16_t keepalive_cnt; /*!< Number of keep alive events sent after successful on-press 395 | detection. Value is reset after on-release */ 396 | uint16_t click_cnt; /*!< Number of consecutive clicks detected, respecting maximum timeout 397 | between clicks */ 398 | 399 | const ebtn_btn_param_t *param; 400 | } ebtn_btn_t; 401 | ``` 402 | 403 | 404 | 405 | 406 | 407 | ### 组合按键控制结构体说明-ebtn_btn_combo_t 408 | 409 | 每个组合按键有一个管理结构体,用于记录组合按键组合配置参数,以及按键信息。 410 | 411 | | 名称 | 说明 | 412 | | -------- | --------------------------------- | 413 | | comb_key | 用独立按键的key_idx设置的BitArray | 414 | | btn | ebtn_btn_t管理对象,管理按键状态 | 415 | 416 | 417 | 418 | ```c 419 | typedef struct ebtn_btn_combo 420 | { 421 | BIT_ARRAY_DEFINE( 422 | comb_key, 423 | EBTN_MAX_KEYNUM); /*!< select key index - `1` means active, `0` means inactive */ 424 | 425 | ebtn_btn_t btn; 426 | } ebtn_btn_combo_t; 427 | ``` 428 | 429 | 430 | 431 | 432 | 433 | ### 动态注册按键控制结构体说明-ebtn_btn_dyn_t 434 | 435 | 动态注册需要维护一个列表,所以需要一个next指针。 436 | 437 | | 名称 | 说明 | 438 | | ---- | -------------------------------- | 439 | | next | 用于链表链接每个节点 | 440 | | btn | ebtn_btn_t管理对象,管理按键状态 | 441 | 442 | 443 | 444 | ```c 445 | typedef struct ebtn_btn_dyn 446 | { 447 | struct ebtn_btn_dyn *next; 448 | 449 | ebtn_btn_t btn; 450 | } ebtn_btn_dyn_t; 451 | ``` 452 | 453 | 454 | 455 | 456 | 457 | ### 动态注册组合按键控制结构体说明-ebtn_btn_combo_dyn_t 458 | 459 | 动态注册需要维护一个列表,所以需要一个next指针。 460 | 461 | | 名称 | 说明 | 462 | | ---- | ------------------------------------------ | 463 | | next | 用于链表链接每个节点 | 464 | | btn | ebtn_btn_combo_t管理对象,管理组合按键状态 | 465 | 466 | 467 | 468 | ```c 469 | typedef struct ebtn_btn_combo_dyn 470 | { 471 | struct ebtn_btn_combo_dyn *next; /*!< point to next combo-button */ 472 | 473 | ebtn_btn_combo_t btn; 474 | } ebtn_btn_combo_dyn_t; 475 | ``` 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | ### 按键驱动管理结构体-ebtn_t 484 | 485 | 按键驱动需要管理所有静态注册和动态注册的按键和组合按键信息,并且记录接口以及最后的按键状态。 486 | 487 | | 名称 | 说明 | 488 | | ------------------ | ------------------------------ | 489 | | btns | 管理静态注册按键的指针 | 490 | | btns_cnt | 记录静态注册按键的个数 | 491 | | btns_combo | 管理静态注册组合按键的指针 | 492 | | btns_combo_cnt | 记录静态注册组合按键的个数 | 493 | | btn_dyn_head | 管理动态注册按键的列表指针 | 494 | | btn_combo_dyn_head | 管理动态注册组合按键的列表指针 | 495 | | evt_fn | 事件上报的回调接口 | 496 | | get_state_fn | 按键状态获取的回调接口 | 497 | | old_state | 记录按键上一次状态 | 498 | 499 | 500 | 501 | ```c 502 | typedef struct ebtn 503 | { 504 | ebtn_btn_t *btns; /*!< Pointer to buttons array */ 505 | uint16_t btns_cnt; /*!< Number of buttons in array */ 506 | ebtn_btn_combo_t *btns_combo; /*!< Pointer to comb-buttons array */ 507 | uint16_t btns_combo_cnt; /*!< Number of comb-buttons in array */ 508 | 509 | ebtn_btn_dyn_t *btn_dyn_head; /*!< Pointer to btn-dynamic list */ 510 | ebtn_btn_combo_dyn_t *btn_combo_dyn_head; /*!< Pointer to btn-combo-dynamic list */ 511 | 512 | ebtn_evt_fn evt_fn; /*!< Pointer to event function */ 513 | ebtn_get_state_fn get_state_fn; /*!< Pointer to get state function */ 514 | 515 | BIT_ARRAY_DEFINE( 516 | old_state, 517 | EBTN_MAX_KEYNUM); /*!< Old button state - `1` means active, `0` means inactive */ 518 | } ebtn_t; 519 | ``` 520 | 521 | 522 | 523 | ## 操作API 524 | 525 | 526 | 527 | ### 核心API 528 | 529 | 主要的就是初始化和运行接口,加上动态注册接口。 530 | 531 | ```c 532 | void ebtn_process(ebtn_time_t mstime); 533 | int ebtn_init(ebtn_btn_t *btns, uint16_t btns_cnt, ebtn_btn_combo_t *btns_combo, 534 | uint16_t btns_combo_cnt, ebtn_get_state_fn get_state_fn, ebtn_evt_fn evt_fn); 535 | int ebtn_register(ebtn_btn_dyn_t *button); 536 | int ebtn_combo_register(ebtn_btn_combo_dyn_t *button); 537 | ``` 538 | 539 | 540 | 541 | ### 组合按键注册key的API 542 | 543 | 用于给组合按键绑定btn使用,最终都是关联到`key_idx`上。 544 | 545 | **注意**,`key_id`注册接口必需先确保对应的Button已经注册到驱动中。 546 | 547 | ```c 548 | void ebtn_combo_btn_add_btn_by_idx(ebtn_btn_combo_t *btn, int idx); 549 | void ebtn_combo_btn_remove_btn_by_idx(ebtn_btn_combo_t *btn, int idx); 550 | void ebtn_combo_btn_add_btn(ebtn_btn_combo_t *btn, uint16_t key_id); 551 | void ebtn_combo_btn_remove_btn(ebtn_btn_combo_t *btn, uint16_t key_id); 552 | ``` 553 | 554 | 555 | 556 | ### 其他API 557 | 558 | 一些工具函数,按需使用。 559 | 560 | ```c 561 | void ebtn_process_with_curr_state(bit_array_t *curr_state, ebtn_time_t mstime); 562 | 563 | int ebtn_get_total_btn_cnt(void); 564 | int ebtn_get_btn_index_by_key_id(uint16_t key_id); 565 | ebtn_btn_t *ebtn_get_btn_by_key_id(uint16_t key_id); 566 | int ebtn_get_btn_index_by_btn(ebtn_btn_t *btn); 567 | int ebtn_get_btn_index_by_btn_dyn(ebtn_btn_dyn_t *btn); 568 | 569 | int ebtn_is_btn_active(const ebtn_btn_t *btn); 570 | int ebtn_is_btn_in_process(const ebtn_btn_t *btn); 571 | int ebtn_is_in_process(void); 572 | ``` 573 | 574 | 575 | 576 | 其中`ebtn_is_in_process()`可以用于超低功耗业务场景,这时候MCU只有靠IO翻转唤醒。 577 | 578 | 579 | 580 | 581 | 582 | # 按键核心处理逻辑说明 583 | 584 | 这里参考[用户手册 — LwBTN 文档 (majerle.eu)](https://docs.majerle.eu/projects/lwbtn/en/latest/user-manual/index.html#how-it-works)对本驱动的按键实现机制进行说明。 585 | 586 | 在驱动运行中,应用程序可以会接收到如下事件: 587 | 588 | - `EBTN_EVT_ONPRESS`(简称:`ONPRESS`),每当输入从非活动状态变为活动状态并且最短去抖动时间过去时,都会将事件发送到应用程序 589 | - `EBTN_EVT_ONRELEASE`(简称:`ONRELEASE`),每当输入发送 `ONPRESS`事件时,以及当输入从活动状态变为非活动状态时,都会将事件发送到应用程序 590 | - `EBTN_EVT_KEEPALIVE`(简称:`KEEPALIVE`),事件在 `ONPRESS` 和`ONRELEASE`事件之间定期发送 591 | - `EBTN_EVT_ONCLICK`(简称:`ONCLICK`),事件在`ONRELEASE`后发送,并且仅当活动按钮状态在有效单击事件的允许窗口内时发送。 592 | 593 | ## ONPRESS事件 594 | 595 | `ONPRESS` 事件是检测到按键处于活动状态时的第一个事件。 由于嵌入式系统的性质和连接到设备的各种按钮,有必要过滤掉潜在的噪音,以忽略无意的多次按下。 这是通过检查输入至少在一些最短时间内处于稳定水平来完成的,通常称为*消抖时间*,通常需要大约`20ms` 。 596 | 597 | 按键*消抖时间*分为按下消抖时间`time_debounce`和松开消抖时间`time_debounce_release`。 598 | 599 | ![image-20240223135908798](https://markdown-1306347444.cos.ap-shanghai.myqcloud.com/img/image-20240223135908798.png) 600 | 601 | ## ONRELEASE事件 602 | 603 | 当按键从活动状态变为非活动状态时,才会立即触发 `ONRELEASE`事件,前提是在此之前检测到`ONPRESS` 事件。也就是 `ONRELEASE`事件是伴随着`ONPRESS` 事件发生的。 604 | 605 | ![image-20240223143215840](https://markdown-1306347444.cos.ap-shanghai.myqcloud.com/img/image-20240223143215840.png) 606 | 607 | ## ONCLICK事件 608 | 609 | `ONCLICK`事件在多个事件组合后触发: 610 | 611 | - 应正确检测到`ONPRESS` 事件,表示按钮已按下 612 | - 应检测到`ONRELEASE`事件,表示按钮已松开 613 | - `ONPRESS`和`ONRELEASE`事件之间的时间必须在时间窗口内,也就是在`time_click_pressed_min`和`time_click_pressed_max`之间时。 614 | 615 | 当满足条件时,在`ONRELEASE`事件之后的`time_click_multi_max`时间,发送`ONCLICK`事件。 616 | 617 | ![image-20240223143426179](https://markdown-1306347444.cos.ap-shanghai.myqcloud.com/img/image-20240223143426179.png) 618 | 619 | 下面显示了在 Windows 测试下的单击事件演示。 620 | 621 | ![image-20240223173405665](https://markdown-1306347444.cos.ap-shanghai.myqcloud.com/img/image-20240223173405665.png) 622 | 623 | 624 | 625 | 626 | 627 | ## Multi-Click事件 628 | 629 | 实际需求除了单击需求外,还需要满足多击需求。本驱动是靠`time_click_multi_max`来满足此功能,虽然有多次点击,但是只发送**一次 `ONCLICK`事件**。 630 | 631 | 注意:想象一下,有一个按钮可以在单击时切换一盏灯,并在双击时关闭房间中的所有灯。 通过超时功能和单次点击通知,用户将只收到**一次点击**,并且会根据连续按压次数值,来执行适当的操作。 632 | 633 | 下面是**Multi-Click**的简化图,忽略了消抖时间。`click_cnt`表示检测到的**Multi-Click** 事件数,将在最终的`ONCLICK`事件中上报。 634 | 635 | 需要注意前一个按键的`ONRELEASE`事件和下次的`ONPRESS`事件间隔时间应小于`time_click_multi_max`,`ONCLICK`事件会在最后一次按键的`ONRELEASE`事件之后`time_click_multi_max`时间上报。 636 | 637 | ![image-20240223144701688](https://markdown-1306347444.cos.ap-shanghai.myqcloud.com/img/image-20240223144701688.png) 638 | 639 | 下面显示了在 Windows 测试下的三击事件演示。 640 | 641 | ![image-20240223173435824](https://markdown-1306347444.cos.ap-shanghai.myqcloud.com/img/image-20240223173435824.png) 642 | 643 | 644 | 645 | ## KEEPALIVE事件 646 | 647 | `KEEPALIVE`事件在 `ONPRESS`事件和`ONRELEASE`事件之间定期发送,它可用于长按处理,根据过程中有多少`KEEPALIVE`事件以及`time_keepalive_period`可以实现各种复杂的长按功能需求。 648 | 649 | 需要注意这里根据配置的时间参数的不同,可能会出现`KEEPALIVE`事件和`ONCLICK`事件在一次按键事件都上报的情况。这个情况一般发生在按下保持时间(`ONPRESS`事件和`ONRELEASE`事件之间)大于`time_keepalive_period`却小于`time_click_pressed_max`的场景下。 650 | 651 | ![image-20240223173042135](https://markdown-1306347444.cos.ap-shanghai.myqcloud.com/img/image-20240223173042135.png) 652 | 653 | 下面显示了在 Windows 测试下的`KEEPALIVE`事件和`ONCLICK`事件在一次按键事件出现的演示。 654 | 655 | ![image-20240223173002558](https://markdown-1306347444.cos.ap-shanghai.myqcloud.com/img/image-20240223173002558.png) 656 | 657 | 658 | 659 | 660 | 661 | 而当按下保持时间大于`time_click_pressed_max`时,就不会上报`ONCLICK`事件,如下图所示。 662 | 663 | ![image-20240227112945641](https://markdown-1306347444.cos.ap-shanghai.myqcloud.com/img/image-20240227112945641.png) 664 | 665 | 666 | 667 | ## 其他边界场景 668 | 669 | 在`example_test.c`中对一些场景进行了覆盖性测试,具体可以看代码实现,测试都符合预期。 670 | 671 | **注意**:time_overflow相关的case需要`EBTN_CONFIG_TIMER_16`宏,不然测试时间太长了。 672 | 673 | 674 | 675 | 676 | 677 | 678 | 679 | # 测试说明 680 | 681 | ## 环境搭建 682 | 683 | 目前测试暂时只支持Windows编译,最终生成exe,可以直接在PC上跑。 684 | 685 | 目前需要安装如下环境: 686 | - GCC环境,笔者用的msys64+mingw,用于编译生成exe,参考这个文章安装即可。[Win7下msys64安装mingw工具链 - Milton - 博客园 (cnblogs.com)](https://www.cnblogs.com/milton/p/11808091.html)。 687 | 688 | 689 | ## 编译说明 690 | 691 | 本项目都是由makefile组织编译的,编译整个项目只需要执行`make all`即可。 692 | 693 | 694 | 也就是可以通过如下指令来编译工程: 695 | 696 | ```shell 697 | make all 698 | ``` 699 | 700 | 而后运行执行`make run`即可运行例程,例程默认运行测试例程,覆盖绝大多数场景,从结果上看测试通过。 701 | 702 | ```shell 703 | PS D:\workspace\github\easy_button> make run 704 | Building : "output/main.exe" 705 | Start Build Image. 706 | objcopy -v -O binary output/main.exe output/main.bin 707 | copy from `output/main.exe' [pei-i386] to `output/main.bin' [binary] 708 | objdump --source --all-headers --demangle --line-numbers --wide output/main.exe > output/main.lst 709 | Print Size 710 | text data bss dec hex filename 711 | 49616 6572 2644 58832 e5d0 output/main.exe 712 | ./output/main.exe 713 | Test running 714 | [ 20][ 20] ID(hex): 0, evt: ONPRESS, keep-alive cnt: 0, click cnt: 0 715 | [ 42][ 22] ID(hex): 0, evt: ONRELEASE, keep-alive cnt: 0, click cnt: 0 716 | [ 242][ 200] ID(hex): 0, evt: ONCLICK, keep-alive cnt: 0, click cnt: 1 717 | Testing test_sequence_single_click ......................................... pass 718 | [ 20][ 20] ID(hex): 0, evt: ONPRESS, keep-alive cnt: 0, click cnt: 0 719 | [ 42][ 22] ID(hex): 0, evt: ONRELEASE, keep-alive cnt: 0, click cnt: 0 720 | [ 163][ 121] ID(hex): 0, evt: ONPRESS, keep-alive cnt: 0, click cnt: 1 721 | [ 184][ 21] ID(hex): 0, evt: ONRELEASE, keep-alive cnt: 0, click cnt: 1 722 | [ 384][ 200] ID(hex): 0, evt: ONCLICK, keep-alive cnt: 0, click cnt: 2 723 | Testing test_sequence_double_click ......................................... pass 724 | [ 20][ 20] ID(hex): 0, evt: ONPRESS, keep-alive cnt: 0, click cnt: 0 725 | [ 42][ 22] ID(hex): 0, evt: ONRELEASE, keep-alive cnt: 0, click cnt: 0 726 | [ 163][ 121] ID(hex): 0, evt: ONPRESS, keep-alive cnt: 0, click cnt: 1 727 | [ 184][ 21] ID(hex): 0, evt: ONRELEASE, keep-alive cnt: 0, click cnt: 1 728 | [ 305][ 121] ID(hex): 0, evt: ONPRESS, keep-alive cnt: 0, click cnt: 2 729 | [ 326][ 21] ID(hex): 0, evt: ONRELEASE, keep-alive cnt: 0, click cnt: 2 730 | [ 526][ 200] ID(hex): 0, evt: ONCLICK, keep-alive cnt: 0, click cnt: 3 731 | Testing test_sequence_triple_click ......................................... pass 732 | [ 20][ 20] ID(hex): 0, evt: ONPRESS, keep-alive cnt: 0, click cnt: 0 733 | [ 42][ 22] ID(hex): 0, evt: ONRELEASE, keep-alive cnt: 0, click cnt: 0 734 | [ 241][ 199] ID(hex): 0, evt: ONPRESS, keep-alive cnt: 0, click cnt: 1 735 | [ 262][ 21] ID(hex): 0, evt: ONRELEASE, keep-alive cnt: 0, click cnt: 1 736 | [ 462][ 200] ID(hex): 0, evt: ONCLICK, keep-alive cnt: 0, click cnt: 2 737 | Testing test_sequence_double_click_critical_time ........................... pass 738 | [ 20][ 20] ID(hex): 0, evt: ONPRESS, keep-alive cnt: 0, click cnt: 0 739 | [ 42][ 22] ID(hex): 0, evt: ONRELEASE, keep-alive cnt: 0, click cnt: 0 740 | [ 243][ 201] ID(hex): 0, evt: ONCLICK, keep-alive cnt: 0, click cnt: 1 741 | [ 243][ 0] ID(hex): 0, evt: ONPRESS, keep-alive cnt: 0, click cnt: 0 742 | [ 264][ 21] ID(hex): 0, evt: ONRELEASE, keep-alive cnt: 0, click cnt: 0 743 | [ 464][ 200] ID(hex): 0, evt: ONCLICK, keep-alive cnt: 0, click cnt: 1 744 | Testing test_sequence_double_click_critical_time_over ...................... pass 745 | [ 20][ 20] ID(hex): 0, evt: ONPRESS, keep-alive cnt: 0, click cnt: 0 746 | [ 42][ 22] ID(hex): 0, evt: ONRELEASE, keep-alive cnt: 0, click cnt: 0 747 | [ 163][ 121] ID(hex): 0, evt: ONPRESS, keep-alive cnt: 0, click cnt: 1 748 | [ 464][ 301] ID(hex): 0, evt: ONCLICK, keep-alive cnt: 0, click cnt: 1 749 | [ 663][ 199] ID(hex): 0, evt: KEEPALIVE, keep-alive cnt: 1, click cnt: 0 750 | [ 1163][ 500] ID(hex): 0, evt: KEEPALIVE, keep-alive cnt: 2, click cnt: 0 751 | [ 1663][ 500] ID(hex): 0, evt: KEEPALIVE, keep-alive cnt: 3, click cnt: 0 752 | [ 1667][ 4] ID(hex): 0, evt: ONRELEASE, keep-alive cnt: 3, click cnt: 0 753 | Testing test_sequence_click_with_keepalive ................................. pass 754 | [ 20][ 20] ID(hex): 0, evt: ONPRESS, keep-alive cnt: 3, click cnt: 0 755 | [ 32][ 12] ID(hex): 0, evt: ONRELEASE, keep-alive cnt: 0, click cnt: 0 756 | Testing test_sequence_click_with_short ..................................... pass 757 | [ 20][ 20] ID(hex): 0, evt: ONPRESS, keep-alive cnt: 0, click cnt: 0 758 | [ 32][ 12] ID(hex): 0, evt: ONRELEASE, keep-alive cnt: 0, click cnt: 0 759 | [ 153][ 121] ID(hex): 0, evt: ONPRESS, keep-alive cnt: 0, click cnt: 0 760 | [ 174][ 21] ID(hex): 0, evt: ONRELEASE, keep-alive cnt: 0, click cnt: 0 761 | [ 295][ 121] ID(hex): 0, evt: ONPRESS, keep-alive cnt: 0, click cnt: 1 762 | [ 316][ 21] ID(hex): 0, evt: ONRELEASE, keep-alive cnt: 0, click cnt: 1 763 | [ 516][ 200] ID(hex): 0, evt: ONCLICK, keep-alive cnt: 0, click cnt: 2 764 | Testing test_sequence_click_with_short_with_multi .......................... pass 765 | [ 20][ 20] ID(hex): 0, evt: ONPRESS, keep-alive cnt: 0, click cnt: 0 766 | [ 42][ 22] ID(hex): 0, evt: ONRELEASE, keep-alive cnt: 0, click cnt: 0 767 | [ 163][ 121] ID(hex): 0, evt: ONPRESS, keep-alive cnt: 0, click cnt: 1 768 | [ 184][ 21] ID(hex): 0, evt: ONRELEASE, keep-alive cnt: 0, click cnt: 1 769 | [ 305][ 121] ID(hex): 0, evt: ONPRESS, keep-alive cnt: 0, click cnt: 2 770 | [ 316][ 11] ID(hex): 0, evt: ONRELEASE, keep-alive cnt: 0, click cnt: 2 771 | [ 316][ 0] ID(hex): 0, evt: ONCLICK, keep-alive cnt: 0, click cnt: 2 772 | Testing test_sequence_multi_click_with_short ............................... pass 773 | [ 60][ 60] ID(hex): 0, evt: ONPRESS, keep-alive cnt: 0, click cnt: 0 774 | [ 81][ 21] ID(hex): 0, evt: ONRELEASE, keep-alive cnt: 0, click cnt: 0 775 | [ 281][ 200] ID(hex): 0, evt: ONCLICK, keep-alive cnt: 0, click cnt: 1 776 | Testing test_sequence_onpress_debounce ..................................... pass 777 | [ 65547][ 65547] ID(hex): 0, evt: ONPRESS, keep-alive cnt: 0, click cnt: 0 778 | [ 65568][ 21] ID(hex): 0, evt: ONRELEASE, keep-alive cnt: 0, click cnt: 0 779 | [ 65768][ 200] ID(hex): 0, evt: ONCLICK, keep-alive cnt: 0, click cnt: 1 780 | Testing test_sequence_time_overflow_onpress_debounce ....................... pass 781 | [ 65527][ 65527] ID(hex): 0, evt: ONPRESS, keep-alive cnt: 0, click cnt: 0 782 | [ 65548][ 21] ID(hex): 0, evt: ONRELEASE, keep-alive cnt: 0, click cnt: 0 783 | [ 65748][ 200] ID(hex): 0, evt: ONCLICK, keep-alive cnt: 0, click cnt: 1 784 | Testing test_sequence_time_overflow_onpress ................................ pass 785 | [ 65507][ 65507] ID(hex): 0, evt: ONPRESS, keep-alive cnt: 0, click cnt: 0 786 | [ 65528][ 21] ID(hex): 0, evt: ONRELEASE, keep-alive cnt: 0, click cnt: 0 787 | [ 65728][ 200] ID(hex): 0, evt: ONCLICK, keep-alive cnt: 0, click cnt: 1 788 | Testing test_sequence_time_overflow_onrelease_muti ......................... pass 789 | [ 65267][ 65267] ID(hex): 0, evt: ONPRESS, keep-alive cnt: 0, click cnt: 0 790 | [ 65767][ 500] ID(hex): 0, evt: KEEPALIVE, keep-alive cnt: 1, click cnt: 0 791 | [ 65789][ 22] ID(hex): 0, evt: ONRELEASE, keep-alive cnt: 1, click cnt: 0 792 | Testing test_sequence_time_overflow_keepalive .............................. pass 793 | [ 20][ 20] ID(hex): 1, evt: ONPRESS, keep-alive cnt: 0, click cnt: 0 794 | [ 164][ 144] ID(hex): 1, evt: ONRELEASE, keep-alive cnt: 0, click cnt: 0 795 | [ 364][ 200] ID(hex): 1, evt: ONCLICK, keep-alive cnt: 0, click cnt: 1 796 | Testing test_sequence_onrelease_debounce ................................... pass 797 | [ 20][ 20] ID(hex): 1, evt: ONPRESS, keep-alive cnt: 0, click cnt: 0 798 | [ 102][ 82] ID(hex): 1, evt: ONRELEASE, keep-alive cnt: 0, click cnt: 0 799 | [ 123][ 21] ID(hex): 1, evt: ONPRESS, keep-alive cnt: 0, click cnt: 1 800 | [ 204][ 81] ID(hex): 1, evt: ONRELEASE, keep-alive cnt: 0, click cnt: 1 801 | [ 404][ 200] ID(hex): 1, evt: ONCLICK, keep-alive cnt: 0, click cnt: 2 802 | Testing test_sequence_onrelease_debounce_over .............................. pass 803 | [ 65497][ 65497] ID(hex): 1, evt: ONPRESS, keep-alive cnt: 0, click cnt: 0 804 | [ 65578][ 81] ID(hex): 1, evt: ONRELEASE, keep-alive cnt: 0, click cnt: 0 805 | [ 65778][ 200] ID(hex): 1, evt: ONCLICK, keep-alive cnt: 0, click cnt: 1 806 | Testing test_sequence_onrelease_debounce_time_overflow ..................... pass 807 | [ 20][ 20] ID(hex): 2, evt: ONPRESS, keep-alive cnt: 0, click cnt: 0 808 | [ 120][ 100] ID(hex): 2, evt: KEEPALIVE, keep-alive cnt: 1, click cnt: 0 809 | [ 203][ 83] ID(hex): 2, evt: ONRELEASE, keep-alive cnt: 1, click cnt: 0 810 | [ 403][ 200] ID(hex): 2, evt: ONCLICK, keep-alive cnt: 1, click cnt: 1 811 | Testing test_sequence_keepalive_with_click ................................. pass 812 | [ 20][ 20] ID(hex): 2, evt: ONPRESS, keep-alive cnt: 1, click cnt: 0 813 | [ 120][ 100] ID(hex): 2, evt: KEEPALIVE, keep-alive cnt: 1, click cnt: 0 814 | [ 220][ 100] ID(hex): 2, evt: KEEPALIVE, keep-alive cnt: 2, click cnt: 0 815 | [ 304][ 84] ID(hex): 2, evt: ONRELEASE, keep-alive cnt: 2, click cnt: 0 816 | [ 504][ 200] ID(hex): 2, evt: ONCLICK, keep-alive cnt: 2, click cnt: 1 817 | Testing test_sequence_keepalive_with_click_double .......................... pass 818 | [ 20][ 20] ID(hex): 3, evt: ONPRESS, keep-alive cnt: 0, click cnt: 0 819 | [ 102][ 82] ID(hex): 3, evt: ONRELEASE, keep-alive cnt: 0, click cnt: 0 820 | [ 223][ 121] ID(hex): 3, evt: ONPRESS, keep-alive cnt: 0, click cnt: 1 821 | [ 304][ 81] ID(hex): 3, evt: ONRELEASE, keep-alive cnt: 0, click cnt: 1 822 | [ 425][ 121] ID(hex): 3, evt: ONPRESS, keep-alive cnt: 0, click cnt: 2 823 | [ 506][ 81] ID(hex): 3, evt: ONRELEASE, keep-alive cnt: 0, click cnt: 2 824 | [ 506][ 0] ID(hex): 3, evt: ONCLICK, keep-alive cnt: 0, click cnt: 3 825 | Testing test_sequence_max_click_3 .......................................... pass 826 | [ 20][ 20] ID(hex): 3, evt: ONPRESS, keep-alive cnt: 0, click cnt: 0 827 | [ 102][ 82] ID(hex): 3, evt: ONRELEASE, keep-alive cnt: 0, click cnt: 0 828 | [ 223][ 121] ID(hex): 3, evt: ONPRESS, keep-alive cnt: 0, click cnt: 1 829 | [ 304][ 81] ID(hex): 3, evt: ONRELEASE, keep-alive cnt: 0, click cnt: 1 830 | [ 425][ 121] ID(hex): 3, evt: ONPRESS, keep-alive cnt: 0, click cnt: 2 831 | [ 506][ 81] ID(hex): 3, evt: ONRELEASE, keep-alive cnt: 0, click cnt: 2 832 | [ 506][ 0] ID(hex): 3, evt: ONCLICK, keep-alive cnt: 0, click cnt: 3 833 | [ 627][ 121] ID(hex): 3, evt: ONPRESS, keep-alive cnt: 0, click cnt: 0 834 | [ 708][ 81] ID(hex): 3, evt: ONRELEASE, keep-alive cnt: 0, click cnt: 0 835 | [ 908][ 200] ID(hex): 3, evt: ONCLICK, keep-alive cnt: 0, click cnt: 1 836 | Testing test_sequence_max_click_3_over ..................................... pass 837 | [ 20][ 20] ID(hex): 4, evt: ONPRESS, keep-alive cnt: 0, click cnt: 0 838 | [ 102][ 82] ID(hex): 4, evt: ONRELEASE, keep-alive cnt: 0, click cnt: 0 839 | [ 123][ 21] ID(hex): 4, evt: ONCLICK, keep-alive cnt: 0, click cnt: 1 840 | [ 123][ 0] ID(hex): 4, evt: ONPRESS, keep-alive cnt: 0, click cnt: 0 841 | [ 204][ 81] ID(hex): 4, evt: ONRELEASE, keep-alive cnt: 0, click cnt: 0 842 | [ 225][ 21] ID(hex): 4, evt: ONCLICK, keep-alive cnt: 0, click cnt: 1 843 | [ 225][ 0] ID(hex): 4, evt: ONPRESS, keep-alive cnt: 0, click cnt: 0 844 | [ 306][ 81] ID(hex): 4, evt: ONRELEASE, keep-alive cnt: 0, click cnt: 0 845 | [ 307][ 1] ID(hex): 4, evt: ONCLICK, keep-alive cnt: 0, click cnt: 1 846 | Testing test_sequence_click_multi_max_0 .................................... pass 847 | [ 20][ 20] ID(hex): 5, evt: ONPRESS, keep-alive cnt: 0, click cnt: 0 848 | [ 303][ 283] ID(hex): 5, evt: ONRELEASE, keep-alive cnt: 0, click cnt: 0 849 | [ 503][ 200] ID(hex): 5, evt: ONCLICK, keep-alive cnt: 0, click cnt: 1 850 | Testing test_sequence_keep_alive_0 ......................................... pass 851 | Executing 'run: all' complete! 852 | ``` 853 | 854 | 当然可以用windows的按键进行交互测试,详见`example_user.c`的处理,`main.c`选择调用`example_user()`。 855 | 856 | 857 | 858 | 859 | 860 | 861 | 862 | 863 | 864 | 865 | 866 | 867 | 868 | -------------------------------------------------------------------------------- /README.vsdx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bobwenstudy/easy_button/7a4aaaadaff42a537010cb26958ba50b4d4cb430/README.vsdx -------------------------------------------------------------------------------- /build.mk: -------------------------------------------------------------------------------- 1 | SRC += . 2 | SRC += ebtn 3 | 4 | INCLUDE += . 5 | INCLUDE += ebtn 6 | -------------------------------------------------------------------------------- /code_format.py: -------------------------------------------------------------------------------- 1 | import os 2 | import subprocess 3 | import re 4 | import sys 5 | 6 | def format_all_file(root): 7 | for root, dirs, files in os.walk(root): 8 | 9 | # root 表示当前正在访问的文件夹路径 10 | # dirs 表示该文件夹下的子目录名list 11 | # files 表示该文件夹下的文件list 12 | 13 | # 遍历文件 14 | for f in files: 15 | if f.endswith('.c') or f.endswith('.h'): 16 | full_path = os.path.join(root, f) 17 | #print("root: %s, path: %s" % (root, full_path)) 18 | print(full_path) 19 | 20 | command = 'clang-format -style=file -i %s' % full_path 21 | #print(command) 22 | proc = subprocess.run(command, shell=True, stdout=subprocess.PIPE) 23 | 24 | if __name__ == '__main__': 25 | format_all_file('.') 26 | 27 | #proc = subprocess.run(command, shell=True, stdout=subprocess.PIPE) 28 | -------------------------------------------------------------------------------- /ebtn/bit_array.h: -------------------------------------------------------------------------------- 1 | #ifndef _BIT_ARRAY_H_ 2 | #define _BIT_ARRAY_H_ 3 | 4 | #include 5 | #include 6 | 7 | #ifdef __cplusplus 8 | extern "C" { 9 | #endif /* __cplusplus */ 10 | 11 | // #define BIT_ARRAY_CONFIG_64 12 | 13 | // here can change to uint64_t, if you system is 64bit. 14 | #ifdef BIT_ARRAY_CONFIG_64 15 | typedef uint64_t bit_array_t; 16 | #define BIT_ARRAY_BIT(n) (1ULL << (n)) 17 | #else 18 | typedef uint32_t bit_array_t; 19 | #define BIT_ARRAY_BIT(n) (1UL << (n)) 20 | #endif 21 | typedef bit_array_t bit_array_val_t; 22 | 23 | #define BIT_ARRAY_BITS (sizeof(bit_array_val_t) * 8) 24 | 25 | #define BIT_ARRAY_BIT_WORD(bit) ((bit) / BIT_ARRAY_BITS) 26 | #define BIT_ARRAY_BIT_INDEX(bit) ((bit_array_val_t)(bit) & (BIT_ARRAY_BITS - 1U)) 27 | 28 | #define BIT_ARRAY_MASK(bit) BIT_ARRAY_BIT(BIT_ARRAY_BIT_INDEX(bit)) 29 | #define BIT_ARRAY_ELEM(addr, bit) ((addr)[BIT_ARRAY_BIT_WORD(bit)]) 30 | 31 | // word of all 1s 32 | #define BIT_ARRAY_WORD_MAX (~(bit_array_val_t)0) 33 | 34 | #define BIT_ARRAY_SUB_MASK(nbits) ((nbits) ? BIT_ARRAY_WORD_MAX >> (BIT_ARRAY_BITS - (nbits)) : (bit_array_val_t)0) 35 | 36 | // A possibly faster way to combine two words with a mask 37 | // #define bitmask_merge(a,b,abits) ((a & abits) | (b & ~abits)) 38 | #define bitmask_merge(a, b, abits) (b ^ ((a ^ b) & abits)) 39 | 40 | /** 41 | * @brief This macro computes the number of bit array variables necessary to 42 | * represent a bitmap with @a num_bits. 43 | * 44 | * @param num_bits Number of bits. 45 | */ 46 | #define BIT_ARRAY_BITMAP_SIZE(num_bits) (1 + ((num_bits)-1) / BIT_ARRAY_BITS) 47 | 48 | /** 49 | * @brief Define an array of bit array variables. 50 | * 51 | * This macro defines an array of bit array variables containing at least 52 | * @a num_bits bits. 53 | * 54 | * @note 55 | * If used from file scope, the bits of the array are initialized to zero; 56 | * if used from within a function, the bits are left uninitialized. 57 | * 58 | * @cond INTERNAL_HIDDEN 59 | * @note 60 | * This macro should be replicated in the PREDEFINED field of the documentation 61 | * Doxyfile. 62 | * @endcond 63 | * 64 | * @param name Name of array of bit array variables. 65 | * @param num_bits Number of bits needed. 66 | */ 67 | #define BIT_ARRAY_DEFINE(name, num_bits) bit_array_t name[BIT_ARRAY_BITMAP_SIZE(num_bits)] 68 | 69 | #if 1 70 | // See http://graphics.stanford.edu/~seander/bithacks.html#CountBitsSetParallel 71 | static inline bit_array_val_t _windows_popcount(bit_array_val_t w) 72 | { 73 | w = w - ((w >> 1) & (bit_array_val_t) ~(bit_array_val_t)0 / 3); 74 | w = (w & (bit_array_val_t) ~(bit_array_val_t)0 / 15 * 3) + ((w >> 2) & (bit_array_val_t) ~(bit_array_val_t)0 / 15 * 3); 75 | w = (w + (w >> 4)) & (bit_array_val_t) ~(bit_array_val_t)0 / 255 * 15; 76 | return (bit_array_val_t)(w * ((bit_array_val_t) ~(bit_array_val_t)0 / 255)) >> (sizeof(bit_array_val_t) - 1) * 8; 77 | } 78 | 79 | #define POPCOUNT(x) _windows_popcount(x) 80 | #else 81 | #define POPCOUNT(x) (unsigned)__builtin_popcountll(x) 82 | #endif 83 | 84 | #define bits_in_top_word(nbits) ((nbits) ? BIT_ARRAY_BIT_INDEX((nbits)-1) + 1 : 0) 85 | 86 | static inline void _bit_array_mask_top_word(bit_array_t *target, int num_bits) 87 | { 88 | // Mask top word 89 | int num_of_words = BIT_ARRAY_BITMAP_SIZE(num_bits); 90 | int bits_active = bits_in_top_word(num_bits); 91 | target[num_of_words - 1] &= BIT_ARRAY_SUB_MASK(bits_active); 92 | } 93 | 94 | /** 95 | * @brief Bit Array test a bit. 96 | * 97 | * This routine tests whether bit number @a bit of @a target is set or not. 98 | * 99 | * @param target Address of bit array variable or array. 100 | * @param bit Bit number (starting from 0). 101 | * 102 | * @return true if the bit was set, false if it wasn't. 103 | */ 104 | static inline int bit_array_get(const bit_array_t *target, int bit) 105 | { 106 | bit_array_val_t val = BIT_ARRAY_ELEM(target, bit); 107 | 108 | return (1 & (val >> (bit & (BIT_ARRAY_BITS - 1)))) != 0; 109 | } 110 | 111 | /** 112 | * @brief Bit Array clear a bit. 113 | * 114 | * Bit Array clear bit number @a bit of @a target. 115 | * 116 | * @param target Address of bit array variable or array. 117 | * @param bit Bit number (starting from 0). 118 | */ 119 | static inline void bit_array_clear(bit_array_t *target, int bit) 120 | { 121 | bit_array_val_t mask = BIT_ARRAY_MASK(bit); 122 | 123 | BIT_ARRAY_ELEM(target, bit) &= ~mask; 124 | } 125 | 126 | /** 127 | * @brief Bit Array set a bit. 128 | * 129 | * Bit Array set bit number @a bit of @a target. 130 | * 131 | * @param target Address of bit array variable or array. 132 | * @param bit Bit number (starting from 0). 133 | */ 134 | static inline void bit_array_set(bit_array_t *target, int bit) 135 | { 136 | bit_array_val_t mask = BIT_ARRAY_MASK(bit); 137 | 138 | BIT_ARRAY_ELEM(target, bit) |= mask; 139 | } 140 | 141 | /** 142 | * @brief Bit Array toggle a bit. 143 | * 144 | * Bit Array toggle bit number @a bit of @a target. 145 | * 146 | * @param target Address of bit array variable or array. 147 | * @param bit Bit number (starting from 0). 148 | */ 149 | static inline void bit_array_toggle(bit_array_t *target, int bit) 150 | { 151 | bit_array_val_t mask = BIT_ARRAY_MASK(bit); 152 | 153 | BIT_ARRAY_ELEM(target, bit) ^= mask; 154 | } 155 | 156 | /** 157 | * @brief Bit Array set a bit to a given value. 158 | * 159 | * Bit Array set bit number @a bit of @a target to value @a val. 160 | * 161 | * @param target Address of bit array variable or array. 162 | * @param bit Bit number (starting from 0). 163 | * @param val true for 1, false for 0. 164 | */ 165 | static inline void bit_array_assign(bit_array_t *target, int bit, int val) 166 | { 167 | bit_array_val_t mask = BIT_ARRAY_MASK(bit); 168 | 169 | if (val) 170 | { 171 | BIT_ARRAY_ELEM(target, bit) |= mask; 172 | } 173 | else 174 | { 175 | BIT_ARRAY_ELEM(target, bit) &= ~mask; 176 | } 177 | } 178 | 179 | static inline void bit_array_clear_all(bit_array_t *target, int num_bits) 180 | { 181 | memset((void *)target, 0, BIT_ARRAY_BITMAP_SIZE(num_bits) * sizeof(bit_array_val_t)); 182 | } 183 | 184 | static inline void bit_array_set_all(bit_array_t *target, int num_bits) 185 | { 186 | memset((void *)target, 0xff, BIT_ARRAY_BITMAP_SIZE(num_bits) * sizeof(bit_array_val_t)); 187 | _bit_array_mask_top_word(target, num_bits); 188 | } 189 | 190 | static inline void bit_array_toggle_all(bit_array_t *target, int num_bits) 191 | { 192 | for (int i = 0; i < BIT_ARRAY_BITMAP_SIZE(num_bits); i++) 193 | { 194 | target[i] ^= BIT_ARRAY_WORD_MAX; 195 | } 196 | _bit_array_mask_top_word(target, num_bits); 197 | } 198 | 199 | // 200 | // Strings and printing 201 | // 202 | 203 | // Construct a BIT_ARRAY from a substring with given on and off characters. 204 | 205 | // From string method 206 | static inline void bit_array_from_str(bit_array_t *bitarr, const char *str) 207 | { 208 | int i, index; 209 | int space = 0; 210 | int len = strlen(str); 211 | 212 | for (i = 0; i < len; i++) 213 | { 214 | index = i - space; 215 | if (strchr("1", str[i]) != NULL) 216 | { 217 | bit_array_set(bitarr, index); 218 | } 219 | else if (strchr("0", str[i]) != NULL) 220 | { 221 | bit_array_clear(bitarr, index); 222 | } 223 | else 224 | { 225 | // error. 226 | space++; 227 | } 228 | } 229 | } 230 | 231 | // Takes a char array to write to. `str` must be bitarr->num_of_bits+1 in length 232 | // Terminates string with '\0' 233 | static inline char *bit_array_to_str(const bit_array_t *bitarr, int num_bits, char *str) 234 | { 235 | int i; 236 | 237 | for (i = 0; i < num_bits; i++) 238 | { 239 | str[i] = bit_array_get(bitarr, i) ? '1' : '0'; 240 | } 241 | 242 | str[num_bits] = '\0'; 243 | 244 | return str; 245 | } 246 | 247 | // Takes a char array to write to. `str` must be bitarr->num_of_bits+1 in length 248 | // Terminates string with '\0' 249 | static inline char *bit_array_to_str_8(const bit_array_t *bitarr, int num_bits, char *str) 250 | { 251 | int i; 252 | int space = 0; 253 | 254 | for (i = 0; i < num_bits; i++) 255 | { 256 | str[i + space] = bit_array_get(bitarr, i) ? '1' : '0'; 257 | 258 | if ((i + 1) % 8 == 0) 259 | { 260 | space++; 261 | str[i + space] = ' '; 262 | } 263 | } 264 | 265 | str[num_bits + space] = '\0'; 266 | 267 | return str; 268 | } 269 | 270 | // 271 | // Get and set words (internal use only -- no bounds checking) 272 | // 273 | 274 | static inline bit_array_val_t _bit_array_get_word(const bit_array_t *target, int num_bits, int start) 275 | { 276 | int word_index = BIT_ARRAY_BIT_WORD(start); 277 | int word_offset = BIT_ARRAY_BIT_INDEX(start); 278 | 279 | bit_array_val_t result = target[word_index] >> word_offset; 280 | 281 | int bits_taken = BIT_ARRAY_BITS - word_offset; 282 | 283 | // word_offset is now the number of bits we need from the next word 284 | // Check the next word has at least some bits 285 | if (word_offset > 0 && start + bits_taken < num_bits) 286 | { 287 | result |= target[word_index + 1] << (BIT_ARRAY_BITS - word_offset); 288 | } 289 | 290 | return result; 291 | } 292 | 293 | // Set 64 bits from a particular start position 294 | // Doesn't extend bit array 295 | static inline void _bit_array_set_word(bit_array_t *target, int num_bits, int start, bit_array_val_t word) 296 | { 297 | int word_index = BIT_ARRAY_BIT_WORD(start); 298 | int word_offset = BIT_ARRAY_BIT_INDEX(start); 299 | 300 | if (word_offset == 0) 301 | { 302 | target[word_index] = word; 303 | } 304 | else 305 | { 306 | target[word_index] = (word << word_offset) | (target[word_index] & BIT_ARRAY_SUB_MASK(word_offset)); 307 | 308 | if (word_index + 1 < BIT_ARRAY_BITMAP_SIZE(num_bits)) 309 | { 310 | target[word_index + 1] = (word >> (BIT_ARRAY_BITS - word_offset)) | (target[word_index + 1] & (BIT_ARRAY_WORD_MAX << word_offset)); 311 | } 312 | } 313 | 314 | // Mask top word 315 | _bit_array_mask_top_word(target, num_bits); 316 | } 317 | 318 | // 319 | // Fill a region (internal use only) 320 | // 321 | 322 | // FillAction is fill with 0 or 1 or toggle 323 | typedef enum 324 | { 325 | ZERO_REGION, 326 | FILL_REGION, 327 | SWAP_REGION 328 | } FillAction; 329 | 330 | static inline void _bit_array_set_region(bit_array_t *target, int start, int length, FillAction action) 331 | { 332 | if (length == 0) 333 | return; 334 | 335 | int first_word = BIT_ARRAY_BIT_WORD(start); 336 | int last_word = BIT_ARRAY_BIT_WORD(start + length - 1); 337 | int foffset = BIT_ARRAY_BIT_INDEX(start); 338 | int loffset = BIT_ARRAY_BIT_INDEX(start + length - 1); 339 | 340 | if (first_word == last_word) 341 | { 342 | bit_array_val_t mask = BIT_ARRAY_SUB_MASK(length) << foffset; 343 | 344 | switch (action) 345 | { 346 | case ZERO_REGION: 347 | target[first_word] &= ~mask; 348 | break; 349 | case FILL_REGION: 350 | target[first_word] |= mask; 351 | break; 352 | case SWAP_REGION: 353 | target[first_word] ^= mask; 354 | break; 355 | } 356 | } 357 | else 358 | { 359 | // Set first word 360 | switch (action) 361 | { 362 | case ZERO_REGION: 363 | target[first_word] &= BIT_ARRAY_SUB_MASK(foffset); 364 | break; 365 | case FILL_REGION: 366 | target[first_word] |= ~BIT_ARRAY_SUB_MASK(foffset); 367 | break; 368 | case SWAP_REGION: 369 | target[first_word] ^= ~BIT_ARRAY_SUB_MASK(foffset); 370 | break; 371 | } 372 | 373 | int i; 374 | 375 | // Set whole words 376 | switch (action) 377 | { 378 | case ZERO_REGION: 379 | for (i = first_word + 1; i < last_word; i++) 380 | target[i] = (bit_array_val_t)0; 381 | break; 382 | case FILL_REGION: 383 | for (i = first_word + 1; i < last_word; i++) 384 | target[i] = BIT_ARRAY_WORD_MAX; 385 | break; 386 | case SWAP_REGION: 387 | for (i = first_word + 1; i < last_word; i++) 388 | target[i] ^= BIT_ARRAY_WORD_MAX; 389 | break; 390 | } 391 | 392 | // Set last word 393 | switch (action) 394 | { 395 | case ZERO_REGION: 396 | target[last_word] &= ~BIT_ARRAY_SUB_MASK(loffset + 1); 397 | break; 398 | case FILL_REGION: 399 | target[last_word] |= BIT_ARRAY_SUB_MASK(loffset + 1); 400 | break; 401 | case SWAP_REGION: 402 | target[last_word] ^= BIT_ARRAY_SUB_MASK(loffset + 1); 403 | break; 404 | } 405 | } 406 | } 407 | 408 | // Get the number of bits set (hamming weight) 409 | static inline int bit_array_num_bits_set(bit_array_t *target, int num_bits) 410 | { 411 | int i; 412 | 413 | int num_of_bits_set = 0; 414 | 415 | for (i = 0; i < BIT_ARRAY_BITMAP_SIZE(num_bits); i++) 416 | { 417 | if (target[i] > 0) 418 | { 419 | num_of_bits_set += POPCOUNT(target[i]); 420 | } 421 | } 422 | 423 | return num_of_bits_set; 424 | } 425 | 426 | // Get the number of bits not set (1 - hamming weight) 427 | static inline int bit_array_num_bits_cleared(bit_array_t *target, int num_bits) 428 | { 429 | return num_bits - bit_array_num_bits_set(target, num_bits); 430 | } 431 | 432 | // Copy bits from one array to another 433 | // Note: use MACRO bit_array_copy 434 | // Destination and source can be the same bit_array and 435 | // src/dst regions can overlap 436 | static inline void bit_array_copy(bit_array_t *dst, int dstindx, const bit_array_t *src, int srcindx, int length, int src_num_bits, int dst_num_bits) 437 | { 438 | // Num of full words to copy 439 | int num_of_full_words = length / BIT_ARRAY_BITS; 440 | int i; 441 | 442 | int bits_in_last_word = bits_in_top_word(length); 443 | 444 | if (dst == src && srcindx > dstindx) 445 | { 446 | // Work left to right 447 | for (i = 0; i < num_of_full_words; i++) 448 | { 449 | bit_array_val_t word = _bit_array_get_word(src, src_num_bits, srcindx + i * BIT_ARRAY_BITS); 450 | _bit_array_set_word(dst, dst_num_bits, dstindx + i * BIT_ARRAY_BITS, word); 451 | } 452 | 453 | if (bits_in_last_word > 0) 454 | { 455 | bit_array_val_t src_word = _bit_array_get_word(src, src_num_bits, srcindx + i * BIT_ARRAY_BITS); 456 | bit_array_val_t dst_word = _bit_array_get_word(dst, dst_num_bits, dstindx + i * BIT_ARRAY_BITS); 457 | 458 | bit_array_val_t mask = BIT_ARRAY_SUB_MASK(bits_in_last_word); 459 | bit_array_val_t word = bitmask_merge(src_word, dst_word, mask); 460 | 461 | _bit_array_set_word(dst, dst_num_bits, dstindx + num_of_full_words * BIT_ARRAY_BITS, word); 462 | } 463 | } 464 | else 465 | { 466 | // Work right to left 467 | for (i = 0; i < num_of_full_words; i++) 468 | { 469 | bit_array_val_t word = _bit_array_get_word(src, src_num_bits, srcindx + length - (i + 1) * BIT_ARRAY_BITS); 470 | _bit_array_set_word(dst, dst_num_bits, dstindx + length - (i + 1) * BIT_ARRAY_BITS, word); 471 | } 472 | 473 | if (bits_in_last_word > 0) 474 | { 475 | bit_array_val_t src_word = _bit_array_get_word(src, src_num_bits, srcindx); 476 | bit_array_val_t dst_word = _bit_array_get_word(dst, dst_num_bits, dstindx); 477 | 478 | bit_array_val_t mask = BIT_ARRAY_SUB_MASK(bits_in_last_word); 479 | bit_array_val_t word = bitmask_merge(src_word, dst_word, mask); 480 | _bit_array_set_word(dst, dst_num_bits, dstindx, word); 481 | } 482 | } 483 | 484 | _bit_array_mask_top_word(dst, dst_num_bits); 485 | } 486 | 487 | // copy all of src to dst. dst is resized to match src. 488 | static inline void bit_array_copy_all(bit_array_t *dst, const bit_array_t *src, int num_bits) 489 | { 490 | for (int i = 0; i < BIT_ARRAY_BITMAP_SIZE(num_bits); i++) 491 | { 492 | dst[i] = src[i]; 493 | } 494 | } 495 | 496 | // 497 | // Logic operators 498 | // 499 | 500 | // Destination can be the same as one or both of the sources 501 | static inline void bit_array_and(bit_array_t *dest, const bit_array_t *src1, const bit_array_t *src2, int num_bits) 502 | { 503 | for (int i = 0; i < BIT_ARRAY_BITMAP_SIZE(num_bits); i++) 504 | { 505 | dest[i] = src1[i] & src2[i]; 506 | } 507 | } 508 | 509 | static inline void bit_array_or(bit_array_t *dest, const bit_array_t *src1, const bit_array_t *src2, int num_bits) 510 | { 511 | for (int i = 0; i < BIT_ARRAY_BITMAP_SIZE(num_bits); i++) 512 | { 513 | dest[i] = src1[i] | src2[i]; 514 | } 515 | } 516 | 517 | static inline void bit_array_xor(bit_array_t *dest, const bit_array_t *src1, const bit_array_t *src2, int num_bits) 518 | { 519 | for (int i = 0; i < BIT_ARRAY_BITMAP_SIZE(num_bits); i++) 520 | { 521 | dest[i] = src1[i] ^ src2[i]; 522 | } 523 | } 524 | 525 | static inline void bit_array_not(bit_array_t *dest, const bit_array_t *src, int num_bits) 526 | { 527 | for (int i = 0; i < BIT_ARRAY_BITMAP_SIZE(num_bits); i++) 528 | { 529 | dest[i] = ~src[i]; 530 | } 531 | } 532 | 533 | // 534 | // Shift array left/right. If fill is zero, filled with 0, otherwise 1 535 | // 536 | 537 | // Shift towards LSB / lower index 538 | static inline void bit_array_shift_right(bit_array_t *target, int num_bits, int shift_dist, int fill) 539 | { 540 | if (shift_dist >= num_bits) 541 | { 542 | fill ? bit_array_set_all(target, num_bits) : bit_array_clear_all(target, num_bits); 543 | return; 544 | } 545 | else if (shift_dist == 0) 546 | { 547 | return; 548 | } 549 | 550 | FillAction action = fill ? FILL_REGION : ZERO_REGION; 551 | 552 | int cpy_length = num_bits - shift_dist; 553 | bit_array_copy(target, 0, target, shift_dist, cpy_length, num_bits, num_bits); 554 | 555 | _bit_array_set_region(target, cpy_length, shift_dist, action); 556 | } 557 | 558 | // Shift towards MSB / higher index 559 | static inline void bit_array_shift_left(bit_array_t *target, int num_bits, int shift_dist, int fill) 560 | { 561 | if (shift_dist >= num_bits) 562 | { 563 | fill ? bit_array_set_all(target, num_bits) : bit_array_clear_all(target, num_bits); 564 | return; 565 | } 566 | else if (shift_dist == 0) 567 | { 568 | return; 569 | } 570 | 571 | FillAction action = fill ? FILL_REGION : ZERO_REGION; 572 | 573 | int cpy_length = num_bits - shift_dist; 574 | bit_array_copy(target, shift_dist, target, 0, cpy_length, num_bits, num_bits); 575 | _bit_array_set_region(target, 0, shift_dist, action); 576 | } 577 | 578 | // 579 | // Comparisons 580 | // 581 | 582 | // Compare two bit arrays by value stored, with index 0 being the Least 583 | // Significant Bit (LSB). Arrays must have the same length. 584 | // returns: 585 | // >0 iff bitarr1 > bitarr2 586 | // 0 iff bitarr1 == bitarr2 587 | // <0 iff bitarr1 < bitarr2 588 | static inline int bit_array_cmp(const bit_array_t *bitarr1, const bit_array_t *bitarr2, int num_bits) 589 | { 590 | return memcmp(bitarr1, bitarr2, BIT_ARRAY_BITMAP_SIZE(num_bits) * sizeof(bit_array_val_t)); 591 | } 592 | 593 | #ifdef __cplusplus 594 | } 595 | #endif /* __cplusplus */ 596 | 597 | #endif /* _BIT_ARRAY_H_ */ 598 | -------------------------------------------------------------------------------- /ebtn/ebtn.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "ebtn.h" 3 | 4 | #define EBTN_FLAG_ONPRESS_SENT ((uint8_t)0x01) /*!< Flag indicates that on-press event has been sent */ 5 | #define EBTN_FLAG_IN_PROCESS ((uint8_t)0x02) /*!< Flag indicates that button in process */ 6 | 7 | /* Default button group instance */ 8 | static ebtn_t ebtn_default; 9 | 10 | /** 11 | * \brief Process the button information and state 12 | * 13 | * \param[in] btn: Button instance to process 14 | * \param[in] old_state: old state 15 | * \param[in] new_state: new state 16 | * \param[in] mstime: Current milliseconds system time 17 | */ 18 | static void prv_process_btn(ebtn_btn_t *btn, uint8_t old_state, uint8_t new_state, ebtn_time_t mstime) 19 | { 20 | ebtn_t *ebtobj = &ebtn_default; 21 | 22 | /* Check params set or not. */ 23 | if (btn->param == NULL) 24 | { 25 | return; 26 | } 27 | 28 | /* Button state has just changed */ 29 | if (new_state != old_state) 30 | { 31 | btn->time_state_change = mstime; 32 | 33 | if (new_state) 34 | { 35 | btn->flags |= EBTN_FLAG_IN_PROCESS; 36 | } 37 | } 38 | /* Button is still pressed */ 39 | if (new_state) 40 | { 41 | /* 42 | * Handle debounce and send on-press event 43 | * 44 | * This is when we detect valid press 45 | */ 46 | if (!(btn->flags & EBTN_FLAG_ONPRESS_SENT)) 47 | { 48 | /* 49 | * Run if statement when: 50 | * 51 | * - Runtime mode is enabled -> user sets its own config for debounce 52 | * - Config debounce time for press is more than `0` 53 | */ 54 | if (ebtn_timer_sub(mstime, btn->time_state_change) >= btn->param->time_debounce) 55 | { 56 | /* 57 | * Check mutlti click limit reach or not. 58 | */ 59 | if ((btn->click_cnt > 0) && (ebtn_timer_sub(mstime, btn->click_last_time) >= btn->param->time_click_multi_max)) 60 | { 61 | if (btn->event_mask & EBTN_EVT_MASK_ONCLICK) 62 | { 63 | ebtobj->evt_fn(btn, EBTN_EVT_ONCLICK); 64 | } 65 | btn->click_cnt = 0; 66 | } 67 | 68 | /* Set keep alive time */ 69 | btn->keepalive_last_time = mstime; 70 | btn->keepalive_cnt = 0; 71 | 72 | /* Start with new on-press */ 73 | btn->flags |= EBTN_FLAG_ONPRESS_SENT; 74 | if (btn->event_mask & EBTN_EVT_MASK_ONPRESS) 75 | { 76 | ebtobj->evt_fn(btn, EBTN_EVT_ONPRESS); 77 | } 78 | 79 | btn->time_change = mstime; /* Button state has now changed */ 80 | } 81 | } 82 | 83 | /* 84 | * Handle keep alive, but only if on-press event has been sent 85 | * 86 | * Keep alive is sent when valid press is being detected 87 | */ 88 | else 89 | { 90 | while ((btn->param->time_keepalive_period > 0) && (ebtn_timer_sub(mstime, btn->keepalive_last_time) >= btn->param->time_keepalive_period)) 91 | { 92 | btn->keepalive_last_time += btn->param->time_keepalive_period; 93 | ++btn->keepalive_cnt; 94 | if (btn->event_mask & EBTN_EVT_MASK_KEEPALIVE) 95 | { 96 | ebtobj->evt_fn(btn, EBTN_EVT_KEEPALIVE); 97 | } 98 | } 99 | 100 | // Scene1: multi click end with a long press, need send onclick event. 101 | if ((btn->click_cnt > 0) && (ebtn_timer_sub(mstime, btn->time_change) > btn->param->time_click_pressed_max)) 102 | { 103 | if (btn->event_mask & EBTN_EVT_MASK_ONCLICK) 104 | { 105 | ebtobj->evt_fn(btn, EBTN_EVT_ONCLICK); 106 | } 107 | 108 | btn->click_cnt = 0; 109 | } 110 | } 111 | } 112 | /* Button is still released */ 113 | else 114 | { 115 | /* 116 | * We only need to react if on-press event has even been started. 117 | * 118 | * Do nothing if that was not the case 119 | */ 120 | if (btn->flags & EBTN_FLAG_ONPRESS_SENT) 121 | { 122 | /* 123 | * Run if statement when: 124 | * 125 | * - Runtime mode is enabled -> user sets its own config for debounce 126 | * - Config debounce time for release is more than `0` 127 | */ 128 | if (ebtn_timer_sub(mstime, btn->time_state_change) >= btn->param->time_debounce_release) 129 | { 130 | /* Handle on-release event */ 131 | btn->flags &= ~EBTN_FLAG_ONPRESS_SENT; 132 | if (btn->event_mask & EBTN_EVT_MASK_ONRELEASE) 133 | { 134 | ebtobj->evt_fn(btn, EBTN_EVT_ONRELEASE); 135 | } 136 | 137 | /* Check time validity for click event */ 138 | if (ebtn_timer_sub(mstime, btn->time_change) >= btn->param->time_click_pressed_min && 139 | ebtn_timer_sub(mstime, btn->time_change) <= btn->param->time_click_pressed_max) 140 | { 141 | ++btn->click_cnt; 142 | 143 | btn->click_last_time = mstime; 144 | } 145 | else 146 | { 147 | // Scene2: If last press was too short, and previous sequence of clicks was 148 | // positive, send event to user. 149 | if ((btn->click_cnt > 0) && (ebtn_timer_sub(mstime, btn->time_change) < btn->param->time_click_pressed_min)) 150 | { 151 | if (btn->event_mask & EBTN_EVT_MASK_ONCLICK) 152 | { 153 | ebtobj->evt_fn(btn, EBTN_EVT_ONCLICK); 154 | } 155 | } 156 | /* 157 | * There was an on-release event, but timing 158 | * for click event detection is outside allowed window. 159 | * 160 | * Reset clicks counter -> not valid sequence for click event. 161 | */ 162 | btn->click_cnt = 0; 163 | } 164 | 165 | // Scene3: this part will send on-click event immediately after release event, if 166 | // maximum number of consecutive clicks has been reached. 167 | if ((btn->click_cnt > 0) && (btn->click_cnt == btn->param->max_consecutive)) 168 | { 169 | if (btn->event_mask & EBTN_EVT_MASK_ONCLICK) 170 | { 171 | ebtobj->evt_fn(btn, EBTN_EVT_ONCLICK); 172 | } 173 | btn->click_cnt = 0; 174 | } 175 | 176 | btn->time_change = mstime; /* Button state has now changed */ 177 | } 178 | } 179 | else 180 | { 181 | /* 182 | * Based on te configuration, this part of the code 183 | * will send on-click event after certain timeout. 184 | * 185 | * This feature is useful if users prefers multi-click feature 186 | * that is reported only after last click event happened, 187 | * including number of clicks made by user 188 | */ 189 | if (btn->click_cnt > 0) 190 | { 191 | if (ebtn_timer_sub(mstime, btn->click_last_time) >= btn->param->time_click_multi_max) 192 | { 193 | if (btn->event_mask & EBTN_EVT_MASK_ONCLICK) 194 | { 195 | ebtobj->evt_fn(btn, EBTN_EVT_ONCLICK); 196 | } 197 | btn->click_cnt = 0; 198 | } 199 | } 200 | else 201 | { 202 | // check button in process 203 | if (btn->flags & EBTN_FLAG_IN_PROCESS) 204 | { 205 | btn->flags &= ~EBTN_FLAG_IN_PROCESS; 206 | } 207 | } 208 | } 209 | } 210 | } 211 | 212 | int ebtn_init(ebtn_btn_t *btns, uint16_t btns_cnt, ebtn_btn_combo_t *btns_combo, uint16_t btns_combo_cnt, ebtn_get_state_fn get_state_fn, ebtn_evt_fn evt_fn) 213 | { 214 | ebtn_t *ebtobj = &ebtn_default; 215 | 216 | if (evt_fn == NULL || get_state_fn == NULL /* Parameter is a must only in callback-only mode */ 217 | ) 218 | { 219 | return 0; 220 | } 221 | 222 | memset(ebtobj, 0x00, sizeof(*ebtobj)); 223 | ebtobj->btns = btns; 224 | ebtobj->btns_cnt = btns_cnt; 225 | ebtobj->btns_combo = btns_combo; 226 | ebtobj->btns_combo_cnt = btns_combo_cnt; 227 | ebtobj->evt_fn = evt_fn; 228 | ebtobj->get_state_fn = get_state_fn; 229 | 230 | return 1; 231 | } 232 | 233 | /** 234 | * \brief Get all button state with get_state_fn. 235 | * 236 | * \param[out] state_array: store the button state 237 | */ 238 | static void ebtn_get_current_state(bit_array_t *state_array) 239 | { 240 | ebtn_t *ebtobj = &ebtn_default; 241 | ebtn_btn_dyn_t *target; 242 | int i; 243 | 244 | /* Process all buttons */ 245 | for (i = 0; i < ebtobj->btns_cnt; ++i) 246 | { 247 | /* Get button state */ 248 | uint8_t new_state = ebtobj->get_state_fn(&ebtobj->btns[i]); 249 | // save state 250 | bit_array_assign(state_array, i, new_state); 251 | } 252 | 253 | for (target = ebtobj->btn_dyn_head, i = ebtobj->btns_cnt; target; target = target->next, i++) 254 | { 255 | /* Get button state */ 256 | uint8_t new_state = ebtobj->get_state_fn(&target->btn); 257 | 258 | // save state 259 | bit_array_assign(state_array, i, new_state); 260 | } 261 | } 262 | 263 | /** 264 | * \brief Process the button state 265 | * 266 | * \param[in] btn: Button instance to process 267 | * \param[in] old_state: all button old state 268 | * \param[in] curr_state: all button current state 269 | * \param[in] idx: Button internal key_idx 270 | * \param[in] mstime: Current milliseconds system time 271 | */ 272 | static void ebtn_process_btn(ebtn_btn_t *btn, bit_array_t *old_state, bit_array_t *curr_state, int idx, ebtn_time_t mstime) 273 | { 274 | prv_process_btn(btn, bit_array_get(old_state, idx), bit_array_get(curr_state, idx), mstime); 275 | } 276 | 277 | /** 278 | * \brief Process the combo-button state 279 | * 280 | * \param[in] btn: Button instance to process 281 | * \param[in] old_state: all button old state 282 | * \param[in] curr_state: all button current state 283 | * \param[in] comb_key: Combo key 284 | * \param[in] mstime: Current milliseconds system time 285 | */ 286 | static void ebtn_process_btn_combo(ebtn_btn_t *btn, bit_array_t *old_state, bit_array_t *curr_state, bit_array_t *comb_key, ebtn_time_t mstime) 287 | { 288 | BIT_ARRAY_DEFINE(tmp_data, EBTN_MAX_KEYNUM) = {0}; 289 | 290 | if (bit_array_num_bits_set(comb_key, EBTN_MAX_KEYNUM) == 0) 291 | { 292 | return; 293 | } 294 | bit_array_and(tmp_data, curr_state, comb_key, EBTN_MAX_KEYNUM); 295 | uint8_t curr = bit_array_cmp(tmp_data, comb_key, EBTN_MAX_KEYNUM) == 0; 296 | 297 | bit_array_and(tmp_data, old_state, comb_key, EBTN_MAX_KEYNUM); 298 | uint8_t old = bit_array_cmp(tmp_data, comb_key, EBTN_MAX_KEYNUM) == 0; 299 | 300 | prv_process_btn(btn, old, curr, mstime); 301 | } 302 | 303 | void ebtn_process_with_curr_state(bit_array_t *curr_state, ebtn_time_t mstime) 304 | { 305 | ebtn_t *ebtobj = &ebtn_default; 306 | ebtn_btn_dyn_t *target; 307 | ebtn_btn_combo_dyn_t *target_combo; 308 | int i; 309 | 310 | /* Process all buttons */ 311 | for (i = 0; i < ebtobj->btns_cnt; ++i) 312 | { 313 | ebtn_process_btn(&ebtobj->btns[i], ebtobj->old_state, curr_state, i, mstime); 314 | } 315 | 316 | for (target = ebtobj->btn_dyn_head, i = ebtobj->btns_cnt; target; target = target->next, i++) 317 | { 318 | ebtn_process_btn(&target->btn, ebtobj->old_state, curr_state, i, mstime); 319 | } 320 | 321 | /* Process all comb buttons */ 322 | for (i = 0; i < ebtobj->btns_combo_cnt; ++i) 323 | { 324 | ebtn_process_btn_combo(&ebtobj->btns_combo[i].btn, ebtobj->old_state, curr_state, ebtobj->btns_combo[i].comb_key, mstime); 325 | } 326 | 327 | for (target_combo = ebtobj->btn_combo_dyn_head; target_combo; target_combo = target_combo->next) 328 | { 329 | ebtn_process_btn_combo(&target_combo->btn.btn, ebtobj->old_state, curr_state, target_combo->btn.comb_key, mstime); 330 | } 331 | 332 | bit_array_copy_all(ebtobj->old_state, curr_state, EBTN_MAX_KEYNUM); 333 | } 334 | 335 | void ebtn_process(ebtn_time_t mstime) 336 | { 337 | BIT_ARRAY_DEFINE(curr_state, EBTN_MAX_KEYNUM) = {0}; 338 | 339 | // Get Current State 340 | ebtn_get_current_state(curr_state); 341 | 342 | ebtn_process_with_curr_state(curr_state, mstime); 343 | } 344 | 345 | int ebtn_get_total_btn_cnt(void) 346 | { 347 | ebtn_t *ebtobj = &ebtn_default; 348 | int total_cnt = 0; 349 | ebtn_btn_dyn_t *curr = ebtobj->btn_dyn_head; 350 | 351 | total_cnt += ebtobj->btns_cnt; 352 | 353 | while (curr) 354 | { 355 | total_cnt++; 356 | curr = curr->next; 357 | } 358 | return total_cnt; 359 | } 360 | 361 | int ebtn_get_btn_index_by_key_id(uint16_t key_id) 362 | { 363 | ebtn_t *ebtobj = &ebtn_default; 364 | int i = 0; 365 | ebtn_btn_dyn_t *target; 366 | 367 | for (i = 0; i < ebtobj->btns_cnt; ++i) 368 | { 369 | if (ebtobj->btns[i].key_id == key_id) 370 | { 371 | return i; 372 | } 373 | } 374 | 375 | for (target = ebtobj->btn_dyn_head, i = ebtobj->btns_cnt; target; target = target->next, i++) 376 | { 377 | if (target->btn.key_id == key_id) 378 | { 379 | return i; 380 | } 381 | } 382 | 383 | return -1; 384 | } 385 | 386 | ebtn_btn_t *ebtn_get_btn_by_key_id(uint16_t key_id) 387 | { 388 | ebtn_t *ebtobj = &ebtn_default; 389 | int i = 0; 390 | ebtn_btn_dyn_t *target; 391 | 392 | for (i = 0; i < ebtobj->btns_cnt; ++i) 393 | { 394 | if (ebtobj->btns[i].key_id == key_id) 395 | { 396 | return &ebtobj->btns[i]; 397 | } 398 | } 399 | 400 | for (target = ebtobj->btn_dyn_head, i = ebtobj->btns_cnt; target; target = target->next, i++) 401 | { 402 | if (target->btn.key_id == key_id) 403 | { 404 | return &target->btn; 405 | } 406 | } 407 | 408 | return NULL; 409 | } 410 | 411 | int ebtn_get_btn_index_by_btn(ebtn_btn_t *btn) 412 | { 413 | return ebtn_get_btn_index_by_key_id(btn->key_id); 414 | } 415 | 416 | int ebtn_get_btn_index_by_btn_dyn(ebtn_btn_dyn_t *btn) 417 | { 418 | return ebtn_get_btn_index_by_key_id(btn->btn.key_id); 419 | } 420 | 421 | void ebtn_combo_btn_add_btn_by_idx(ebtn_btn_combo_t *btn, int idx) 422 | { 423 | bit_array_set(btn->comb_key, idx); 424 | } 425 | 426 | void ebtn_combo_btn_remove_btn_by_idx(ebtn_btn_combo_t *btn, int idx) 427 | { 428 | bit_array_clear(btn->comb_key, idx); 429 | } 430 | 431 | void ebtn_combo_btn_add_btn(ebtn_btn_combo_t *btn, uint16_t key_id) 432 | { 433 | int idx = ebtn_get_btn_index_by_key_id(key_id); 434 | if (idx < 0) 435 | { 436 | return; 437 | } 438 | ebtn_combo_btn_add_btn_by_idx(btn, idx); 439 | } 440 | 441 | void ebtn_combo_btn_remove_btn(ebtn_btn_combo_t *btn, uint16_t key_id) 442 | { 443 | int idx = ebtn_get_btn_index_by_key_id(key_id); 444 | if (idx < 0) 445 | { 446 | return; 447 | } 448 | ebtn_combo_btn_remove_btn_by_idx(btn, idx); 449 | } 450 | 451 | int ebtn_is_btn_active(const ebtn_btn_t *btn) 452 | { 453 | return btn != NULL && (btn->flags & EBTN_FLAG_ONPRESS_SENT); 454 | } 455 | 456 | int ebtn_is_btn_in_process(const ebtn_btn_t *btn) 457 | { 458 | return btn != NULL && (btn->flags & EBTN_FLAG_IN_PROCESS); 459 | } 460 | 461 | int ebtn_is_in_process(void) 462 | { 463 | ebtn_t *ebtobj = &ebtn_default; 464 | ebtn_btn_dyn_t *target; 465 | ebtn_btn_combo_dyn_t *target_combo; 466 | int i; 467 | 468 | /* Process all buttons */ 469 | for (i = 0; i < ebtobj->btns_cnt; ++i) 470 | { 471 | if (ebtn_is_btn_in_process(&ebtobj->btns[i])) 472 | { 473 | return 1; 474 | } 475 | } 476 | 477 | for (target = ebtobj->btn_dyn_head, i = ebtobj->btns_cnt; target; target = target->next, i++) 478 | { 479 | if (ebtn_is_btn_in_process(&target->btn)) 480 | { 481 | return 1; 482 | } 483 | } 484 | 485 | /* Process all comb buttons */ 486 | for (i = 0; i < ebtobj->btns_combo_cnt; ++i) 487 | { 488 | if (ebtn_is_btn_in_process(&ebtobj->btns_combo[i].btn)) 489 | { 490 | return 1; 491 | } 492 | } 493 | 494 | for (target_combo = ebtobj->btn_combo_dyn_head; target_combo; target_combo = target_combo->next) 495 | { 496 | if (ebtn_is_btn_in_process(&target_combo->btn.btn)) 497 | { 498 | return 1; 499 | } 500 | } 501 | 502 | return 0; 503 | } 504 | 505 | int ebtn_register(ebtn_btn_dyn_t *button) 506 | { 507 | ebtn_t *ebtobj = &ebtn_default; 508 | 509 | ebtn_btn_dyn_t *curr = ebtobj->btn_dyn_head; 510 | ebtn_btn_dyn_t *last = NULL; 511 | 512 | if (!button) 513 | { 514 | return 0; 515 | } 516 | 517 | if (ebtn_get_total_btn_cnt() >= EBTN_MAX_KEYNUM) 518 | { 519 | return 0; /* reach max cnt. */ 520 | } 521 | 522 | if (curr == NULL) 523 | { 524 | ebtobj->btn_dyn_head = button; 525 | return 1; 526 | } 527 | 528 | while (curr) 529 | { 530 | if (curr == button) 531 | { 532 | return 0; /* already exist. */ 533 | } 534 | last = curr; 535 | curr = curr->next; 536 | } 537 | 538 | last->next = button; 539 | 540 | return 1; 541 | } 542 | 543 | int ebtn_combo_register(ebtn_btn_combo_dyn_t *button) 544 | { 545 | ebtn_t *ebtobj = &ebtn_default; 546 | 547 | ebtn_btn_combo_dyn_t *curr = ebtobj->btn_combo_dyn_head; 548 | ebtn_btn_combo_dyn_t *last = NULL; 549 | 550 | if (!button) 551 | { 552 | return 0; 553 | } 554 | 555 | if (curr == NULL) 556 | { 557 | ebtobj->btn_combo_dyn_head = button; 558 | return 1; 559 | } 560 | 561 | while (curr) 562 | { 563 | if (curr == button) 564 | { 565 | return 0; /* already exist. */ 566 | } 567 | last = curr; 568 | curr = curr->next; 569 | } 570 | 571 | last->next = button; 572 | 573 | return 1; 574 | } 575 | -------------------------------------------------------------------------------- /ebtn/ebtn.h: -------------------------------------------------------------------------------- 1 | #ifndef _EBTN_H 2 | #define _EBTN_H 3 | 4 | #include 5 | #include 6 | 7 | #include "bit_array.h" 8 | 9 | #ifdef __cplusplus 10 | extern "C" { 11 | #endif /* __cplusplus */ 12 | 13 | // #define EBTN_CONFIG_TIMER_16 14 | 15 | // here can change to uint16_t, if you want reduce RAM size. 16 | #ifdef EBTN_CONFIG_TIMER_16 17 | typedef uint16_t ebtn_time_t; 18 | typedef int16_t ebtn_time_sign_t; 19 | #else 20 | typedef uint32_t ebtn_time_t; 21 | typedef int32_t ebtn_time_sign_t; 22 | #endif 23 | 24 | /* Forward declarations */ 25 | struct ebtn_btn; 26 | struct ebtn; 27 | 28 | #define EBTN_MAX_KEYNUM (64) 29 | 30 | /** 31 | * \brief List of button events 32 | * 33 | */ 34 | typedef enum 35 | { 36 | EBTN_EVT_ONPRESS = 0x00, /*!< On press event - sent when valid press is detected */ 37 | EBTN_EVT_ONRELEASE, /*!< On release event - sent when valid release event is detected (from 38 | active to inactive) */ 39 | EBTN_EVT_ONCLICK, /*!< On Click event - sent when valid sequence of on-press and on-release 40 | events occurs */ 41 | EBTN_EVT_KEEPALIVE, /*!< Keep alive event - sent periodically when button is active */ 42 | } ebtn_evt_t; 43 | 44 | #define EBTN_EVT_MASK_ONPRESS (1 << EBTN_EVT_ONPRESS) 45 | #define EBTN_EVT_MASK_ONRELEASE (1 << EBTN_EVT_ONRELEASE) 46 | #define EBTN_EVT_MASK_ONCLICK (1 << EBTN_EVT_ONCLICK) 47 | #define EBTN_EVT_MASK_KEEPALIVE (1 << EBTN_EVT_KEEPALIVE) 48 | 49 | #define EBTN_EVT_MASK_ALL (EBTN_EVT_MASK_ONPRESS | EBTN_EVT_MASK_ONRELEASE | EBTN_EVT_MASK_ONCLICK | EBTN_EVT_MASK_KEEPALIVE) 50 | 51 | /** 52 | * @brief Returns the difference between two absolute times: time1-time2. 53 | * @param[in] time1: Absolute time expressed in internal time units. 54 | * @param[in] time2: Absolute time expressed in internal time units. 55 | * @return resulting signed relative time expressed in internal time units. 56 | */ 57 | static inline ebtn_time_sign_t ebtn_timer_sub(ebtn_time_t time1, ebtn_time_t time2) 58 | { 59 | return time1 - time2; 60 | } 61 | 62 | // test time overflow error 63 | // #define ebtn_timer_sub(time1, time2) (time1 - time2) 64 | 65 | /** 66 | * \brief Button event function callback prototype 67 | * \param[in] btn: Button instance from array for which event occured 68 | * \param[in] evt: Event type 69 | */ 70 | typedef void (*ebtn_evt_fn)(struct ebtn_btn *btn, ebtn_evt_t evt); 71 | 72 | /** 73 | * \brief Get button/input state callback function 74 | * 75 | * \param[in] btn: Button instance from array to read state 76 | * \return `1` when button is considered `active`, `0` otherwise 77 | */ 78 | typedef uint8_t (*ebtn_get_state_fn)(struct ebtn_btn *btn); 79 | 80 | /** 81 | * \brief Button Params structure 82 | */ 83 | typedef struct ebtn_btn_param 84 | { 85 | /** 86 | * \brief Minimum debounce time for press event in units of milliseconds 87 | * 88 | * This is the time when the input shall have stable active level to detect valid *onpress* 89 | * event. 90 | * 91 | * When value is set to `> 0`, input must be in active state for at least 92 | * minimum milliseconds time, before valid *onpress* event is detected. 93 | * 94 | * \note If value is set to `0`, debounce is not used and *press* event will be 95 | * triggered immediately when input states goes to *inactive* state. 96 | * 97 | * To be safe not using this feature, external logic must ensure stable 98 | * transition at input level. 99 | * 100 | */ 101 | uint16_t time_debounce; /*!< Debounce time in milliseconds */ 102 | 103 | /** 104 | * \brief Minimum debounce time for release event in units of milliseconds 105 | * 106 | * This is the time when the input shall have minimum stable released level to detect valid 107 | * *onrelease* event. 108 | * 109 | * This setting can be useful if application wants to protect against 110 | * unwanted glitches on the line when input is considered "active". 111 | * 112 | * When value is set to `> 0`, input must be in inactive low for at least 113 | * minimum milliseconds time, before valid *onrelease* event is detected 114 | * 115 | * \note If value is set to `0`, debounce is not used and *release* event will be 116 | * triggered immediately when input states goes to *inactive* state 117 | * 118 | */ 119 | uint16_t time_debounce_release; /*!< Debounce time in milliseconds for release event */ 120 | 121 | /** 122 | * \brief Minimum active input time for valid click event, in milliseconds 123 | * 124 | * Input shall be in active state (after debounce) at least this amount of time to even consider 125 | * the potential valid click event. Set the value to `0` to disable this feature 126 | * 127 | */ 128 | uint16_t time_click_pressed_min; /*!< Minimum pressed time for valid click event */ 129 | 130 | /** 131 | * \brief Maximum active input time for valid click event, in milliseconds 132 | * 133 | * Input shall be pressed at most this amount of time to still trigger valid click. 134 | * Set to `-1` to allow any time triggering click event. 135 | * 136 | * When input is active for more than the configured time, click even is not detected and is 137 | * ignored. 138 | * 139 | */ 140 | uint16_t time_click_pressed_max; /*!< Maximum pressed time for valid click event*/ 141 | 142 | /** 143 | * \brief Maximum allowed time between last on-release and next valid on-press, 144 | * to still allow multi-click events, in milliseconds 145 | * 146 | * This value is also used as a timeout length to send the *onclick* event to application from 147 | * previously detected valid click events. 148 | * 149 | * If application relies on multi consecutive clicks, this is the max time to allow user 150 | * to trigger potential new click, or structure will get reset (before sent to user if any 151 | * clicks have been detected so far) 152 | * 153 | */ 154 | uint16_t time_click_multi_max; /*!< Maximum time between 2 clicks to be considered consecutive 155 | click */ 156 | 157 | /** 158 | * \brief Keep-alive event period, in milliseconds 159 | * 160 | * When input is active, keep alive events will be sent through this period of time. 161 | * First keep alive will be sent after input being considered 162 | * active. 163 | * 164 | */ 165 | uint16_t time_keepalive_period; /*!< Time in ms for periodic keep alive event */ 166 | 167 | /** 168 | * \brief Maximum number of allowed consecutive click events, 169 | * before structure gets reset to default value. 170 | * 171 | * \note When consecutive value is reached, application will get notification of 172 | * clicks. This can be executed immediately after last click has been detected, or after 173 | * standard timeout (unless next on-press has already been detected, then it is send to 174 | * application just before valid next press event). 175 | * 176 | */ 177 | uint16_t max_consecutive; /*!< Max number of consecutive clicks */ 178 | } ebtn_btn_param_t; 179 | 180 | #define EBTN_PARAMS_INIT(_time_debounce, _time_debounce_release, _time_click_pressed_min, _time_click_pressed_max, _time_click_multi_max, \ 181 | _time_keepalive_period, _max_consecutive) \ 182 | { \ 183 | .time_debounce = _time_debounce, .time_debounce_release = _time_debounce_release, .time_click_pressed_min = _time_click_pressed_min, \ 184 | .time_click_pressed_max = _time_click_pressed_max, .time_click_multi_max = _time_click_multi_max, .time_keepalive_period = _time_keepalive_period, \ 185 | .max_consecutive = _max_consecutive \ 186 | } 187 | 188 | #define EBTN_BUTTON_INIT_RAW(_key_id, _param, _mask) \ 189 | { \ 190 | .key_id = _key_id, .param = _param, .event_mask = _mask, \ 191 | } 192 | 193 | #define EBTN_BUTTON_INIT(_key_id, _param) EBTN_BUTTON_INIT_RAW(_key_id, _param, EBTN_EVT_MASK_ALL) 194 | 195 | #define EBTN_BUTTON_DYN_INIT(_key_id, _param) \ 196 | { \ 197 | .next = NULL, .btn = EBTN_BUTTON_INIT(_key_id, _param), \ 198 | } 199 | 200 | #define EBTN_BUTTON_COMBO_INIT_RAW(_key_id, _param, _mask) \ 201 | { \ 202 | .comb_key = {0}, .btn = EBTN_BUTTON_INIT_RAW(_key_id, _param, _mask), \ 203 | } 204 | 205 | #define EBTN_BUTTON_COMBO_INIT(_key_id, _param) \ 206 | { \ 207 | .comb_key = {0}, .btn = EBTN_BUTTON_INIT(_key_id, _param), \ 208 | } 209 | 210 | #define EBTN_BUTTON_COMBO_DYN_INIT(_key_id, _param) \ 211 | { \ 212 | .next = NULL, .btn = EBTN_BUTTON_COMBO_INIT(_key_id, _param), \ 213 | } 214 | 215 | #define EBTN_ARRAY_SIZE(_arr) sizeof(_arr) / sizeof((_arr)[0]) 216 | 217 | /** 218 | * \brief Button structure 219 | */ 220 | typedef struct ebtn_btn 221 | { 222 | uint16_t key_id; /*!< User defined custom argument for callback function purpose */ 223 | uint8_t flags; /*!< Private button flags management */ 224 | uint8_t event_mask; /*!< Private button event mask management */ 225 | 226 | ebtn_time_t time_change; /*!< Time in ms when button state got changed last time after valid 227 | debounce */ 228 | ebtn_time_t time_state_change; /*!< Time in ms when button state got changed last time */ 229 | 230 | ebtn_time_t keepalive_last_time; /*!< Time in ms of last send keep alive event */ 231 | ebtn_time_t click_last_time; /*!< Time in ms of last successfully detected (not sent!) click event 232 | */ 233 | 234 | uint16_t keepalive_cnt; /*!< Number of keep alive events sent after successful on-press 235 | detection. Value is reset after on-release */ 236 | uint16_t click_cnt; /*!< Number of consecutive clicks detected, respecting maximum timeout 237 | between clicks */ 238 | 239 | const ebtn_btn_param_t *param; 240 | } ebtn_btn_t; 241 | 242 | /** 243 | * \brief ComboButton structure 244 | */ 245 | typedef struct ebtn_btn_combo 246 | { 247 | BIT_ARRAY_DEFINE(comb_key, EBTN_MAX_KEYNUM); /*!< select key index - `1` means active, `0` means inactive */ 248 | 249 | ebtn_btn_t btn; 250 | } ebtn_btn_combo_t; 251 | 252 | /** 253 | * \brief Dynamic Button structure 254 | */ 255 | typedef struct ebtn_btn_dyn 256 | { 257 | struct ebtn_btn_dyn *next; /*!< point to next button */ 258 | 259 | ebtn_btn_t btn; 260 | } ebtn_btn_dyn_t; 261 | 262 | /** 263 | * \brief Dynamic ComboButton structure 264 | */ 265 | typedef struct ebtn_btn_combo_dyn 266 | { 267 | struct ebtn_btn_combo_dyn *next; /*!< point to next combo-button */ 268 | 269 | ebtn_btn_combo_t btn; 270 | } ebtn_btn_combo_dyn_t; 271 | 272 | /** 273 | * \brief easy_button group structure 274 | */ 275 | typedef struct ebtn 276 | { 277 | ebtn_btn_t *btns; /*!< Pointer to buttons array */ 278 | uint16_t btns_cnt; /*!< Number of buttons in array */ 279 | ebtn_btn_combo_t *btns_combo; /*!< Pointer to comb-buttons array */ 280 | uint16_t btns_combo_cnt; /*!< Number of comb-buttons in array */ 281 | 282 | ebtn_btn_dyn_t *btn_dyn_head; /*!< Pointer to btn-dynamic list */ 283 | ebtn_btn_combo_dyn_t *btn_combo_dyn_head; /*!< Pointer to btn-combo-dynamic list */ 284 | 285 | ebtn_evt_fn evt_fn; /*!< Pointer to event function */ 286 | ebtn_get_state_fn get_state_fn; /*!< Pointer to get state function */ 287 | 288 | BIT_ARRAY_DEFINE(old_state, EBTN_MAX_KEYNUM); /*!< Old button state - `1` means active, `0` means inactive */ 289 | } ebtn_t; 290 | 291 | /** 292 | * \brief Button processing function, that reads the inputs and makes actions accordingly. 293 | * 294 | * 295 | * \param[in] mstime: Current system time in milliseconds 296 | */ 297 | void ebtn_process(ebtn_time_t mstime); 298 | 299 | /** 300 | * \brief Button processing function, with all button input state. 301 | * 302 | * \param[in] curr_state: Current all button input state 303 | * \param[in] mstime: Current system time in milliseconds 304 | */ 305 | void ebtn_process_with_curr_state(bit_array_t *curr_state, ebtn_time_t mstime); 306 | 307 | /** 308 | * \brief Check if button is active. 309 | * Active is considered when initial debounce period has been a pass. 310 | * This is the period between on-press and on-release events. 311 | * 312 | * \param[in] btn: Button handle to check 313 | * \return `1` if active, `0` otherwise 314 | */ 315 | int ebtn_is_btn_active(const ebtn_btn_t *btn); 316 | 317 | /** 318 | * \brief Check if button is in process. 319 | * Used for low-power processing, indicating that the buttons are temporarily idle, and embedded systems can consider entering deep sleep. 320 | * 321 | * \param[in] btn: Button handle to check 322 | * \return `1` if in process, `0` otherwise 323 | */ 324 | int ebtn_is_btn_in_process(const ebtn_btn_t *btn); 325 | 326 | /** 327 | * \brief Check if some button is in process. 328 | * Used for low-power processing, indicating that the buttons are temporarily idle, and embedded systems can consider entering deep sleep. 329 | * 330 | * \return `1` if in process, `0` otherwise 331 | */ 332 | int ebtn_is_in_process(void); 333 | 334 | /** 335 | * \brief Initialize button manager 336 | * \param[in] btns: Array of buttons to process 337 | * \param[in] btns_cnt: Number of buttons to process 338 | * \param[in] btns_combo: Array of combo-buttons to process 339 | * \param[in] btns_combo_cnt: Number of combo-buttons to process 340 | * \param[in] get_state_fn: Pointer to function providing button state on demand. 341 | * \param[in] evt_fn: Button event function callback 342 | * 343 | * \return `1` on success, `0` otherwise 344 | */ 345 | int ebtn_init(ebtn_btn_t *btns, uint16_t btns_cnt, ebtn_btn_combo_t *btns_combo, uint16_t btns_combo_cnt, ebtn_get_state_fn get_state_fn, ebtn_evt_fn evt_fn); 346 | 347 | /** 348 | * @brief Register a dynamic button 349 | * 350 | * @param button: Dynamic button structure instance 351 | * \return `1` on success, `0` otherwise 352 | */ 353 | int ebtn_register(ebtn_btn_dyn_t *button); 354 | 355 | /** 356 | * \brief Register a dynamic combo-button 357 | * \param[in] button: Dynamic combo-button structure instance 358 | * 359 | * \return `1` on success, `0` otherwise 360 | */ 361 | int ebtn_combo_register(ebtn_btn_combo_dyn_t *button); 362 | 363 | /** 364 | * \brief Get the current total button cnt 365 | * 366 | * \return size of button. 367 | */ 368 | int ebtn_get_total_btn_cnt(void); 369 | 370 | /** 371 | * \brief Get the internal key_idx of the key_id 372 | * \param[in] key_id: key_id 373 | * 374 | * \return '-1' on error, other is key_idx 375 | */ 376 | int ebtn_get_btn_index_by_key_id(uint16_t key_id); 377 | 378 | /** 379 | * \brief Get the internal btn instance of the key_id, here is the button instance, and what is dynamically registered is also to obtain its button 380 | * instance 381 | * 382 | * \param[in] key_id: key_id 383 | * 384 | * \return 'NULL' on error, other is button instance 385 | */ 386 | ebtn_btn_t *ebtn_get_btn_by_key_id(uint16_t key_id); 387 | 388 | /** 389 | * \brief Get the internal key_idx of the button 390 | * \param[in] btn: Button 391 | * 392 | * \return '-1' on error, other is key_idx 393 | */ 394 | int ebtn_get_btn_index_by_btn(ebtn_btn_t *btn); 395 | 396 | /** 397 | * \brief Get the internal key_idx of the dynamic button 398 | * \param[in] btn: Button 399 | * 400 | * \return '-1' on error, other is key_idx 401 | */ 402 | int ebtn_get_btn_index_by_btn_dyn(ebtn_btn_dyn_t *btn); 403 | 404 | /** 405 | * \brief Bind combo-button key with key_idx 406 | * \param[in] btn: Combo Button 407 | * \param[in] idx: key_idx 408 | * 409 | */ 410 | void ebtn_combo_btn_add_btn_by_idx(ebtn_btn_combo_t *btn, int idx); 411 | 412 | /** 413 | * \brief Remove combo-button key with key_idx 414 | * \param[in] btn: Combo Button 415 | * \param[in] idx: key_idx 416 | * 417 | */ 418 | void ebtn_combo_btn_remove_btn_by_idx(ebtn_btn_combo_t *btn, int idx); 419 | 420 | /** 421 | * \brief Bind combo-button key with key_id, make sure key_id(button) is already register. 422 | * \param[in] btn: Combo Button 423 | * \param[in] key_id: key_id 424 | * 425 | */ 426 | void ebtn_combo_btn_add_btn(ebtn_btn_combo_t *btn, uint16_t key_id); 427 | 428 | /** 429 | * \brief Remove combo-button key with key_id, make sure key_id(button) is already 430 | * register. \param[in] btn: Combo Button \param[in] key_id: key_id 431 | * 432 | */ 433 | void ebtn_combo_btn_remove_btn(ebtn_btn_combo_t *btn, uint16_t key_id); 434 | 435 | /** 436 | * \brief Get keep alive period for specific button 437 | * \param[in] btn: Button instance to get keep alive period for 438 | * \return Keep alive period in `ms` 439 | */ 440 | #define ebtn_keepalive_get_period(btn) ((btn)->time_keepalive_period) 441 | 442 | /** 443 | * \brief Get actual number of keep alive counts since the last on-press event. 444 | * It is set to `0` if btn isn't pressed 445 | * \param[in] btn: Button instance to get keep alive period for 446 | * \return Number of keep alive events since on-press event 447 | */ 448 | #define ebtn_keepalive_get_count(btn) ((btn)->keepalive_cnt) 449 | 450 | /** 451 | * \brief Get number of keep alive counts for specific required time in milliseconds. 452 | * It will calculate number of keepalive ticks specific button shall make, 453 | * before requested time is reached. 454 | * 455 | * Result of the function can be used with \ref ebtn_keepalive_get_count which returns 456 | * actual number of keep alive counts since last on-press event of the button. 457 | * 458 | * \note Value is always integer aligned, with granularity of one keepalive time period 459 | * \note Implemented as macro, as it may be optimized by compiler when static keep alive 460 | * is used 461 | * 462 | * \param[in] btn: Button to use for check 463 | * \param[in] ms_time: Time in ms to calculate number of keep alive counts 464 | * \return Number of keep alive counts 465 | */ 466 | #define ebtn_keepalive_get_count_for_time(btn, ms_time) ((ms_time) / ebtn_keepalive_get_period(btn)) 467 | 468 | /** 469 | * \brief Get number of consecutive click events on a button 470 | * \param[in] btn: Button instance to get number of clicks 471 | * \return Number of consecutive clicks on a button 472 | */ 473 | #define ebtn_click_get_count(btn) ((btn)->click_cnt) 474 | 475 | #ifdef __cplusplus 476 | } 477 | #endif /* __cplusplus */ 478 | 479 | #endif /* _EBTN_H */ 480 | -------------------------------------------------------------------------------- /example_test.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "windows.h" 5 | 6 | #include "ebtn.h" 7 | 8 | // 9 | // Tests 10 | // 11 | const char *suite_name; 12 | char suite_pass; 13 | int suites_run = 0, suites_failed = 0, suites_empty = 0; 14 | int tests_in_suite = 0, tests_run = 0, tests_failed = 0; 15 | 16 | #define QUOTE(str) #str 17 | #define ASSERT(x) \ 18 | { \ 19 | tests_run++; \ 20 | tests_in_suite++; \ 21 | if (!(x)) \ 22 | { \ 23 | printf("failed assert [%s:%i] %s\n", __FILE__, __LINE__, QUOTE(x)); \ 24 | suite_pass = 0; \ 25 | tests_failed++; \ 26 | } \ 27 | } 28 | 29 | void SUITE_START(const char *name) 30 | { 31 | suite_pass = 1; 32 | suite_name = name; 33 | suites_run++; 34 | tests_in_suite = 0; 35 | } 36 | 37 | void SUITE_END(void) 38 | { 39 | printf("Testing %s ", suite_name); 40 | size_t suite_i; 41 | for (suite_i = strlen(suite_name); suite_i < 80 - 8 - 5; suite_i++) 42 | printf("."); 43 | printf("%s\n", suite_pass ? " pass" : " fail"); 44 | if (!suite_pass) 45 | suites_failed++; 46 | if (!tests_in_suite) 47 | suites_empty++; 48 | } 49 | 50 | typedef enum 51 | { 52 | USER_BUTTON_default = 0, 53 | USER_BUTTON_onrelease_debounce, 54 | USER_BUTTON_keepalive_with_click, 55 | USER_BUTTON_max_click_3, 56 | USER_BUTTON_click_multi_max_0, 57 | USER_BUTTON_keep_alive_0, 58 | USER_BUTTON_MAX, 59 | 60 | USER_BUTTON_COMBO_0 = 0x100, 61 | USER_BUTTON_COMBO_1, 62 | USER_BUTTON_COMBO_2, 63 | USER_BUTTON_COMBO_3, 64 | USER_BUTTON_COMBO_MAX, 65 | } user_button_t; 66 | 67 | /** 68 | * \brief Input state information 69 | */ 70 | typedef struct 71 | { 72 | uint16_t key_id; /*!< Select Key id */ 73 | uint8_t state; /*!< Input state -> 1 = active, 0 = inactive */ 74 | uint32_t duration; /*!< Time until this state is enabled */ 75 | } btn_test_time_t; 76 | 77 | /** 78 | * \brief Event sequence 79 | */ 80 | typedef struct 81 | { 82 | ebtn_evt_t evt; /*!< Event type */ 83 | uint8_t keepalive_cnt; /*!< Number of keep alive events while button is active */ 84 | uint8_t conseq_clicks; /*!< Number of consecutive clicks detected */ 85 | } btn_test_evt_t; 86 | 87 | typedef struct 88 | { 89 | const char *test_name; 90 | uint16_t test_key_id; 91 | int test_sequence_cnt; 92 | btn_test_time_t *test_sequence; /*!< Input state -> 1 = active, 0 = inactive */ 93 | int test_events_cnt; 94 | const btn_test_evt_t *test_events; /*!< Time until this state is enabled */ 95 | } btn_test_arr_t; 96 | 97 | #define TEST_ARRAY_DEFINE(_key_id, _seq, _evt) \ 98 | { \ 99 | .test_key_id = _key_id, .test_name = #_seq, .test_sequence_cnt = EBTN_ARRAY_SIZE(_seq), .test_sequence = _seq, \ 100 | .test_events_cnt = EBTN_ARRAY_SIZE(_evt), .test_events = _evt \ 101 | } 102 | 103 | /* Max number of ms to demonstrate */ 104 | #define MAX_TIME_MS 0x3FFFF 105 | 106 | #define EBTN_PARAM_TIME_DEBOUNCE_PRESS(_param) _param.time_debounce 107 | #define EBTN_PARAM_TIME_DEBOUNCE_RELEASE(_param) _param.time_debounce_release 108 | #define EBTN_PARAM_TIME_CLICK_MIN(_param) _param.time_click_pressed_min 109 | #define EBTN_PARAM_TIME_CLICK_MAX(_param) _param.time_click_pressed_max 110 | #define EBTN_PARAM_TIME_CLICK_MULTI_MAX(_param) _param.time_click_multi_max 111 | #define EBTN_PARAM_TIME_KEEPALIVE_PERIOD(_param) _param.time_keepalive_period 112 | #define EBTN_PARAM_CLICK_MAX_CONSECUTIVE(_param) _param.max_consecutive 113 | 114 | static const ebtn_btn_param_t param_default = EBTN_PARAMS_INIT(20, 0, 20, 300, 200, 500, 10); 115 | 116 | static const ebtn_btn_param_t param_onrelease_debounce = EBTN_PARAMS_INIT(20, 80, 0, 300, 200, 500, 10); 117 | 118 | static const ebtn_btn_param_t param_keepalive_with_click = EBTN_PARAMS_INIT(20, 80, 0, 400, 200, 100, 10); 119 | 120 | static const ebtn_btn_param_t param_max_click_3 = EBTN_PARAMS_INIT(20, 80, 0, 400, 200, 100, 3); 121 | 122 | static const ebtn_btn_param_t param_click_multi_max_0 = EBTN_PARAMS_INIT(20, 80, 0, 400, 0, 100, 3); 123 | 124 | static const ebtn_btn_param_t param_keep_alive_0 = EBTN_PARAMS_INIT(20, 80, 0, 400, 200, 0, 3); 125 | 126 | /* List of used buttons -> test case */ 127 | static ebtn_btn_t btns[] = {EBTN_BUTTON_INIT(USER_BUTTON_default, ¶m_default), 128 | EBTN_BUTTON_INIT(USER_BUTTON_onrelease_debounce, ¶m_onrelease_debounce), 129 | EBTN_BUTTON_INIT(USER_BUTTON_keepalive_with_click, ¶m_keepalive_with_click), 130 | EBTN_BUTTON_INIT(USER_BUTTON_max_click_3, ¶m_max_click_3), 131 | EBTN_BUTTON_INIT(USER_BUTTON_click_multi_max_0, ¶m_click_multi_max_0), 132 | EBTN_BUTTON_INIT(USER_BUTTON_keep_alive_0, ¶m_keep_alive_0)}; 133 | 134 | static volatile uint32_t test_processed_time_current; 135 | 136 | /* Set button state -> used for test purposes */ 137 | #define BTN_STATE_RAW(_key_id_, _state_, _duration_) \ 138 | { \ 139 | .key_id = (_key_id_), .state = (_state_), .duration = (_duration_) \ 140 | } 141 | 142 | #define BTN_STATE(_state_, _duration_) BTN_STATE_RAW(USER_BUTTON_default, _state_, _duration_) 143 | 144 | /* On-Press event */ 145 | #define BTN_EVENT_ONPRESS() \ 146 | { \ 147 | .evt = EBTN_EVT_ONPRESS \ 148 | } 149 | /* On-Release event */ 150 | #define BTN_EVENT_ONRELEASE() \ 151 | { \ 152 | .evt = EBTN_EVT_ONRELEASE \ 153 | } 154 | /* On-Click event */ 155 | #define BTN_EVENT_ONCLICK(_conseq_clicks_) \ 156 | { \ 157 | .evt = EBTN_EVT_ONCLICK, .conseq_clicks = (_conseq_clicks_) \ 158 | } 159 | /* On-Click event */ 160 | #define BTN_EVENT_KEEPALIVE(_keepalive_cnt_) \ 161 | { \ 162 | .evt = EBTN_EVT_KEEPALIVE, .keepalive_cnt = (_keepalive_cnt_) \ 163 | } 164 | 165 | /* 166 | * Simulate click event 167 | */ 168 | static btn_test_time_t test_sequence_single_click[] = { 169 | /* 170 | * Step 1: 171 | * Go to active state and stay there for a period 172 | * of minimum debounce time and minimum 173 | * time input must be active to later detect click event 174 | * 175 | * Step 2: 176 | * Go low and stay inactive until time to report click has elapsed. 177 | * Include debounce timing for release event. 178 | * 179 | * Add +1 to the end, to force click event, 180 | * and not to go to "consecutive clicks" if any further tests are added in this sequence 181 | */ 182 | BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_default) + EBTN_PARAM_TIME_CLICK_MIN(param_default)), 183 | BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_default) + EBTN_PARAM_TIME_CLICK_MULTI_MAX(param_default)), 184 | }; 185 | 186 | static const btn_test_evt_t test_events_single_click[] = { 187 | BTN_EVENT_ONPRESS(), 188 | BTN_EVENT_ONRELEASE(), 189 | BTN_EVENT_ONCLICK(1), 190 | }; 191 | 192 | static btn_test_time_t test_sequence_double_click[] = { 193 | /* 194 | * Repeat above steps, this time w/o +1 at the end. 195 | * 196 | * Simulate "2" consecutive clicks and report final "click" event at the end of the 197 | * sequence, with "2" consecutive clicks in the report info 198 | */ 199 | BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_default) + EBTN_PARAM_TIME_CLICK_MIN(param_default)), 200 | BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_default) + EBTN_PARAM_TIME_CLICK_MULTI_MAX(param_default) / 2), 201 | BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_default) + EBTN_PARAM_TIME_CLICK_MIN(param_default)), 202 | BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_default) + EBTN_PARAM_TIME_CLICK_MULTI_MAX(param_default)), 203 | }; 204 | 205 | static const btn_test_evt_t test_events_double_click[] = { 206 | BTN_EVENT_ONPRESS(), BTN_EVENT_ONRELEASE(), BTN_EVENT_ONPRESS(), BTN_EVENT_ONRELEASE(), BTN_EVENT_ONCLICK(2), 207 | }; 208 | 209 | static btn_test_time_t test_sequence_triple_click[] = { 210 | BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_default) + EBTN_PARAM_TIME_CLICK_MIN(param_default)), 211 | BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_default) + EBTN_PARAM_TIME_CLICK_MULTI_MAX(param_default) / 2), 212 | BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_default) + EBTN_PARAM_TIME_CLICK_MIN(param_default)), 213 | BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_default) + EBTN_PARAM_TIME_CLICK_MULTI_MAX(param_default) / 2), 214 | BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_default) + EBTN_PARAM_TIME_CLICK_MIN(param_default)), 215 | BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_default) + EBTN_PARAM_TIME_CLICK_MULTI_MAX(param_default)), 216 | }; 217 | 218 | static const btn_test_evt_t test_events_triple_click[] = { 219 | BTN_EVENT_ONPRESS(), BTN_EVENT_ONRELEASE(), BTN_EVENT_ONPRESS(), BTN_EVENT_ONRELEASE(), 220 | BTN_EVENT_ONPRESS(), BTN_EVENT_ONRELEASE(), BTN_EVENT_ONCLICK(3), 221 | }; 222 | 223 | static btn_test_time_t test_sequence_double_click_critical_time[] = { 224 | /* 225 | * Repeat above steps, this time w/o +1 at the end. 226 | * 227 | * Simulate "2" consecutive clicks and report final "click" event at the end of the 228 | * sequence, with "2" consecutive clicks in the report info 229 | */ 230 | BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_default) + EBTN_PARAM_TIME_CLICK_MIN(param_default)), 231 | /* Hold button in release state for time that is max for 2 clicks - time that we will 232 | indicate in the next press state -> this is the frequency between detected events */ 233 | BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_default) + 234 | EBTN_PARAM_TIME_CLICK_MULTI_MAX(param_default) 235 | /* Decrease by active time in next step */ 236 | - (EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_default)) - 2), 237 | /* Active time */ 238 | BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_default) + EBTN_PARAM_TIME_CLICK_MIN(param_default)), 239 | BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_default) + EBTN_PARAM_TIME_CLICK_MULTI_MAX(param_default)), 240 | }; 241 | 242 | static const btn_test_evt_t test_events_double_click_critical_time[] = { 243 | BTN_EVENT_ONPRESS(), BTN_EVENT_ONRELEASE(), BTN_EVENT_ONPRESS(), BTN_EVENT_ONRELEASE(), BTN_EVENT_ONCLICK(2), 244 | }; 245 | 246 | static btn_test_time_t test_sequence_double_click_critical_time_over[] = { 247 | /* 248 | * This test shows how to handle case when 2 clicks are being executed, 249 | * but time between 2 release events is larger than maximum 250 | * allowed time for consecutive clicks. 251 | * 252 | * In this case, 2 onclick events are sent, 253 | * both with consecutive clicks counter set to 1 254 | */ 255 | BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_default) + EBTN_PARAM_TIME_CLICK_MIN(param_default)), 256 | BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_default) + EBTN_PARAM_TIME_CLICK_MULTI_MAX(param_default) - 257 | (EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_default))), 258 | BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_default) + EBTN_PARAM_TIME_CLICK_MIN(param_default)), 259 | BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_default) + EBTN_PARAM_TIME_CLICK_MULTI_MAX(param_default)), 260 | }; 261 | 262 | static const btn_test_evt_t test_events_double_click_critical_time_over[] = { 263 | BTN_EVENT_ONPRESS(), 264 | BTN_EVENT_ONRELEASE(), 265 | /* This one is to handle click for first sequence */ 266 | BTN_EVENT_ONCLICK(1), 267 | BTN_EVENT_ONPRESS(), 268 | BTN_EVENT_ONRELEASE(), 269 | /* This one is to handle click for second sequence */ 270 | BTN_EVENT_ONCLICK(1), 271 | }; 272 | 273 | static btn_test_time_t test_sequence_click_with_keepalive[] = { 274 | /* 275 | * Make a click event, followed by the longer press. 276 | * Simulate "long press" w/ previous click, that has click counter set to 1 277 | */ 278 | BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_default) + EBTN_PARAM_TIME_CLICK_MIN(param_default)), 279 | BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_default) + EBTN_PARAM_TIME_CLICK_MULTI_MAX(param_default) / 2), 280 | BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_default)), 281 | BTN_STATE(1, EBTN_PARAM_TIME_KEEPALIVE_PERIOD(param_default)), 282 | BTN_STATE(1, EBTN_PARAM_TIME_KEEPALIVE_PERIOD(param_default)), 283 | BTN_STATE(1, EBTN_PARAM_TIME_KEEPALIVE_PERIOD(param_default)), 284 | BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_default) + EBTN_PARAM_TIME_CLICK_MULTI_MAX(param_default)), 285 | }; 286 | 287 | static const btn_test_evt_t test_events_click_with_keepalive[] = { 288 | BTN_EVENT_ONPRESS(), 289 | BTN_EVENT_ONRELEASE(), 290 | BTN_EVENT_ONPRESS(), 291 | /* This one is to handle click before long press */ 292 | BTN_EVENT_ONCLICK(1), 293 | BTN_EVENT_KEEPALIVE(1), 294 | BTN_EVENT_KEEPALIVE(2), 295 | BTN_EVENT_KEEPALIVE(3), 296 | BTN_EVENT_ONRELEASE(), 297 | }; 298 | 299 | static btn_test_time_t test_sequence_click_with_short[] = { 300 | /* Make with short press (shorter than minimum required) */ 301 | BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_default) + EBTN_PARAM_TIME_CLICK_MIN(param_default) / 2), 302 | BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_default) + EBTN_PARAM_TIME_CLICK_MULTI_MAX(param_default)), 303 | }; 304 | 305 | static const btn_test_evt_t test_events_click_with_short[] = { 306 | BTN_EVENT_ONPRESS(), 307 | BTN_EVENT_ONRELEASE(), 308 | }; 309 | 310 | static btn_test_time_t test_sequence_click_with_short_with_multi[] = { 311 | /* Make with short press (shorter than minimum required) */ 312 | BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_default) + EBTN_PARAM_TIME_CLICK_MIN(param_default) / 2), 313 | BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_default) + EBTN_PARAM_TIME_CLICK_MULTI_MAX(param_default) / 2), 314 | BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_default) + EBTN_PARAM_TIME_CLICK_MIN(param_default)), 315 | BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_default) + EBTN_PARAM_TIME_CLICK_MULTI_MAX(param_default) / 2), 316 | BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_default) + EBTN_PARAM_TIME_CLICK_MIN(param_default)), 317 | BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_default) + EBTN_PARAM_TIME_CLICK_MULTI_MAX(param_default)), 318 | }; 319 | 320 | static const btn_test_evt_t test_events_click_with_short_with_multi[] = { 321 | /* This one is short... */ 322 | BTN_EVENT_ONPRESS(), BTN_EVENT_ONRELEASE(), BTN_EVENT_ONPRESS(), BTN_EVENT_ONRELEASE(), 323 | BTN_EVENT_ONPRESS(), BTN_EVENT_ONRELEASE(), BTN_EVENT_ONCLICK(2), 324 | }; 325 | 326 | static btn_test_time_t test_sequence_multi_click_with_short[] = { 327 | /* Make 2 clicks, and 3rd one with short press (shorter than minimum required) */ 328 | BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_default) + EBTN_PARAM_TIME_CLICK_MIN(param_default)), 329 | BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_default) + EBTN_PARAM_TIME_CLICK_MULTI_MAX(param_default) / 2), 330 | BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_default) + EBTN_PARAM_TIME_CLICK_MIN(param_default)), 331 | BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_default) + EBTN_PARAM_TIME_CLICK_MULTI_MAX(param_default) / 2), 332 | BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_default) + EBTN_PARAM_TIME_CLICK_MIN(param_default) / 2), 333 | BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_default)), 334 | }; 335 | 336 | static const btn_test_evt_t test_events_multi_click_with_short[] = { 337 | BTN_EVENT_ONPRESS(), 338 | BTN_EVENT_ONRELEASE(), 339 | BTN_EVENT_ONPRESS(), 340 | BTN_EVENT_ONRELEASE(), 341 | /* This one is short... */ 342 | BTN_EVENT_ONPRESS(), 343 | BTN_EVENT_ONRELEASE(), 344 | BTN_EVENT_ONCLICK(2), 345 | }; 346 | 347 | static btn_test_time_t test_sequence_onpress_debounce[] = { 348 | BTN_STATE(0, 0), 349 | BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_default) / 2), 350 | BTN_STATE(0, 1), 351 | BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_default) / 2), 352 | BTN_STATE(0, 1), 353 | BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_default) / 2), 354 | BTN_STATE(0, 1), 355 | BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_default) + EBTN_PARAM_TIME_CLICK_MIN(param_default)), 356 | BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_default) + EBTN_PARAM_TIME_CLICK_MULTI_MAX(param_default) + 1), 357 | }; 358 | 359 | static const btn_test_evt_t test_events_onpress_debounce[] = { 360 | BTN_EVENT_ONPRESS(), 361 | BTN_EVENT_ONRELEASE(), 362 | BTN_EVENT_ONCLICK(1), 363 | }; 364 | 365 | // for test overflow, make sure enable macro 'EBTN_CONFIG_TIMER_16' or compile with 'make all 366 | // CFLAGS=-DEBTN_CONFIG_TIMER_16' 367 | static btn_test_time_t test_sequence_time_overflow_onpress_debounce[] = { 368 | BTN_STATE(0, 0x0ffff - (EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_default) / 2)), 369 | BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_default) + EBTN_PARAM_TIME_CLICK_MIN(param_default)), 370 | BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_default) + EBTN_PARAM_TIME_CLICK_MULTI_MAX(param_default)), 371 | }; 372 | 373 | static const btn_test_evt_t test_events_time_overflow_onpress_debounce[] = { 374 | BTN_EVENT_ONPRESS(), 375 | BTN_EVENT_ONRELEASE(), 376 | BTN_EVENT_ONCLICK(1), 377 | }; 378 | 379 | static btn_test_time_t test_sequence_time_overflow_onpress[] = { 380 | BTN_STATE(0, 0x0ffff - (EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_default) + EBTN_PARAM_TIME_CLICK_MIN(param_default) / 2)), 381 | BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_default) + EBTN_PARAM_TIME_CLICK_MIN(param_default)), 382 | BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_default) + EBTN_PARAM_TIME_CLICK_MULTI_MAX(param_default)), 383 | }; 384 | 385 | static const btn_test_evt_t test_events_time_overflow_onpress[] = { 386 | BTN_EVENT_ONPRESS(), 387 | BTN_EVENT_ONRELEASE(), 388 | BTN_EVENT_ONCLICK(1), 389 | }; 390 | 391 | static btn_test_time_t test_sequence_time_overflow_onrelease_muti[] = { 392 | BTN_STATE(0, 0x0ffff - (EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_default) + EBTN_PARAM_TIME_CLICK_MIN(param_default) + 393 | EBTN_PARAM_TIME_CLICK_MIN(param_default) / 2)), 394 | BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_default) + EBTN_PARAM_TIME_CLICK_MIN(param_default)), 395 | BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_default) + EBTN_PARAM_TIME_CLICK_MULTI_MAX(param_default)), 396 | }; 397 | 398 | static const btn_test_evt_t test_events_time_overflow_onrelease_muti[] = { 399 | BTN_EVENT_ONPRESS(), 400 | BTN_EVENT_ONRELEASE(), 401 | BTN_EVENT_ONCLICK(1), 402 | }; 403 | 404 | static btn_test_time_t test_sequence_time_overflow_keepalive[] = { 405 | BTN_STATE(0, 0x0ffff - (EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_default) + EBTN_PARAM_TIME_CLICK_MIN(param_default) + 406 | EBTN_PARAM_TIME_KEEPALIVE_PERIOD(param_default) / 2)), 407 | BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_default) + EBTN_PARAM_TIME_CLICK_MIN(param_default)), 408 | BTN_STATE(1, EBTN_PARAM_TIME_KEEPALIVE_PERIOD(param_default)), 409 | BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_default) + EBTN_PARAM_TIME_CLICK_MULTI_MAX(param_default)), 410 | }; 411 | 412 | static const btn_test_evt_t test_events_time_overflow_keepalive[] = { 413 | BTN_EVENT_ONPRESS(), 414 | BTN_EVENT_KEEPALIVE(1), 415 | BTN_EVENT_ONRELEASE(), 416 | }; 417 | 418 | /// 419 | /// Test onrelease debounce 420 | /// 421 | 422 | static btn_test_time_t test_sequence_onrelease_debounce[] = { 423 | BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_onrelease_debounce) + EBTN_PARAM_TIME_CLICK_MIN(param_onrelease_debounce)), 424 | BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_onrelease_debounce) / 2), 425 | BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_onrelease_debounce)), 426 | BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_onrelease_debounce) + EBTN_PARAM_TIME_CLICK_MULTI_MAX(param_onrelease_debounce)), 427 | }; 428 | 429 | static const btn_test_evt_t test_events_onrelease_debounce[] = { 430 | BTN_EVENT_ONPRESS(), 431 | BTN_EVENT_ONRELEASE(), 432 | BTN_EVENT_ONCLICK(1), 433 | }; 434 | 435 | static btn_test_time_t test_sequence_onrelease_debounce_over[] = { 436 | BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_onrelease_debounce) + EBTN_PARAM_TIME_CLICK_MIN(param_onrelease_debounce)), 437 | BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_onrelease_debounce)), 438 | BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_onrelease_debounce) + EBTN_PARAM_TIME_CLICK_MIN(param_onrelease_debounce)), 439 | BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_onrelease_debounce) + EBTN_PARAM_TIME_CLICK_MULTI_MAX(param_onrelease_debounce)), 440 | }; 441 | 442 | static const btn_test_evt_t test_events_onrelease_debounce_over[] = { 443 | BTN_EVENT_ONPRESS(), BTN_EVENT_ONRELEASE(), BTN_EVENT_ONPRESS(), BTN_EVENT_ONRELEASE(), BTN_EVENT_ONCLICK(2), 444 | }; 445 | 446 | static btn_test_time_t test_sequence_onrelease_debounce_time_overflow[] = { 447 | BTN_STATE(0, 0x0ffff - (EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_onrelease_debounce) + EBTN_PARAM_TIME_CLICK_MIN(param_onrelease_debounce) + 448 | EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_onrelease_debounce) / 2)), 449 | BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_onrelease_debounce) + EBTN_PARAM_TIME_CLICK_MIN(param_onrelease_debounce)), 450 | BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_onrelease_debounce) + EBTN_PARAM_TIME_CLICK_MULTI_MAX(param_onrelease_debounce)), 451 | }; 452 | 453 | static const btn_test_evt_t test_events_onrelease_debounce_time_overflow[] = { 454 | BTN_EVENT_ONPRESS(), 455 | BTN_EVENT_ONRELEASE(), 456 | BTN_EVENT_ONCLICK(1), 457 | }; 458 | 459 | /// 460 | /// Test keepalive with click debounce 461 | /// 462 | static btn_test_time_t test_sequence_keepalive_with_click[] = { 463 | BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_keepalive_with_click) + EBTN_PARAM_TIME_CLICK_MIN(param_keepalive_with_click)), 464 | BTN_STATE(1, EBTN_PARAM_TIME_KEEPALIVE_PERIOD(param_keepalive_with_click)), 465 | BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_keepalive_with_click) + EBTN_PARAM_TIME_CLICK_MULTI_MAX(param_keepalive_with_click)), 466 | }; 467 | 468 | static const btn_test_evt_t test_events_keepalive_with_click[] = { 469 | BTN_EVENT_ONPRESS(), 470 | BTN_EVENT_KEEPALIVE(1), 471 | BTN_EVENT_ONRELEASE(), 472 | BTN_EVENT_ONCLICK(1), 473 | }; 474 | 475 | static btn_test_time_t test_sequence_keepalive_with_click_double[] = { 476 | BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_keepalive_with_click) + EBTN_PARAM_TIME_CLICK_MIN(param_keepalive_with_click)), 477 | BTN_STATE(1, EBTN_PARAM_TIME_KEEPALIVE_PERIOD(param_keepalive_with_click)), 478 | BTN_STATE(1, EBTN_PARAM_TIME_KEEPALIVE_PERIOD(param_keepalive_with_click)), 479 | BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_keepalive_with_click) + EBTN_PARAM_TIME_CLICK_MULTI_MAX(param_keepalive_with_click)), 480 | }; 481 | 482 | static const btn_test_evt_t test_events_keepalive_with_click_double[] = { 483 | BTN_EVENT_ONPRESS(), BTN_EVENT_KEEPALIVE(1), BTN_EVENT_KEEPALIVE(2), BTN_EVENT_ONRELEASE(), BTN_EVENT_ONCLICK(1), 484 | }; 485 | 486 | /// 487 | /// Test max multi click with 3 488 | /// 489 | static btn_test_time_t test_sequence_max_click_3[] = { 490 | BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_max_click_3) + EBTN_PARAM_TIME_CLICK_MIN(param_max_click_3)), 491 | BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_max_click_3) + EBTN_PARAM_TIME_CLICK_MULTI_MAX(param_max_click_3) / 2), 492 | BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_max_click_3) + EBTN_PARAM_TIME_CLICK_MIN(param_max_click_3)), 493 | BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_max_click_3) + EBTN_PARAM_TIME_CLICK_MULTI_MAX(param_max_click_3) / 2), 494 | BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_max_click_3) + EBTN_PARAM_TIME_CLICK_MIN(param_max_click_3)), 495 | BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_max_click_3) + EBTN_PARAM_TIME_CLICK_MULTI_MAX(param_max_click_3)), 496 | }; 497 | 498 | static const btn_test_evt_t test_events_max_click_3[] = { 499 | BTN_EVENT_ONPRESS(), BTN_EVENT_ONRELEASE(), BTN_EVENT_ONPRESS(), BTN_EVENT_ONRELEASE(), 500 | BTN_EVENT_ONPRESS(), BTN_EVENT_ONRELEASE(), BTN_EVENT_ONCLICK(3), 501 | }; 502 | 503 | static btn_test_time_t test_sequence_max_click_3_over[] = { 504 | BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_max_click_3) + EBTN_PARAM_TIME_CLICK_MIN(param_max_click_3)), 505 | BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_max_click_3) + EBTN_PARAM_TIME_CLICK_MULTI_MAX(param_max_click_3) / 2), 506 | BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_max_click_3) + EBTN_PARAM_TIME_CLICK_MIN(param_max_click_3)), 507 | BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_max_click_3) + EBTN_PARAM_TIME_CLICK_MULTI_MAX(param_max_click_3) / 2), 508 | BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_max_click_3) + EBTN_PARAM_TIME_CLICK_MIN(param_max_click_3)), 509 | BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_max_click_3) + EBTN_PARAM_TIME_CLICK_MULTI_MAX(param_max_click_3) / 2), 510 | BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_max_click_3) + EBTN_PARAM_TIME_CLICK_MIN(param_max_click_3)), 511 | BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_max_click_3) + EBTN_PARAM_TIME_CLICK_MULTI_MAX(param_max_click_3)), 512 | }; 513 | 514 | static const btn_test_evt_t test_events_max_click_3_over[] = { 515 | BTN_EVENT_ONPRESS(), BTN_EVENT_ONRELEASE(), BTN_EVENT_ONPRESS(), BTN_EVENT_ONRELEASE(), BTN_EVENT_ONPRESS(), 516 | BTN_EVENT_ONRELEASE(), BTN_EVENT_ONCLICK(3), BTN_EVENT_ONPRESS(), BTN_EVENT_ONRELEASE(), BTN_EVENT_ONCLICK(1), 517 | }; 518 | 519 | /// 520 | /// Test click multi max with 0 521 | /// 522 | static btn_test_time_t test_sequence_click_multi_max_0[] = { 523 | BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_click_multi_max_0) + EBTN_PARAM_TIME_CLICK_MIN(param_click_multi_max_0)), 524 | BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_click_multi_max_0) + EBTN_PARAM_TIME_CLICK_MULTI_MAX(param_click_multi_max_0) / 2), 525 | BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_click_multi_max_0) + EBTN_PARAM_TIME_CLICK_MIN(param_click_multi_max_0)), 526 | BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_click_multi_max_0) + EBTN_PARAM_TIME_CLICK_MULTI_MAX(param_click_multi_max_0) / 2), 527 | BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_click_multi_max_0) + EBTN_PARAM_TIME_CLICK_MIN(param_click_multi_max_0)), 528 | BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_click_multi_max_0) + EBTN_PARAM_TIME_CLICK_MULTI_MAX(param_click_multi_max_0)), 529 | }; 530 | 531 | static const btn_test_evt_t test_events_click_multi_max_0[] = { 532 | BTN_EVENT_ONPRESS(), BTN_EVENT_ONRELEASE(), BTN_EVENT_ONCLICK(1), BTN_EVENT_ONPRESS(), BTN_EVENT_ONRELEASE(), 533 | BTN_EVENT_ONCLICK(1), BTN_EVENT_ONPRESS(), BTN_EVENT_ONRELEASE(), BTN_EVENT_ONCLICK(1), 534 | }; 535 | 536 | /// 537 | /// Test max keepalive with 0 538 | /// 539 | static btn_test_time_t test_sequence_keep_alive_0[] = { 540 | BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_keep_alive_0) + EBTN_PARAM_TIME_CLICK_MIN(param_keep_alive_0)), 541 | BTN_STATE(1, EBTN_PARAM_TIME_CLICK_MAX(param_keep_alive_0) / 2), 542 | BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_keep_alive_0) + EBTN_PARAM_TIME_CLICK_MULTI_MAX(param_keep_alive_0)), 543 | }; 544 | 545 | static const btn_test_evt_t test_events_keep_alive_0[] = { 546 | BTN_EVENT_ONPRESS(), 547 | BTN_EVENT_ONRELEASE(), 548 | BTN_EVENT_ONCLICK(1), 549 | }; 550 | 551 | static btn_test_arr_t test_list[] = { 552 | TEST_ARRAY_DEFINE(USER_BUTTON_default, test_sequence_single_click, test_events_single_click), 553 | TEST_ARRAY_DEFINE(USER_BUTTON_default, test_sequence_double_click, test_events_double_click), 554 | TEST_ARRAY_DEFINE(USER_BUTTON_default, test_sequence_triple_click, test_events_triple_click), 555 | TEST_ARRAY_DEFINE(USER_BUTTON_default, test_sequence_double_click_critical_time, test_events_double_click_critical_time), 556 | TEST_ARRAY_DEFINE(USER_BUTTON_default, test_sequence_double_click_critical_time_over, test_events_double_click_critical_time_over), 557 | TEST_ARRAY_DEFINE(USER_BUTTON_default, test_sequence_click_with_keepalive, test_events_click_with_keepalive), 558 | TEST_ARRAY_DEFINE(USER_BUTTON_default, test_sequence_click_with_short, test_events_click_with_short), 559 | TEST_ARRAY_DEFINE(USER_BUTTON_default, test_sequence_click_with_short_with_multi, test_events_click_with_short_with_multi), 560 | TEST_ARRAY_DEFINE(USER_BUTTON_default, test_sequence_multi_click_with_short, test_events_multi_click_with_short), 561 | TEST_ARRAY_DEFINE(USER_BUTTON_default, test_sequence_onpress_debounce, test_events_onpress_debounce), 562 | TEST_ARRAY_DEFINE(USER_BUTTON_default, test_sequence_time_overflow_onpress_debounce, test_events_time_overflow_onpress_debounce), 563 | TEST_ARRAY_DEFINE(USER_BUTTON_default, test_sequence_time_overflow_onpress, test_events_time_overflow_onpress), 564 | TEST_ARRAY_DEFINE(USER_BUTTON_default, test_sequence_time_overflow_onrelease_muti, test_events_time_overflow_onrelease_muti), 565 | TEST_ARRAY_DEFINE(USER_BUTTON_default, test_sequence_time_overflow_keepalive, test_events_time_overflow_keepalive), 566 | 567 | TEST_ARRAY_DEFINE(USER_BUTTON_onrelease_debounce, test_sequence_onrelease_debounce, test_events_onrelease_debounce), 568 | TEST_ARRAY_DEFINE(USER_BUTTON_onrelease_debounce, test_sequence_onrelease_debounce_over, test_events_onrelease_debounce_over), 569 | TEST_ARRAY_DEFINE(USER_BUTTON_onrelease_debounce, test_sequence_onrelease_debounce_time_overflow, test_events_onrelease_debounce_time_overflow), 570 | 571 | TEST_ARRAY_DEFINE(USER_BUTTON_keepalive_with_click, test_sequence_keepalive_with_click, test_events_keepalive_with_click), 572 | TEST_ARRAY_DEFINE(USER_BUTTON_keepalive_with_click, test_sequence_keepalive_with_click_double, test_events_keepalive_with_click_double), 573 | 574 | TEST_ARRAY_DEFINE(USER_BUTTON_max_click_3, test_sequence_max_click_3, test_events_max_click_3), 575 | TEST_ARRAY_DEFINE(USER_BUTTON_max_click_3, test_sequence_max_click_3_over, test_events_max_click_3_over), 576 | 577 | TEST_ARRAY_DEFINE(USER_BUTTON_click_multi_max_0, test_sequence_click_multi_max_0, test_events_click_multi_max_0), 578 | 579 | TEST_ARRAY_DEFINE(USER_BUTTON_keep_alive_0, test_sequence_keep_alive_0, test_events_keep_alive_0), 580 | }; 581 | 582 | static btn_test_arr_t *select_test_item; 583 | 584 | /* Get button state for given current time */ 585 | static uint8_t prv_get_state_for_time(uint16_t key_id, uint32_t time) 586 | { 587 | uint8_t state = 0; 588 | uint32_t duration = 0; 589 | 590 | if (select_test_item->test_key_id == key_id) 591 | { 592 | // printf("time: %d, key_id: %d\n", time, key_id); 593 | for (size_t i = 0; i < select_test_item->test_sequence_cnt; ++i) 594 | { 595 | if (select_test_item->test_sequence[i].duration == 0) 596 | { 597 | continue; 598 | } 599 | duration += select_test_item->test_sequence[i].duration + 1; /* Advance time, need add 1 for state switch time. */ 600 | // printf("i: %d, duration: %d\n", i, duration); 601 | if (time <= duration) 602 | { 603 | state = select_test_item->test_sequence[i].state; 604 | // printf("i: %d, time: %d, duration: %d, state: %d\n", i, time, duration, state); 605 | // printf("state: %d\n", state); 606 | break; 607 | } 608 | } 609 | } 610 | 611 | return state; 612 | } 613 | 614 | static uint32_t test_get_state_total_duration(void) 615 | { 616 | uint32_t duration = 0; 617 | 618 | for (size_t i = 0; i < select_test_item->test_sequence_cnt; ++i) 619 | { 620 | if (select_test_item->test_sequence[i].duration == 0) 621 | { 622 | continue; 623 | } 624 | duration += select_test_item->test_sequence[i].duration + 1; /* Advance time, need add 1 for state switch time. */ 625 | } 626 | 627 | return duration; 628 | } 629 | 630 | /* Get button state */ 631 | static uint8_t prv_btn_get_state(struct ebtn_btn *btn) 632 | { 633 | uint8_t state = prv_get_state_for_time(btn->key_id, test_processed_time_current); 634 | 635 | (void)btn; 636 | return state; 637 | } 638 | 639 | static uint32_t test_processed_event_time_prev; 640 | static uint32_t test_processed_array_index = 0; 641 | /* Process button event */ 642 | static void prv_btn_event(struct ebtn_btn *btn, ebtn_evt_t evt) 643 | { 644 | const char *s; 645 | uint32_t color, keepalive_cnt = 0, diff_time; 646 | const btn_test_evt_t *test_evt_data = NULL; 647 | HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE); 648 | 649 | if (test_processed_array_index >= select_test_item->test_events_cnt) 650 | { 651 | SetConsoleTextAttribute(hConsole, FOREGROUND_RED); 652 | printf("[%7u] ERROR! Array index is out of bounds!\r\n", (unsigned)test_processed_time_current); 653 | SetConsoleTextAttribute(hConsole, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE); 654 | } 655 | else 656 | { 657 | test_evt_data = &select_test_item->test_events[test_processed_array_index]; 658 | } 659 | 660 | /* Handle timing */ 661 | diff_time = test_processed_time_current - test_processed_event_time_prev; 662 | test_processed_event_time_prev = test_processed_time_current; 663 | keepalive_cnt = btn->keepalive_cnt; 664 | 665 | /* Event type must match */ 666 | ASSERT((test_evt_data != NULL) && (test_evt_data->evt == evt)); 667 | 668 | /* Get event string */ 669 | if (evt == EBTN_EVT_KEEPALIVE) 670 | { 671 | s = "KEEPALIVE"; 672 | color = FOREGROUND_RED | FOREGROUND_BLUE; 673 | 674 | ASSERT((test_evt_data != NULL) && (test_evt_data->keepalive_cnt == keepalive_cnt)); 675 | } 676 | else if (evt == EBTN_EVT_ONPRESS) 677 | { 678 | s = "ONPRESS"; 679 | color = FOREGROUND_GREEN; 680 | } 681 | else if (evt == EBTN_EVT_ONRELEASE) 682 | { 683 | s = "ONRELEASE"; 684 | color = FOREGROUND_BLUE; 685 | } 686 | else if (evt == EBTN_EVT_ONCLICK) 687 | { 688 | s = "ONCLICK"; 689 | color = FOREGROUND_RED | FOREGROUND_GREEN; 690 | 691 | ASSERT((test_evt_data != NULL) && (test_evt_data->conseq_clicks == btn->click_cnt)); 692 | } 693 | else 694 | { 695 | s = "UNKNOWN"; 696 | color = FOREGROUND_RED; 697 | } 698 | 699 | SetConsoleTextAttribute(hConsole, color); 700 | printf("[%7u][%6u] ID(hex):%4x, evt:%10s, keep-alive cnt: %3u, click cnt: %3u\r\n", (unsigned)test_processed_time_current, (unsigned)diff_time, btn->key_id, 701 | s, (unsigned)keepalive_cnt, (unsigned)btn->click_cnt); 702 | 703 | SetConsoleTextAttribute(hConsole, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE); 704 | ++test_processed_array_index; /* Go to next step in next event */ 705 | } 706 | 707 | /** 708 | * \brief Test function 709 | */ 710 | int example_test(void) 711 | { 712 | printf("Test running\r\n"); 713 | 714 | for (int index = 0; index < EBTN_ARRAY_SIZE(test_list); index++) 715 | { 716 | select_test_item = &test_list[index]; 717 | 718 | SUITE_START(select_test_item->test_name); 719 | 720 | // printf("\n"); 721 | 722 | // init variable 723 | test_processed_event_time_prev = 0; 724 | test_processed_array_index = 0; 725 | 726 | /* Define buttons */ 727 | ebtn_init(btns, EBTN_ARRAY_SIZE(btns), NULL, 0, prv_btn_get_state, prv_btn_event); 728 | 729 | /* Counter simulates ms tick */ 730 | for (uint32_t i = 0; i < MAX_TIME_MS; ++i) 731 | { 732 | test_processed_time_current = i; /* Set current time used in callback */ 733 | ebtn_process(i); /* Now run processing */ 734 | 735 | // printf("time: %d, end: %d, in_process(): %d/%d\n", i, test_processed_array_index >= select_test_item->test_events_cnt 736 | // , ebtn_is_btn_in_process(ebtn_get_btn_by_key_id(select_test_item->test_key_id)), ebtn_is_in_process()); 737 | // check end 738 | if (test_processed_array_index >= select_test_item->test_events_cnt) 739 | { 740 | uint32_t duration = test_get_state_total_duration(); 741 | if (i > duration + 1) 742 | { 743 | ASSERT(!ebtn_is_btn_in_process(ebtn_get_btn_by_key_id(select_test_item->test_key_id))); 744 | ASSERT(!ebtn_is_in_process()); 745 | } 746 | } 747 | } 748 | ASSERT(test_processed_array_index == select_test_item->test_events_cnt); 749 | 750 | // printf("\n"); 751 | 752 | SUITE_END(); 753 | } 754 | return 0; 755 | } 756 | -------------------------------------------------------------------------------- /example_user.c: -------------------------------------------------------------------------------- 1 | #include "windows.h" 2 | #include 3 | #include 4 | 5 | #include "ebtn.h" 6 | 7 | static LARGE_INTEGER freq, sys_start_time; 8 | static uint32_t get_tick(void); 9 | 10 | typedef enum 11 | { 12 | USER_BUTTON_0 = 0, 13 | USER_BUTTON_1, 14 | USER_BUTTON_2, 15 | USER_BUTTON_3, 16 | USER_BUTTON_4, 17 | USER_BUTTON_5, 18 | USER_BUTTON_6, 19 | USER_BUTTON_7, 20 | USER_BUTTON_8, 21 | USER_BUTTON_9, 22 | USER_BUTTON_INVALID, 23 | USER_BUTTON_MAX, 24 | 25 | USER_BUTTON_COMBO_0 = 0x100, 26 | USER_BUTTON_COMBO_1, 27 | USER_BUTTON_COMBO_2, 28 | USER_BUTTON_COMBO_3, 29 | USER_BUTTON_COMBO_MAX, 30 | } user_button_t; 31 | 32 | /* User defined settings */ 33 | static const ebtn_btn_param_t defaul_ebtn_param = EBTN_PARAMS_INIT(20, 0, 20, 300, 200, 500, 10); 34 | 35 | static ebtn_btn_t btns[] = { 36 | // For key_idx double test, need full key map size 37 | EBTN_BUTTON_INIT(USER_BUTTON_INVALID, &defaul_ebtn_param), 38 | EBTN_BUTTON_INIT(USER_BUTTON_INVALID, &defaul_ebtn_param), 39 | EBTN_BUTTON_INIT(USER_BUTTON_INVALID, &defaul_ebtn_param), 40 | EBTN_BUTTON_INIT(USER_BUTTON_INVALID, &defaul_ebtn_param), 41 | 42 | EBTN_BUTTON_INIT(USER_BUTTON_0, &defaul_ebtn_param), 43 | EBTN_BUTTON_INIT(USER_BUTTON_1, &defaul_ebtn_param), 44 | EBTN_BUTTON_INIT(USER_BUTTON_2, &defaul_ebtn_param), 45 | EBTN_BUTTON_INIT(USER_BUTTON_3, &defaul_ebtn_param), 46 | 47 | // For key_idx double test, need full key map size 48 | EBTN_BUTTON_INIT(USER_BUTTON_INVALID, &defaul_ebtn_param), 49 | EBTN_BUTTON_INIT(USER_BUTTON_INVALID, &defaul_ebtn_param), 50 | EBTN_BUTTON_INIT(USER_BUTTON_INVALID, &defaul_ebtn_param), 51 | EBTN_BUTTON_INIT(USER_BUTTON_INVALID, &defaul_ebtn_param), 52 | 53 | EBTN_BUTTON_INIT(USER_BUTTON_4, &defaul_ebtn_param), 54 | EBTN_BUTTON_INIT(USER_BUTTON_5, &defaul_ebtn_param), 55 | 56 | // For key_idx double test, need full key map size 57 | EBTN_BUTTON_INIT(USER_BUTTON_INVALID, &defaul_ebtn_param), 58 | EBTN_BUTTON_INIT(USER_BUTTON_INVALID, &defaul_ebtn_param), 59 | EBTN_BUTTON_INIT(USER_BUTTON_INVALID, &defaul_ebtn_param), 60 | EBTN_BUTTON_INIT(USER_BUTTON_INVALID, &defaul_ebtn_param), 61 | 62 | EBTN_BUTTON_INIT(USER_BUTTON_INVALID, &defaul_ebtn_param), 63 | EBTN_BUTTON_INIT(USER_BUTTON_INVALID, &defaul_ebtn_param), 64 | EBTN_BUTTON_INIT(USER_BUTTON_INVALID, &defaul_ebtn_param), 65 | EBTN_BUTTON_INIT(USER_BUTTON_INVALID, &defaul_ebtn_param), 66 | 67 | EBTN_BUTTON_INIT(USER_BUTTON_INVALID, &defaul_ebtn_param), 68 | EBTN_BUTTON_INIT(USER_BUTTON_INVALID, &defaul_ebtn_param), 69 | EBTN_BUTTON_INIT(USER_BUTTON_INVALID, &defaul_ebtn_param), 70 | EBTN_BUTTON_INIT(USER_BUTTON_INVALID, &defaul_ebtn_param), 71 | 72 | EBTN_BUTTON_INIT(USER_BUTTON_INVALID, &defaul_ebtn_param), 73 | EBTN_BUTTON_INIT(USER_BUTTON_INVALID, &defaul_ebtn_param), 74 | EBTN_BUTTON_INIT(USER_BUTTON_INVALID, &defaul_ebtn_param), 75 | EBTN_BUTTON_INIT(USER_BUTTON_INVALID, &defaul_ebtn_param), 76 | }; 77 | 78 | static ebtn_btn_dyn_t btns_dyn[] = { 79 | // For key_idx double test, need full key map size 80 | EBTN_BUTTON_DYN_INIT(USER_BUTTON_INVALID, &defaul_ebtn_param), 81 | EBTN_BUTTON_DYN_INIT(USER_BUTTON_INVALID, &defaul_ebtn_param), 82 | EBTN_BUTTON_DYN_INIT(USER_BUTTON_INVALID, &defaul_ebtn_param), 83 | EBTN_BUTTON_DYN_INIT(USER_BUTTON_INVALID, &defaul_ebtn_param), 84 | 85 | EBTN_BUTTON_DYN_INIT(USER_BUTTON_6, &defaul_ebtn_param), 86 | EBTN_BUTTON_DYN_INIT(USER_BUTTON_7, &defaul_ebtn_param), 87 | 88 | // For key_idx double test, need full key map size 89 | EBTN_BUTTON_DYN_INIT(USER_BUTTON_INVALID, &defaul_ebtn_param), 90 | EBTN_BUTTON_DYN_INIT(USER_BUTTON_INVALID, &defaul_ebtn_param), 91 | EBTN_BUTTON_DYN_INIT(USER_BUTTON_INVALID, &defaul_ebtn_param), 92 | EBTN_BUTTON_DYN_INIT(USER_BUTTON_INVALID, &defaul_ebtn_param), 93 | 94 | EBTN_BUTTON_DYN_INIT(USER_BUTTON_8, &defaul_ebtn_param), 95 | EBTN_BUTTON_DYN_INIT(USER_BUTTON_9, &defaul_ebtn_param), 96 | }; 97 | 98 | uint32_t last_time_keys[USER_BUTTON_MAX - USER_BUTTON_0] = {0}; 99 | 100 | static ebtn_btn_combo_t btns_combo[] = { 101 | EBTN_BUTTON_COMBO_INIT(USER_BUTTON_COMBO_0, &defaul_ebtn_param), 102 | EBTN_BUTTON_COMBO_INIT(USER_BUTTON_COMBO_1, &defaul_ebtn_param), 103 | }; 104 | 105 | static ebtn_btn_combo_dyn_t btns_combo_dyn[] = { 106 | EBTN_BUTTON_COMBO_DYN_INIT(USER_BUTTON_COMBO_2, &defaul_ebtn_param), 107 | EBTN_BUTTON_COMBO_DYN_INIT(USER_BUTTON_COMBO_3, &defaul_ebtn_param), 108 | }; 109 | 110 | uint32_t last_time_keys_combo[USER_BUTTON_COMBO_MAX - USER_BUTTON_COMBO_0] = {0}; 111 | 112 | static int windows_get_match_key(uint16_t key_id) 113 | { 114 | int key = 0; 115 | switch (key_id) 116 | { 117 | case USER_BUTTON_0: 118 | key = '0'; 119 | break; 120 | case USER_BUTTON_1: 121 | key = '1'; 122 | break; 123 | case USER_BUTTON_2: 124 | key = '2'; 125 | break; 126 | case USER_BUTTON_3: 127 | key = '3'; 128 | break; 129 | case USER_BUTTON_4: 130 | key = '4'; 131 | break; 132 | case USER_BUTTON_5: 133 | key = '5'; 134 | break; 135 | case USER_BUTTON_6: 136 | key = '6'; 137 | break; 138 | case USER_BUTTON_7: 139 | key = '7'; 140 | break; 141 | case USER_BUTTON_8: 142 | key = '8'; 143 | break; 144 | case USER_BUTTON_9: 145 | key = '9'; 146 | break; 147 | } 148 | 149 | return key; 150 | } 151 | 152 | /** 153 | * \brief Get input state callback 154 | * \param btn: Button instance 155 | * \return `1` if button active, `0` otherwise 156 | */ 157 | uint8_t prv_btn_get_state(struct ebtn_btn *btn) 158 | { 159 | /* 160 | * Function will return negative number if button is pressed, 161 | * or zero if button is releases 162 | */ 163 | return GetAsyncKeyState(windows_get_match_key(btn->key_id)) < 0; 164 | } 165 | 166 | /** 167 | * \brief Button event 168 | * 169 | * \param btn: Button instance 170 | * \param evt: Button event 171 | */ 172 | void prv_btn_event(struct ebtn_btn *btn, ebtn_evt_t evt) 173 | { 174 | const char *s; 175 | uint32_t color, keepalive_cnt = 0; 176 | HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE); 177 | uint32_t diff_time = 0; 178 | uint32_t *diff_time_ptr = NULL; 179 | if (btn->key_id < USER_BUTTON_MAX) 180 | { 181 | diff_time_ptr = &last_time_keys[btn->key_id - USER_BUTTON_0]; 182 | } 183 | else 184 | { 185 | diff_time_ptr = &last_time_keys[btn->key_id - USER_BUTTON_COMBO_0]; 186 | } 187 | diff_time = get_tick() - *diff_time_ptr; 188 | 189 | /* This is for purpose of test and timing validation */ 190 | if (diff_time > 2000) 191 | { 192 | diff_time = 0; 193 | } 194 | 195 | *diff_time_ptr = get_tick(); /* Set current date as last one */ 196 | 197 | /* Get event string */ 198 | if (evt == EBTN_EVT_KEEPALIVE) 199 | { 200 | s = "KEEPALIVE"; 201 | color = FOREGROUND_RED | FOREGROUND_BLUE; 202 | } 203 | else if (evt == EBTN_EVT_ONPRESS) 204 | { 205 | s = "ONPRESS"; 206 | color = FOREGROUND_GREEN; 207 | } 208 | else if (evt == EBTN_EVT_ONRELEASE) 209 | { 210 | s = "ONRELEASE"; 211 | color = FOREGROUND_BLUE; 212 | } 213 | else if (evt == EBTN_EVT_ONCLICK) 214 | { 215 | s = "ONCLICK"; 216 | color = FOREGROUND_RED | FOREGROUND_GREEN; 217 | } 218 | else 219 | { 220 | s = "UNKNOWN"; 221 | color = FOREGROUND_RED; 222 | } 223 | 224 | SetConsoleTextAttribute(hConsole, color); 225 | printf("[%7u][%6u] ID(hex):%4x, evt: %10s, keep-alive cnt: %3u, click cnt: %3u\r\n", (unsigned)get_tick(), (unsigned)diff_time, btn->key_id, s, 226 | (unsigned)ebtn_keepalive_get_count(btn), (unsigned)ebtn_click_get_count(btn)); 227 | SetConsoleTextAttribute(hConsole, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE); 228 | } 229 | 230 | /** 231 | * \brief Example function 232 | */ 233 | int example_user(void) 234 | { 235 | uint32_t time_last; 236 | printf("Application running\r\n"); 237 | QueryPerformanceFrequency(&freq); 238 | QueryPerformanceCounter(&sys_start_time); 239 | 240 | /* Define buttons */ 241 | ebtn_init(btns, EBTN_ARRAY_SIZE(btns), btns_combo, EBTN_ARRAY_SIZE(btns_combo), prv_btn_get_state, prv_btn_event); 242 | 243 | ebtn_combo_btn_add_btn(&btns_combo[0], USER_BUTTON_0); 244 | ebtn_combo_btn_add_btn(&btns_combo[0], USER_BUTTON_1); 245 | 246 | ebtn_combo_btn_add_btn(&btns_combo[1], USER_BUTTON_2); 247 | ebtn_combo_btn_add_btn(&btns_combo[1], USER_BUTTON_3); 248 | 249 | // dynamic register 250 | for (int i = 0; i < (EBTN_ARRAY_SIZE(btns_dyn)); i++) 251 | { 252 | ebtn_register(&btns_dyn[i]); 253 | } 254 | 255 | ebtn_combo_btn_add_btn(&btns_combo_dyn[0].btn, USER_BUTTON_4); 256 | ebtn_combo_btn_add_btn(&btns_combo_dyn[0].btn, USER_BUTTON_5); 257 | 258 | ebtn_combo_btn_add_btn(&btns_combo_dyn[1].btn, USER_BUTTON_6); 259 | ebtn_combo_btn_add_btn(&btns_combo_dyn[1].btn, USER_BUTTON_7); 260 | 261 | for (int i = 0; i < (EBTN_ARRAY_SIZE(btns_combo_dyn)); i++) 262 | { 263 | ebtn_combo_register(&btns_combo_dyn[i]); 264 | } 265 | 266 | while (1) 267 | { 268 | /* Process forever */ 269 | ebtn_process(get_tick()); 270 | 271 | /* Artificial sleep to offload win process */ 272 | Sleep(5); 273 | } 274 | return 0; 275 | } 276 | 277 | /** 278 | * \brief Get current tick in ms from start of program 279 | * \return uint32_t: Tick in ms 280 | */ 281 | static uint32_t get_tick(void) 282 | { 283 | LONGLONG ret; 284 | LARGE_INTEGER now; 285 | 286 | QueryPerformanceFrequency(&freq); 287 | QueryPerformanceCounter(&now); 288 | ret = now.QuadPart - sys_start_time.QuadPart; 289 | return (uint32_t)((ret * 1000) / freq.QuadPart); 290 | } 291 | -------------------------------------------------------------------------------- /main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "ebtn.h" 4 | #include "windows.h" 5 | 6 | extern int example_test(void); 7 | extern int example_user(void); 8 | 9 | int main(void) 10 | { 11 | // example_test(); 12 | example_user(); 13 | return 0; 14 | } 15 | --------------------------------------------------------------------------------