commit 1ec0a5e33683b3dd3fd978f8ba82f59866bb7c1a Author: wenbo13579 Date: Sat Feb 24 10:39:51 2024 +0800 first commit diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..ae691d2 --- /dev/null +++ b/.clang-format @@ -0,0 +1,101 @@ +# SPDX-License-Identifier: Apache-2.0 +# +# Note: The list of ForEachMacros can be obtained using: +# +# git grep -h '^#define [^[:space:]]*FOR_EACH[^[:space:]]*(' include/ \ +# | sed "s,^#define \([^[:space:]]*FOR_EACH[^[:space:]]*\)(.*$, - '\1'," \ +# | sort | uniq +# +# References: +# - https://clang.llvm.org/docs/ClangFormatStyleOptions.html + +--- +BasedOnStyle: LLVM +AlignConsecutiveMacros: AcrossComments +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortEnumsOnASingleLine: false +AllowShortFunctionsOnASingleLine: None +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false +AttributeMacros: + - __aligned + - __deprecated + - __packed + - __printf_like + - __syscall + - __subsystem +ColumnLimit: 160 +ConstructorInitializerIndentWidth: 8 +ContinuationIndentWidth: 8 +ForEachMacros: + - 'FOR_EACH' + - 'FOR_EACH_FIXED_ARG' + - 'FOR_EACH_IDX' + - 'FOR_EACH_IDX_FIXED_ARG' + - 'FOR_EACH_NONEMPTY_TERM' + - 'RB_FOR_EACH' + - 'RB_FOR_EACH_CONTAINER' + - 'SYS_DLIST_FOR_EACH_CONTAINER' + - 'SYS_DLIST_FOR_EACH_CONTAINER_SAFE' + - 'SYS_DLIST_FOR_EACH_NODE' + - 'SYS_DLIST_FOR_EACH_NODE_SAFE' + - 'SYS_SFLIST_FOR_EACH_CONTAINER' + - 'SYS_SFLIST_FOR_EACH_CONTAINER_SAFE' + - 'SYS_SFLIST_FOR_EACH_NODE' + - 'SYS_SFLIST_FOR_EACH_NODE_SAFE' + - 'SYS_SLIST_FOR_EACH_CONTAINER' + - 'SYS_SLIST_FOR_EACH_CONTAINER_SAFE' + - 'SYS_SLIST_FOR_EACH_NODE' + - 'SYS_SLIST_FOR_EACH_NODE_SAFE' + - '_WAIT_Q_FOR_EACH' + - 'Z_FOR_EACH' + - 'Z_FOR_EACH_ENGINE' + - 'Z_FOR_EACH_EXEC' + - 'Z_FOR_EACH_FIXED_ARG' + - 'Z_FOR_EACH_FIXED_ARG_EXEC' + - 'Z_FOR_EACH_IDX' + - 'Z_FOR_EACH_IDX_EXEC' + - 'Z_FOR_EACH_IDX_FIXED_ARG' + - 'Z_FOR_EACH_IDX_FIXED_ARG_EXEC' + - 'Z_GENLIST_FOR_EACH_CONTAINER' + - 'Z_GENLIST_FOR_EACH_CONTAINER_SAFE' + - 'Z_GENLIST_FOR_EACH_NODE' + - 'Z_GENLIST_FOR_EACH_NODE_SAFE' +SortIncludes: Never +IncludeBlocks: Regroup +IncludeCategories: + - 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>$' + Priority: 0 + - Regex: '.*' + Priority: 1 +IndentCaseLabels: false +IndentWidth: 4 +# SpaceBeforeParens: ControlStatementsExceptControlMacros # clang-format >= 13.0 +UseTab: Never +WhitespaceSensitiveMacros: + - STRINGIFY + - Z_STRINGIFY + +AlignAfterOpenBracket: Align +BraceWrapping: + AfterClass: true + AfterControlStatement: true + AfterEnum: true + AfterFunction: true + AfterNamespace: true + AfterObjCDeclaration: true + AfterStruct: true + AfterUnion: true + AfterCaseLabel: true + AfterExternBlock: false # Fix "extern "C"" + BeforeCatch: true + BeforeElse: true + BeforeWhile: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakBeforeBraces: Custom # Fix "extern "C"" +IndentExternBlock: AfterExternBlock # Not useful for "extern "C"" + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1f57b97 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/output diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json new file mode 100644 index 0000000..9f46445 --- /dev/null +++ b/.vscode/c_cpp_properties.json @@ -0,0 +1,21 @@ +{ + "configurations": [ + { + "name": "Win32", + "includePath": [ + "${workspaceFolder}/**" + ], + "defines": [ + "_DEBUG", + "UNICODE", + "_UNICODE" + ], + "compilerPath": "C:\\msys64\\mingw64\\bin\\gcc.exe", + "cStandard": "c99", + "cppStandard": "gnu++03", + "intelliSenseMode": "windows-gcc-x64", + "configurationProvider": "ms-vscode.makefile-tools" + } + ], + "version": 4 +} \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..ef2bc56 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,19 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Debug", + "type": "cppdbg", + "request": "launch", + "args": [], + "stopAtEntry": false, + "cwd": "${workspaceFolder}", + "environment": [], + "externalConsole": false, + "MIMode": "gdb", + "miDebuggerPath": "gdb.exe", + "program": "${workspaceFolder}/output/main.exe", + "preLaunchTask": "build" + } + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..b9e1699 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,11 @@ +{ + "files.associations": { + "bare_heap.h": "c", + "bare_task.h": "c", + "bare_list.h": "c", + "stddef.h": "c", + "bare_common.h": "c", + "stdlib.h": "c", + "windows.h": "c" + } +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..f12924e --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,84 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "build", + "type": "shell", + "group": { + "kind": "build", + "isDefault": true + }, + "windows": { + "command": "powershell", + "args": [ + "-c", + "make" + ] + }, + "linux": { + "command": "bash", + "args": [ + "-c", + "make" + ] + }, + "osx": { + "command": "bash", + "args": [ + "-c", + "make" + ] + } + }, + { + "label": "build & run", + "type": "shell", + "windows": { + "command": "powershell", + "args": [ + "-c", + "make run'" + ] + }, + "linux": { + "command": "bash", + "args": [ + "-c", + "'make run'" + ] + }, + "osx": { + "command": "bash", + "args": [ + "-c", + "'make run'" + ] + } + }, + { + "label": "clean", + "type": "shell", + "windows": { + "command": "powershell", + "args": [ + "-c", + "'make clean'" + ] + }, + "linux": { + "command": "bash", + "args": [ + "-c", + "'make clean'" + ] + }, + "osx": { + "command": "bash", + "args": [ + "-c", + "'make clean'" + ] + } + } + ] +} \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..8f71f43 --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..40b5787 --- /dev/null +++ b/Makefile @@ -0,0 +1,201 @@ +# +# 'make' build executable file 'main' +# 'make clean' removes all .o and executable files +# +.DEFAULT_GOAL := all + +# CROSS_COMPILE ?= arm-linux-gnueabihf- +CROSS_COMPILE ?= +TARGET ?= main + +# define the C compiler to use +CC := $(CROSS_COMPILE)gcc +LD := $(CROSS_COMPILE)ld +OBJCOPY := $(CROSS_COMPILE)objcopy +OBJDUMP := $(CROSS_COMPILE)objdump +SIZE := $(CROSS_COMPILE)size + +# define any compile-time flags +CFLAGS ?= +CFLAGS += -O0 +CFLAGS += -g + +# warning param setting +CFLAGS += -Wall +# think use -Os. +CFLAGS += -Wno-unused-function +CFLAGS += -Wno-unused-variable + +CFLAGS += -Wstrict-prototypes +CFLAGS += -Wshadow +# CFLAGS += -Werror + +# spec c version +CFLAGS += -std=c99 +# CFLAGS += -Wno-format + +# for makefile depend tree create +CFLAGS += -MMD -MP + +# for weak. +CFLAGS += -fno-common + +## MAKEFILE COMPILE MESSAGE CONTROL ## +ifeq ($(V),1) + Q= +else + Q=@ +endif + +# Put functions and data in their own binary sections so that ld can +# garbage collect them +ifeq ($(NOGC),1) +GC_CFLAGS = +GC_LDFLAGS = +else +GC_CFLAGS = -ffunction-sections -fdata-sections +GC_LDFLAGS = -Wl,--gc-sections -Wl,--check-sections +endif + + +CFLAGS += $(GC_CFLAGS) + +# define ld params +LDFLAGS := +LDFLAGS += $(GC_LDFLAGS) + + +# define library paths in addition to /usr/lib +# if I wanted to include libraries not in /usr/lib I'd specify +# their path using -Lpath, something like: +LFLAGS := + +# define output directory +OUTPUT_PATH := output + +# define source directory +SRC := + +# define include directory +INCLUDE := + +# define lib directory +LIB := + + +OUTPUT_TARGET := $(OUTPUT_PATH)/$(TARGET) + +OBJDIR = $(OUTPUT_PATH)/obj + + +# include user build.mk +include build.mk + + +ifeq ($(OS),Windows_NT) +ifdef ComSpec + WINCMD:=$(ComSpec) +endif +ifdef COMSPEC + WINCMD:=$(COMSPEC) +endif + +SHELL:=$(WINCMD) + +MAIN := $(TARGET).exe +ECHO=echo +SOURCEDIRS := $(SRC) +INCLUDEDIRS := $(INCLUDE) +LIBDIRS := $(LIB) +FIXPATH = $(subst /,\,$1) +RM := del /q /s +MD := mkdir +else +MAIN := $(TARGET) +ECHO=echo +SOURCEDIRS := $(shell find $(SRC) -type d) +INCLUDEDIRS := $(shell find $(INCLUDE) -type d) +LIBDIRS := $(shell find $(LIB) -type d) +FIXPATH = $1 +RM = rm -rf +MD := mkdir -p +endif + +# define any directories containing header files other than /usr/include +INCLUDES := $(patsubst %,-I%, $(INCLUDEDIRS:%/=%)) +@echo INCLUDES: $(INCLUDES) + +# define the C libs +LIBS := $(patsubst %,-L%, $(LIBDIRS:%/=%)) + +# define the C source files +SOURCES := $(wildcard $(patsubst %,%/*.c, $(SOURCEDIRS))) + +# define the C object files +OBJECTS := $(patsubst %, $(OBJDIR)/%, $(SOURCES:.c=.o)) +OBJ_MD := $(addprefix $(OBJDIR)/, $(SOURCEDIRS)) + + + +ALL_DEPS := $(OBJECTS:.o=.d) + +# include dependency files of application +ifneq ($(MAKECMDGOALS),clean) +-include $(ALL_DEPS) +endif + +# +# The following part of the makefile is generic; it can be used to +# build any executable just by changing the definitions above and by +# deleting dependencies appended to the file from 'make depend' +# + +OUTPUT_MAIN := $(OUTPUT_PATH)/$(MAIN) + +# Fix path error. +#OUTPUT_MAIN := $(call FIXPATH,$(OUTPUT_MAIN)) + +.PHONY: all clean + +all: main + @$(ECHO) Start Build Image. + $(OBJCOPY) -v -O binary $(OUTPUT_MAIN) $(OUTPUT_TARGET).bin + $(OBJDUMP) --source --all-headers --demangle --line-numbers --wide $(OUTPUT_MAIN) > $(OUTPUT_TARGET).lst + @$(ECHO) Print Size + $(Q)@$(SIZE) --format=berkeley $(OUTPUT_MAIN) +# @$(ECHO) Executing 'all' complete! + +# mk path for object. +$(OBJ_MD): + $(Q)if not exist "$@" $(Q)$(MD) $(call FIXPATH, $@) + +# mk output path. +$(OUTPUT_PATH): + $(Q)if not exist "$@" $(Q)$(MD) $(call FIXPATH, $@) + +$(OBJDIR): + $(Q)if not exist "$@" $(Q)$(MD) $(call FIXPATH, $@) + +$(OUTPUT_MAIN): $(OBJECTS) + @$(ECHO) Linking : "$@" + $(Q)$(CC) $(CFLAGS) $(LDFLAGS) $(INCLUDES) -Wl,-Map,$(OUTPUT_TARGET).map -o $(OUTPUT_MAIN) $(OBJECTS) $(LFLAGS) $(LIBS) $(OUTPUT_BT_LIB) + +main: | $(OUTPUT_PATH) $(OBJDIR) $(OBJ_MD) $(OUTPUT_MAIN) + @$(ECHO) Building : "$(OUTPUT_MAIN)" + +# Static Pattern Rules [targets ...: target-pattern: prereq-patterns ...] +$(OBJECTS): $(OBJDIR)/%.o : %.c + @$(ECHO) Compiling : "$<" + $(Q)$(CC) $(CFLAGS) $(INCLUDES) -c $< -o $@ + + +clean: +# $(RM) $(OUTPUT_MAIN) +# $(RM) $(OBJECTS) +# $(RM) $(OBJDIR) + $(Q)$(RM) $(call FIXPATH, $(OUTPUT_PATH)) + @$(ECHO) Cleanup complete! + +run: all + ./$(OUTPUT_MAIN) + @$(ECHO) Executing 'run: all' complete! diff --git a/README.md b/README.md new file mode 100644 index 0000000..0a0827b --- /dev/null +++ b/README.md @@ -0,0 +1,751 @@ +# 简介 + +在嵌入式裸机开发中,经常有按键的管理需求,GitHub上已经有蛮多成熟的按键驱动了,但是由于这样那样的问题,最终还是自己实现了一套。本项目地址:[bobwenstudy/easy_button (github.com)](https://github.com/bobwenstudy/easy_button)。 + +项目开发过程中参考了如下几个项目[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)。 + +其中核心的按键管理机制借鉴的是[lwbtn](https://github.com/MaJerle/lwbtn),并在其基础上做了比较多的改动,部分事件上报行为和原本处理有些不同。 + +## 对比分析 + +下面从几个维度来对比几个开源库的差异。 + +注意:分析纯属个人观点,如有异议请随时与我沟通。 + +| | [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) | +| ------------------------------- | --------------------------------------------------------- | ------------------------------------------------------------ | ----------------------------------------------------- | ----------------------------------------- | +| 最大支持按键数 | 无限 | 32 | 无限 | 无限 | +| 按键时间参数独立配置 | 支持 | 支持 | 部分支持 | 支持 | +| 单个按键RAM Size(Bytes) | 20(ebtn_btn_t) | 28(flex_button_t) | 44(Button) | 48(lwbtn_btn_t) | +| 支持组合按键 | 支持 | 不支持 | 不支持 | 不支持 | +| 支持静态注册(可以省Code Size) | 支持 | 不支持 | 不支持 | 支持 | +| 支持动态注册 | 支持 | 支持 | 支持 | 不支持 | +| 单击最大次数 | 无限 | 无限 | 2 | 无限 | +| 长按种类 | 无限 | 1 | 1 | 无限 | +| 批量扫描支持 | 支持 | 不支持 | 不支持 | 不支持 | + +可以看出easy_button功能是最全的,并且使用的RAM Size也是最小的,这个在键盘之类有很多按键场景下非常有意义。 + + + +## 组合按键支持 + +现有的项目基本都不支持组合按键,基本都是要求用户根据ID在应用层将多个按键作为一个ID来实现,虽然这样也能实现组合按键的功能需要。 + +但是这样的实现逻辑不够优雅,并且扫描按键行为的逻辑不可避免有重复的部分,增加了mips。 + +本项目基于[bit_array_static](https://github.com/bobwenstudy/bit_array_static)实现了优雅的组合按键处理机制,无需重复定义按键扫描逻辑,驱动会利用已经读取到的按键状态来实现组合按键的功能逻辑。 + + + +## 长按支持 + +实际项目中会遇到各种功能需求,如长按3s是功能A,长按5s是功能B,长按30s是功能C。通过`keepalive_cnt`和`time_keepalive_period`的设计,能够支持各种场景的长按功能需要。 + +如定义`time_keepalive_period=1000`,那么每隔1s会上报一个`KEEPALIVE(EBTN_EVT_KEEPALIVE)`事件,应用层在收到上报事件后,当`keepalive_cnt==3`时,执行功能A;当`keepalive_cnt==5`时,执行功能B;当`keepalive_cnt==30`时,执行功能C。 + + + +## 批量扫描支持 + +现有的按键库都是一个个按键扫描再单独处理,这个在按键比较少的时候,比较好管理,但是在多按键场景下,尤其是矩阵键盘下,这个会大大增加扫描延迟,通过批量扫描支持,可以先在用户层将所有按键状态记录好(用户层根据具体应用优化获取速度),而后一次性将当前状态传给(`ebtn_process_with_curr_state`)驱动。 + +嵌入式按键处理驱动,支持单击、双击、多击、自动消抖、长按、长长按、超长按 | 低功耗支持 | 组合按键支持 | 静态/动态注册支持 + + + +## 简易但灵活的事件类型 + +参考[lwbtn](https://github.com/MaJerle/lwbtn)实现,当有按键事件发生时,所上报的事件类型只有4种,通过`click_cnt`和`keepalive_cnt`来支持灵活的按键点击和长按功能需要,这样的设计大大简化了代码行为,也大大降低了后续维护成本。 + +如果用户觉得不好用,也可以在该驱动基础上再封装出自己所需的驱动。 + +```c +typedef enum +{ + EBTN_EVT_ONPRESS = 0x00, /*!< On press event - sent when valid press is detected */ + EBTN_EVT_ONRELEASE, /*!< On release event - sent when valid release event is detected (from + active to inactive) */ + EBTN_EVT_ONCLICK, /*!< On Click event - sent when valid sequence of on-press and on-release + events occurs */ + EBTN_EVT_KEEPALIVE, /*!< Keep alive event - sent periodically when button is active */ +} ebtn_evt_t; +``` + + + +## 关于低功耗 + +参考[Flexible Button](https://github.com/murphyzhao/FlexibleButton),本按键库是通过不间断扫描的方式来检查按键状态,因此会一直占用 CPU 资源,这对低功耗应用场景是不友好的。为了降低正常工作模式下的功耗,建议合理配置扫描周期(5ms - 20ms),扫描间隙里 CPU 可以进入轻度睡眠。 + +一般MCU都有深度睡眠模式,这是CPU只能被IO切换唤醒,所以驱动为大家提供了`int ebtn_is_in_process(void)`接口来判断是否可以进入深度睡眠模式。 + + + + + + +# 代码结构 + +代码结构如下所示: + +- **ebtn**:驱动库,主要包含BitArray管理和EasyButton管理。 +- **example_user.c**:捕获windows的0-9作为按键输入,测试用户交互的例程。 +- **example_test.c**:模拟一些场景的按键事件,对驱动进行测试。 +- **main.c**:程序主入口,配置进行测试模式函数用户交互模式。 +- **build.mk**和**Makefile**:Makefile编译环境。 +- **README.md**:说明文档 + +```shell +bare_task_msg + ├── ebtn + │ ├── bit_array.h + │ ├── ebtn.c + │ └── ebtn.h + ├── build.mk + ├── example_user.c + └── example_test.c + ├── main.c + ├── Makefile + └── README.md +``` + + + + + + + +# 使用说明 + +## 使用简易步骤 + +Step1:定义KEY_ID、按键参数和按键数组和组合按键数组。 + +```c + +typedef enum +{ + USER_BUTTON_0 = 0, + USER_BUTTON_1, + USER_BUTTON_2, + USER_BUTTON_3, + USER_BUTTON_4, + USER_BUTTON_5, + USER_BUTTON_6, + USER_BUTTON_7, + USER_BUTTON_8, + USER_BUTTON_9, + USER_BUTTON_MAX, + + USER_BUTTON_COMBO_0 = 0x100, + USER_BUTTON_COMBO_1, + USER_BUTTON_COMBO_2, + USER_BUTTON_COMBO_3, + USER_BUTTON_COMBO_MAX, +} user_button_t; + +/* User defined settings */ +static const ebtn_btn_param_t defaul_ebtn_param = EBTN_PARAMS_INIT(20, 0, 20, 300, 200, 500, 10); + +static ebtn_btn_t btns[] = { + EBTN_BUTTON_INIT(USER_BUTTON_0, &defaul_ebtn_param), + EBTN_BUTTON_INIT(USER_BUTTON_1, &defaul_ebtn_param), + EBTN_BUTTON_INIT(USER_BUTTON_2, &defaul_ebtn_param), + EBTN_BUTTON_INIT(USER_BUTTON_3, &defaul_ebtn_param), + EBTN_BUTTON_INIT(USER_BUTTON_4, &defaul_ebtn_param), + EBTN_BUTTON_INIT(USER_BUTTON_5, &defaul_ebtn_param), +}; + + +static ebtn_btn_combo_t btns_combo[] = { + EBTN_BUTTON_COMBO_INIT(USER_BUTTON_COMBO_0, &defaul_ebtn_param), + EBTN_BUTTON_COMBO_INIT(USER_BUTTON_COMBO_1, &defaul_ebtn_param), +}; +``` + +Step2:初始化按键驱动 + +```c +ebtn_init(btns, EBTN_ARRAY_SIZE(btns), btns_combo, EBTN_ARRAY_SIZE(btns_combo), + prv_btn_get_state, prv_btn_event); +``` + +Step3:配置组合按键comb_key,必须在按键注册完毕后再配置,不然需要`ebtn_combo_btn_add_btn_by_idx`用这个接口。 + +```c +ebtn_combo_btn_add_btn(&btns_combo[0], USER_BUTTON_0); +ebtn_combo_btn_add_btn(&btns_combo[0], USER_BUTTON_1); + +ebtn_combo_btn_add_btn(&btns_combo[1], USER_BUTTON_2); +ebtn_combo_btn_add_btn(&btns_combo[1], USER_BUTTON_3); +``` + +Step4:动态注册所需按键,并配置comb_key。 + +```c +// dynamic register +for (int i = 0; i < (EBTN_ARRAY_SIZE(btns_dyn)); i++) +{ + ebtn_register(&btns_dyn[i]); +} + +ebtn_combo_btn_add_btn(&btns_combo_dyn[0].btn, USER_BUTTON_4); +ebtn_combo_btn_add_btn(&btns_combo_dyn[0].btn, USER_BUTTON_5); + +ebtn_combo_btn_add_btn(&btns_combo_dyn[1].btn, USER_BUTTON_6); +ebtn_combo_btn_add_btn(&btns_combo_dyn[1].btn, USER_BUTTON_7); + +for (int i = 0; i < (EBTN_ARRAY_SIZE(btns_combo_dyn)); i++) +{ + ebtn_combo_register(&btns_combo_dyn[i]); +} +``` + +Step5:启动按键扫描,具体实现可以用定时器做,也可以启任务或者轮询处理。需要注意需要将当前系统时钟`get_tick()`传给驱动接口`ebtn_process`。 + +```c +while (1) +{ + /* Process forever */ + ebtn_process(get_tick()); + + /* Artificial sleep to offload win process */ + Sleep(5); +} +``` + +具体可以参考`example_win32.c`和`test.c`的实现。 + + + + + +## key_id和key_idx的说明 + +为了更好的实现**组合按键**以及**批量扫描**的支持,驱动引入了BitArray来管理按键的历史状态和组合按键信息。这样就间接引入了key_index的概念,其代表独立按键在驱动的位置,该值不可直接设置,是按照一定规则隐式定义的。 + +key_id是用户定义的,用于标识按键的,该值可以随意更改,但是尽量保证该值独立。 + +如下图所示,驱动有2个静态注册的按键,还有3个动态注册的按键。每个按键的key_id是随意定义的,但是key_idx却是驱动内部隐式定义的,先是静态数组,而后按照动态数组顺先依次定义。 + +**注意**:由于组合按键也会用到key_idx的信息,所以动态按键目前并不提供删除按键的行为,这个可能引发一些风险。 + +![image-20240223103317921](https://markdown-1306347444.cos.ap-shanghai.myqcloud.com/img/image-20240223103317921.png) + + + +## 结构体说明 + +### 按键配置参数结构体说明-ebtn_btn_param_t + +按键根据不同的时间触发不同的事件,目前每个按键可以配置的参数如下。 + +| 名称 | 说明 | +| ---------------------- | ------------------------------------------------------------ | +| time_debounce | 防抖处理,按下防抖超时,配置为0,代表不启动 | +| time_debounce_release | 防抖处理,松开防抖超时,配置为0,代表不启动 | +| time_click_pressed_min | 按键超时处理,按键最短时间,配置为0,代表不检查最小值 | +| time_click_pressed_max | 按键超时处理,按键最长时间,配置为0xFFFF,代表不检查最大值,用于区分长按和按键事件。 | +| time_click_multi_max | 多击处理,两个按键之间认为是连击的超时时间 | +| time_keepalive_period | 长按处理,长按周期,每个周期增加keepalive_cnt计数 | +| max_consecutive | 最大连击次数,配置为0,代表不进行连击检查。 | + + + +```c +typedef struct ebtn_btn_param +{ + /** + * \brief Minimum debounce time for press event in units of milliseconds + * + * This is the time when the input shall have stable active level to detect valid *onpress* + * event. + * + * When value is set to `> 0`, input must be in active state for at least + * minimum milliseconds time, before valid *onpress* event is detected. + * + * \note If value is set to `0`, debounce is not used and *press* event will be + * triggered immediately when input states goes to *inactive* state. + * + * To be safe not using this feature, external logic must ensure stable + * transition at input level. + * + */ + uint16_t time_debounce; /*!< Debounce time in milliseconds */ + + /** + * \brief Minimum debounce time for release event in units of milliseconds + * + * This is the time when the input shall have minimum stable released level to detect valid + * *onrelease* event. + * + * This setting can be useful if application wants to protect against + * unwanted glitches on the line when input is considered "active". + * + * When value is set to `> 0`, input must be in inactive low for at least + * minimum milliseconds time, before valid *onrelease* event is detected + * + * \note If value is set to `0`, debounce is not used and *release* event will be + * triggered immediately when input states goes to *inactive* state + * + */ + uint16_t time_debounce_release; /*!< Debounce time in milliseconds for release event */ + + /** + * \brief Minimum active input time for valid click event, in milliseconds + * + * Input shall be in active state (after debounce) at least this amount of time to even consider + * the potential valid click event. Set the value to `0` to disable this feature + * + */ + uint16_t time_click_pressed_min; /*!< Minimum pressed time for valid click event */ + + /** + * \brief Maximum active input time for valid click event, in milliseconds + * + * Input shall be pressed at most this amount of time to still trigger valid click. + * Set to `-1` to allow any time triggering click event. + * + * When input is active for more than the configured time, click even is not detected and is + * ignored. + * + */ + uint16_t time_click_pressed_max; /*!< Maximum pressed time for valid click event*/ + + /** + * \brief Maximum allowed time between last on-release and next valid on-press, + * to still allow multi-click events, in milliseconds + * + * This value is also used as a timeout length to send the *onclick* event to application from + * previously detected valid click events. + * + * If application relies on multi consecutive clicks, this is the max time to allow user + * to trigger potential new click, or structure will get reset (before sent to user if any + * clicks have been detected so far) + * + */ + uint16_t time_click_multi_max; /*!< Maximum time between 2 clicks to be considered consecutive + click */ + + /** + * \brief Keep-alive event period, in milliseconds + * + * When input is active, keep alive events will be sent through this period of time. + * First keep alive will be sent after input being considered + * active. + * + */ + uint16_t time_keepalive_period; /*!< Time in ms for periodic keep alive event */ + + /** + * \brief Maximum number of allowed consecutive click events, + * before structure gets reset to default value. + * + * \note When consecutive value is reached, application will get notification of + * clicks. This can be executed immediately after last click has been detected, or after + * standard timeout (unless next on-press has already been detected, then it is send to + * application just before valid next press event). + * + */ + uint16_t max_consecutive; /*!< Max number of consecutive clicks */ +} ebtn_btn_param_t; +``` + + + + + +### 按键控制结构体说明-ebtn_btn_t + +每个按键有一个管理结构体,用于记录按键当前状态,按键参数等信息。 + +| 名称 | 说明 | +| ------------------- | ------------------------------------------------------------ | +| key_id | 用户定义的key_id信息,该值建议唯一 | +| flags | 用于记录一些状态,目前只支持`EBTN_FLAG_ONPRESS_SENT`和`EBTN_FLAG_IN_PROCESS` | +| time_change | 记录按键按下或者松开状态的时间点 | +| time_state_change | 记录按键状态切换时间点(并不考虑防抖,单纯记录状态切换时间点) | +| keepalive_last_time | 长按最后一次上报长按时间的时间点,用于管理keepalive_cnt | +| click_last_time | 点击最后一次松开状态的时间点,用于管理click_cnt | +| keepalive_cnt | 长按的KEEP_ALIVE次数 | +| click_cnt | 多击的次数 | +| param | 按键时间参数,指向ebtn_btn_param_t,方便节省RAM,并且多个按键可公用一组参数 | + + + + + +```c +typedef struct ebtn_btn +{ + uint16_t key_id; /*!< User defined custom argument for callback function purpose */ + uint16_t flags; /*!< Private button flags management */ + ebtn_time_t time_change; /*!< Time in ms when button state got changed last time after valid + debounce */ + ebtn_time_t time_state_change; /*!< Time in ms when button state got changed last time */ + + ebtn_time_t keepalive_last_time; /*!< Time in ms of last send keep alive event */ + ebtn_time_t + click_last_time; /*!< Time in ms of last successfully detected (not sent!) click event + */ + + uint16_t keepalive_cnt; /*!< Number of keep alive events sent after successful on-press + detection. Value is reset after on-release */ + uint16_t click_cnt; /*!< Number of consecutive clicks detected, respecting maximum timeout + between clicks */ + + const ebtn_btn_param_t *param; +} ebtn_btn_t; +``` + + + + + +### 组合按键控制结构体说明-ebtn_btn_combo_t + +每个组合按键有一个管理结构体,用于记录组合按键组合配置参数,以及按键信息。 + +| 名称 | 说明 | +| -------- | --------------------------------- | +| comb_key | 用独立按键的key_idx设置的BitArray | +| btn | ebtn_btn_t管理对象,管理按键状态 | + + + +```c +typedef struct ebtn_btn_combo +{ + BIT_ARRAY_DEFINE( + comb_key, + EBTN_MAX_KEYNUM); /*!< select key index - `1` means active, `0` means inactive */ + + ebtn_btn_t btn; +} ebtn_btn_combo_t; +``` + + + + + +### 动态注册按键控制结构体说明-ebtn_btn_dyn_t + +动态注册需要维护一个列表,所以需要一个next指针。 + +| 名称 | 说明 | +| ---- | -------------------------------- | +| next | 用于链表链接每个节点 | +| btn | ebtn_btn_t管理对象,管理按键状态 | + + + +```c +typedef struct ebtn_btn_dyn +{ + struct ebtn_btn_dyn *next; + + ebtn_btn_t btn; +} ebtn_btn_dyn_t; +``` + + + + + +### 动态注册组合按键控制结构体说明-ebtn_btn_combo_dyn_t + +动态注册需要维护一个列表,所以需要一个next指针。 + +| 名称 | 说明 | +| ---- | ------------------------------------------ | +| next | 用于链表链接每个节点 | +| btn | ebtn_btn_combo_t管理对象,管理组合按键状态 | + + + +```c +typedef struct ebtn_btn_combo_dyn +{ + struct ebtn_btn_combo_dyn *next; /*!< point to next combo-button */ + + ebtn_btn_combo_t btn; +} ebtn_btn_combo_dyn_t; +``` + + + + + + + +### 按键驱动管理结构体-ebtn_t + +按键驱动需要管理所有静态注册和动态注册的按键和组合按键信息,并且记录接口以及最后的按键状态。 + +| 名称 | 说明 | +| ------------------ | ------------------------------ | +| btns | 管理静态注册按键的指针 | +| btns_cnt | 记录静态注册按键的个数 | +| btns_combo | 管理静态注册组合按键的指针 | +| btns_combo_cnt | 记录静态注册组合按键的个数 | +| btn_dyn_head | 管理动态注册按键的列表指针 | +| btn_combo_dyn_head | 管理动态注册组合按键的列表指针 | +| evt_fn | 事件上报的回调接口 | +| get_state_fn | 按键状态获取的回调接口 | +| old_state | 记录按键上一次状态 | + + + +```c +typedef struct ebtn +{ + ebtn_btn_t *btns; /*!< Pointer to buttons array */ + uint16_t btns_cnt; /*!< Number of buttons in array */ + ebtn_btn_combo_t *btns_combo; /*!< Pointer to comb-buttons array */ + uint16_t btns_combo_cnt; /*!< Number of comb-buttons in array */ + + ebtn_btn_dyn_t *btn_dyn_head; /*!< Pointer to btn-dynamic list */ + ebtn_btn_combo_dyn_t *btn_combo_dyn_head; /*!< Pointer to btn-combo-dynamic list */ + + ebtn_evt_fn evt_fn; /*!< Pointer to event function */ + ebtn_get_state_fn get_state_fn; /*!< Pointer to get state function */ + + BIT_ARRAY_DEFINE( + old_state, + EBTN_MAX_KEYNUM); /*!< Old button state - `1` means active, `0` means inactive */ +} ebtn_t; +``` + + + +## 操作API + + + +### 核心API + +主要的就是初始化和运行接口,加上动态注册接口。 + +```c +void ebtn_process(ebtn_time_t mstime); +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); +int ebtn_register(ebtn_btn_dyn_t *button); +int ebtn_combo_register(ebtn_btn_combo_dyn_t *button); +``` + + + +### 组合按键注册key的API + +用于给组合按键绑定btn使用,最终都是关联到`key_idx`上。 + +**注意**,`key_id`注册接口必需先确保对应的Button已经注册到驱动中。 + +```c +void ebtn_combo_btn_add_btn_by_idx(ebtn_btn_combo_t *btn, int idx); +void ebtn_combo_btn_remove_btn_by_idx(ebtn_btn_combo_t *btn, int idx); +void ebtn_combo_btn_add_btn(ebtn_btn_combo_t *btn, uint16_t key_id); +void ebtn_combo_btn_remove_btn(ebtn_btn_combo_t *btn, uint16_t key_id); +``` + + + +### 其他API + +一些工具函数,按需使用。 + +```c +void ebtn_process_with_curr_state(bit_array_t *curr_state, ebtn_time_t mstime); + +int ebtn_get_total_btn_cnt(void); +int ebtn_get_btn_index_by_key_id(uint16_t key_id); +ebtn_btn_t *ebtn_get_btn_by_key_id(uint16_t key_id); +int ebtn_get_btn_index_by_btn(ebtn_btn_t *btn); +int ebtn_get_btn_index_by_btn_dyn(ebtn_btn_dyn_t *btn); + +int ebtn_is_btn_active(const ebtn_btn_t *btn); +int ebtn_is_btn_in_process(const ebtn_btn_t *btn); +int ebtn_is_in_process(void); +``` + + + +其中`ebtn_is_in_process()`可以用于超低功耗业务场景,这时候MCU只有靠IO翻转唤醒。 + + + + + +# 按键核心处理逻辑说明 + +这里参考[用户手册 — LwBTN 文档 (majerle.eu)](https://docs.majerle.eu/projects/lwbtn/en/latest/user-manual/index.html#how-it-works)对本驱动的按键实现机制进行说明。 + +在驱动运行中,应用程序可以会接收到如下事件: + +- `EBTN_EVT_ONPRESS`(简称:`ONPRESS`),每当输入从非活动状态变为活动状态并且最短去抖动时间过去时,都会将事件发送到应用程序 +- `EBTN_EVT_ONRELEASE`(简称:`ONRELEASE`),每当输入发送 `ONPRESS`事件时,以及当输入从活动状态变为非活动状态时,都会将事件发送到应用程序 +- `EBTN_EVT_KEEPALIVE`(简称:`KEEPALIVE`),事件在 `ONPRESS` 和`ONRELEASE`事件之间定期发送 +- `EBTN_EVT_ONCLICK`(简称:`ONCLICK`),事件在`ONRELEASE`后发送,并且仅当活动按钮状态在有效单击事件的允许窗口内时发送。 + +## ONPRESS事件 + +`ONPRESS` 事件是检测到按键处于活动状态时的第一个事件。 由于嵌入式系统的性质和连接到设备的各种按钮,有必要过滤掉潜在的噪音,以忽略无意的多次按下。 这是通过检查输入至少在一些最短时间内处于稳定水平来完成的,通常称为*消抖时间*,通常需要大约`20ms` 。 + +按键*消抖时间*分为按下消抖时间`time_debounce`和松开消抖时间`time_debounce_release`。 + +![image-20240223135908798](https://markdown-1306347444.cos.ap-shanghai.myqcloud.com/img/image-20240223135908798.png) + +## ONRELEASE事件 + +当按键从活动状态变为非活动状态时,才会立即触发 `ONRELEASE`事件,前提是在此之前检测到`ONPRESS` 事件。也就是 `ONRELEASE`事件是伴随着`ONPRESS` 事件发生的。 + +![image-20240223143215840](https://markdown-1306347444.cos.ap-shanghai.myqcloud.com/img/image-20240223143215840.png) + +## ONCLICK事件 + +`ONCLICK`事件在多个事件组合后触发: + +- 应正确检测到`ONPRESS` 事件,表示按钮已按下 +- 应检测到`ONRELEASE`事件,表示按钮已松开 +- `ONPRESS`和`ONRELEASE`事件之间的时间必须在时间窗口内,也就是在`time_click_pressed_min`和`time_click_pressed_max`之间时。 + +当满足条件时,在`ONRELEASE`事件之后的`time_click_multi_max`时间,发送`ONCLICK`事件。 + +![image-20240223143426179](https://markdown-1306347444.cos.ap-shanghai.myqcloud.com/img/image-20240223143426179.png) + +下面显示了在 Windows 测试下的单击事件演示。 + +![image-20240223173405665](https://markdown-1306347444.cos.ap-shanghai.myqcloud.com/img/image-20240223173405665.png) + + + + + +## Multi-Click事件 + +实际需求除了单击需求外,还需要满足多击需求。本驱动是靠`time_click_multi_max`来满足此功能,虽然有多次点击,但是只发送**一次 `ONCLICK`事件**。 + +注意:想象一下,有一个按钮可以在单击时切换一盏灯,并在双击时关闭房间中的所有灯。 通过超时功能和单次点击通知,用户将只收到**一次点击**,并且会根据连续按压次数值,来执行适当的操作。 + +下面是**Multi-Click**的简化图,忽略了消抖时间。`click_cnt`表示检测到的**Multi-Click** 事件数,将在最终的`ONCLICK`事件中上报。 + +需要注意前一个按键的`ONRELEASE`事件和下次的`ONPRESS`事件间隔时间应小于`time_click_multi_max`,`ONCLICK`事件会在最后一次按键的`ONRELEASE`事件之后`time_click_multi_max`时间上报。 + +![image-20240223144701688](https://markdown-1306347444.cos.ap-shanghai.myqcloud.com/img/image-20240223144701688.png) + +下面显示了在 Windows 测试下的三击事件演示。 + +![image-20240223173435824](https://markdown-1306347444.cos.ap-shanghai.myqcloud.com/img/image-20240223173435824.png) + + + +## KEEPALIVE事件 + +`KEEPALIVE`事件在 `ONPRESS`事件和`ONRELEASE`事件之间定期发送,它可用于长按处理,根据过程中有多少`KEEPALIVE`事件以及`time_keepalive_period`可以实现各种复杂的长按功能需求。 + +需要注意这里根据配置的时间参数的不同,可能会出现`KEEPALIVE`事件和`ONCLICK`事件在一次按键事件都上报的情况。这个情况一般发生在按下保持时间(`ONPRESS`事件和`ONRELEASE`事件之间)大于`time_keepalive_period`却小于`time_click_pressed_max`的场景下。 + +![image-20240223173042135](https://markdown-1306347444.cos.ap-shanghai.myqcloud.com/img/image-20240223173042135.png) + +下面显示了在 Windows 测试下的`KEEPALIVE`事件和`ONCLICK`事件在一次按键事件出现的演示。 + +![image-20240223173002558](https://markdown-1306347444.cos.ap-shanghai.myqcloud.com/img/image-20240223173002558.png) + + + + + +而当按下保持时间大于`time_click_pressed_max`时,就不会上报`ONCLICK`事件,如下图所示。 + +![image-20240223145802203](https://markdown-1306347444.cos.ap-shanghai.myqcloud.com/img/image-20240223145802203.png) + + + +## 其他边界场景 + +在`example_test.c`中对一些场景进行了覆盖性测试,具体可以看代码实现,测试都符合预期。 + +**注意**:time_overflow相关的case需要`EBTN_CONFIG_TIMER_16`宏,不然测试时间太长了。 + + + + + + + +# 测试说明 + +## 环境搭建 + +目前测试暂时只支持Windows编译,最终生成exe,可以直接在PC上跑。 + +目前需要安装如下环境: +- GCC环境,笔者用的msys64+mingw,用于编译生成exe,参考这个文章安装即可。[Win7下msys64安装mingw工具链 - Milton - 博客园 (cnblogs.com)](https://www.cnblogs.com/milton/p/11808091.html)。 + + +## 编译说明 + +本项目都是由makefile组织编译的,编译整个项目只需要执行`make all`即可。 + + +也就是可以通过如下指令来编译工程: + +```shell +make all +``` + +而后运行执行`make run`即可运行例程,例程中实现了2个Task的消息管理,并实现了Task Save的相关逻辑。 + +```shell +PS D:\workspace\github\bare_task_msg> make run +Building : "output/main.exe" +Start Build Image. +objcopy -v -O binary output/main.exe output/main.bin +copy from `output/main.exe' [pei-i386] to `output/main.bin' [binary] +objdump --source --all-headers --demangle --line-numbers --wide output/main.exe > output/main.lst +Print Size + text data bss dec hex filename + 41452 7040 2644 51136 c7c0 output/main.exe +./output/main.exe +Heap Remain Size: 0xff8 +Task Start Work! +user_task1(), id: 0x1, len: 0 +BARE_TASK_HDL_SAVED +user_task2(), id: 0x8, len: 0 +user_task2(), id: 0x9, len: 0 +user_task2(), id: 0x50, len: 20 +0x0:0x1:0x2:0x3:0x4:0x5:0x6:0x7:0x8:0x9:0xa:0xb:0xc:0xd:0xe:0xf:0x10:0x11:0x12:0x13: +BARE_TASK_HDL_SAVED +user_task1(), id: 0x1, len: 0 +user_task1(), id: 0x2, len: 0 +user_task1(), id: 0x10, len: 10 +0x0:0x1:0x2:0x3:0x4:0x5:0x6:0x7:0x8:0x9: +user_task1(), id: 0x11, len: 10 +0x10:0x11:0x12:0x13:0x14:0x15:0x16:0x17:0x18:0x19: +user_task2(), id: 0x50, len: 20 +0x0:0x1:0x2:0x3:0x4:0x5:0x6:0x7:0x8:0x9:0xa:0xb:0xc:0xd:0xe:0xf:0x10:0x11:0x12:0x13: +user_task2(), id: 0x51, len: 20 +0x10:0x11:0x12:0x13:0x14:0x15:0x16:0x17:0x18:0x19:0x1a:0x1b:0x1c:0x1d:0x1e:0x1f:0x20:0x21:0x22:0x23: +user_task2(), id: 0x51, len: 100 +0x50:0x51:0x52:0x53:0x54:0x55:0x56:0x57:0x58:0x59:0x5a:0x5b:0x5c:0x5d:0x5e:0x5f:0x60:0x61:0x62:0x63:0x64:0x65:0x66:0x67:0x68:0x69:0x6a:0x6b:0x6c:0x6d:0x6e:0x6f:0x70:0x71:0x72:0x73:0x74:0x75:0x76:0x77:0x78:0x79:0x7a:0x7b:0x7c:0x7d:0x7e:0x7f:0x80:0x81:0x82:0x83:0x84:0x85:0x86:0x87:0x88:0x89:0x8a:0x8b:0x8c:0x8d:0x8e:0x8f:0x90:0x91:0x92:0x93:0x94:0x95:0x96:0x97:0x98:0x99:0x9a:0x9b:0x9c:0x9d:0x9e:0x9f:0xa0:0xa1:0xa2:0xa3:0xa4:0xa5:0xa6:0xa7:0xa8:0xa9:0xaa:0xab:0xac:0xad:0xae:0xaf:0xb0:0xb1:0xb2:0xb3: +Task End Work! +Heap Remain Size: 0xff8 +Executing 'run: all' complete! +``` + +可以看到,2个task同时支持消息处理,当有Save的场景会pending消息,先执行另外一个task,task执行完所有消息后,heap保持不变。 + + + + + + + + + + + + + diff --git a/README.vsdx b/README.vsdx new file mode 100644 index 0000000..e3c6740 Binary files /dev/null and b/README.vsdx differ diff --git a/build.mk b/build.mk new file mode 100644 index 0000000..dc2ae5e --- /dev/null +++ b/build.mk @@ -0,0 +1,5 @@ +SRC += . +SRC += ebtn + +INCLUDE += . +INCLUDE += ebtn diff --git a/code_format.py b/code_format.py new file mode 100644 index 0000000..48a9092 --- /dev/null +++ b/code_format.py @@ -0,0 +1,27 @@ +import os +import subprocess +import re +import sys + +def format_all_file(root): + for root, dirs, files in os.walk(root): + + # root 表示当前正在访问的文件夹路径 + # dirs 表示该文件夹下的子目录名list + # files 表示该文件夹下的文件list + + # 遍历文件 + for f in files: + if f.endswith('.c') or f.endswith('.h'): + full_path = os.path.join(root, f) + #print("root: %s, path: %s" % (root, full_path)) + print(full_path) + + command = 'clang-format -style=file -i %s' % full_path + #print(command) + proc = subprocess.run(command, shell=True, stdout=subprocess.PIPE) + +if __name__ == '__main__': + format_all_file('.') + + #proc = subprocess.run(command, shell=True, stdout=subprocess.PIPE) diff --git a/ebtn/bit_array.h b/ebtn/bit_array.h new file mode 100644 index 0000000..06070d3 --- /dev/null +++ b/ebtn/bit_array.h @@ -0,0 +1,597 @@ +#ifndef _BIT_ARRAY_H_ +#define _BIT_ARRAY_H_ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +// #define BIT_ARRAY_CONFIG_64 + +// here can change to uint64_t, if you system is 64bit. +#ifdef BIT_ARRAY_CONFIG_64 +typedef uint64_t bit_array_t; +#define BIT_ARRAY_BIT(n) (1ULL << (n)) +#else +typedef uint32_t bit_array_t; +#define BIT_ARRAY_BIT(n) (1UL << (n)) +#endif +typedef bit_array_t bit_array_val_t; + +#define BIT_ARRAY_BITS (sizeof(bit_array_val_t) * 8) + +#define BIT_ARRAY_BIT_WORD(bit) ((bit) / BIT_ARRAY_BITS) +#define BIT_ARRAY_BIT_INDEX(bit) ((bit_array_val_t)(bit) & (BIT_ARRAY_BITS - 1U)) + +#define BIT_ARRAY_MASK(bit) BIT_ARRAY_BIT(BIT_ARRAY_BIT_INDEX(bit)) +#define BIT_ARRAY_ELEM(addr, bit) ((addr)[BIT_ARRAY_BIT_WORD(bit)]) + +// word of all 1s +#define BIT_ARRAY_WORD_MAX (~(bit_array_val_t)0) + +#define BIT_ARRAY_SUB_MASK(nbits) ((nbits) ? BIT_ARRAY_WORD_MAX >> (BIT_ARRAY_BITS - (nbits)) : (bit_array_val_t)0) + +// A possibly faster way to combine two words with a mask +// #define bitmask_merge(a,b,abits) ((a & abits) | (b & ~abits)) +#define bitmask_merge(a, b, abits) (b ^ ((a ^ b) & abits)) + +/** + * @brief This macro computes the number of bit array variables necessary to + * represent a bitmap with @a num_bits. + * + * @param num_bits Number of bits. + */ +#define BIT_ARRAY_BITMAP_SIZE(num_bits) (1 + ((num_bits)-1) / BIT_ARRAY_BITS) + +/** + * @brief Define an array of bit array variables. + * + * This macro defines an array of bit array variables containing at least + * @a num_bits bits. + * + * @note + * If used from file scope, the bits of the array are initialized to zero; + * if used from within a function, the bits are left uninitialized. + * + * @cond INTERNAL_HIDDEN + * @note + * This macro should be replicated in the PREDEFINED field of the documentation + * Doxyfile. + * @endcond + * + * @param name Name of array of bit array variables. + * @param num_bits Number of bits needed. + */ +#define BIT_ARRAY_DEFINE(name, num_bits) bit_array_t name[BIT_ARRAY_BITMAP_SIZE(num_bits)] + +#if 1 +// See http://graphics.stanford.edu/~seander/bithacks.html#CountBitsSetParallel +static inline bit_array_val_t _windows_popcount(bit_array_val_t w) +{ + w = w - ((w >> 1) & (bit_array_val_t) ~(bit_array_val_t)0 / 3); + 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); + w = (w + (w >> 4)) & (bit_array_val_t) ~(bit_array_val_t)0 / 255 * 15; + return (bit_array_val_t)(w * ((bit_array_val_t) ~(bit_array_val_t)0 / 255)) >> (sizeof(bit_array_val_t) - 1) * 8; +} + +#define POPCOUNT(x) _windows_popcount(x) +#else +#define POPCOUNT(x) (unsigned)__builtin_popcountll(x) +#endif + +#define bits_in_top_word(nbits) ((nbits) ? BIT_ARRAY_BIT_INDEX((nbits)-1) + 1 : 0) + +static inline void _bit_array_mask_top_word(bit_array_t *target, int num_bits) +{ + // Mask top word + int num_of_words = BIT_ARRAY_BITMAP_SIZE(num_bits); + int bits_active = bits_in_top_word(num_bits); + target[num_of_words - 1] &= BIT_ARRAY_SUB_MASK(bits_active); +} + +/** + * @brief Bit Array test a bit. + * + * This routine tests whether bit number @a bit of @a target is set or not. + * + * @param target Address of bit array variable or array. + * @param bit Bit number (starting from 0). + * + * @return true if the bit was set, false if it wasn't. + */ +static inline int bit_array_get(const bit_array_t *target, int bit) +{ + bit_array_val_t val = BIT_ARRAY_ELEM(target, bit); + + return (1 & (val >> (bit & (BIT_ARRAY_BITS - 1)))) != 0; +} + +/** + * @brief Bit Array clear a bit. + * + * Bit Array clear bit number @a bit of @a target. + * + * @param target Address of bit array variable or array. + * @param bit Bit number (starting from 0). + */ +static inline void bit_array_clear(bit_array_t *target, int bit) +{ + bit_array_val_t mask = BIT_ARRAY_MASK(bit); + + BIT_ARRAY_ELEM(target, bit) &= ~mask; +} + +/** + * @brief Bit Array set a bit. + * + * Bit Array set bit number @a bit of @a target. + * + * @param target Address of bit array variable or array. + * @param bit Bit number (starting from 0). + */ +static inline void bit_array_set(bit_array_t *target, int bit) +{ + bit_array_val_t mask = BIT_ARRAY_MASK(bit); + + BIT_ARRAY_ELEM(target, bit) |= mask; +} + +/** + * @brief Bit Array toggle a bit. + * + * Bit Array toggle bit number @a bit of @a target. + * + * @param target Address of bit array variable or array. + * @param bit Bit number (starting from 0). + */ +static inline void bit_array_toggle(bit_array_t *target, int bit) +{ + bit_array_val_t mask = BIT_ARRAY_MASK(bit); + + BIT_ARRAY_ELEM(target, bit) ^= mask; +} + +/** + * @brief Bit Array set a bit to a given value. + * + * Bit Array set bit number @a bit of @a target to value @a val. + * + * @param target Address of bit array variable or array. + * @param bit Bit number (starting from 0). + * @param val true for 1, false for 0. + */ +static inline void bit_array_assign(bit_array_t *target, int bit, int val) +{ + bit_array_val_t mask = BIT_ARRAY_MASK(bit); + + if (val) + { + BIT_ARRAY_ELEM(target, bit) |= mask; + } + else + { + BIT_ARRAY_ELEM(target, bit) &= ~mask; + } +} + +static inline void bit_array_clear_all(bit_array_t *target, int num_bits) +{ + memset((void *)target, 0, BIT_ARRAY_BITMAP_SIZE(num_bits) * sizeof(bit_array_val_t)); +} + +static inline void bit_array_set_all(bit_array_t *target, int num_bits) +{ + memset((void *)target, 0xff, BIT_ARRAY_BITMAP_SIZE(num_bits) * sizeof(bit_array_val_t)); + _bit_array_mask_top_word(target, num_bits); +} + +static inline void bit_array_toggle_all(bit_array_t *target, int num_bits) +{ + for (int i = 0; i < BIT_ARRAY_BITMAP_SIZE(num_bits); i++) + { + target[i] ^= BIT_ARRAY_WORD_MAX; + } + _bit_array_mask_top_word(target, num_bits); +} + +// +// Strings and printing +// + +// Construct a BIT_ARRAY from a substring with given on and off characters. + +// From string method +static inline void bit_array_from_str(bit_array_t *bitarr, const char *str) +{ + int i, index; + int space = 0; + int len = strlen(str); + + for (i = 0; i < len; i++) + { + index = i - space; + if (strchr("1", str[i]) != NULL) + { + bit_array_set(bitarr, index); + } + else if (strchr("0", str[i]) != NULL) + { + bit_array_clear(bitarr, index); + } + else + { + // error. + space++; + } + } +} + +// Takes a char array to write to. `str` must be bitarr->num_of_bits+1 in length +// Terminates string with '\0' +static inline char *bit_array_to_str(const bit_array_t *bitarr, int num_bits, char *str) +{ + int i; + + for (i = 0; i < num_bits; i++) + { + str[i] = bit_array_get(bitarr, i) ? '1' : '0'; + } + + str[num_bits] = '\0'; + + return str; +} + +// Takes a char array to write to. `str` must be bitarr->num_of_bits+1 in length +// Terminates string with '\0' +static inline char *bit_array_to_str_8(const bit_array_t *bitarr, int num_bits, char *str) +{ + int i; + int space = 0; + + for (i = 0; i < num_bits; i++) + { + str[i + space] = bit_array_get(bitarr, i) ? '1' : '0'; + + if ((i + 1) % 8 == 0) + { + space++; + str[i + space] = ' '; + } + } + + str[num_bits + space] = '\0'; + + return str; +} + +// +// Get and set words (internal use only -- no bounds checking) +// + +static inline bit_array_val_t _bit_array_get_word(const bit_array_t *target, int num_bits, int start) +{ + int word_index = BIT_ARRAY_BIT_WORD(start); + int word_offset = BIT_ARRAY_BIT_INDEX(start); + + bit_array_val_t result = target[word_index] >> word_offset; + + int bits_taken = BIT_ARRAY_BITS - word_offset; + + // word_offset is now the number of bits we need from the next word + // Check the next word has at least some bits + if (word_offset > 0 && start + bits_taken < num_bits) + { + result |= target[word_index + 1] << (BIT_ARRAY_BITS - word_offset); + } + + return result; +} + +// Set 64 bits from a particular start position +// Doesn't extend bit array +static inline void _bit_array_set_word(bit_array_t *target, int num_bits, int start, bit_array_val_t word) +{ + int word_index = BIT_ARRAY_BIT_WORD(start); + int word_offset = BIT_ARRAY_BIT_INDEX(start); + + if (word_offset == 0) + { + target[word_index] = word; + } + else + { + target[word_index] = (word << word_offset) | (target[word_index] & BIT_ARRAY_SUB_MASK(word_offset)); + + if (word_index + 1 < BIT_ARRAY_BITMAP_SIZE(num_bits)) + { + target[word_index + 1] = (word >> (BIT_ARRAY_BITS - word_offset)) | (target[word_index + 1] & (BIT_ARRAY_WORD_MAX << word_offset)); + } + } + + // Mask top word + _bit_array_mask_top_word(target, num_bits); +} + +// +// Fill a region (internal use only) +// + +// FillAction is fill with 0 or 1 or toggle +typedef enum +{ + ZERO_REGION, + FILL_REGION, + SWAP_REGION +} FillAction; + +static inline void _bit_array_set_region(bit_array_t *target, int start, int length, FillAction action) +{ + if (length == 0) + return; + + int first_word = BIT_ARRAY_BIT_WORD(start); + int last_word = BIT_ARRAY_BIT_WORD(start + length - 1); + int foffset = BIT_ARRAY_BIT_INDEX(start); + int loffset = BIT_ARRAY_BIT_INDEX(start + length - 1); + + if (first_word == last_word) + { + bit_array_val_t mask = BIT_ARRAY_SUB_MASK(length) << foffset; + + switch (action) + { + case ZERO_REGION: + target[first_word] &= ~mask; + break; + case FILL_REGION: + target[first_word] |= mask; + break; + case SWAP_REGION: + target[first_word] ^= mask; + break; + } + } + else + { + // Set first word + switch (action) + { + case ZERO_REGION: + target[first_word] &= BIT_ARRAY_SUB_MASK(foffset); + break; + case FILL_REGION: + target[first_word] |= ~BIT_ARRAY_SUB_MASK(foffset); + break; + case SWAP_REGION: + target[first_word] ^= ~BIT_ARRAY_SUB_MASK(foffset); + break; + } + + int i; + + // Set whole words + switch (action) + { + case ZERO_REGION: + for (i = first_word + 1; i < last_word; i++) + target[i] = (bit_array_val_t)0; + break; + case FILL_REGION: + for (i = first_word + 1; i < last_word; i++) + target[i] = BIT_ARRAY_WORD_MAX; + break; + case SWAP_REGION: + for (i = first_word + 1; i < last_word; i++) + target[i] ^= BIT_ARRAY_WORD_MAX; + break; + } + + // Set last word + switch (action) + { + case ZERO_REGION: + target[last_word] &= ~BIT_ARRAY_SUB_MASK(loffset + 1); + break; + case FILL_REGION: + target[last_word] |= BIT_ARRAY_SUB_MASK(loffset + 1); + break; + case SWAP_REGION: + target[last_word] ^= BIT_ARRAY_SUB_MASK(loffset + 1); + break; + } + } +} + +// Get the number of bits set (hamming weight) +static inline int bit_array_num_bits_set(bit_array_t *target, int num_bits) +{ + int i; + + int num_of_bits_set = 0; + + for (i = 0; i < BIT_ARRAY_BITMAP_SIZE(num_bits); i++) + { + if (target[i] > 0) + { + num_of_bits_set += POPCOUNT(target[i]); + } + } + + return num_of_bits_set; +} + +// Get the number of bits not set (1 - hamming weight) +static inline int bit_array_num_bits_cleared(bit_array_t *target, int num_bits) +{ + return num_bits - bit_array_num_bits_set(target, num_bits); +} + +// Copy bits from one array to another +// Note: use MACRO bit_array_copy +// Destination and source can be the same bit_array and +// src/dst regions can overlap +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) +{ + // Num of full words to copy + int num_of_full_words = length / BIT_ARRAY_BITS; + int i; + + int bits_in_last_word = bits_in_top_word(length); + + if (dst == src && srcindx > dstindx) + { + // Work left to right + for (i = 0; i < num_of_full_words; i++) + { + bit_array_val_t word = _bit_array_get_word(src, src_num_bits, srcindx + i * BIT_ARRAY_BITS); + _bit_array_set_word(dst, dst_num_bits, dstindx + i * BIT_ARRAY_BITS, word); + } + + if (bits_in_last_word > 0) + { + bit_array_val_t src_word = _bit_array_get_word(src, src_num_bits, srcindx + i * BIT_ARRAY_BITS); + bit_array_val_t dst_word = _bit_array_get_word(dst, dst_num_bits, dstindx + i * BIT_ARRAY_BITS); + + bit_array_val_t mask = BIT_ARRAY_SUB_MASK(bits_in_last_word); + bit_array_val_t word = bitmask_merge(src_word, dst_word, mask); + + _bit_array_set_word(dst, dst_num_bits, dstindx + num_of_full_words * BIT_ARRAY_BITS, word); + } + } + else + { + // Work right to left + for (i = 0; i < num_of_full_words; i++) + { + bit_array_val_t word = _bit_array_get_word(src, src_num_bits, srcindx + length - (i + 1) * BIT_ARRAY_BITS); + _bit_array_set_word(dst, dst_num_bits, dstindx + length - (i + 1) * BIT_ARRAY_BITS, word); + } + + if (bits_in_last_word > 0) + { + bit_array_val_t src_word = _bit_array_get_word(src, src_num_bits, srcindx); + bit_array_val_t dst_word = _bit_array_get_word(dst, dst_num_bits, dstindx); + + bit_array_val_t mask = BIT_ARRAY_SUB_MASK(bits_in_last_word); + bit_array_val_t word = bitmask_merge(src_word, dst_word, mask); + _bit_array_set_word(dst, dst_num_bits, dstindx, word); + } + } + + _bit_array_mask_top_word(dst, dst_num_bits); +} + +// copy all of src to dst. dst is resized to match src. +static inline void bit_array_copy_all(bit_array_t *dst, const bit_array_t *src, int num_bits) +{ + for (int i = 0; i < BIT_ARRAY_BITMAP_SIZE(num_bits); i++) + { + dst[i] = src[i]; + } +} + +// +// Logic operators +// + +// Destination can be the same as one or both of the sources +static inline void bit_array_and(bit_array_t *dest, const bit_array_t *src1, const bit_array_t *src2, int num_bits) +{ + for (int i = 0; i < BIT_ARRAY_BITMAP_SIZE(num_bits); i++) + { + dest[i] = src1[i] & src2[i]; + } +} + +static inline void bit_array_or(bit_array_t *dest, const bit_array_t *src1, const bit_array_t *src2, int num_bits) +{ + for (int i = 0; i < BIT_ARRAY_BITMAP_SIZE(num_bits); i++) + { + dest[i] = src1[i] | src2[i]; + } +} + +static inline void bit_array_xor(bit_array_t *dest, const bit_array_t *src1, const bit_array_t *src2, int num_bits) +{ + for (int i = 0; i < BIT_ARRAY_BITMAP_SIZE(num_bits); i++) + { + dest[i] = src1[i] ^ src2[i]; + } +} + +static inline void bit_array_not(bit_array_t *dest, const bit_array_t *src, int num_bits) +{ + for (int i = 0; i < BIT_ARRAY_BITMAP_SIZE(num_bits); i++) + { + dest[i] = ~src[i]; + } +} + +// +// Shift array left/right. If fill is zero, filled with 0, otherwise 1 +// + +// Shift towards LSB / lower index +static inline void bit_array_shift_right(bit_array_t *target, int num_bits, int shift_dist, int fill) +{ + if (shift_dist >= num_bits) + { + fill ? bit_array_set_all(target, num_bits) : bit_array_clear_all(target, num_bits); + return; + } + else if (shift_dist == 0) + { + return; + } + + FillAction action = fill ? FILL_REGION : ZERO_REGION; + + int cpy_length = num_bits - shift_dist; + bit_array_copy(target, 0, target, shift_dist, cpy_length, num_bits, num_bits); + + _bit_array_set_region(target, cpy_length, shift_dist, action); +} + +// Shift towards MSB / higher index +static inline void bit_array_shift_left(bit_array_t *target, int num_bits, int shift_dist, int fill) +{ + if (shift_dist >= num_bits) + { + fill ? bit_array_set_all(target, num_bits) : bit_array_clear_all(target, num_bits); + return; + } + else if (shift_dist == 0) + { + return; + } + + FillAction action = fill ? FILL_REGION : ZERO_REGION; + + int cpy_length = num_bits - shift_dist; + bit_array_copy(target, shift_dist, target, 0, cpy_length, num_bits, num_bits); + _bit_array_set_region(target, 0, shift_dist, action); +} + +// +// Comparisons +// + +// Compare two bit arrays by value stored, with index 0 being the Least +// Significant Bit (LSB). Arrays must have the same length. +// returns: +// >0 iff bitarr1 > bitarr2 +// 0 iff bitarr1 == bitarr2 +// <0 iff bitarr1 < bitarr2 +static inline int bit_array_cmp(const bit_array_t *bitarr1, const bit_array_t *bitarr2, int num_bits) +{ + return memcmp(bitarr1, bitarr2, BIT_ARRAY_BITMAP_SIZE(num_bits) * sizeof(bit_array_val_t)); +} + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* _BIT_ARRAY_H_ */ diff --git a/ebtn/ebtn.c b/ebtn/ebtn.c new file mode 100644 index 0000000..302d2a0 --- /dev/null +++ b/ebtn/ebtn.c @@ -0,0 +1,550 @@ +#include +#include "ebtn.h" + +#define EBTN_FLAG_ONPRESS_SENT ((uint16_t)0x0001) /*!< Flag indicates that on-press event has been sent */ +#define EBTN_FLAG_IN_PROCESS ((uint16_t)0x0002) /*!< Flag indicates that button in process */ + +/* Default button group instance */ +static ebtn_t ebtn_default; + +/** + * \brief Process the button information and state + * + * \param[in] btn: Button instance to process + * \param[in] old_state: old state + * \param[in] new_state: new state + * \param[in] mstime: Current milliseconds system time + */ +static void prv_process_btn(ebtn_btn_t *btn, uint8_t old_state, uint8_t new_state, ebtn_time_t mstime) +{ + ebtn_t *ebtobj = &ebtn_default; + + /* Check params set or not. */ + if (btn->param == NULL) + { + return; + } + + /* Button state has just changed */ + if (new_state != old_state) + { + btn->time_state_change = mstime; + + if (new_state) + { + btn->flags |= EBTN_FLAG_IN_PROCESS; + } + } + /* Button is still pressed */ + if (new_state) + { + /* + * Handle debounce and send on-press event + * + * This is when we detect valid press + */ + if (!(btn->flags & EBTN_FLAG_ONPRESS_SENT)) + { + /* + * Run if statement when: + * + * - Runtime mode is enabled -> user sets its own config for debounce + * - Config debounce time for press is more than `0` + */ + if (ebtn_timer_sub(mstime, btn->time_state_change) >= btn->param->time_debounce) + { + /* + * Check mutlti click limit reach or not. + */ + if ((btn->click_cnt > 0) && (ebtn_timer_sub(mstime, btn->click_last_time) >= btn->param->time_click_multi_max)) + { + ebtobj->evt_fn(btn, EBTN_EVT_ONCLICK); + btn->click_cnt = 0; + } + + /* Start with new on-press */ + btn->flags |= EBTN_FLAG_ONPRESS_SENT; + ebtobj->evt_fn(btn, EBTN_EVT_ONPRESS); + + /* Set keep alive time */ + btn->keepalive_last_time = mstime; + btn->keepalive_cnt = 0; + + btn->time_change = mstime; /* Button state has now changed */ + } + } + + /* + * Handle keep alive, but only if on-press event has been sent + * + * Keep alive is sent when valid press is being detected + */ + else + { + while ((btn->param->time_keepalive_period > 0) && (ebtn_timer_sub(mstime, btn->keepalive_last_time) >= btn->param->time_keepalive_period)) + { + btn->keepalive_last_time += btn->param->time_keepalive_period; + ++btn->keepalive_cnt; + ebtobj->evt_fn(btn, EBTN_EVT_KEEPALIVE); + } + + // Scene1: multi click end with a long press, need send onclick event. + if ((btn->click_cnt > 0) && (ebtn_timer_sub(mstime, btn->time_change) > btn->param->time_click_pressed_max)) + { + ebtobj->evt_fn(btn, EBTN_EVT_ONCLICK); + + btn->click_cnt = 0; + } + } + } + /* Button is still released */ + else + { + /* + * We only need to react if on-press event has even been started. + * + * Do nothing if that was not the case + */ + if (btn->flags & EBTN_FLAG_ONPRESS_SENT) + { + /* + * Run if statement when: + * + * - Runtime mode is enabled -> user sets its own config for debounce + * - Config debounce time for release is more than `0` + */ + if (ebtn_timer_sub(mstime, btn->time_state_change) >= btn->param->time_debounce_release) + { + /* Handle on-release event */ + btn->flags &= ~EBTN_FLAG_ONPRESS_SENT; + ebtobj->evt_fn(btn, EBTN_EVT_ONRELEASE); + + /* Check time validity for click event */ + if (ebtn_timer_sub(mstime, btn->time_change) >= btn->param->time_click_pressed_min && + ebtn_timer_sub(mstime, btn->time_change) <= btn->param->time_click_pressed_max) + { + ++btn->click_cnt; + + btn->click_last_time = mstime; + } + else + { + // Scene2: If last press was too short, and previous sequence of clicks was + // positive, send event to user. + if ((btn->click_cnt > 0) && (ebtn_timer_sub(mstime, btn->time_change) < btn->param->time_click_pressed_min)) + { + ebtobj->evt_fn(btn, EBTN_EVT_ONCLICK); + } + /* + * There was an on-release event, but timing + * for click event detection is outside allowed window. + * + * Reset clicks counter -> not valid sequence for click event. + */ + btn->click_cnt = 0; + } + + // Scene3: this part will send on-click event immediately after release event, if + // maximum number of consecutive clicks has been reached. + if ((btn->click_cnt > 0) && (btn->click_cnt == btn->param->max_consecutive)) + { + ebtobj->evt_fn(btn, EBTN_EVT_ONCLICK); + btn->click_cnt = 0; + } + + btn->time_change = mstime; /* Button state has now changed */ + } + } + else + { + /* + * Based on te configuration, this part of the code + * will send on-click event after certain timeout. + * + * This feature is useful if users prefers multi-click feature + * that is reported only after last click event happened, + * including number of clicks made by user + */ + if (btn->click_cnt > 0) + { + if (ebtn_timer_sub(mstime, btn->click_last_time) >= btn->param->time_click_multi_max) + { + ebtobj->evt_fn(btn, EBTN_EVT_ONCLICK); + btn->click_cnt = 0; + } + } + else + { + // check button in process + if (btn->flags & EBTN_FLAG_IN_PROCESS) + { + btn->flags &= ~EBTN_FLAG_IN_PROCESS; + } + } + } + } +} + +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) +{ + ebtn_t *ebtobj = &ebtn_default; + + if (evt_fn == NULL || get_state_fn == NULL /* Parameter is a must only in callback-only mode */ + ) + { + return 0; + } + + memset(ebtobj, 0x00, sizeof(*ebtobj)); + ebtobj->btns = btns; + ebtobj->btns_cnt = btns_cnt; + ebtobj->btns_combo = btns_combo; + ebtobj->btns_combo_cnt = btns_combo_cnt; + ebtobj->evt_fn = evt_fn; + ebtobj->get_state_fn = get_state_fn; + + return 1; +} + +/** + * \brief Get all button state with get_state_fn. + * + * \param[out] state_array: store the button state + */ +static void ebtn_get_current_state(bit_array_t *state_array) +{ + ebtn_t *ebtobj = &ebtn_default; + ebtn_btn_dyn_t *target; + int i; + + /* Process all buttons */ + for (i = 0; i < ebtobj->btns_cnt; ++i) + { + /* Get button state */ + uint8_t new_state = ebtobj->get_state_fn(&ebtobj->btns[i]); + // save state + bit_array_assign(state_array, i, new_state); + } + + for (target = ebtobj->btn_dyn_head, i = ebtobj->btns_cnt; target; target = target->next, i++) + { + /* Get button state */ + uint8_t new_state = ebtobj->get_state_fn(&target->btn); + + // save state + bit_array_assign(state_array, i, new_state); + } +} + +/** + * \brief Process the button state + * + * \param[in] btn: Button instance to process + * \param[in] old_state: all button old state + * \param[in] curr_state: all button current state + * \param[in] idx: Button internal key_idx + * \param[in] mstime: Current milliseconds system time + */ +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) +{ + prv_process_btn(btn, bit_array_get(old_state, idx), bit_array_get(curr_state, idx), mstime); +} + +/** + * \brief Process the combo-button state + * + * \param[in] btn: Button instance to process + * \param[in] old_state: all button old state + * \param[in] curr_state: all button current state + * \param[in] comb_key: Combo key + * \param[in] mstime: Current milliseconds system time + */ +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) +{ + BIT_ARRAY_DEFINE(tmp_data, EBTN_MAX_KEYNUM) = {0}; + + if (bit_array_num_bits_set(comb_key, EBTN_MAX_KEYNUM) == 0) + { + return; + } + bit_array_and(tmp_data, curr_state, comb_key, EBTN_MAX_KEYNUM); + uint8_t curr = bit_array_cmp(tmp_data, comb_key, EBTN_MAX_KEYNUM) == 0; + + bit_array_and(tmp_data, old_state, comb_key, EBTN_MAX_KEYNUM); + uint8_t old = bit_array_cmp(tmp_data, comb_key, EBTN_MAX_KEYNUM) == 0; + + prv_process_btn(btn, old, curr, mstime); +} + +void ebtn_process_with_curr_state(bit_array_t *curr_state, ebtn_time_t mstime) +{ + ebtn_t *ebtobj = &ebtn_default; + ebtn_btn_dyn_t *target; + ebtn_btn_combo_dyn_t *target_combo; + int i; + + /* Process all buttons */ + for (i = 0; i < ebtobj->btns_cnt; ++i) + { + ebtn_process_btn(&ebtobj->btns[i], ebtobj->old_state, curr_state, i, mstime); + } + + for (target = ebtobj->btn_dyn_head, i = ebtobj->btns_cnt; target; target = target->next, i++) + { + ebtn_process_btn(&target->btn, ebtobj->old_state, curr_state, i, mstime); + } + + /* Process all comb buttons */ + for (i = 0; i < ebtobj->btns_combo_cnt; ++i) + { + ebtn_process_btn_combo(&ebtobj->btns_combo[i].btn, ebtobj->old_state, curr_state, ebtobj->btns_combo[i].comb_key, mstime); + } + + for (target_combo = ebtobj->btn_combo_dyn_head; target_combo; target_combo = target_combo->next) + { + ebtn_process_btn_combo(&target_combo->btn.btn, ebtobj->old_state, curr_state, target_combo->btn.comb_key, mstime); + } + + bit_array_copy_all(ebtobj->old_state, curr_state, EBTN_MAX_KEYNUM); +} + +void ebtn_process(ebtn_time_t mstime) +{ + BIT_ARRAY_DEFINE(curr_state, EBTN_MAX_KEYNUM) = {0}; + + // Get Current State + ebtn_get_current_state(curr_state); + + ebtn_process_with_curr_state(curr_state, mstime); +} + +int ebtn_get_total_btn_cnt(void) +{ + ebtn_t *ebtobj = &ebtn_default; + int total_cnt = 0; + ebtn_btn_dyn_t *curr = ebtobj->btn_dyn_head; + + total_cnt += ebtobj->btns_cnt; + + while (curr) + { + total_cnt++; + curr = curr->next; + } + return total_cnt; +} + +int ebtn_get_btn_index_by_key_id(uint16_t key_id) +{ + ebtn_t *ebtobj = &ebtn_default; + int i = 0; + ebtn_btn_dyn_t *target; + + for (i = 0; i < ebtobj->btns_cnt; ++i) + { + if (ebtobj->btns[i].key_id == key_id) + { + return i; + } + } + + for (target = ebtobj->btn_dyn_head, i = ebtobj->btns_cnt; target; target = target->next, i++) + { + if (target->btn.key_id == key_id) + { + return i; + } + } + + return -1; +} + +ebtn_btn_t *ebtn_get_btn_by_key_id(uint16_t key_id) +{ + ebtn_t *ebtobj = &ebtn_default; + int i = 0; + ebtn_btn_dyn_t *target; + + for (i = 0; i < ebtobj->btns_cnt; ++i) + { + if (ebtobj->btns[i].key_id == key_id) + { + return &ebtobj->btns[i]; + } + } + + for (target = ebtobj->btn_dyn_head, i = ebtobj->btns_cnt; target; target = target->next, i++) + { + if (target->btn.key_id == key_id) + { + return &target->btn; + } + } + + return NULL; +} + +int ebtn_get_btn_index_by_btn(ebtn_btn_t *btn) +{ + return ebtn_get_btn_index_by_key_id(btn->key_id); +} + +int ebtn_get_btn_index_by_btn_dyn(ebtn_btn_dyn_t *btn) +{ + return ebtn_get_btn_index_by_key_id(btn->btn.key_id); +} + +void ebtn_combo_btn_add_btn_by_idx(ebtn_btn_combo_t *btn, int idx) +{ + bit_array_set(btn->comb_key, idx); +} + +void ebtn_combo_btn_remove_btn_by_idx(ebtn_btn_combo_t *btn, int idx) +{ + bit_array_clear(btn->comb_key, idx); +} + +void ebtn_combo_btn_add_btn(ebtn_btn_combo_t *btn, uint16_t key_id) +{ + int idx = ebtn_get_btn_index_by_key_id(key_id); + if (idx < 0) + { + return; + } + ebtn_combo_btn_add_btn_by_idx(btn, idx); +} + +void ebtn_combo_btn_remove_btn(ebtn_btn_combo_t *btn, uint16_t key_id) +{ + int idx = ebtn_get_btn_index_by_key_id(key_id); + if (idx < 0) + { + return; + } + ebtn_combo_btn_remove_btn_by_idx(btn, idx); +} + +int ebtn_is_btn_active(const ebtn_btn_t *btn) +{ + return btn != NULL && (btn->flags & EBTN_FLAG_ONPRESS_SENT); +} + +int ebtn_is_btn_in_process(const ebtn_btn_t *btn) +{ + return btn != NULL && (btn->flags & EBTN_FLAG_IN_PROCESS); +} + +int ebtn_is_in_process(void) +{ + ebtn_t *ebtobj = &ebtn_default; + ebtn_btn_dyn_t *target; + ebtn_btn_combo_dyn_t *target_combo; + int i; + + /* Process all buttons */ + for (i = 0; i < ebtobj->btns_cnt; ++i) + { + if (ebtn_is_btn_in_process(&ebtobj->btns[i])) + { + return 1; + } + } + + for (target = ebtobj->btn_dyn_head, i = ebtobj->btns_cnt; target; target = target->next, i++) + { + if (ebtn_is_btn_in_process(&target->btn)) + { + return 1; + } + } + + /* Process all comb buttons */ + for (i = 0; i < ebtobj->btns_combo_cnt; ++i) + { + if (ebtn_is_btn_in_process(&ebtobj->btns_combo[i].btn)) + { + return 1; + } + } + + for (target_combo = ebtobj->btn_combo_dyn_head; target_combo; target_combo = target_combo->next) + { + if (ebtn_is_btn_in_process(&target_combo->btn.btn)) + { + return 1; + } + } + + return 0; +} + +int ebtn_register(ebtn_btn_dyn_t *button) +{ + ebtn_t *ebtobj = &ebtn_default; + + ebtn_btn_dyn_t *curr = ebtobj->btn_dyn_head; + ebtn_btn_dyn_t *last = NULL; + + if (!button) + { + return 0; + } + + if (ebtn_get_total_btn_cnt() >= EBTN_MAX_KEYNUM) + { + return 0; /* reach max cnt. */ + } + + if (curr == NULL) + { + ebtobj->btn_dyn_head = button; + return 1; + } + + while (curr) + { + if (curr == button) + { + return 0; /* already exist. */ + } + last = curr; + curr = curr->next; + } + + last->next = button; + + return 1; +} + +int ebtn_combo_register(ebtn_btn_combo_dyn_t *button) +{ + ebtn_t *ebtobj = &ebtn_default; + + ebtn_btn_combo_dyn_t *curr = ebtobj->btn_combo_dyn_head; + ebtn_btn_combo_dyn_t *last = NULL; + + if (!button) + { + return 0; + } + + if (curr == NULL) + { + ebtobj->btn_combo_dyn_head = button; + return 1; + } + + while (curr) + { + if (curr == button) + { + return 0; /* already exist. */ + } + last = curr; + curr = curr->next; + } + + last->next = button; + + return 1; +} diff --git a/ebtn/ebtn.h b/ebtn/ebtn.h new file mode 100644 index 0000000..72f343e --- /dev/null +++ b/ebtn/ebtn.h @@ -0,0 +1,463 @@ +#ifndef _EBTN_H +#define _EBTN_H + +#include +#include + +#include "bit_array.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +// #define EBTN_CONFIG_TIMER_16 + +// here can change to uint16_t, if you want reduce RAM size. +#ifdef EBTN_CONFIG_TIMER_16 +typedef uint16_t ebtn_time_t; +typedef int16_t ebtn_time_sign_t; +#else +typedef uint32_t ebtn_time_t; +typedef int32_t ebtn_time_sign_t; +#endif + +/* Forward declarations */ +struct ebtn_btn; +struct ebtn; + +#define EBTN_MAX_KEYNUM (64) + +/** + * \brief List of button events + * + */ +typedef enum +{ + EBTN_EVT_ONPRESS = 0x00, /*!< On press event - sent when valid press is detected */ + EBTN_EVT_ONRELEASE, /*!< On release event - sent when valid release event is detected (from + active to inactive) */ + EBTN_EVT_ONCLICK, /*!< On Click event - sent when valid sequence of on-press and on-release + events occurs */ + EBTN_EVT_KEEPALIVE, /*!< Keep alive event - sent periodically when button is active */ +} ebtn_evt_t; + +/** + * @brief Returns the difference between two absolute times: time1-time2. + * @param[in] time1: Absolute time expressed in internal time units. + * @param[in] time2: Absolute time expressed in internal time units. + * @return resulting signed relative time expressed in internal time units. + */ +static inline ebtn_time_sign_t ebtn_timer_sub(ebtn_time_t time1, ebtn_time_t time2) +{ + return time1 - time2; +} + +// test time overflow error +// #define ebtn_timer_sub(time1, time2) (time1 - time2) + +/** + * \brief Button event function callback prototype + * \param[in] btn: Button instance from array for which event occured + * \param[in] evt: Event type + */ +typedef void (*ebtn_evt_fn)(struct ebtn_btn *btn, ebtn_evt_t evt); + +/** + * \brief Get button/input state callback function + * + * \param[in] btn: Button instance from array to read state + * \return `1` when button is considered `active`, `0` otherwise + */ +typedef uint8_t (*ebtn_get_state_fn)(struct ebtn_btn *btn); + +/** + * \brief Button Params structure + */ +typedef struct ebtn_btn_param +{ + /** + * \brief Minimum debounce time for press event in units of milliseconds + * + * This is the time when the input shall have stable active level to detect valid *onpress* + * event. + * + * When value is set to `> 0`, input must be in active state for at least + * minimum milliseconds time, before valid *onpress* event is detected. + * + * \note If value is set to `0`, debounce is not used and *press* event will be + * triggered immediately when input states goes to *inactive* state. + * + * To be safe not using this feature, external logic must ensure stable + * transition at input level. + * + */ + uint16_t time_debounce; /*!< Debounce time in milliseconds */ + + /** + * \brief Minimum debounce time for release event in units of milliseconds + * + * This is the time when the input shall have minimum stable released level to detect valid + * *onrelease* event. + * + * This setting can be useful if application wants to protect against + * unwanted glitches on the line when input is considered "active". + * + * When value is set to `> 0`, input must be in inactive low for at least + * minimum milliseconds time, before valid *onrelease* event is detected + * + * \note If value is set to `0`, debounce is not used and *release* event will be + * triggered immediately when input states goes to *inactive* state + * + */ + uint16_t time_debounce_release; /*!< Debounce time in milliseconds for release event */ + + /** + * \brief Minimum active input time for valid click event, in milliseconds + * + * Input shall be in active state (after debounce) at least this amount of time to even consider + * the potential valid click event. Set the value to `0` to disable this feature + * + */ + uint16_t time_click_pressed_min; /*!< Minimum pressed time for valid click event */ + + /** + * \brief Maximum active input time for valid click event, in milliseconds + * + * Input shall be pressed at most this amount of time to still trigger valid click. + * Set to `-1` to allow any time triggering click event. + * + * When input is active for more than the configured time, click even is not detected and is + * ignored. + * + */ + uint16_t time_click_pressed_max; /*!< Maximum pressed time for valid click event*/ + + /** + * \brief Maximum allowed time between last on-release and next valid on-press, + * to still allow multi-click events, in milliseconds + * + * This value is also used as a timeout length to send the *onclick* event to application from + * previously detected valid click events. + * + * If application relies on multi consecutive clicks, this is the max time to allow user + * to trigger potential new click, or structure will get reset (before sent to user if any + * clicks have been detected so far) + * + */ + uint16_t time_click_multi_max; /*!< Maximum time between 2 clicks to be considered consecutive + click */ + + /** + * \brief Keep-alive event period, in milliseconds + * + * When input is active, keep alive events will be sent through this period of time. + * First keep alive will be sent after input being considered + * active. + * + */ + uint16_t time_keepalive_period; /*!< Time in ms for periodic keep alive event */ + + /** + * \brief Maximum number of allowed consecutive click events, + * before structure gets reset to default value. + * + * \note When consecutive value is reached, application will get notification of + * clicks. This can be executed immediately after last click has been detected, or after + * standard timeout (unless next on-press has already been detected, then it is send to + * application just before valid next press event). + * + */ + uint16_t max_consecutive; /*!< Max number of consecutive clicks */ +} ebtn_btn_param_t; + +#define EBTN_PARAMS_INIT(_time_debounce, _time_debounce_release, _time_click_pressed_min, _time_click_pressed_max, _time_click_multi_max, \ + _time_keepalive_period, _max_consecutive) \ + { \ + .time_debounce = _time_debounce, .time_debounce_release = _time_debounce_release, .time_click_pressed_min = _time_click_pressed_min, \ + .time_click_pressed_max = _time_click_pressed_max, .time_click_multi_max = _time_click_multi_max, .time_keepalive_period = _time_keepalive_period, \ + .max_consecutive = _max_consecutive \ + } + +#define EBTN_BUTTON_INIT(_key_id, _param) \ + { \ + .key_id = _key_id, .param = _param, \ + } + +#define EBTN_BUTTON_DYN_INIT(_key_id, _param) \ + { \ + .next = NULL, .btn = EBTN_BUTTON_INIT(_key_id, _param), \ + } + +#define EBTN_BUTTON_COMBO_INIT(_key_id, _param) \ + { \ + .comb_key = {0}, .btn = EBTN_BUTTON_INIT(_key_id, _param), \ + } + +#define EBTN_BUTTON_COMBO_DYN_INIT(_key_id, _param) \ + { \ + .next = NULL, .btn = EBTN_BUTTON_COMBO_INIT(_key_id, _param), \ + } + +#define EBTN_ARRAY_SIZE(_arr) sizeof(_arr) / sizeof((_arr)[0]) + +/** + * \brief Button structure + */ +typedef struct ebtn_btn +{ + uint16_t key_id; /*!< User defined custom argument for callback function purpose */ + uint16_t flags; /*!< Private button flags management */ + ebtn_time_t time_change; /*!< Time in ms when button state got changed last time after valid + debounce */ + ebtn_time_t time_state_change; /*!< Time in ms when button state got changed last time */ + + ebtn_time_t keepalive_last_time; /*!< Time in ms of last send keep alive event */ + ebtn_time_t click_last_time; /*!< Time in ms of last successfully detected (not sent!) click event + */ + + uint16_t keepalive_cnt; /*!< Number of keep alive events sent after successful on-press + detection. Value is reset after on-release */ + uint16_t click_cnt; /*!< Number of consecutive clicks detected, respecting maximum timeout + between clicks */ + + const ebtn_btn_param_t *param; +} ebtn_btn_t; + +/** + * \brief ComboButton structure + */ +typedef struct ebtn_btn_combo +{ + BIT_ARRAY_DEFINE(comb_key, EBTN_MAX_KEYNUM); /*!< select key index - `1` means active, `0` means inactive */ + + ebtn_btn_t btn; +} ebtn_btn_combo_t; + +/** + * \brief Dynamic Button structure + */ +typedef struct ebtn_btn_dyn +{ + struct ebtn_btn_dyn *next; /*!< point to next button */ + + ebtn_btn_t btn; +} ebtn_btn_dyn_t; + +/** + * \brief Dynamic ComboButton structure + */ +typedef struct ebtn_btn_combo_dyn +{ + struct ebtn_btn_combo_dyn *next; /*!< point to next combo-button */ + + ebtn_btn_combo_t btn; +} ebtn_btn_combo_dyn_t; + +/** + * \brief easy_button group structure + */ +typedef struct ebtn +{ + ebtn_btn_t *btns; /*!< Pointer to buttons array */ + uint16_t btns_cnt; /*!< Number of buttons in array */ + ebtn_btn_combo_t *btns_combo; /*!< Pointer to comb-buttons array */ + uint16_t btns_combo_cnt; /*!< Number of comb-buttons in array */ + + ebtn_btn_dyn_t *btn_dyn_head; /*!< Pointer to btn-dynamic list */ + ebtn_btn_combo_dyn_t *btn_combo_dyn_head; /*!< Pointer to btn-combo-dynamic list */ + + ebtn_evt_fn evt_fn; /*!< Pointer to event function */ + ebtn_get_state_fn get_state_fn; /*!< Pointer to get state function */ + + BIT_ARRAY_DEFINE(old_state, EBTN_MAX_KEYNUM); /*!< Old button state - `1` means active, `0` means inactive */ +} ebtn_t; + +/** + * \brief Button processing function, that reads the inputs and makes actions accordingly. + * + * + * \param[in] mstime: Current system time in milliseconds + */ +void ebtn_process(ebtn_time_t mstime); + +/** + * \brief Button processing function, with all button input state. + * + * \param[in] curr_state: Current all button input state + * \param[in] mstime: Current system time in milliseconds + */ +void ebtn_process_with_curr_state(bit_array_t *curr_state, ebtn_time_t mstime); + +/** + * \brief Check if button is active. + * Active is considered when initial debounce period has been a pass. + * This is the period between on-press and on-release events. + * + * \param[in] btn: Button handle to check + * \return `1` if active, `0` otherwise + */ +int ebtn_is_btn_active(const ebtn_btn_t *btn); + +/** + * \brief Check if button is in process. + * Used for low-power processing, indicating that the buttons are temporarily idle, and embedded systems can consider entering deep sleep. + * + * \param[in] btn: Button handle to check + * \return `1` if in process, `0` otherwise + */ +int ebtn_is_btn_in_process(const ebtn_btn_t *btn); + +/** + * \brief Check if some button is in process. + * Used for low-power processing, indicating that the buttons are temporarily idle, and embedded systems can consider entering deep sleep. + * + * \return `1` if in process, `0` otherwise + */ +int ebtn_is_in_process(void); + +/** + * \brief Initialize button manager + * \param[in] btns: Array of buttons to process + * \param[in] btns_cnt: Number of buttons to process + * \param[in] btns_combo: Array of combo-buttons to process + * \param[in] btns_combo_cnt: Number of combo-buttons to process + * \param[in] get_state_fn: Pointer to function providing button state on demand. + * \param[in] evt_fn: Button event function callback + * + * \return `1` on success, `0` otherwise + */ +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); + +/** + * @brief Register a dynamic button + * + * @param button: Dynamic button structure instance + * \return `1` on success, `0` otherwise + */ +int ebtn_register(ebtn_btn_dyn_t *button); + +/** + * \brief Register a dynamic combo-button + * \param[in] button: Dynamic combo-button structure instance + * + * \return `1` on success, `0` otherwise + */ +int ebtn_combo_register(ebtn_btn_combo_dyn_t *button); + +/** + * \brief Get the current total button cnt + * + * \return size of button. + */ +int ebtn_get_total_btn_cnt(void); + +/** + * \brief Get the internal key_idx of the key_id + * \param[in] key_id: key_id + * + * \return '-1' on error, other is key_idx + */ +int ebtn_get_btn_index_by_key_id(uint16_t key_id); + +/** + * \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 + * instance + * + * \param[in] key_id: key_id + * + * \return 'NULL' on error, other is button instance + */ +ebtn_btn_t *ebtn_get_btn_by_key_id(uint16_t key_id); + +/** + * \brief Get the internal key_idx of the button + * \param[in] btn: Button + * + * \return '-1' on error, other is key_idx + */ +int ebtn_get_btn_index_by_btn(ebtn_btn_t *btn); + +/** + * \brief Get the internal key_idx of the dynamic button + * \param[in] btn: Button + * + * \return '-1' on error, other is key_idx + */ +int ebtn_get_btn_index_by_btn_dyn(ebtn_btn_dyn_t *btn); + +/** + * \brief Bind combo-button key with key_idx + * \param[in] btn: Combo Button + * \param[in] idx: key_idx + * + */ +void ebtn_combo_btn_add_btn_by_idx(ebtn_btn_combo_t *btn, int idx); + +/** + * \brief Remove combo-button key with key_idx + * \param[in] btn: Combo Button + * \param[in] idx: key_idx + * + */ +void ebtn_combo_btn_remove_btn_by_idx(ebtn_btn_combo_t *btn, int idx); + +/** + * \brief Bind combo-button key with key_id, make sure key_id(button) is already register. + * \param[in] btn: Combo Button + * \param[in] key_id: key_id + * + */ +void ebtn_combo_btn_add_btn(ebtn_btn_combo_t *btn, uint16_t key_id); + +/** + * \brief Remove combo-button key with key_id, make sure key_id(button) is already + * register. \param[in] btn: Combo Button \param[in] key_id: key_id + * + */ +void ebtn_combo_btn_remove_btn(ebtn_btn_combo_t *btn, uint16_t key_id); + +/** + * \brief Get keep alive period for specific button + * \param[in] btn: Button instance to get keep alive period for + * \return Keep alive period in `ms` + */ +#define ebtn_keepalive_get_period(btn) ((btn)->time_keepalive_period) + +/** + * \brief Get actual number of keep alive counts since the last on-press event. + * It is set to `0` if btn isn't pressed + * \param[in] btn: Button instance to get keep alive period for + * \return Number of keep alive events since on-press event + */ +#define ebtn_keepalive_get_count(btn) ((btn)->keepalive_cnt) + +/** + * \brief Get number of keep alive counts for specific required time in milliseconds. + * It will calculate number of keepalive ticks specific button shall make, + * before requested time is reached. + * + * Result of the function can be used with \ref ebtn_keepalive_get_count which returns + * actual number of keep alive counts since last on-press event of the button. + * + * \note Value is always integer aligned, with granularity of one keepalive time period + * \note Implemented as macro, as it may be optimized by compiler when static keep alive + * is used + * + * \param[in] btn: Button to use for check + * \param[in] ms_time: Time in ms to calculate number of keep alive counts + * \return Number of keep alive counts + */ +#define ebtn_keepalive_get_count_for_time(btn, ms_time) ((ms_time) / ebtn_keepalive_get_period(btn)) + +/** + * \brief Get number of consecutive click events on a button + * \param[in] btn: Button instance to get number of clicks + * \return Number of consecutive clicks on a button + */ +#define ebtn_click_get_count(btn) ((btn)->click_cnt) + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* _EBTN_H */ diff --git a/example_test.c b/example_test.c new file mode 100644 index 0000000..7a391b6 --- /dev/null +++ b/example_test.c @@ -0,0 +1,755 @@ +#include +#include + +#include "windows.h" + +#include "ebtn.h" + +// +// Tests +// +const char *suite_name; +char suite_pass; +int suites_run = 0, suites_failed = 0, suites_empty = 0; +int tests_in_suite = 0, tests_run = 0, tests_failed = 0; + +#define QUOTE(str) #str +#define ASSERT(x) \ + { \ + tests_run++; \ + tests_in_suite++; \ + if (!(x)) \ + { \ + printf("failed assert [%s:%i] %s\n", __FILE__, __LINE__, QUOTE(x)); \ + suite_pass = 0; \ + tests_failed++; \ + } \ + } + +void SUITE_START(const char *name) +{ + suite_pass = 1; + suite_name = name; + suites_run++; + tests_in_suite = 0; +} + +void SUITE_END(void) +{ + printf("Testing %s ", suite_name); + size_t suite_i; + for (suite_i = strlen(suite_name); suite_i < 80 - 8 - 5; suite_i++) + printf("."); + printf("%s\n", suite_pass ? " pass" : " fail"); + if (!suite_pass) + suites_failed++; + if (!tests_in_suite) + suites_empty++; +} + +typedef enum +{ + USER_BUTTON_default = 0, + USER_BUTTON_onrelease_debounce, + USER_BUTTON_keepalive_with_click, + USER_BUTTON_max_click_3, + USER_BUTTON_click_multi_max_0, + USER_BUTTON_keep_alive_0, + USER_BUTTON_MAX, + + USER_BUTTON_COMBO_0 = 0x100, + USER_BUTTON_COMBO_1, + USER_BUTTON_COMBO_2, + USER_BUTTON_COMBO_3, + USER_BUTTON_COMBO_MAX, +} user_button_t; + +/** + * \brief Input state information + */ +typedef struct +{ + uint16_t key_id; /*!< Select Key id */ + uint8_t state; /*!< Input state -> 1 = active, 0 = inactive */ + uint32_t duration; /*!< Time until this state is enabled */ +} btn_test_time_t; + +/** + * \brief Event sequence + */ +typedef struct +{ + ebtn_evt_t evt; /*!< Event type */ + uint8_t keepalive_cnt; /*!< Number of keep alive events while button is active */ + uint8_t conseq_clicks; /*!< Number of consecutive clicks detected */ +} btn_test_evt_t; + +typedef struct +{ + const char *test_name; + uint16_t test_key_id; + int test_sequence_cnt; + btn_test_time_t *test_sequence; /*!< Input state -> 1 = active, 0 = inactive */ + int test_events_cnt; + const btn_test_evt_t *test_events; /*!< Time until this state is enabled */ +} btn_test_arr_t; + +#define TEST_ARRAY_DEFINE(_key_id, _seq, _evt) \ + { \ + .test_key_id = _key_id, .test_name = #_seq, .test_sequence_cnt = EBTN_ARRAY_SIZE(_seq), .test_sequence = _seq, \ + .test_events_cnt = EBTN_ARRAY_SIZE(_evt), .test_events = _evt \ + } + +/* Max number of ms to demonstrate */ +#define MAX_TIME_MS 0x3FFFF + +#define EBTN_PARAM_TIME_DEBOUNCE_PRESS(_param) _param.time_debounce +#define EBTN_PARAM_TIME_DEBOUNCE_RELEASE(_param) _param.time_debounce_release +#define EBTN_PARAM_TIME_CLICK_MIN(_param) _param.time_click_pressed_min +#define EBTN_PARAM_TIME_CLICK_MAX(_param) _param.time_click_pressed_max +#define EBTN_PARAM_TIME_CLICK_MULTI_MAX(_param) _param.time_click_multi_max +#define EBTN_PARAM_TIME_KEEPALIVE_PERIOD(_param) _param.time_keepalive_period +#define EBTN_PARAM_CLICK_MAX_CONSECUTIVE(_param) _param.max_consecutive + +static const ebtn_btn_param_t param_default = EBTN_PARAMS_INIT(20, 0, 20, 300, 200, 500, 10); + +static const ebtn_btn_param_t param_onrelease_debounce = EBTN_PARAMS_INIT(20, 80, 0, 300, 200, 500, 10); + +static const ebtn_btn_param_t param_keepalive_with_click = EBTN_PARAMS_INIT(20, 80, 0, 400, 200, 100, 10); + +static const ebtn_btn_param_t param_max_click_3 = EBTN_PARAMS_INIT(20, 80, 0, 400, 200, 100, 3); + +static const ebtn_btn_param_t param_click_multi_max_0 = EBTN_PARAMS_INIT(20, 80, 0, 400, 0, 100, 3); + +static const ebtn_btn_param_t param_keep_alive_0 = EBTN_PARAMS_INIT(20, 80, 0, 400, 200, 0, 3); + +/* List of used buttons -> test case */ +static ebtn_btn_t btns[] = {EBTN_BUTTON_INIT(USER_BUTTON_default, ¶m_default), + EBTN_BUTTON_INIT(USER_BUTTON_onrelease_debounce, ¶m_onrelease_debounce), + EBTN_BUTTON_INIT(USER_BUTTON_keepalive_with_click, ¶m_keepalive_with_click), + EBTN_BUTTON_INIT(USER_BUTTON_max_click_3, ¶m_max_click_3), + EBTN_BUTTON_INIT(USER_BUTTON_click_multi_max_0, ¶m_click_multi_max_0), + EBTN_BUTTON_INIT(USER_BUTTON_keep_alive_0, ¶m_keep_alive_0)}; + +static volatile uint32_t test_processed_time_current; + +/* Set button state -> used for test purposes */ +#define BTN_STATE_RAW(_key_id_, _state_, _duration_) \ + { \ + .key_id = (_key_id_), .state = (_state_), .duration = (_duration_) \ + } + +#define BTN_STATE(_state_, _duration_) BTN_STATE_RAW(USER_BUTTON_default, _state_, _duration_) + +/* On-Press event */ +#define BTN_EVENT_ONPRESS() \ + { \ + .evt = EBTN_EVT_ONPRESS \ + } +/* On-Release event */ +#define BTN_EVENT_ONRELEASE() \ + { \ + .evt = EBTN_EVT_ONRELEASE \ + } +/* On-Click event */ +#define BTN_EVENT_ONCLICK(_conseq_clicks_) \ + { \ + .evt = EBTN_EVT_ONCLICK, .conseq_clicks = (_conseq_clicks_) \ + } +/* On-Click event */ +#define BTN_EVENT_KEEPALIVE(_keepalive_cnt_) \ + { \ + .evt = EBTN_EVT_KEEPALIVE, .keepalive_cnt = (_keepalive_cnt_) \ + } + +/* + * Simulate click event + */ +static btn_test_time_t test_sequence_single_click[] = { + /* + * Step 1: + * Go to active state and stay there for a period + * of minimum debounce time and minimum + * time input must be active to later detect click event + * + * Step 2: + * Go low and stay inactive until time to report click has elapsed. + * Include debounce timing for release event. + * + * Add +1 to the end, to force click event, + * and not to go to "consecutive clicks" if any further tests are added in this sequence + */ + BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_default) + EBTN_PARAM_TIME_CLICK_MIN(param_default)), + BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_default) + EBTN_PARAM_TIME_CLICK_MULTI_MAX(param_default)), +}; + +static const btn_test_evt_t test_events_single_click[] = { + BTN_EVENT_ONPRESS(), + BTN_EVENT_ONRELEASE(), + BTN_EVENT_ONCLICK(1), +}; + +static btn_test_time_t test_sequence_double_click[] = { + /* + * Repeat above steps, this time w/o +1 at the end. + * + * Simulate "2" consecutive clicks and report final "click" event at the end of the + * sequence, with "2" consecutive clicks in the report info + */ + BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_default) + EBTN_PARAM_TIME_CLICK_MIN(param_default)), + BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_default) + EBTN_PARAM_TIME_CLICK_MULTI_MAX(param_default) / 2), + BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_default) + EBTN_PARAM_TIME_CLICK_MIN(param_default)), + BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_default) + EBTN_PARAM_TIME_CLICK_MULTI_MAX(param_default)), +}; + +static const btn_test_evt_t test_events_double_click[] = { + BTN_EVENT_ONPRESS(), BTN_EVENT_ONRELEASE(), BTN_EVENT_ONPRESS(), BTN_EVENT_ONRELEASE(), BTN_EVENT_ONCLICK(2), +}; + +static btn_test_time_t test_sequence_triple_click[] = { + BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_default) + EBTN_PARAM_TIME_CLICK_MIN(param_default)), + BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_default) + EBTN_PARAM_TIME_CLICK_MULTI_MAX(param_default) / 2), + BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_default) + EBTN_PARAM_TIME_CLICK_MIN(param_default)), + BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_default) + EBTN_PARAM_TIME_CLICK_MULTI_MAX(param_default) / 2), + BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_default) + EBTN_PARAM_TIME_CLICK_MIN(param_default)), + BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_default) + EBTN_PARAM_TIME_CLICK_MULTI_MAX(param_default)), +}; + +static const btn_test_evt_t test_events_triple_click[] = { + BTN_EVENT_ONPRESS(), BTN_EVENT_ONRELEASE(), BTN_EVENT_ONPRESS(), BTN_EVENT_ONRELEASE(), + BTN_EVENT_ONPRESS(), BTN_EVENT_ONRELEASE(), BTN_EVENT_ONCLICK(3), +}; + +static btn_test_time_t test_sequence_double_click_critical_time[] = { + /* + * Repeat above steps, this time w/o +1 at the end. + * + * Simulate "2" consecutive clicks and report final "click" event at the end of the + * sequence, with "2" consecutive clicks in the report info + */ + BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_default) + EBTN_PARAM_TIME_CLICK_MIN(param_default)), + /* Hold button in release state for time that is max for 2 clicks - time that we will + indicate in the next press state -> this is the frequency between detected events */ + BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_default) + + EBTN_PARAM_TIME_CLICK_MULTI_MAX(param_default) + /* Decrease by active time in next step */ + - (EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_default)) - 2), + /* Active time */ + BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_default) + EBTN_PARAM_TIME_CLICK_MIN(param_default)), + BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_default) + EBTN_PARAM_TIME_CLICK_MULTI_MAX(param_default)), +}; + +static const btn_test_evt_t test_events_double_click_critical_time[] = { + BTN_EVENT_ONPRESS(), BTN_EVENT_ONRELEASE(), BTN_EVENT_ONPRESS(), BTN_EVENT_ONRELEASE(), BTN_EVENT_ONCLICK(2), +}; + +static btn_test_time_t test_sequence_double_click_critical_time_over[] = { + /* + * This test shows how to handle case when 2 clicks are being executed, + * but time between 2 release events is larger than maximum + * allowed time for consecutive clicks. + * + * In this case, 2 onclick events are sent, + * both with consecutive clicks counter set to 1 + */ + BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_default) + EBTN_PARAM_TIME_CLICK_MIN(param_default)), + BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_default) + EBTN_PARAM_TIME_CLICK_MULTI_MAX(param_default) - + (EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_default))), + BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_default) + EBTN_PARAM_TIME_CLICK_MIN(param_default)), + BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_default) + EBTN_PARAM_TIME_CLICK_MULTI_MAX(param_default)), +}; + +static const btn_test_evt_t test_events_double_click_critical_time_over[] = { + BTN_EVENT_ONPRESS(), + BTN_EVENT_ONRELEASE(), + /* This one is to handle click for first sequence */ + BTN_EVENT_ONCLICK(1), + BTN_EVENT_ONPRESS(), + BTN_EVENT_ONRELEASE(), + /* This one is to handle click for second sequence */ + BTN_EVENT_ONCLICK(1), +}; + +static btn_test_time_t test_sequence_click_with_keepalive[] = { + /* + * Make a click event, followed by the longer press. + * Simulate "long press" w/ previous click, that has click counter set to 1 + */ + BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_default) + EBTN_PARAM_TIME_CLICK_MIN(param_default)), + BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_default) + EBTN_PARAM_TIME_CLICK_MULTI_MAX(param_default) / 2), + BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_default)), + BTN_STATE(1, EBTN_PARAM_TIME_KEEPALIVE_PERIOD(param_default)), + BTN_STATE(1, EBTN_PARAM_TIME_KEEPALIVE_PERIOD(param_default)), + BTN_STATE(1, EBTN_PARAM_TIME_KEEPALIVE_PERIOD(param_default)), + BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_default) + EBTN_PARAM_TIME_CLICK_MULTI_MAX(param_default)), +}; + +static const btn_test_evt_t test_events_click_with_keepalive[] = { + BTN_EVENT_ONPRESS(), + BTN_EVENT_ONRELEASE(), + BTN_EVENT_ONPRESS(), + /* This one is to handle click before long press */ + BTN_EVENT_ONCLICK(1), + BTN_EVENT_KEEPALIVE(1), + BTN_EVENT_KEEPALIVE(2), + BTN_EVENT_KEEPALIVE(3), + BTN_EVENT_ONRELEASE(), +}; + +static btn_test_time_t test_sequence_click_with_short[] = { + /* Make with short press (shorter than minimum required) */ + BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_default) + EBTN_PARAM_TIME_CLICK_MIN(param_default) / 2), + BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_default) + EBTN_PARAM_TIME_CLICK_MULTI_MAX(param_default)), +}; + +static const btn_test_evt_t test_events_click_with_short[] = { + BTN_EVENT_ONPRESS(), + BTN_EVENT_ONRELEASE(), +}; + +static btn_test_time_t test_sequence_click_with_short_with_multi[] = { + /* Make with short press (shorter than minimum required) */ + BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_default) + EBTN_PARAM_TIME_CLICK_MIN(param_default) / 2), + BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_default) + EBTN_PARAM_TIME_CLICK_MULTI_MAX(param_default) / 2), + BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_default) + EBTN_PARAM_TIME_CLICK_MIN(param_default)), + BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_default) + EBTN_PARAM_TIME_CLICK_MULTI_MAX(param_default) / 2), + BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_default) + EBTN_PARAM_TIME_CLICK_MIN(param_default)), + BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_default) + EBTN_PARAM_TIME_CLICK_MULTI_MAX(param_default)), +}; + +static const btn_test_evt_t test_events_click_with_short_with_multi[] = { + /* This one is short... */ + BTN_EVENT_ONPRESS(), BTN_EVENT_ONRELEASE(), BTN_EVENT_ONPRESS(), BTN_EVENT_ONRELEASE(), + BTN_EVENT_ONPRESS(), BTN_EVENT_ONRELEASE(), BTN_EVENT_ONCLICK(2), +}; + +static btn_test_time_t test_sequence_multi_click_with_short[] = { + /* Make 2 clicks, and 3rd one with short press (shorter than minimum required) */ + BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_default) + EBTN_PARAM_TIME_CLICK_MIN(param_default)), + BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_default) + EBTN_PARAM_TIME_CLICK_MULTI_MAX(param_default) / 2), + BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_default) + EBTN_PARAM_TIME_CLICK_MIN(param_default)), + BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_default) + EBTN_PARAM_TIME_CLICK_MULTI_MAX(param_default) / 2), + BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_default) + EBTN_PARAM_TIME_CLICK_MIN(param_default) / 2), + BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_default)), +}; + +static const btn_test_evt_t test_events_multi_click_with_short[] = { + BTN_EVENT_ONPRESS(), + BTN_EVENT_ONRELEASE(), + BTN_EVENT_ONPRESS(), + BTN_EVENT_ONRELEASE(), + /* This one is short... */ + BTN_EVENT_ONPRESS(), + BTN_EVENT_ONRELEASE(), + BTN_EVENT_ONCLICK(2), +}; + +static btn_test_time_t test_sequence_onpress_debounce[] = { + BTN_STATE(0, 0), + BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_default) / 2), + BTN_STATE(0, 1), + BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_default) / 2), + BTN_STATE(0, 1), + BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_default) / 2), + BTN_STATE(0, 1), + BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_default) + EBTN_PARAM_TIME_CLICK_MIN(param_default)), + BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_default) + EBTN_PARAM_TIME_CLICK_MULTI_MAX(param_default) + 1), +}; + +static const btn_test_evt_t test_events_onpress_debounce[] = { + BTN_EVENT_ONPRESS(), + BTN_EVENT_ONRELEASE(), + BTN_EVENT_ONCLICK(1), +}; + +// for test overflow, make sure enable macro 'EBTN_CONFIG_TIMER_16' or compile with 'make all +// CFLAGS=-DEBTN_CONFIG_TIMER_16' +static btn_test_time_t test_sequence_time_overflow_onpress_debounce[] = { + BTN_STATE(0, 0x0ffff - (EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_default) / 2)), + BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_default) + EBTN_PARAM_TIME_CLICK_MIN(param_default)), + BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_default) + EBTN_PARAM_TIME_CLICK_MULTI_MAX(param_default)), +}; + +static const btn_test_evt_t test_events_time_overflow_onpress_debounce[] = { + BTN_EVENT_ONPRESS(), + BTN_EVENT_ONRELEASE(), + BTN_EVENT_ONCLICK(1), +}; + +static btn_test_time_t test_sequence_time_overflow_onpress[] = { + BTN_STATE(0, 0x0ffff - (EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_default) + EBTN_PARAM_TIME_CLICK_MIN(param_default) / 2)), + BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_default) + EBTN_PARAM_TIME_CLICK_MIN(param_default)), + BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_default) + EBTN_PARAM_TIME_CLICK_MULTI_MAX(param_default)), +}; + +static const btn_test_evt_t test_events_time_overflow_onpress[] = { + BTN_EVENT_ONPRESS(), + BTN_EVENT_ONRELEASE(), + BTN_EVENT_ONCLICK(1), +}; + +static btn_test_time_t test_sequence_time_overflow_onrelease_muti[] = { + BTN_STATE(0, 0x0ffff - (EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_default) + EBTN_PARAM_TIME_CLICK_MIN(param_default) + + EBTN_PARAM_TIME_CLICK_MIN(param_default) / 2)), + BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_default) + EBTN_PARAM_TIME_CLICK_MIN(param_default)), + BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_default) + EBTN_PARAM_TIME_CLICK_MULTI_MAX(param_default)), +}; + +static const btn_test_evt_t test_events_time_overflow_onrelease_muti[] = { + BTN_EVENT_ONPRESS(), + BTN_EVENT_ONRELEASE(), + BTN_EVENT_ONCLICK(1), +}; + +static btn_test_time_t test_sequence_time_overflow_keepalive[] = { + BTN_STATE(0, 0x0ffff - (EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_default) + EBTN_PARAM_TIME_CLICK_MIN(param_default) + + EBTN_PARAM_TIME_KEEPALIVE_PERIOD(param_default) / 2)), + BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_default) + EBTN_PARAM_TIME_CLICK_MIN(param_default)), + BTN_STATE(1, EBTN_PARAM_TIME_KEEPALIVE_PERIOD(param_default)), + BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_default) + EBTN_PARAM_TIME_CLICK_MULTI_MAX(param_default)), +}; + +static const btn_test_evt_t test_events_time_overflow_keepalive[] = { + BTN_EVENT_ONPRESS(), + BTN_EVENT_KEEPALIVE(1), + BTN_EVENT_ONRELEASE(), +}; + +/// +/// Test onrelease debounce +/// + +static btn_test_time_t test_sequence_onrelease_debounce[] = { + BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_onrelease_debounce) + EBTN_PARAM_TIME_CLICK_MIN(param_onrelease_debounce)), + BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_onrelease_debounce) / 2), + BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_onrelease_debounce)), + BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_onrelease_debounce) + EBTN_PARAM_TIME_CLICK_MULTI_MAX(param_onrelease_debounce)), +}; + +static const btn_test_evt_t test_events_onrelease_debounce[] = { + BTN_EVENT_ONPRESS(), + BTN_EVENT_ONRELEASE(), + BTN_EVENT_ONCLICK(1), +}; + +static btn_test_time_t test_sequence_onrelease_debounce_over[] = { + BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_onrelease_debounce) + EBTN_PARAM_TIME_CLICK_MIN(param_onrelease_debounce)), + BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_onrelease_debounce)), + BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_onrelease_debounce) + EBTN_PARAM_TIME_CLICK_MIN(param_onrelease_debounce)), + BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_onrelease_debounce) + EBTN_PARAM_TIME_CLICK_MULTI_MAX(param_onrelease_debounce)), +}; + +static const btn_test_evt_t test_events_onrelease_debounce_over[] = { + BTN_EVENT_ONPRESS(), BTN_EVENT_ONRELEASE(), BTN_EVENT_ONPRESS(), BTN_EVENT_ONRELEASE(), BTN_EVENT_ONCLICK(2), +}; + +static btn_test_time_t test_sequence_onrelease_debounce_time_overflow[] = { + BTN_STATE(0, 0x0ffff - (EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_onrelease_debounce) + EBTN_PARAM_TIME_CLICK_MIN(param_onrelease_debounce) + + EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_onrelease_debounce) / 2)), + BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_onrelease_debounce) + EBTN_PARAM_TIME_CLICK_MIN(param_onrelease_debounce)), + BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_onrelease_debounce) + EBTN_PARAM_TIME_CLICK_MULTI_MAX(param_onrelease_debounce)), +}; + +static const btn_test_evt_t test_events_onrelease_debounce_time_overflow[] = { + BTN_EVENT_ONPRESS(), + BTN_EVENT_ONRELEASE(), + BTN_EVENT_ONCLICK(1), +}; + +/// +/// Test keepalive with click debounce +/// +static btn_test_time_t test_sequence_keepalive_with_click[] = { + BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_keepalive_with_click) + EBTN_PARAM_TIME_CLICK_MIN(param_keepalive_with_click)), + BTN_STATE(1, EBTN_PARAM_TIME_KEEPALIVE_PERIOD(param_keepalive_with_click)), + BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_keepalive_with_click) + EBTN_PARAM_TIME_CLICK_MULTI_MAX(param_keepalive_with_click)), +}; + +static const btn_test_evt_t test_events_keepalive_with_click[] = { + BTN_EVENT_ONPRESS(), + BTN_EVENT_KEEPALIVE(1), + BTN_EVENT_ONRELEASE(), + BTN_EVENT_ONCLICK(1), +}; + +static btn_test_time_t test_sequence_keepalive_with_click_double[] = { + BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_keepalive_with_click) + EBTN_PARAM_TIME_CLICK_MIN(param_keepalive_with_click)), + BTN_STATE(1, EBTN_PARAM_TIME_KEEPALIVE_PERIOD(param_keepalive_with_click)), + BTN_STATE(1, EBTN_PARAM_TIME_KEEPALIVE_PERIOD(param_keepalive_with_click)), + BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_keepalive_with_click) + EBTN_PARAM_TIME_CLICK_MULTI_MAX(param_keepalive_with_click)), +}; + +static const btn_test_evt_t test_events_keepalive_with_click_double[] = { + BTN_EVENT_ONPRESS(), BTN_EVENT_KEEPALIVE(1), BTN_EVENT_KEEPALIVE(2), BTN_EVENT_ONRELEASE(), BTN_EVENT_ONCLICK(1), +}; + +/// +/// Test max multi click with 3 +/// +static btn_test_time_t test_sequence_max_click_3[] = { + BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_max_click_3) + EBTN_PARAM_TIME_CLICK_MIN(param_max_click_3)), + BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_max_click_3) + EBTN_PARAM_TIME_CLICK_MULTI_MAX(param_max_click_3) / 2), + BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_max_click_3) + EBTN_PARAM_TIME_CLICK_MIN(param_max_click_3)), + BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_max_click_3) + EBTN_PARAM_TIME_CLICK_MULTI_MAX(param_max_click_3) / 2), + BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_max_click_3) + EBTN_PARAM_TIME_CLICK_MIN(param_max_click_3)), + BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_max_click_3) + EBTN_PARAM_TIME_CLICK_MULTI_MAX(param_max_click_3)), +}; + +static const btn_test_evt_t test_events_max_click_3[] = { + BTN_EVENT_ONPRESS(), BTN_EVENT_ONRELEASE(), BTN_EVENT_ONPRESS(), BTN_EVENT_ONRELEASE(), + BTN_EVENT_ONPRESS(), BTN_EVENT_ONRELEASE(), BTN_EVENT_ONCLICK(3), +}; + +static btn_test_time_t test_sequence_max_click_3_over[] = { + BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_max_click_3) + EBTN_PARAM_TIME_CLICK_MIN(param_max_click_3)), + BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_max_click_3) + EBTN_PARAM_TIME_CLICK_MULTI_MAX(param_max_click_3) / 2), + BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_max_click_3) + EBTN_PARAM_TIME_CLICK_MIN(param_max_click_3)), + BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_max_click_3) + EBTN_PARAM_TIME_CLICK_MULTI_MAX(param_max_click_3) / 2), + BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_max_click_3) + EBTN_PARAM_TIME_CLICK_MIN(param_max_click_3)), + BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_max_click_3) + EBTN_PARAM_TIME_CLICK_MULTI_MAX(param_max_click_3) / 2), + BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_max_click_3) + EBTN_PARAM_TIME_CLICK_MIN(param_max_click_3)), + BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_max_click_3) + EBTN_PARAM_TIME_CLICK_MULTI_MAX(param_max_click_3)), +}; + +static const btn_test_evt_t test_events_max_click_3_over[] = { + BTN_EVENT_ONPRESS(), BTN_EVENT_ONRELEASE(), BTN_EVENT_ONPRESS(), BTN_EVENT_ONRELEASE(), BTN_EVENT_ONPRESS(), + BTN_EVENT_ONRELEASE(), BTN_EVENT_ONCLICK(3), BTN_EVENT_ONPRESS(), BTN_EVENT_ONRELEASE(), BTN_EVENT_ONCLICK(1), +}; + +/// +/// Test click multi max with 0 +/// +static btn_test_time_t test_sequence_click_multi_max_0[] = { + BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_click_multi_max_0) + EBTN_PARAM_TIME_CLICK_MIN(param_click_multi_max_0)), + 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), + BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_click_multi_max_0) + EBTN_PARAM_TIME_CLICK_MIN(param_click_multi_max_0)), + 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), + BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_click_multi_max_0) + EBTN_PARAM_TIME_CLICK_MIN(param_click_multi_max_0)), + BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_click_multi_max_0) + EBTN_PARAM_TIME_CLICK_MULTI_MAX(param_click_multi_max_0)), +}; + +static const btn_test_evt_t test_events_click_multi_max_0[] = { + BTN_EVENT_ONPRESS(), BTN_EVENT_ONRELEASE(), BTN_EVENT_ONCLICK(1), BTN_EVENT_ONPRESS(), BTN_EVENT_ONRELEASE(), + BTN_EVENT_ONCLICK(1), BTN_EVENT_ONPRESS(), BTN_EVENT_ONRELEASE(), BTN_EVENT_ONCLICK(1), +}; + +/// +/// Test max keepalive with 0 +/// +static btn_test_time_t test_sequence_keep_alive_0[] = { + BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_keep_alive_0) + EBTN_PARAM_TIME_CLICK_MIN(param_keep_alive_0)), + BTN_STATE(1, EBTN_PARAM_TIME_CLICK_MAX(param_keep_alive_0) / 2), + BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_keep_alive_0) + EBTN_PARAM_TIME_CLICK_MULTI_MAX(param_keep_alive_0)), +}; + +static const btn_test_evt_t test_events_keep_alive_0[] = { + BTN_EVENT_ONPRESS(), + BTN_EVENT_ONRELEASE(), + BTN_EVENT_ONCLICK(1), +}; + +static btn_test_arr_t test_list[] = { + TEST_ARRAY_DEFINE(USER_BUTTON_default, test_sequence_single_click, test_events_single_click), + TEST_ARRAY_DEFINE(USER_BUTTON_default, test_sequence_double_click, test_events_double_click), + TEST_ARRAY_DEFINE(USER_BUTTON_default, test_sequence_triple_click, test_events_triple_click), + TEST_ARRAY_DEFINE(USER_BUTTON_default, test_sequence_double_click_critical_time, test_events_double_click_critical_time), + TEST_ARRAY_DEFINE(USER_BUTTON_default, test_sequence_double_click_critical_time_over, test_events_double_click_critical_time_over), + TEST_ARRAY_DEFINE(USER_BUTTON_default, test_sequence_click_with_keepalive, test_events_click_with_keepalive), + TEST_ARRAY_DEFINE(USER_BUTTON_default, test_sequence_click_with_short, test_events_click_with_short), + TEST_ARRAY_DEFINE(USER_BUTTON_default, test_sequence_click_with_short_with_multi, test_events_click_with_short_with_multi), + TEST_ARRAY_DEFINE(USER_BUTTON_default, test_sequence_multi_click_with_short, test_events_multi_click_with_short), + TEST_ARRAY_DEFINE(USER_BUTTON_default, test_sequence_onpress_debounce, test_events_onpress_debounce), + TEST_ARRAY_DEFINE(USER_BUTTON_default, test_sequence_time_overflow_onpress_debounce, test_events_time_overflow_onpress_debounce), + TEST_ARRAY_DEFINE(USER_BUTTON_default, test_sequence_time_overflow_onpress, test_events_time_overflow_onpress), + TEST_ARRAY_DEFINE(USER_BUTTON_default, test_sequence_time_overflow_onrelease_muti, test_events_time_overflow_onrelease_muti), + TEST_ARRAY_DEFINE(USER_BUTTON_default, test_sequence_time_overflow_keepalive, test_events_time_overflow_keepalive), + + TEST_ARRAY_DEFINE(USER_BUTTON_onrelease_debounce, test_sequence_onrelease_debounce, test_events_onrelease_debounce), + TEST_ARRAY_DEFINE(USER_BUTTON_onrelease_debounce, test_sequence_onrelease_debounce_over, test_events_onrelease_debounce_over), + TEST_ARRAY_DEFINE(USER_BUTTON_onrelease_debounce, test_sequence_onrelease_debounce_time_overflow, test_events_onrelease_debounce_time_overflow), + + TEST_ARRAY_DEFINE(USER_BUTTON_keepalive_with_click, test_sequence_keepalive_with_click, test_events_keepalive_with_click), + TEST_ARRAY_DEFINE(USER_BUTTON_keepalive_with_click, test_sequence_keepalive_with_click_double, test_events_keepalive_with_click_double), + + TEST_ARRAY_DEFINE(USER_BUTTON_max_click_3, test_sequence_max_click_3, test_events_max_click_3), + TEST_ARRAY_DEFINE(USER_BUTTON_max_click_3, test_sequence_max_click_3_over, test_events_max_click_3_over), + + TEST_ARRAY_DEFINE(USER_BUTTON_click_multi_max_0, test_sequence_click_multi_max_0, test_events_click_multi_max_0), + + TEST_ARRAY_DEFINE(USER_BUTTON_keep_alive_0, test_sequence_keep_alive_0, test_events_keep_alive_0), +}; + +static btn_test_arr_t *select_test_item; + +/* Get button state for given current time */ +static uint8_t prv_get_state_for_time(uint16_t key_id, uint32_t time) +{ + uint8_t state = 0; + uint32_t duration = 0; + + if (select_test_item->test_key_id == key_id) + { + // printf("time: %d, key_id: %d\n", time, key_id); + for (size_t i = 0; i < select_test_item->test_sequence_cnt; ++i) + { + if (select_test_item->test_sequence[i].duration == 0) + { + continue; + } + duration += select_test_item->test_sequence[i].duration + 1; /* Advance time, need add 1 for state switch time. */ + // printf("i: %d, duration: %d\n", i, duration); + if (time <= duration) + { + state = select_test_item->test_sequence[i].state; + // printf("i: %d, time: %d, duration: %d, state: %d\n", i, time, duration, state); + // printf("state: %d\n", state); + break; + } + } + } + + return state; +} + +static uint32_t test_get_state_total_duration(void) +{ + uint32_t duration = 0; + + for (size_t i = 0; i < select_test_item->test_sequence_cnt; ++i) + { + if (select_test_item->test_sequence[i].duration == 0) + { + continue; + } + duration += select_test_item->test_sequence[i].duration + 1; /* Advance time, need add 1 for state switch time. */ + } + + return duration; +} + +/* Get button state */ +static uint8_t prv_btn_get_state(struct ebtn_btn *btn) +{ + uint8_t state = prv_get_state_for_time(btn->key_id, test_processed_time_current); + + (void)btn; + return state; +} + +static uint32_t test_processed_event_time_prev; +static uint32_t test_processed_array_index = 0; +/* Process button event */ +static void prv_btn_event(struct ebtn_btn *btn, ebtn_evt_t evt) +{ + const char *s; + uint32_t color, keepalive_cnt = 0, diff_time; + const btn_test_evt_t *test_evt_data = NULL; + HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE); + + if (test_processed_array_index >= select_test_item->test_events_cnt) + { + SetConsoleTextAttribute(hConsole, FOREGROUND_RED); + printf("[%7u] ERROR! Array index is out of bounds!\r\n", (unsigned)test_processed_time_current); + SetConsoleTextAttribute(hConsole, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE); + } + else + { + test_evt_data = &select_test_item->test_events[test_processed_array_index]; + } + + /* Handle timing */ + diff_time = test_processed_time_current - test_processed_event_time_prev; + test_processed_event_time_prev = test_processed_time_current; + keepalive_cnt = btn->keepalive_cnt; + + /* Event type must match */ + ASSERT((test_evt_data != NULL) && (test_evt_data->evt == evt)); + + /* Get event string */ + if (evt == EBTN_EVT_KEEPALIVE) + { + s = "KEEPALIVE"; + color = FOREGROUND_RED | FOREGROUND_BLUE; + + ASSERT((test_evt_data != NULL) && (test_evt_data->keepalive_cnt == keepalive_cnt)); + } + else if (evt == EBTN_EVT_ONPRESS) + { + s = "ONPRESS"; + color = FOREGROUND_GREEN; + } + else if (evt == EBTN_EVT_ONRELEASE) + { + s = "ONRELEASE"; + color = FOREGROUND_BLUE; + } + else if (evt == EBTN_EVT_ONCLICK) + { + s = "ONCLICK"; + color = FOREGROUND_RED | FOREGROUND_GREEN; + + ASSERT((test_evt_data != NULL) && (test_evt_data->conseq_clicks == btn->click_cnt)); + } + else + { + s = "UNKNOWN"; + color = FOREGROUND_RED; + } + + SetConsoleTextAttribute(hConsole, color); + 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, + s, (unsigned)keepalive_cnt, (unsigned)btn->click_cnt); + + SetConsoleTextAttribute(hConsole, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE); + ++test_processed_array_index; /* Go to next step in next event */ +} + +/** + * \brief Test function + */ +int example_test(void) +{ + printf("Test running\r\n"); + + for (int index = 0; index < EBTN_ARRAY_SIZE(test_list); index++) + { + select_test_item = &test_list[index]; + + SUITE_START(select_test_item->test_name); + + // printf("\n"); + + // init variable + test_processed_event_time_prev = 0; + test_processed_array_index = 0; + + /* Define buttons */ + ebtn_init(btns, EBTN_ARRAY_SIZE(btns), NULL, 0, prv_btn_get_state, prv_btn_event); + + /* Counter simulates ms tick */ + for (uint32_t i = 0; i < MAX_TIME_MS; ++i) + { + test_processed_time_current = i; /* Set current time used in callback */ + ebtn_process(i); /* Now run processing */ + + // printf("time: %d, end: %d, in_process(): %d/%d\n", i, test_processed_array_index >= select_test_item->test_events_cnt + // , ebtn_is_btn_in_process(ebtn_get_btn_by_key_id(select_test_item->test_key_id)), ebtn_is_in_process()); + // check end + if (test_processed_array_index >= select_test_item->test_events_cnt) + { + uint32_t duration = test_get_state_total_duration(); + if (i > duration + 1) + { + ASSERT(!ebtn_is_btn_in_process(ebtn_get_btn_by_key_id(select_test_item->test_key_id))); + ASSERT(!ebtn_is_in_process()); + } + } + } + ASSERT(test_processed_array_index == select_test_item->test_events_cnt); + + // printf("\n"); + + SUITE_END(); + } + return 0; +} diff --git a/example_user.c b/example_user.c new file mode 100644 index 0000000..ab0dd11 --- /dev/null +++ b/example_user.c @@ -0,0 +1,290 @@ +#include "windows.h" +#include +#include + +#include "ebtn.h" + +static LARGE_INTEGER freq, sys_start_time; +static uint32_t get_tick(void); + +typedef enum +{ + USER_BUTTON_0 = 0, + USER_BUTTON_1, + USER_BUTTON_2, + USER_BUTTON_3, + USER_BUTTON_4, + USER_BUTTON_5, + USER_BUTTON_6, + USER_BUTTON_7, + USER_BUTTON_8, + USER_BUTTON_9, + USER_BUTTON_INVALID, + USER_BUTTON_MAX, + + USER_BUTTON_COMBO_0 = 0x100, + USER_BUTTON_COMBO_1, + USER_BUTTON_COMBO_2, + USER_BUTTON_COMBO_3, + USER_BUTTON_COMBO_MAX, +} user_button_t; + +/* User defined settings */ +static const ebtn_btn_param_t defaul_ebtn_param = EBTN_PARAMS_INIT(20, 0, 20, 300, 200, 500, 10); + +static ebtn_btn_t btns[] = { + // For key_idx double test, need full key map size + EBTN_BUTTON_INIT(USER_BUTTON_INVALID, &defaul_ebtn_param), + EBTN_BUTTON_INIT(USER_BUTTON_INVALID, &defaul_ebtn_param), + EBTN_BUTTON_INIT(USER_BUTTON_INVALID, &defaul_ebtn_param), + EBTN_BUTTON_INIT(USER_BUTTON_INVALID, &defaul_ebtn_param), + + EBTN_BUTTON_INIT(USER_BUTTON_0, &defaul_ebtn_param), + EBTN_BUTTON_INIT(USER_BUTTON_1, &defaul_ebtn_param), + EBTN_BUTTON_INIT(USER_BUTTON_2, &defaul_ebtn_param), + EBTN_BUTTON_INIT(USER_BUTTON_3, &defaul_ebtn_param), + + // For key_idx double test, need full key map size + EBTN_BUTTON_INIT(USER_BUTTON_INVALID, &defaul_ebtn_param), + EBTN_BUTTON_INIT(USER_BUTTON_INVALID, &defaul_ebtn_param), + EBTN_BUTTON_INIT(USER_BUTTON_INVALID, &defaul_ebtn_param), + EBTN_BUTTON_INIT(USER_BUTTON_INVALID, &defaul_ebtn_param), + + EBTN_BUTTON_INIT(USER_BUTTON_4, &defaul_ebtn_param), + EBTN_BUTTON_INIT(USER_BUTTON_5, &defaul_ebtn_param), + + // For key_idx double test, need full key map size + EBTN_BUTTON_INIT(USER_BUTTON_INVALID, &defaul_ebtn_param), + EBTN_BUTTON_INIT(USER_BUTTON_INVALID, &defaul_ebtn_param), + EBTN_BUTTON_INIT(USER_BUTTON_INVALID, &defaul_ebtn_param), + EBTN_BUTTON_INIT(USER_BUTTON_INVALID, &defaul_ebtn_param), + + EBTN_BUTTON_INIT(USER_BUTTON_INVALID, &defaul_ebtn_param), + EBTN_BUTTON_INIT(USER_BUTTON_INVALID, &defaul_ebtn_param), + EBTN_BUTTON_INIT(USER_BUTTON_INVALID, &defaul_ebtn_param), + EBTN_BUTTON_INIT(USER_BUTTON_INVALID, &defaul_ebtn_param), + + EBTN_BUTTON_INIT(USER_BUTTON_INVALID, &defaul_ebtn_param), + EBTN_BUTTON_INIT(USER_BUTTON_INVALID, &defaul_ebtn_param), + EBTN_BUTTON_INIT(USER_BUTTON_INVALID, &defaul_ebtn_param), + EBTN_BUTTON_INIT(USER_BUTTON_INVALID, &defaul_ebtn_param), + + EBTN_BUTTON_INIT(USER_BUTTON_INVALID, &defaul_ebtn_param), + EBTN_BUTTON_INIT(USER_BUTTON_INVALID, &defaul_ebtn_param), + EBTN_BUTTON_INIT(USER_BUTTON_INVALID, &defaul_ebtn_param), + EBTN_BUTTON_INIT(USER_BUTTON_INVALID, &defaul_ebtn_param), +}; + +static ebtn_btn_dyn_t btns_dyn[] = { + // For key_idx double test, need full key map size + EBTN_BUTTON_DYN_INIT(USER_BUTTON_INVALID, &defaul_ebtn_param), + EBTN_BUTTON_DYN_INIT(USER_BUTTON_INVALID, &defaul_ebtn_param), + EBTN_BUTTON_DYN_INIT(USER_BUTTON_INVALID, &defaul_ebtn_param), + EBTN_BUTTON_DYN_INIT(USER_BUTTON_INVALID, &defaul_ebtn_param), + + EBTN_BUTTON_DYN_INIT(USER_BUTTON_6, &defaul_ebtn_param), + EBTN_BUTTON_DYN_INIT(USER_BUTTON_7, &defaul_ebtn_param), + + // For key_idx double test, need full key map size + EBTN_BUTTON_DYN_INIT(USER_BUTTON_INVALID, &defaul_ebtn_param), + EBTN_BUTTON_DYN_INIT(USER_BUTTON_INVALID, &defaul_ebtn_param), + EBTN_BUTTON_DYN_INIT(USER_BUTTON_INVALID, &defaul_ebtn_param), + EBTN_BUTTON_DYN_INIT(USER_BUTTON_INVALID, &defaul_ebtn_param), + + EBTN_BUTTON_DYN_INIT(USER_BUTTON_8, &defaul_ebtn_param), + EBTN_BUTTON_DYN_INIT(USER_BUTTON_9, &defaul_ebtn_param), +}; + +uint32_t last_time_keys[USER_BUTTON_MAX - USER_BUTTON_0] = {0}; + +static ebtn_btn_combo_t btns_combo[] = { + EBTN_BUTTON_COMBO_INIT(USER_BUTTON_COMBO_0, &defaul_ebtn_param), + EBTN_BUTTON_COMBO_INIT(USER_BUTTON_COMBO_1, &defaul_ebtn_param), +}; + +static ebtn_btn_combo_dyn_t btns_combo_dyn[] = { + EBTN_BUTTON_COMBO_DYN_INIT(USER_BUTTON_COMBO_2, &defaul_ebtn_param), + EBTN_BUTTON_COMBO_DYN_INIT(USER_BUTTON_COMBO_3, &defaul_ebtn_param), +}; + +uint32_t last_time_keys_combo[USER_BUTTON_COMBO_MAX - USER_BUTTON_COMBO_0] = {0}; + +static int windows_get_match_key(uint16_t key_id) +{ + int key = 0; + switch (key_id) + { + case USER_BUTTON_0: + key = '0'; + break; + case USER_BUTTON_1: + key = '1'; + break; + case USER_BUTTON_2: + key = '2'; + break; + case USER_BUTTON_3: + key = '3'; + break; + case USER_BUTTON_4: + key = '4'; + break; + case USER_BUTTON_5: + key = '5'; + break; + case USER_BUTTON_6: + key = '6'; + break; + case USER_BUTTON_7: + key = '7'; + break; + case USER_BUTTON_8: + key = '8'; + break; + case USER_BUTTON_9: + key = '9'; + break; + } + + return key; +} + +/** + * \brief Get input state callback + * \param btn: Button instance + * \return `1` if button active, `0` otherwise + */ +uint8_t prv_btn_get_state(struct ebtn_btn *btn) +{ + /* + * Function will return negative number if button is pressed, + * or zero if button is releases + */ + return GetAsyncKeyState(windows_get_match_key(btn->key_id)) < 0; +} + +/** + * \brief Button event + * + * \param btn: Button instance + * \param evt: Button event + */ +void prv_btn_event(struct ebtn_btn *btn, ebtn_evt_t evt) +{ + const char *s; + uint32_t color, keepalive_cnt = 0; + HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE); + uint32_t diff_time = 0; + uint32_t *diff_time_ptr = NULL; + if (btn->key_id < USER_BUTTON_MAX) + { + diff_time_ptr = &last_time_keys[btn->key_id - USER_BUTTON_0]; + } + else + { + diff_time_ptr = &last_time_keys[btn->key_id - USER_BUTTON_COMBO_0]; + } + diff_time = get_tick() - *diff_time_ptr; + + /* This is for purpose of test and timing validation */ + if (diff_time > 2000) + { + diff_time = 0; + } + + *diff_time_ptr = get_tick(); /* Set current date as last one */ + + /* Get event string */ + if (evt == EBTN_EVT_KEEPALIVE) + { + s = "KEEPALIVE"; + color = FOREGROUND_RED | FOREGROUND_BLUE; + } + else if (evt == EBTN_EVT_ONPRESS) + { + s = "ONPRESS"; + color = FOREGROUND_GREEN; + } + else if (evt == EBTN_EVT_ONRELEASE) + { + s = "ONRELEASE"; + color = FOREGROUND_BLUE; + } + else if (evt == EBTN_EVT_ONCLICK) + { + s = "ONCLICK"; + color = FOREGROUND_RED | FOREGROUND_GREEN; + } + else + { + s = "UNKNOWN"; + color = FOREGROUND_RED; + } + + SetConsoleTextAttribute(hConsole, color); + 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, + (unsigned)ebtn_keepalive_get_count(btn), (unsigned)ebtn_click_get_count(btn)); + SetConsoleTextAttribute(hConsole, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE); +} + +/** + * \brief Example function + */ +int example_user(void) +{ + uint32_t time_last; + printf("Application running\r\n"); + QueryPerformanceFrequency(&freq); + QueryPerformanceCounter(&sys_start_time); + + /* Define buttons */ + ebtn_init(btns, EBTN_ARRAY_SIZE(btns), btns_combo, EBTN_ARRAY_SIZE(btns_combo), prv_btn_get_state, prv_btn_event); + + ebtn_combo_btn_add_btn(&btns_combo[0], USER_BUTTON_0); + ebtn_combo_btn_add_btn(&btns_combo[0], USER_BUTTON_1); + + ebtn_combo_btn_add_btn(&btns_combo[1], USER_BUTTON_2); + ebtn_combo_btn_add_btn(&btns_combo[1], USER_BUTTON_3); + + // dynamic register + for (int i = 0; i < (EBTN_ARRAY_SIZE(btns_dyn)); i++) + { + ebtn_register(&btns_dyn[i]); + } + + ebtn_combo_btn_add_btn(&btns_combo_dyn[0].btn, USER_BUTTON_4); + ebtn_combo_btn_add_btn(&btns_combo_dyn[0].btn, USER_BUTTON_5); + + ebtn_combo_btn_add_btn(&btns_combo_dyn[1].btn, USER_BUTTON_6); + ebtn_combo_btn_add_btn(&btns_combo_dyn[1].btn, USER_BUTTON_7); + + for (int i = 0; i < (EBTN_ARRAY_SIZE(btns_combo_dyn)); i++) + { + ebtn_combo_register(&btns_combo_dyn[i]); + } + + while (1) + { + /* Process forever */ + ebtn_process(get_tick()); + + /* Artificial sleep to offload win process */ + Sleep(5); + } + return 0; +} + +/** + * \brief Get current tick in ms from start of program + * \return uint32_t: Tick in ms + */ +static uint32_t get_tick(void) +{ + LONGLONG ret; + LARGE_INTEGER now; + + QueryPerformanceFrequency(&freq); + QueryPerformanceCounter(&now); + ret = now.QuadPart - sys_start_time.QuadPart; + return (uint32_t)((ret * 1000) / freq.QuadPart); +} diff --git a/main.c b/main.c new file mode 100644 index 0000000..245f29c --- /dev/null +++ b/main.c @@ -0,0 +1,14 @@ +#include +#include +#include "ebtn.h" +#include "windows.h" + +extern int example_test(void); +extern int example_user(void); + +int main(void) +{ + example_test(); + // example_user(); + return 0; +}