first commit
This commit is contained in:
101
.clang-format
Normal file
101
.clang-format
Normal 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
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
/output
|
||||||
21
.vscode/c_cpp_properties.json
vendored
Normal file
21
.vscode/c_cpp_properties.json
vendored
Normal 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
19
.vscode/launch.json
vendored
Normal 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
11
.vscode/settings.json
vendored
Normal 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
84
.vscode/tasks.json
vendored
Normal 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
202
LICENSE
Normal 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
201
Makefile
Normal 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
751
README.md
Normal 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 Size(Bytes) | 20(ebtn_btn_t) | 28(flex_button_t) | 44(Button) | 48(lwbtn_btn_t) |
|
||||||
|
| 支持组合按键 | 支持 | 不支持 | 不支持 | 不支持 |
|
||||||
|
| 支持静态注册(可以省Code Size) | 支持 | 不支持 | 不支持 | 支持 |
|
||||||
|
| 支持动态注册 | 支持 | 支持 | 支持 | 不支持 |
|
||||||
|
| 单击最大次数 | 无限 | 无限 | 2 | 无限 |
|
||||||
|
| 长按种类 | 无限 | 1 | 1 | 无限 |
|
||||||
|
| 批量扫描支持 | 支持 | 不支持 | 不支持 | 不支持 |
|
||||||
|
|
||||||
|
可以看出easy_button功能是最全的,并且使用的RAM Size也是最小的,这个在键盘之类有很多按键场景下非常有意义。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## 组合按键支持
|
||||||
|
|
||||||
|
现有的项目基本都不支持组合按键,基本都是要求用户根据ID在应用层将多个按键作为一个ID来实现,虽然这样也能实现组合按键的功能需要。
|
||||||
|
|
||||||
|
但是这样的实现逻辑不够优雅,并且扫描按键行为的逻辑不可避免有重复的部分,增加了mips。
|
||||||
|
|
||||||
|
本项目基于[bit_array_static](https://github.com/bobwenstudy/bit_array_static)实现了优雅的组合按键处理机制,无需重复定义按键扫描逻辑,驱动会利用已经读取到的按键状态来实现组合按键的功能逻辑。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## 长按支持
|
||||||
|
|
||||||
|
实际项目中会遇到各种功能需求,如长按3s是功能A,长按5s是功能B,长按30s是功能C。通过`keepalive_cnt`和`time_keepalive_period`的设计,能够支持各种场景的长按功能需要。
|
||||||
|
|
||||||
|
如定义`time_keepalive_period=1000`,那么每隔1s会上报一个`KEEPALIVE(EBTN_EVT_KEEPALIVE)`事件,应用层在收到上报事件后,当`keepalive_cnt==3`时,执行功能A;当`keepalive_cnt==5`时,执行功能B;当`keepalive_cnt==30`时,执行功能C。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## 批量扫描支持
|
||||||
|
|
||||||
|
现有的按键库都是一个个按键扫描再单独处理,这个在按键比较少的时候,比较好管理,但是在多按键场景下,尤其是矩阵键盘下,这个会大大增加扫描延迟,通过批量扫描支持,可以先在用户层将所有按键状态记录好(用户层根据具体应用优化获取速度),而后一次性将当前状态传给(`ebtn_process_with_curr_state`)驱动。
|
||||||
|
|
||||||
|
嵌入式按键处理驱动,支持单击、双击、多击、自动消抖、长按、长长按、超长按 | 低功耗支持 | 组合按键支持 | 静态/动态注册支持
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## 简易但灵活的事件类型
|
||||||
|
|
||||||
|
参考[lwbtn](https://github.com/MaJerle/lwbtn)实现,当有按键事件发生时,所上报的事件类型只有4种,通过`click_cnt`和`keepalive_cnt`来支持灵活的按键点击和长按功能需要,这样的设计大大简化了代码行为,也大大降低了后续维护成本。
|
||||||
|
|
||||||
|
如果用户觉得不好用,也可以在该驱动基础上再封装出自己所需的驱动。
|
||||||
|
|
||||||
|
```c
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
EBTN_EVT_ONPRESS = 0x00, /*!< On press event - sent when valid press is detected */
|
||||||
|
EBTN_EVT_ONRELEASE, /*!< On release event - sent when valid release event is detected (from
|
||||||
|
active to inactive) */
|
||||||
|
EBTN_EVT_ONCLICK, /*!< On Click event - sent when valid sequence of on-press and on-release
|
||||||
|
events occurs */
|
||||||
|
EBTN_EVT_KEEPALIVE, /*!< Keep alive event - sent periodically when button is active */
|
||||||
|
} ebtn_evt_t;
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## 关于低功耗
|
||||||
|
|
||||||
|
参考[Flexible Button](https://github.com/murphyzhao/FlexibleButton),本按键库是通过不间断扫描的方式来检查按键状态,因此会一直占用 CPU 资源,这对低功耗应用场景是不友好的。为了降低正常工作模式下的功耗,建议合理配置扫描周期(5ms - 20ms),扫描间隙里 CPU 可以进入轻度睡眠。
|
||||||
|
|
||||||
|
一般MCU都有深度睡眠模式,这是CPU只能被IO切换唤醒,所以驱动为大家提供了`int ebtn_is_in_process(void)`接口来判断是否可以进入深度睡眠模式。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# 代码结构
|
||||||
|
|
||||||
|
代码结构如下所示:
|
||||||
|
|
||||||
|
- **ebtn**:驱动库,主要包含BitArray管理和EasyButton管理。
|
||||||
|
- **example_user.c**:捕获windows的0-9作为按键输入,测试用户交互的例程。
|
||||||
|
- **example_test.c**:模拟一些场景的按键事件,对驱动进行测试。
|
||||||
|
- **main.c**:程序主入口,配置进行测试模式函数用户交互模式。
|
||||||
|
- **build.mk**和**Makefile**:Makefile编译环境。
|
||||||
|
- **README.md**:说明文档
|
||||||
|
|
||||||
|
```shell
|
||||||
|
bare_task_msg
|
||||||
|
├── ebtn
|
||||||
|
│ ├── bit_array.h
|
||||||
|
│ ├── ebtn.c
|
||||||
|
│ └── ebtn.h
|
||||||
|
├── build.mk
|
||||||
|
├── example_user.c
|
||||||
|
└── example_test.c
|
||||||
|
├── main.c
|
||||||
|
├── Makefile
|
||||||
|
└── README.md
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# 使用说明
|
||||||
|
|
||||||
|
## 使用简易步骤
|
||||||
|
|
||||||
|
Step1:定义KEY_ID、按键参数和按键数组和组合按键数组。
|
||||||
|
|
||||||
|
```c
|
||||||
|
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
USER_BUTTON_0 = 0,
|
||||||
|
USER_BUTTON_1,
|
||||||
|
USER_BUTTON_2,
|
||||||
|
USER_BUTTON_3,
|
||||||
|
USER_BUTTON_4,
|
||||||
|
USER_BUTTON_5,
|
||||||
|
USER_BUTTON_6,
|
||||||
|
USER_BUTTON_7,
|
||||||
|
USER_BUTTON_8,
|
||||||
|
USER_BUTTON_9,
|
||||||
|
USER_BUTTON_MAX,
|
||||||
|
|
||||||
|
USER_BUTTON_COMBO_0 = 0x100,
|
||||||
|
USER_BUTTON_COMBO_1,
|
||||||
|
USER_BUTTON_COMBO_2,
|
||||||
|
USER_BUTTON_COMBO_3,
|
||||||
|
USER_BUTTON_COMBO_MAX,
|
||||||
|
} user_button_t;
|
||||||
|
|
||||||
|
/* User defined settings */
|
||||||
|
static const ebtn_btn_param_t defaul_ebtn_param = EBTN_PARAMS_INIT(20, 0, 20, 300, 200, 500, 10);
|
||||||
|
|
||||||
|
static ebtn_btn_t btns[] = {
|
||||||
|
EBTN_BUTTON_INIT(USER_BUTTON_0, &defaul_ebtn_param),
|
||||||
|
EBTN_BUTTON_INIT(USER_BUTTON_1, &defaul_ebtn_param),
|
||||||
|
EBTN_BUTTON_INIT(USER_BUTTON_2, &defaul_ebtn_param),
|
||||||
|
EBTN_BUTTON_INIT(USER_BUTTON_3, &defaul_ebtn_param),
|
||||||
|
EBTN_BUTTON_INIT(USER_BUTTON_4, &defaul_ebtn_param),
|
||||||
|
EBTN_BUTTON_INIT(USER_BUTTON_5, &defaul_ebtn_param),
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
static ebtn_btn_combo_t btns_combo[] = {
|
||||||
|
EBTN_BUTTON_COMBO_INIT(USER_BUTTON_COMBO_0, &defaul_ebtn_param),
|
||||||
|
EBTN_BUTTON_COMBO_INIT(USER_BUTTON_COMBO_1, &defaul_ebtn_param),
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
Step2:初始化按键驱动
|
||||||
|
|
||||||
|
```c
|
||||||
|
ebtn_init(btns, EBTN_ARRAY_SIZE(btns), btns_combo, EBTN_ARRAY_SIZE(btns_combo),
|
||||||
|
prv_btn_get_state, prv_btn_event);
|
||||||
|
```
|
||||||
|
|
||||||
|
Step3:配置组合按键comb_key,必须在按键注册完毕后再配置,不然需要`ebtn_combo_btn_add_btn_by_idx`用这个接口。
|
||||||
|
|
||||||
|
```c
|
||||||
|
ebtn_combo_btn_add_btn(&btns_combo[0], USER_BUTTON_0);
|
||||||
|
ebtn_combo_btn_add_btn(&btns_combo[0], USER_BUTTON_1);
|
||||||
|
|
||||||
|
ebtn_combo_btn_add_btn(&btns_combo[1], USER_BUTTON_2);
|
||||||
|
ebtn_combo_btn_add_btn(&btns_combo[1], USER_BUTTON_3);
|
||||||
|
```
|
||||||
|
|
||||||
|
Step4:动态注册所需按键,并配置comb_key。
|
||||||
|
|
||||||
|
```c
|
||||||
|
// dynamic register
|
||||||
|
for (int i = 0; i < (EBTN_ARRAY_SIZE(btns_dyn)); i++)
|
||||||
|
{
|
||||||
|
ebtn_register(&btns_dyn[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
ebtn_combo_btn_add_btn(&btns_combo_dyn[0].btn, USER_BUTTON_4);
|
||||||
|
ebtn_combo_btn_add_btn(&btns_combo_dyn[0].btn, USER_BUTTON_5);
|
||||||
|
|
||||||
|
ebtn_combo_btn_add_btn(&btns_combo_dyn[1].btn, USER_BUTTON_6);
|
||||||
|
ebtn_combo_btn_add_btn(&btns_combo_dyn[1].btn, USER_BUTTON_7);
|
||||||
|
|
||||||
|
for (int i = 0; i < (EBTN_ARRAY_SIZE(btns_combo_dyn)); i++)
|
||||||
|
{
|
||||||
|
ebtn_combo_register(&btns_combo_dyn[i]);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Step5:启动按键扫描,具体实现可以用定时器做,也可以启任务或者轮询处理。需要注意需要将当前系统时钟`get_tick()`传给驱动接口`ebtn_process`。
|
||||||
|
|
||||||
|
```c
|
||||||
|
while (1)
|
||||||
|
{
|
||||||
|
/* Process forever */
|
||||||
|
ebtn_process(get_tick());
|
||||||
|
|
||||||
|
/* Artificial sleep to offload win process */
|
||||||
|
Sleep(5);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
具体可以参考`example_win32.c`和`test.c`的实现。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## key_id和key_idx的说明
|
||||||
|
|
||||||
|
为了更好的实现**组合按键**以及**批量扫描**的支持,驱动引入了BitArray来管理按键的历史状态和组合按键信息。这样就间接引入了key_index的概念,其代表独立按键在驱动的位置,该值不可直接设置,是按照一定规则隐式定义的。
|
||||||
|
|
||||||
|
key_id是用户定义的,用于标识按键的,该值可以随意更改,但是尽量保证该值独立。
|
||||||
|
|
||||||
|
如下图所示,驱动有2个静态注册的按键,还有3个动态注册的按键。每个按键的key_id是随意定义的,但是key_idx却是驱动内部隐式定义的,先是静态数组,而后按照动态数组顺先依次定义。
|
||||||
|
|
||||||
|
**注意**:由于组合按键也会用到key_idx的信息,所以动态按键目前并不提供删除按键的行为,这个可能引发一些风险。
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## 结构体说明
|
||||||
|
|
||||||
|
### 按键配置参数结构体说明-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`。
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## ONRELEASE事件
|
||||||
|
|
||||||
|
当按键从活动状态变为非活动状态时,才会立即触发 `ONRELEASE`事件,前提是在此之前检测到`ONPRESS` 事件。也就是 `ONRELEASE`事件是伴随着`ONPRESS` 事件发生的。
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## ONCLICK事件
|
||||||
|
|
||||||
|
`ONCLICK`事件在多个事件组合后触发:
|
||||||
|
|
||||||
|
- 应正确检测到`ONPRESS` 事件,表示按钮已按下
|
||||||
|
- 应检测到`ONRELEASE`事件,表示按钮已松开
|
||||||
|
- `ONPRESS`和`ONRELEASE`事件之间的时间必须在时间窗口内,也就是在`time_click_pressed_min`和`time_click_pressed_max`之间时。
|
||||||
|
|
||||||
|
当满足条件时,在`ONRELEASE`事件之后的`time_click_multi_max`时间,发送`ONCLICK`事件。
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
下面显示了在 Windows 测试下的单击事件演示。
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## 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`时间上报。
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
下面显示了在 Windows 测试下的三击事件演示。
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## KEEPALIVE事件
|
||||||
|
|
||||||
|
`KEEPALIVE`事件在 `ONPRESS`事件和`ONRELEASE`事件之间定期发送,它可用于长按处理,根据过程中有多少`KEEPALIVE`事件以及`time_keepalive_period`可以实现各种复杂的长按功能需求。
|
||||||
|
|
||||||
|
需要注意这里根据配置的时间参数的不同,可能会出现`KEEPALIVE`事件和`ONCLICK`事件在一次按键事件都上报的情况。这个情况一般发生在按下保持时间(`ONPRESS`事件和`ONRELEASE`事件之间)大于`time_keepalive_period`却小于`time_click_pressed_max`的场景下。
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
下面显示了在 Windows 测试下的`KEEPALIVE`事件和`ONCLICK`事件在一次按键事件出现的演示。
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
而当按下保持时间大于`time_click_pressed_max`时,就不会上报`ONCLICK`事件,如下图所示。
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## 其他边界场景
|
||||||
|
|
||||||
|
在`example_test.c`中对一些场景进行了覆盖性测试,具体可以看代码实现,测试都符合预期。
|
||||||
|
|
||||||
|
**注意**:time_overflow相关的case需要`EBTN_CONFIG_TIMER_16`宏,不然测试时间太长了。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# 测试说明
|
||||||
|
|
||||||
|
## 环境搭建
|
||||||
|
|
||||||
|
目前测试暂时只支持Windows编译,最终生成exe,可以直接在PC上跑。
|
||||||
|
|
||||||
|
目前需要安装如下环境:
|
||||||
|
- GCC环境,笔者用的msys64+mingw,用于编译生成exe,参考这个文章安装即可。[Win7下msys64安装mingw工具链 - Milton - 博客园 (cnblogs.com)](https://www.cnblogs.com/milton/p/11808091.html)。
|
||||||
|
|
||||||
|
|
||||||
|
## 编译说明
|
||||||
|
|
||||||
|
本项目都是由makefile组织编译的,编译整个项目只需要执行`make all`即可。
|
||||||
|
|
||||||
|
|
||||||
|
也就是可以通过如下指令来编译工程:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
make all
|
||||||
|
```
|
||||||
|
|
||||||
|
而后运行执行`make run`即可运行例程,例程中实现了2个Task的消息管理,并实现了Task Save的相关逻辑。
|
||||||
|
|
||||||
|
```shell
|
||||||
|
PS D:\workspace\github\bare_task_msg> make run
|
||||||
|
Building : "output/main.exe"
|
||||||
|
Start Build Image.
|
||||||
|
objcopy -v -O binary output/main.exe output/main.bin
|
||||||
|
copy from `output/main.exe' [pei-i386] to `output/main.bin' [binary]
|
||||||
|
objdump --source --all-headers --demangle --line-numbers --wide output/main.exe > output/main.lst
|
||||||
|
Print Size
|
||||||
|
text data bss dec hex filename
|
||||||
|
41452 7040 2644 51136 c7c0 output/main.exe
|
||||||
|
./output/main.exe
|
||||||
|
Heap Remain Size: 0xff8
|
||||||
|
Task Start Work!
|
||||||
|
user_task1(), id: 0x1, len: 0
|
||||||
|
BARE_TASK_HDL_SAVED
|
||||||
|
user_task2(), id: 0x8, len: 0
|
||||||
|
user_task2(), id: 0x9, len: 0
|
||||||
|
user_task2(), id: 0x50, len: 20
|
||||||
|
0x0:0x1:0x2:0x3:0x4:0x5:0x6:0x7:0x8:0x9:0xa:0xb:0xc:0xd:0xe:0xf:0x10:0x11:0x12:0x13:
|
||||||
|
BARE_TASK_HDL_SAVED
|
||||||
|
user_task1(), id: 0x1, len: 0
|
||||||
|
user_task1(), id: 0x2, len: 0
|
||||||
|
user_task1(), id: 0x10, len: 10
|
||||||
|
0x0:0x1:0x2:0x3:0x4:0x5:0x6:0x7:0x8:0x9:
|
||||||
|
user_task1(), id: 0x11, len: 10
|
||||||
|
0x10:0x11:0x12:0x13:0x14:0x15:0x16:0x17:0x18:0x19:
|
||||||
|
user_task2(), id: 0x50, len: 20
|
||||||
|
0x0:0x1:0x2:0x3:0x4:0x5:0x6:0x7:0x8:0x9:0xa:0xb:0xc:0xd:0xe:0xf:0x10:0x11:0x12:0x13:
|
||||||
|
user_task2(), id: 0x51, len: 20
|
||||||
|
0x10:0x11:0x12:0x13:0x14:0x15:0x16:0x17:0x18:0x19:0x1a:0x1b:0x1c:0x1d:0x1e:0x1f:0x20:0x21:0x22:0x23:
|
||||||
|
user_task2(), id: 0x51, len: 100
|
||||||
|
0x50:0x51:0x52:0x53:0x54:0x55:0x56:0x57:0x58:0x59:0x5a:0x5b:0x5c:0x5d:0x5e:0x5f:0x60:0x61:0x62:0x63:0x64:0x65:0x66:0x67:0x68:0x69:0x6a:0x6b:0x6c:0x6d:0x6e:0x6f:0x70:0x71:0x72:0x73:0x74:0x75:0x76:0x77:0x78:0x79:0x7a:0x7b:0x7c:0x7d:0x7e:0x7f:0x80:0x81:0x82:0x83:0x84:0x85:0x86:0x87:0x88:0x89:0x8a:0x8b:0x8c:0x8d:0x8e:0x8f:0x90:0x91:0x92:0x93:0x94:0x95:0x96:0x97:0x98:0x99:0x9a:0x9b:0x9c:0x9d:0x9e:0x9f:0xa0:0xa1:0xa2:0xa3:0xa4:0xa5:0xa6:0xa7:0xa8:0xa9:0xaa:0xab:0xac:0xad:0xae:0xaf:0xb0:0xb1:0xb2:0xb3:
|
||||||
|
Task End Work!
|
||||||
|
Heap Remain Size: 0xff8
|
||||||
|
Executing 'run: all' complete!
|
||||||
|
```
|
||||||
|
|
||||||
|
可以看到,2个task同时支持消息处理,当有Save的场景会pending消息,先执行另外一个task,task执行完所有消息后,heap保持不变。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
BIN
README.vsdx
Normal file
BIN
README.vsdx
Normal file
Binary file not shown.
27
code_format.py
Normal file
27
code_format.py
Normal 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
597
ebtn/bit_array.h
Normal 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
550
ebtn/ebtn.c
Normal 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
463
ebtn/ebtn.h
Normal 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
755
example_test.c
Normal 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, ¶m_default),
|
||||||
|
EBTN_BUTTON_INIT(USER_BUTTON_onrelease_debounce, ¶m_onrelease_debounce),
|
||||||
|
EBTN_BUTTON_INIT(USER_BUTTON_keepalive_with_click, ¶m_keepalive_with_click),
|
||||||
|
EBTN_BUTTON_INIT(USER_BUTTON_max_click_3, ¶m_max_click_3),
|
||||||
|
EBTN_BUTTON_INIT(USER_BUTTON_click_multi_max_0, ¶m_click_multi_max_0),
|
||||||
|
EBTN_BUTTON_INIT(USER_BUTTON_keep_alive_0, ¶m_keep_alive_0)};
|
||||||
|
|
||||||
|
static volatile uint32_t test_processed_time_current;
|
||||||
|
|
||||||
|
/* Set button state -> used for test purposes */
|
||||||
|
#define BTN_STATE_RAW(_key_id_, _state_, _duration_) \
|
||||||
|
{ \
|
||||||
|
.key_id = (_key_id_), .state = (_state_), .duration = (_duration_) \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define BTN_STATE(_state_, _duration_) BTN_STATE_RAW(USER_BUTTON_default, _state_, _duration_)
|
||||||
|
|
||||||
|
/* On-Press event */
|
||||||
|
#define BTN_EVENT_ONPRESS() \
|
||||||
|
{ \
|
||||||
|
.evt = EBTN_EVT_ONPRESS \
|
||||||
|
}
|
||||||
|
/* On-Release event */
|
||||||
|
#define BTN_EVENT_ONRELEASE() \
|
||||||
|
{ \
|
||||||
|
.evt = EBTN_EVT_ONRELEASE \
|
||||||
|
}
|
||||||
|
/* On-Click event */
|
||||||
|
#define BTN_EVENT_ONCLICK(_conseq_clicks_) \
|
||||||
|
{ \
|
||||||
|
.evt = EBTN_EVT_ONCLICK, .conseq_clicks = (_conseq_clicks_) \
|
||||||
|
}
|
||||||
|
/* On-Click event */
|
||||||
|
#define BTN_EVENT_KEEPALIVE(_keepalive_cnt_) \
|
||||||
|
{ \
|
||||||
|
.evt = EBTN_EVT_KEEPALIVE, .keepalive_cnt = (_keepalive_cnt_) \
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Simulate click event
|
||||||
|
*/
|
||||||
|
static btn_test_time_t test_sequence_single_click[] = {
|
||||||
|
/*
|
||||||
|
* Step 1:
|
||||||
|
* Go to active state and stay there for a period
|
||||||
|
* of minimum debounce time and minimum
|
||||||
|
* time input must be active to later detect click event
|
||||||
|
*
|
||||||
|
* Step 2:
|
||||||
|
* Go low and stay inactive until time to report click has elapsed.
|
||||||
|
* Include debounce timing for release event.
|
||||||
|
*
|
||||||
|
* Add +1 to the end, to force click event,
|
||||||
|
* and not to go to "consecutive clicks" if any further tests are added in this sequence
|
||||||
|
*/
|
||||||
|
BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_default) + EBTN_PARAM_TIME_CLICK_MIN(param_default)),
|
||||||
|
BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_default) + EBTN_PARAM_TIME_CLICK_MULTI_MAX(param_default)),
|
||||||
|
};
|
||||||
|
|
||||||
|
static const btn_test_evt_t test_events_single_click[] = {
|
||||||
|
BTN_EVENT_ONPRESS(),
|
||||||
|
BTN_EVENT_ONRELEASE(),
|
||||||
|
BTN_EVENT_ONCLICK(1),
|
||||||
|
};
|
||||||
|
|
||||||
|
static btn_test_time_t test_sequence_double_click[] = {
|
||||||
|
/*
|
||||||
|
* Repeat above steps, this time w/o +1 at the end.
|
||||||
|
*
|
||||||
|
* Simulate "2" consecutive clicks and report final "click" event at the end of the
|
||||||
|
* sequence, with "2" consecutive clicks in the report info
|
||||||
|
*/
|
||||||
|
BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_default) + EBTN_PARAM_TIME_CLICK_MIN(param_default)),
|
||||||
|
BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_default) + EBTN_PARAM_TIME_CLICK_MULTI_MAX(param_default) / 2),
|
||||||
|
BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_default) + EBTN_PARAM_TIME_CLICK_MIN(param_default)),
|
||||||
|
BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_default) + EBTN_PARAM_TIME_CLICK_MULTI_MAX(param_default)),
|
||||||
|
};
|
||||||
|
|
||||||
|
static const btn_test_evt_t test_events_double_click[] = {
|
||||||
|
BTN_EVENT_ONPRESS(), BTN_EVENT_ONRELEASE(), BTN_EVENT_ONPRESS(), BTN_EVENT_ONRELEASE(), BTN_EVENT_ONCLICK(2),
|
||||||
|
};
|
||||||
|
|
||||||
|
static btn_test_time_t test_sequence_triple_click[] = {
|
||||||
|
BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_default) + EBTN_PARAM_TIME_CLICK_MIN(param_default)),
|
||||||
|
BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_default) + EBTN_PARAM_TIME_CLICK_MULTI_MAX(param_default) / 2),
|
||||||
|
BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_default) + EBTN_PARAM_TIME_CLICK_MIN(param_default)),
|
||||||
|
BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_default) + EBTN_PARAM_TIME_CLICK_MULTI_MAX(param_default) / 2),
|
||||||
|
BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_default) + EBTN_PARAM_TIME_CLICK_MIN(param_default)),
|
||||||
|
BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_default) + EBTN_PARAM_TIME_CLICK_MULTI_MAX(param_default)),
|
||||||
|
};
|
||||||
|
|
||||||
|
static const btn_test_evt_t test_events_triple_click[] = {
|
||||||
|
BTN_EVENT_ONPRESS(), BTN_EVENT_ONRELEASE(), BTN_EVENT_ONPRESS(), BTN_EVENT_ONRELEASE(),
|
||||||
|
BTN_EVENT_ONPRESS(), BTN_EVENT_ONRELEASE(), BTN_EVENT_ONCLICK(3),
|
||||||
|
};
|
||||||
|
|
||||||
|
static btn_test_time_t test_sequence_double_click_critical_time[] = {
|
||||||
|
/*
|
||||||
|
* Repeat above steps, this time w/o +1 at the end.
|
||||||
|
*
|
||||||
|
* Simulate "2" consecutive clicks and report final "click" event at the end of the
|
||||||
|
* sequence, with "2" consecutive clicks in the report info
|
||||||
|
*/
|
||||||
|
BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_default) + EBTN_PARAM_TIME_CLICK_MIN(param_default)),
|
||||||
|
/* Hold button in release state for time that is max for 2 clicks - time that we will
|
||||||
|
indicate in the next press state -> this is the frequency between detected events */
|
||||||
|
BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_default) +
|
||||||
|
EBTN_PARAM_TIME_CLICK_MULTI_MAX(param_default)
|
||||||
|
/* Decrease by active time in next step */
|
||||||
|
- (EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_default)) - 2),
|
||||||
|
/* Active time */
|
||||||
|
BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_default) + EBTN_PARAM_TIME_CLICK_MIN(param_default)),
|
||||||
|
BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_default) + EBTN_PARAM_TIME_CLICK_MULTI_MAX(param_default)),
|
||||||
|
};
|
||||||
|
|
||||||
|
static const btn_test_evt_t test_events_double_click_critical_time[] = {
|
||||||
|
BTN_EVENT_ONPRESS(), BTN_EVENT_ONRELEASE(), BTN_EVENT_ONPRESS(), BTN_EVENT_ONRELEASE(), BTN_EVENT_ONCLICK(2),
|
||||||
|
};
|
||||||
|
|
||||||
|
static btn_test_time_t test_sequence_double_click_critical_time_over[] = {
|
||||||
|
/*
|
||||||
|
* This test shows how to handle case when 2 clicks are being executed,
|
||||||
|
* but time between 2 release events is larger than maximum
|
||||||
|
* allowed time for consecutive clicks.
|
||||||
|
*
|
||||||
|
* In this case, 2 onclick events are sent,
|
||||||
|
* both with consecutive clicks counter set to 1
|
||||||
|
*/
|
||||||
|
BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_default) + EBTN_PARAM_TIME_CLICK_MIN(param_default)),
|
||||||
|
BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_default) + EBTN_PARAM_TIME_CLICK_MULTI_MAX(param_default) -
|
||||||
|
(EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_default))),
|
||||||
|
BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_default) + EBTN_PARAM_TIME_CLICK_MIN(param_default)),
|
||||||
|
BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_default) + EBTN_PARAM_TIME_CLICK_MULTI_MAX(param_default)),
|
||||||
|
};
|
||||||
|
|
||||||
|
static const btn_test_evt_t test_events_double_click_critical_time_over[] = {
|
||||||
|
BTN_EVENT_ONPRESS(),
|
||||||
|
BTN_EVENT_ONRELEASE(),
|
||||||
|
/* This one is to handle click for first sequence */
|
||||||
|
BTN_EVENT_ONCLICK(1),
|
||||||
|
BTN_EVENT_ONPRESS(),
|
||||||
|
BTN_EVENT_ONRELEASE(),
|
||||||
|
/* This one is to handle click for second sequence */
|
||||||
|
BTN_EVENT_ONCLICK(1),
|
||||||
|
};
|
||||||
|
|
||||||
|
static btn_test_time_t test_sequence_click_with_keepalive[] = {
|
||||||
|
/*
|
||||||
|
* Make a click event, followed by the longer press.
|
||||||
|
* Simulate "long press" w/ previous click, that has click counter set to 1
|
||||||
|
*/
|
||||||
|
BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_default) + EBTN_PARAM_TIME_CLICK_MIN(param_default)),
|
||||||
|
BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_default) + EBTN_PARAM_TIME_CLICK_MULTI_MAX(param_default) / 2),
|
||||||
|
BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_default)),
|
||||||
|
BTN_STATE(1, EBTN_PARAM_TIME_KEEPALIVE_PERIOD(param_default)),
|
||||||
|
BTN_STATE(1, EBTN_PARAM_TIME_KEEPALIVE_PERIOD(param_default)),
|
||||||
|
BTN_STATE(1, EBTN_PARAM_TIME_KEEPALIVE_PERIOD(param_default)),
|
||||||
|
BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_default) + EBTN_PARAM_TIME_CLICK_MULTI_MAX(param_default)),
|
||||||
|
};
|
||||||
|
|
||||||
|
static const btn_test_evt_t test_events_click_with_keepalive[] = {
|
||||||
|
BTN_EVENT_ONPRESS(),
|
||||||
|
BTN_EVENT_ONRELEASE(),
|
||||||
|
BTN_EVENT_ONPRESS(),
|
||||||
|
/* This one is to handle click before long press */
|
||||||
|
BTN_EVENT_ONCLICK(1),
|
||||||
|
BTN_EVENT_KEEPALIVE(1),
|
||||||
|
BTN_EVENT_KEEPALIVE(2),
|
||||||
|
BTN_EVENT_KEEPALIVE(3),
|
||||||
|
BTN_EVENT_ONRELEASE(),
|
||||||
|
};
|
||||||
|
|
||||||
|
static btn_test_time_t test_sequence_click_with_short[] = {
|
||||||
|
/* Make with short press (shorter than minimum required) */
|
||||||
|
BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_default) + EBTN_PARAM_TIME_CLICK_MIN(param_default) / 2),
|
||||||
|
BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_default) + EBTN_PARAM_TIME_CLICK_MULTI_MAX(param_default)),
|
||||||
|
};
|
||||||
|
|
||||||
|
static const btn_test_evt_t test_events_click_with_short[] = {
|
||||||
|
BTN_EVENT_ONPRESS(),
|
||||||
|
BTN_EVENT_ONRELEASE(),
|
||||||
|
};
|
||||||
|
|
||||||
|
static btn_test_time_t test_sequence_click_with_short_with_multi[] = {
|
||||||
|
/* Make with short press (shorter than minimum required) */
|
||||||
|
BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_default) + EBTN_PARAM_TIME_CLICK_MIN(param_default) / 2),
|
||||||
|
BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_default) + EBTN_PARAM_TIME_CLICK_MULTI_MAX(param_default) / 2),
|
||||||
|
BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_default) + EBTN_PARAM_TIME_CLICK_MIN(param_default)),
|
||||||
|
BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_default) + EBTN_PARAM_TIME_CLICK_MULTI_MAX(param_default) / 2),
|
||||||
|
BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_default) + EBTN_PARAM_TIME_CLICK_MIN(param_default)),
|
||||||
|
BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_default) + EBTN_PARAM_TIME_CLICK_MULTI_MAX(param_default)),
|
||||||
|
};
|
||||||
|
|
||||||
|
static const btn_test_evt_t test_events_click_with_short_with_multi[] = {
|
||||||
|
/* This one is short... */
|
||||||
|
BTN_EVENT_ONPRESS(), BTN_EVENT_ONRELEASE(), BTN_EVENT_ONPRESS(), BTN_EVENT_ONRELEASE(),
|
||||||
|
BTN_EVENT_ONPRESS(), BTN_EVENT_ONRELEASE(), BTN_EVENT_ONCLICK(2),
|
||||||
|
};
|
||||||
|
|
||||||
|
static btn_test_time_t test_sequence_multi_click_with_short[] = {
|
||||||
|
/* Make 2 clicks, and 3rd one with short press (shorter than minimum required) */
|
||||||
|
BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_default) + EBTN_PARAM_TIME_CLICK_MIN(param_default)),
|
||||||
|
BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_default) + EBTN_PARAM_TIME_CLICK_MULTI_MAX(param_default) / 2),
|
||||||
|
BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_default) + EBTN_PARAM_TIME_CLICK_MIN(param_default)),
|
||||||
|
BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_default) + EBTN_PARAM_TIME_CLICK_MULTI_MAX(param_default) / 2),
|
||||||
|
BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_default) + EBTN_PARAM_TIME_CLICK_MIN(param_default) / 2),
|
||||||
|
BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_default)),
|
||||||
|
};
|
||||||
|
|
||||||
|
static const btn_test_evt_t test_events_multi_click_with_short[] = {
|
||||||
|
BTN_EVENT_ONPRESS(),
|
||||||
|
BTN_EVENT_ONRELEASE(),
|
||||||
|
BTN_EVENT_ONPRESS(),
|
||||||
|
BTN_EVENT_ONRELEASE(),
|
||||||
|
/* This one is short... */
|
||||||
|
BTN_EVENT_ONPRESS(),
|
||||||
|
BTN_EVENT_ONRELEASE(),
|
||||||
|
BTN_EVENT_ONCLICK(2),
|
||||||
|
};
|
||||||
|
|
||||||
|
static btn_test_time_t test_sequence_onpress_debounce[] = {
|
||||||
|
BTN_STATE(0, 0),
|
||||||
|
BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_default) / 2),
|
||||||
|
BTN_STATE(0, 1),
|
||||||
|
BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_default) / 2),
|
||||||
|
BTN_STATE(0, 1),
|
||||||
|
BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_default) / 2),
|
||||||
|
BTN_STATE(0, 1),
|
||||||
|
BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_default) + EBTN_PARAM_TIME_CLICK_MIN(param_default)),
|
||||||
|
BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_default) + EBTN_PARAM_TIME_CLICK_MULTI_MAX(param_default) + 1),
|
||||||
|
};
|
||||||
|
|
||||||
|
static const btn_test_evt_t test_events_onpress_debounce[] = {
|
||||||
|
BTN_EVENT_ONPRESS(),
|
||||||
|
BTN_EVENT_ONRELEASE(),
|
||||||
|
BTN_EVENT_ONCLICK(1),
|
||||||
|
};
|
||||||
|
|
||||||
|
// for test overflow, make sure enable macro 'EBTN_CONFIG_TIMER_16' or compile with 'make all
|
||||||
|
// CFLAGS=-DEBTN_CONFIG_TIMER_16'
|
||||||
|
static btn_test_time_t test_sequence_time_overflow_onpress_debounce[] = {
|
||||||
|
BTN_STATE(0, 0x0ffff - (EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_default) / 2)),
|
||||||
|
BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_default) + EBTN_PARAM_TIME_CLICK_MIN(param_default)),
|
||||||
|
BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_default) + EBTN_PARAM_TIME_CLICK_MULTI_MAX(param_default)),
|
||||||
|
};
|
||||||
|
|
||||||
|
static const btn_test_evt_t test_events_time_overflow_onpress_debounce[] = {
|
||||||
|
BTN_EVENT_ONPRESS(),
|
||||||
|
BTN_EVENT_ONRELEASE(),
|
||||||
|
BTN_EVENT_ONCLICK(1),
|
||||||
|
};
|
||||||
|
|
||||||
|
static btn_test_time_t test_sequence_time_overflow_onpress[] = {
|
||||||
|
BTN_STATE(0, 0x0ffff - (EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_default) + EBTN_PARAM_TIME_CLICK_MIN(param_default) / 2)),
|
||||||
|
BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_default) + EBTN_PARAM_TIME_CLICK_MIN(param_default)),
|
||||||
|
BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_default) + EBTN_PARAM_TIME_CLICK_MULTI_MAX(param_default)),
|
||||||
|
};
|
||||||
|
|
||||||
|
static const btn_test_evt_t test_events_time_overflow_onpress[] = {
|
||||||
|
BTN_EVENT_ONPRESS(),
|
||||||
|
BTN_EVENT_ONRELEASE(),
|
||||||
|
BTN_EVENT_ONCLICK(1),
|
||||||
|
};
|
||||||
|
|
||||||
|
static btn_test_time_t test_sequence_time_overflow_onrelease_muti[] = {
|
||||||
|
BTN_STATE(0, 0x0ffff - (EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_default) + EBTN_PARAM_TIME_CLICK_MIN(param_default) +
|
||||||
|
EBTN_PARAM_TIME_CLICK_MIN(param_default) / 2)),
|
||||||
|
BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_default) + EBTN_PARAM_TIME_CLICK_MIN(param_default)),
|
||||||
|
BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_default) + EBTN_PARAM_TIME_CLICK_MULTI_MAX(param_default)),
|
||||||
|
};
|
||||||
|
|
||||||
|
static const btn_test_evt_t test_events_time_overflow_onrelease_muti[] = {
|
||||||
|
BTN_EVENT_ONPRESS(),
|
||||||
|
BTN_EVENT_ONRELEASE(),
|
||||||
|
BTN_EVENT_ONCLICK(1),
|
||||||
|
};
|
||||||
|
|
||||||
|
static btn_test_time_t test_sequence_time_overflow_keepalive[] = {
|
||||||
|
BTN_STATE(0, 0x0ffff - (EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_default) + EBTN_PARAM_TIME_CLICK_MIN(param_default) +
|
||||||
|
EBTN_PARAM_TIME_KEEPALIVE_PERIOD(param_default) / 2)),
|
||||||
|
BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_default) + EBTN_PARAM_TIME_CLICK_MIN(param_default)),
|
||||||
|
BTN_STATE(1, EBTN_PARAM_TIME_KEEPALIVE_PERIOD(param_default)),
|
||||||
|
BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_default) + EBTN_PARAM_TIME_CLICK_MULTI_MAX(param_default)),
|
||||||
|
};
|
||||||
|
|
||||||
|
static const btn_test_evt_t test_events_time_overflow_keepalive[] = {
|
||||||
|
BTN_EVENT_ONPRESS(),
|
||||||
|
BTN_EVENT_KEEPALIVE(1),
|
||||||
|
BTN_EVENT_ONRELEASE(),
|
||||||
|
};
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Test onrelease debounce
|
||||||
|
///
|
||||||
|
|
||||||
|
static btn_test_time_t test_sequence_onrelease_debounce[] = {
|
||||||
|
BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_onrelease_debounce) + EBTN_PARAM_TIME_CLICK_MIN(param_onrelease_debounce)),
|
||||||
|
BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_onrelease_debounce) / 2),
|
||||||
|
BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_onrelease_debounce)),
|
||||||
|
BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_onrelease_debounce) + EBTN_PARAM_TIME_CLICK_MULTI_MAX(param_onrelease_debounce)),
|
||||||
|
};
|
||||||
|
|
||||||
|
static const btn_test_evt_t test_events_onrelease_debounce[] = {
|
||||||
|
BTN_EVENT_ONPRESS(),
|
||||||
|
BTN_EVENT_ONRELEASE(),
|
||||||
|
BTN_EVENT_ONCLICK(1),
|
||||||
|
};
|
||||||
|
|
||||||
|
static btn_test_time_t test_sequence_onrelease_debounce_over[] = {
|
||||||
|
BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_onrelease_debounce) + EBTN_PARAM_TIME_CLICK_MIN(param_onrelease_debounce)),
|
||||||
|
BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_onrelease_debounce)),
|
||||||
|
BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_onrelease_debounce) + EBTN_PARAM_TIME_CLICK_MIN(param_onrelease_debounce)),
|
||||||
|
BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_onrelease_debounce) + EBTN_PARAM_TIME_CLICK_MULTI_MAX(param_onrelease_debounce)),
|
||||||
|
};
|
||||||
|
|
||||||
|
static const btn_test_evt_t test_events_onrelease_debounce_over[] = {
|
||||||
|
BTN_EVENT_ONPRESS(), BTN_EVENT_ONRELEASE(), BTN_EVENT_ONPRESS(), BTN_EVENT_ONRELEASE(), BTN_EVENT_ONCLICK(2),
|
||||||
|
};
|
||||||
|
|
||||||
|
static btn_test_time_t test_sequence_onrelease_debounce_time_overflow[] = {
|
||||||
|
BTN_STATE(0, 0x0ffff - (EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_onrelease_debounce) + EBTN_PARAM_TIME_CLICK_MIN(param_onrelease_debounce) +
|
||||||
|
EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_onrelease_debounce) / 2)),
|
||||||
|
BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_onrelease_debounce) + EBTN_PARAM_TIME_CLICK_MIN(param_onrelease_debounce)),
|
||||||
|
BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_onrelease_debounce) + EBTN_PARAM_TIME_CLICK_MULTI_MAX(param_onrelease_debounce)),
|
||||||
|
};
|
||||||
|
|
||||||
|
static const btn_test_evt_t test_events_onrelease_debounce_time_overflow[] = {
|
||||||
|
BTN_EVENT_ONPRESS(),
|
||||||
|
BTN_EVENT_ONRELEASE(),
|
||||||
|
BTN_EVENT_ONCLICK(1),
|
||||||
|
};
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Test keepalive with click debounce
|
||||||
|
///
|
||||||
|
static btn_test_time_t test_sequence_keepalive_with_click[] = {
|
||||||
|
BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_keepalive_with_click) + EBTN_PARAM_TIME_CLICK_MIN(param_keepalive_with_click)),
|
||||||
|
BTN_STATE(1, EBTN_PARAM_TIME_KEEPALIVE_PERIOD(param_keepalive_with_click)),
|
||||||
|
BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_keepalive_with_click) + EBTN_PARAM_TIME_CLICK_MULTI_MAX(param_keepalive_with_click)),
|
||||||
|
};
|
||||||
|
|
||||||
|
static const btn_test_evt_t test_events_keepalive_with_click[] = {
|
||||||
|
BTN_EVENT_ONPRESS(),
|
||||||
|
BTN_EVENT_KEEPALIVE(1),
|
||||||
|
BTN_EVENT_ONRELEASE(),
|
||||||
|
BTN_EVENT_ONCLICK(1),
|
||||||
|
};
|
||||||
|
|
||||||
|
static btn_test_time_t test_sequence_keepalive_with_click_double[] = {
|
||||||
|
BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_keepalive_with_click) + EBTN_PARAM_TIME_CLICK_MIN(param_keepalive_with_click)),
|
||||||
|
BTN_STATE(1, EBTN_PARAM_TIME_KEEPALIVE_PERIOD(param_keepalive_with_click)),
|
||||||
|
BTN_STATE(1, EBTN_PARAM_TIME_KEEPALIVE_PERIOD(param_keepalive_with_click)),
|
||||||
|
BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_keepalive_with_click) + EBTN_PARAM_TIME_CLICK_MULTI_MAX(param_keepalive_with_click)),
|
||||||
|
};
|
||||||
|
|
||||||
|
static const btn_test_evt_t test_events_keepalive_with_click_double[] = {
|
||||||
|
BTN_EVENT_ONPRESS(), BTN_EVENT_KEEPALIVE(1), BTN_EVENT_KEEPALIVE(2), BTN_EVENT_ONRELEASE(), BTN_EVENT_ONCLICK(1),
|
||||||
|
};
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Test max multi click with 3
|
||||||
|
///
|
||||||
|
static btn_test_time_t test_sequence_max_click_3[] = {
|
||||||
|
BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_max_click_3) + EBTN_PARAM_TIME_CLICK_MIN(param_max_click_3)),
|
||||||
|
BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_max_click_3) + EBTN_PARAM_TIME_CLICK_MULTI_MAX(param_max_click_3) / 2),
|
||||||
|
BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_max_click_3) + EBTN_PARAM_TIME_CLICK_MIN(param_max_click_3)),
|
||||||
|
BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_max_click_3) + EBTN_PARAM_TIME_CLICK_MULTI_MAX(param_max_click_3) / 2),
|
||||||
|
BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_max_click_3) + EBTN_PARAM_TIME_CLICK_MIN(param_max_click_3)),
|
||||||
|
BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_max_click_3) + EBTN_PARAM_TIME_CLICK_MULTI_MAX(param_max_click_3)),
|
||||||
|
};
|
||||||
|
|
||||||
|
static const btn_test_evt_t test_events_max_click_3[] = {
|
||||||
|
BTN_EVENT_ONPRESS(), BTN_EVENT_ONRELEASE(), BTN_EVENT_ONPRESS(), BTN_EVENT_ONRELEASE(),
|
||||||
|
BTN_EVENT_ONPRESS(), BTN_EVENT_ONRELEASE(), BTN_EVENT_ONCLICK(3),
|
||||||
|
};
|
||||||
|
|
||||||
|
static btn_test_time_t test_sequence_max_click_3_over[] = {
|
||||||
|
BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_max_click_3) + EBTN_PARAM_TIME_CLICK_MIN(param_max_click_3)),
|
||||||
|
BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_max_click_3) + EBTN_PARAM_TIME_CLICK_MULTI_MAX(param_max_click_3) / 2),
|
||||||
|
BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_max_click_3) + EBTN_PARAM_TIME_CLICK_MIN(param_max_click_3)),
|
||||||
|
BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_max_click_3) + EBTN_PARAM_TIME_CLICK_MULTI_MAX(param_max_click_3) / 2),
|
||||||
|
BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_max_click_3) + EBTN_PARAM_TIME_CLICK_MIN(param_max_click_3)),
|
||||||
|
BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_max_click_3) + EBTN_PARAM_TIME_CLICK_MULTI_MAX(param_max_click_3) / 2),
|
||||||
|
BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_max_click_3) + EBTN_PARAM_TIME_CLICK_MIN(param_max_click_3)),
|
||||||
|
BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_max_click_3) + EBTN_PARAM_TIME_CLICK_MULTI_MAX(param_max_click_3)),
|
||||||
|
};
|
||||||
|
|
||||||
|
static const btn_test_evt_t test_events_max_click_3_over[] = {
|
||||||
|
BTN_EVENT_ONPRESS(), BTN_EVENT_ONRELEASE(), BTN_EVENT_ONPRESS(), BTN_EVENT_ONRELEASE(), BTN_EVENT_ONPRESS(),
|
||||||
|
BTN_EVENT_ONRELEASE(), BTN_EVENT_ONCLICK(3), BTN_EVENT_ONPRESS(), BTN_EVENT_ONRELEASE(), BTN_EVENT_ONCLICK(1),
|
||||||
|
};
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Test click multi max with 0
|
||||||
|
///
|
||||||
|
static btn_test_time_t test_sequence_click_multi_max_0[] = {
|
||||||
|
BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_click_multi_max_0) + EBTN_PARAM_TIME_CLICK_MIN(param_click_multi_max_0)),
|
||||||
|
BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_click_multi_max_0) + EBTN_PARAM_TIME_CLICK_MULTI_MAX(param_click_multi_max_0) / 2),
|
||||||
|
BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_click_multi_max_0) + EBTN_PARAM_TIME_CLICK_MIN(param_click_multi_max_0)),
|
||||||
|
BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_click_multi_max_0) + EBTN_PARAM_TIME_CLICK_MULTI_MAX(param_click_multi_max_0) / 2),
|
||||||
|
BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_click_multi_max_0) + EBTN_PARAM_TIME_CLICK_MIN(param_click_multi_max_0)),
|
||||||
|
BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_click_multi_max_0) + EBTN_PARAM_TIME_CLICK_MULTI_MAX(param_click_multi_max_0)),
|
||||||
|
};
|
||||||
|
|
||||||
|
static const btn_test_evt_t test_events_click_multi_max_0[] = {
|
||||||
|
BTN_EVENT_ONPRESS(), BTN_EVENT_ONRELEASE(), BTN_EVENT_ONCLICK(1), BTN_EVENT_ONPRESS(), BTN_EVENT_ONRELEASE(),
|
||||||
|
BTN_EVENT_ONCLICK(1), BTN_EVENT_ONPRESS(), BTN_EVENT_ONRELEASE(), BTN_EVENT_ONCLICK(1),
|
||||||
|
};
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Test max keepalive with 0
|
||||||
|
///
|
||||||
|
static btn_test_time_t test_sequence_keep_alive_0[] = {
|
||||||
|
BTN_STATE(1, EBTN_PARAM_TIME_DEBOUNCE_PRESS(param_keep_alive_0) + EBTN_PARAM_TIME_CLICK_MIN(param_keep_alive_0)),
|
||||||
|
BTN_STATE(1, EBTN_PARAM_TIME_CLICK_MAX(param_keep_alive_0) / 2),
|
||||||
|
BTN_STATE(0, EBTN_PARAM_TIME_DEBOUNCE_RELEASE(param_keep_alive_0) + EBTN_PARAM_TIME_CLICK_MULTI_MAX(param_keep_alive_0)),
|
||||||
|
};
|
||||||
|
|
||||||
|
static const btn_test_evt_t test_events_keep_alive_0[] = {
|
||||||
|
BTN_EVENT_ONPRESS(),
|
||||||
|
BTN_EVENT_ONRELEASE(),
|
||||||
|
BTN_EVENT_ONCLICK(1),
|
||||||
|
};
|
||||||
|
|
||||||
|
static btn_test_arr_t test_list[] = {
|
||||||
|
TEST_ARRAY_DEFINE(USER_BUTTON_default, test_sequence_single_click, test_events_single_click),
|
||||||
|
TEST_ARRAY_DEFINE(USER_BUTTON_default, test_sequence_double_click, test_events_double_click),
|
||||||
|
TEST_ARRAY_DEFINE(USER_BUTTON_default, test_sequence_triple_click, test_events_triple_click),
|
||||||
|
TEST_ARRAY_DEFINE(USER_BUTTON_default, test_sequence_double_click_critical_time, test_events_double_click_critical_time),
|
||||||
|
TEST_ARRAY_DEFINE(USER_BUTTON_default, test_sequence_double_click_critical_time_over, test_events_double_click_critical_time_over),
|
||||||
|
TEST_ARRAY_DEFINE(USER_BUTTON_default, test_sequence_click_with_keepalive, test_events_click_with_keepalive),
|
||||||
|
TEST_ARRAY_DEFINE(USER_BUTTON_default, test_sequence_click_with_short, test_events_click_with_short),
|
||||||
|
TEST_ARRAY_DEFINE(USER_BUTTON_default, test_sequence_click_with_short_with_multi, test_events_click_with_short_with_multi),
|
||||||
|
TEST_ARRAY_DEFINE(USER_BUTTON_default, test_sequence_multi_click_with_short, test_events_multi_click_with_short),
|
||||||
|
TEST_ARRAY_DEFINE(USER_BUTTON_default, test_sequence_onpress_debounce, test_events_onpress_debounce),
|
||||||
|
TEST_ARRAY_DEFINE(USER_BUTTON_default, test_sequence_time_overflow_onpress_debounce, test_events_time_overflow_onpress_debounce),
|
||||||
|
TEST_ARRAY_DEFINE(USER_BUTTON_default, test_sequence_time_overflow_onpress, test_events_time_overflow_onpress),
|
||||||
|
TEST_ARRAY_DEFINE(USER_BUTTON_default, test_sequence_time_overflow_onrelease_muti, test_events_time_overflow_onrelease_muti),
|
||||||
|
TEST_ARRAY_DEFINE(USER_BUTTON_default, test_sequence_time_overflow_keepalive, test_events_time_overflow_keepalive),
|
||||||
|
|
||||||
|
TEST_ARRAY_DEFINE(USER_BUTTON_onrelease_debounce, test_sequence_onrelease_debounce, test_events_onrelease_debounce),
|
||||||
|
TEST_ARRAY_DEFINE(USER_BUTTON_onrelease_debounce, test_sequence_onrelease_debounce_over, test_events_onrelease_debounce_over),
|
||||||
|
TEST_ARRAY_DEFINE(USER_BUTTON_onrelease_debounce, test_sequence_onrelease_debounce_time_overflow, test_events_onrelease_debounce_time_overflow),
|
||||||
|
|
||||||
|
TEST_ARRAY_DEFINE(USER_BUTTON_keepalive_with_click, test_sequence_keepalive_with_click, test_events_keepalive_with_click),
|
||||||
|
TEST_ARRAY_DEFINE(USER_BUTTON_keepalive_with_click, test_sequence_keepalive_with_click_double, test_events_keepalive_with_click_double),
|
||||||
|
|
||||||
|
TEST_ARRAY_DEFINE(USER_BUTTON_max_click_3, test_sequence_max_click_3, test_events_max_click_3),
|
||||||
|
TEST_ARRAY_DEFINE(USER_BUTTON_max_click_3, test_sequence_max_click_3_over, test_events_max_click_3_over),
|
||||||
|
|
||||||
|
TEST_ARRAY_DEFINE(USER_BUTTON_click_multi_max_0, test_sequence_click_multi_max_0, test_events_click_multi_max_0),
|
||||||
|
|
||||||
|
TEST_ARRAY_DEFINE(USER_BUTTON_keep_alive_0, test_sequence_keep_alive_0, test_events_keep_alive_0),
|
||||||
|
};
|
||||||
|
|
||||||
|
static btn_test_arr_t *select_test_item;
|
||||||
|
|
||||||
|
/* Get button state for given current time */
|
||||||
|
static uint8_t prv_get_state_for_time(uint16_t key_id, uint32_t time)
|
||||||
|
{
|
||||||
|
uint8_t state = 0;
|
||||||
|
uint32_t duration = 0;
|
||||||
|
|
||||||
|
if (select_test_item->test_key_id == key_id)
|
||||||
|
{
|
||||||
|
// printf("time: %d, key_id: %d\n", time, key_id);
|
||||||
|
for (size_t i = 0; i < select_test_item->test_sequence_cnt; ++i)
|
||||||
|
{
|
||||||
|
if (select_test_item->test_sequence[i].duration == 0)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
duration += select_test_item->test_sequence[i].duration + 1; /* Advance time, need add 1 for state switch time. */
|
||||||
|
// printf("i: %d, duration: %d\n", i, duration);
|
||||||
|
if (time <= duration)
|
||||||
|
{
|
||||||
|
state = select_test_item->test_sequence[i].state;
|
||||||
|
// printf("i: %d, time: %d, duration: %d, state: %d\n", i, time, duration, state);
|
||||||
|
// printf("state: %d\n", state);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint32_t test_get_state_total_duration(void)
|
||||||
|
{
|
||||||
|
uint32_t duration = 0;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < select_test_item->test_sequence_cnt; ++i)
|
||||||
|
{
|
||||||
|
if (select_test_item->test_sequence[i].duration == 0)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
duration += select_test_item->test_sequence[i].duration + 1; /* Advance time, need add 1 for state switch time. */
|
||||||
|
}
|
||||||
|
|
||||||
|
return duration;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Get button state */
|
||||||
|
static uint8_t prv_btn_get_state(struct ebtn_btn *btn)
|
||||||
|
{
|
||||||
|
uint8_t state = prv_get_state_for_time(btn->key_id, test_processed_time_current);
|
||||||
|
|
||||||
|
(void)btn;
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint32_t test_processed_event_time_prev;
|
||||||
|
static uint32_t test_processed_array_index = 0;
|
||||||
|
/* Process button event */
|
||||||
|
static void prv_btn_event(struct ebtn_btn *btn, ebtn_evt_t evt)
|
||||||
|
{
|
||||||
|
const char *s;
|
||||||
|
uint32_t color, keepalive_cnt = 0, diff_time;
|
||||||
|
const btn_test_evt_t *test_evt_data = NULL;
|
||||||
|
HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
|
||||||
|
|
||||||
|
if (test_processed_array_index >= select_test_item->test_events_cnt)
|
||||||
|
{
|
||||||
|
SetConsoleTextAttribute(hConsole, FOREGROUND_RED);
|
||||||
|
printf("[%7u] ERROR! Array index is out of bounds!\r\n", (unsigned)test_processed_time_current);
|
||||||
|
SetConsoleTextAttribute(hConsole, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
test_evt_data = &select_test_item->test_events[test_processed_array_index];
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Handle timing */
|
||||||
|
diff_time = test_processed_time_current - test_processed_event_time_prev;
|
||||||
|
test_processed_event_time_prev = test_processed_time_current;
|
||||||
|
keepalive_cnt = btn->keepalive_cnt;
|
||||||
|
|
||||||
|
/* Event type must match */
|
||||||
|
ASSERT((test_evt_data != NULL) && (test_evt_data->evt == evt));
|
||||||
|
|
||||||
|
/* Get event string */
|
||||||
|
if (evt == EBTN_EVT_KEEPALIVE)
|
||||||
|
{
|
||||||
|
s = "KEEPALIVE";
|
||||||
|
color = FOREGROUND_RED | FOREGROUND_BLUE;
|
||||||
|
|
||||||
|
ASSERT((test_evt_data != NULL) && (test_evt_data->keepalive_cnt == keepalive_cnt));
|
||||||
|
}
|
||||||
|
else if (evt == EBTN_EVT_ONPRESS)
|
||||||
|
{
|
||||||
|
s = "ONPRESS";
|
||||||
|
color = FOREGROUND_GREEN;
|
||||||
|
}
|
||||||
|
else if (evt == EBTN_EVT_ONRELEASE)
|
||||||
|
{
|
||||||
|
s = "ONRELEASE";
|
||||||
|
color = FOREGROUND_BLUE;
|
||||||
|
}
|
||||||
|
else if (evt == EBTN_EVT_ONCLICK)
|
||||||
|
{
|
||||||
|
s = "ONCLICK";
|
||||||
|
color = FOREGROUND_RED | FOREGROUND_GREEN;
|
||||||
|
|
||||||
|
ASSERT((test_evt_data != NULL) && (test_evt_data->conseq_clicks == btn->click_cnt));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
s = "UNKNOWN";
|
||||||
|
color = FOREGROUND_RED;
|
||||||
|
}
|
||||||
|
|
||||||
|
SetConsoleTextAttribute(hConsole, color);
|
||||||
|
printf("[%7u][%6u] ID(hex):%4x, evt:%10s, keep-alive cnt: %3u, click cnt: %3u\r\n", (unsigned)test_processed_time_current, (unsigned)diff_time, btn->key_id,
|
||||||
|
s, (unsigned)keepalive_cnt, (unsigned)btn->click_cnt);
|
||||||
|
|
||||||
|
SetConsoleTextAttribute(hConsole, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);
|
||||||
|
++test_processed_array_index; /* Go to next step in next event */
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Test function
|
||||||
|
*/
|
||||||
|
int example_test(void)
|
||||||
|
{
|
||||||
|
printf("Test running\r\n");
|
||||||
|
|
||||||
|
for (int index = 0; index < EBTN_ARRAY_SIZE(test_list); index++)
|
||||||
|
{
|
||||||
|
select_test_item = &test_list[index];
|
||||||
|
|
||||||
|
SUITE_START(select_test_item->test_name);
|
||||||
|
|
||||||
|
// printf("\n");
|
||||||
|
|
||||||
|
// init variable
|
||||||
|
test_processed_event_time_prev = 0;
|
||||||
|
test_processed_array_index = 0;
|
||||||
|
|
||||||
|
/* Define buttons */
|
||||||
|
ebtn_init(btns, EBTN_ARRAY_SIZE(btns), NULL, 0, prv_btn_get_state, prv_btn_event);
|
||||||
|
|
||||||
|
/* Counter simulates ms tick */
|
||||||
|
for (uint32_t i = 0; i < MAX_TIME_MS; ++i)
|
||||||
|
{
|
||||||
|
test_processed_time_current = i; /* Set current time used in callback */
|
||||||
|
ebtn_process(i); /* Now run processing */
|
||||||
|
|
||||||
|
// printf("time: %d, end: %d, in_process(): %d/%d\n", i, test_processed_array_index >= select_test_item->test_events_cnt
|
||||||
|
// , ebtn_is_btn_in_process(ebtn_get_btn_by_key_id(select_test_item->test_key_id)), ebtn_is_in_process());
|
||||||
|
// check end
|
||||||
|
if (test_processed_array_index >= select_test_item->test_events_cnt)
|
||||||
|
{
|
||||||
|
uint32_t duration = test_get_state_total_duration();
|
||||||
|
if (i > duration + 1)
|
||||||
|
{
|
||||||
|
ASSERT(!ebtn_is_btn_in_process(ebtn_get_btn_by_key_id(select_test_item->test_key_id)));
|
||||||
|
ASSERT(!ebtn_is_in_process());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ASSERT(test_processed_array_index == select_test_item->test_events_cnt);
|
||||||
|
|
||||||
|
// printf("\n");
|
||||||
|
|
||||||
|
SUITE_END();
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
290
example_user.c
Normal file
290
example_user.c
Normal 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);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user