first commit

This commit is contained in:
wenbo13579
2024-02-24 10:39:51 +08:00
commit 1ec0a5e336
18 changed files with 4092 additions and 0 deletions

101
.clang-format Normal file
View File

@@ -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""

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/output

21
.vscode/c_cpp_properties.json vendored Normal file
View File

@@ -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
}

19
.vscode/launch.json vendored Normal file
View File

@@ -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"
}
]
}

11
.vscode/settings.json vendored Normal file
View File

@@ -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"
}
}

84
.vscode/tasks.json vendored Normal file
View File

@@ -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'"
]
}
}
]
}

202
LICENSE Normal file
View File

@@ -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.

201
Makefile Normal file
View File

@@ -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!

751
README.md Normal file
View File

@@ -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 SizeBytes | 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消息先执行另外一个tasktask执行完所有消息后heap保持不变。

BIN
README.vsdx Normal file

Binary file not shown.

5
build.mk Normal file
View File

@@ -0,0 +1,5 @@
SRC += .
SRC += ebtn
INCLUDE += .
INCLUDE += ebtn

27
code_format.py Normal file
View File

@@ -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)

597
ebtn/bit_array.h Normal file
View File

@@ -0,0 +1,597 @@
#ifndef _BIT_ARRAY_H_
#define _BIT_ARRAY_H_
#include <stdint.h>
#include <string.h>
#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_ */

550
ebtn/ebtn.c Normal file
View File

@@ -0,0 +1,550 @@
#include <string.h>
#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;
}

463
ebtn/ebtn.h Normal file
View File

@@ -0,0 +1,463 @@
#ifndef _EBTN_H
#define _EBTN_H
#include <stdint.h>
#include <string.h>
#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 */

755
example_test.c Normal file
View File

@@ -0,0 +1,755 @@
#include <stdio.h>
#include <stdlib.h>
#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, &param_default),
EBTN_BUTTON_INIT(USER_BUTTON_onrelease_debounce, &param_onrelease_debounce),
EBTN_BUTTON_INIT(USER_BUTTON_keepalive_with_click, &param_keepalive_with_click),
EBTN_BUTTON_INIT(USER_BUTTON_max_click_3, &param_max_click_3),
EBTN_BUTTON_INIT(USER_BUTTON_click_multi_max_0, &param_click_multi_max_0),
EBTN_BUTTON_INIT(USER_BUTTON_keep_alive_0, &param_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;
}

290
example_user.c Normal file
View File

@@ -0,0 +1,290 @@
#include "windows.h"
#include <stdio.h>
#include <stdlib.h>
#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);
}

14
main.c Normal file
View File

@@ -0,0 +1,14 @@
#include <stdio.h>
#include <string.h>
#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;
}