diff --git a/.vscode/launch.json b/.vscode/launch.json index a53089a..c76ad22 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,15 +1,12 @@ { - // Use IntelliSense to learn about possible attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ { + /* GDB must in be in the PATH environment */ "name": "(Windows) Launch", "type": "cppdbg", "request": "launch", "program": "${command:cmake.launchTargetPath}", - "miDebuggerPath": "c:\\msys64\\mingw64\\bin\\gdb.exe", "args": [], "stopAtEntry": false, "cwd": "${fileDirname}", diff --git a/.vscode/settings.json b/.vscode/settings.json index 50e7c03..b7a4aa5 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -6,7 +6,9 @@ "string.h": "c", "lwevt_opt.h": "c", "lwbtn.h": "c", - "lwbtn_opt.h": "c" + "lwbtn_opt.h": "c", + "lwbtn_opts.h": "c", + "compare": "c" }, "esbonio.sphinx.confDir": "" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 248fe70..5184e3b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,4 +2,15 @@ ## Develop +## v0.0.2 + +- Add `LWBTN_CFG_GET_STATE_MODE` to control *get state* mode +- Add option to check if button is currently active (after debounce period has elapsed) +- Add option to set time/click parameters at run time for each button specifically +- Rename `_RUNTIME` configuration with `_DYNAMIC` +- Change `LWBTC_CFG_TIME_DEBOUNCE` to `LWBTC_CFG_TIME_DEBOUNCE_PRESS` and `LWBTC_CFG_TIME_DEBOUNCE_RUNTIME` to `LWBTC_CFG_TIME_DEBOUNCE_PRESS_DYNAMIC` respectively +- Add option release debounce with `LWBTC_CFG_TIME_DEBOUNCE_RELEASE` and `LWBTC_CFG_TIME_DEBOUNCE_RELEASE_DYNAMIC` options + +## v0.0.1 + - First commit \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 269701d..2585989 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,6 +12,7 @@ else() # Add key executable block target_sources(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_LIST_DIR}/dev/main.c + ${CMAKE_CURRENT_LIST_DIR}/examples/example_win32.c ) # Add key include paths diff --git a/LICENSE b/LICENSE index 5625f63..3702ca4 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2022 Tilen MAJERLE +Copyright (c) 2023 Tilen MAJERLE Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/dev/lwbtn_opts.h b/dev/lwbtn_opts.h index 13322e5..e7e1ac9 100644 --- a/dev/lwbtn_opts.h +++ b/dev/lwbtn_opts.h @@ -4,7 +4,7 @@ */ /* - * Copyright (c) 2022 Tilen MAJERLE + * Copyright (c) 2023 Tilen MAJERLE * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation @@ -29,7 +29,7 @@ * This file is part of LwBTN - Lightweight button manager. * * Author: Tilen MAJERLE - * Version: v0.0.1 + * Version: v0.0.2 */ #ifndef LWBTN_HDR_OPTS_H #define LWBTN_HDR_OPTS_H @@ -41,4 +41,14 @@ * copy & replace here settings you want to change values */ +/* Press config */ +#define LWBTN_CFG_TIME_DEBOUNCE_PRESS 20 /* No debounce for press event */ +#define LWBTN_CFG_TIME_DEBOUNCE_PRESS_DYNAMIC \ + 1 /* Debounce for press event is statically set with macro -> no dynamic config */ + +/* Release config */ +#define LWBTN_CFG_TIME_DEBOUNCE_RELEASE 20 /* No debounce for release event */ +#define LWBTN_CFG_TIME_DEBOUNCE_RELEASE_DYNAMIC \ + 1 /* Debounce for release event is statically set with macro -> no dynamic config */ + #endif /* LWBTN_HDR_OPTS_H */ diff --git a/dev/main.c b/dev/main.c index 17d4c89..81d091f 100644 --- a/dev/main.c +++ b/dev/main.c @@ -3,105 +3,10 @@ #include "lwbtn/lwbtn.h" #include "windows.h" -static LARGE_INTEGER freq, sys_start_time; -static uint32_t get_tick(void); - -/* User defined settings */ -const int keys[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'}; -uint32_t last_time_keys[sizeof(keys) / sizeof(keys[0])] = {0}; - -/* List of buttons to process */ -static lwbtn_btn_t btns[] = {{.arg = (void*)&keys[0]}, {.arg = (void*)&keys[1]}, {.arg = (void*)&keys[2]}, - {.arg = (void*)&keys[3]}, {.arg = (void*)&keys[4]}, {.arg = (void*)&keys[5]}, - {.arg = (void*)&keys[6]}, {.arg = (void*)&keys[7]}, {.arg = (void*)&keys[8]}, - {.arg = (void*)&keys[9]}}; - -/** - * \brief Get input state callback - * \param lw: LwBTN instance - * \param btn: Button instance - * \return `1` if button active, `0` otherwise - */ -uint8_t -prv_btn_get_state(struct lwbtn* lw, struct lwbtn_btn* btn) { - (void)lw; - return GetAsyncKeyState(*(int*)btn->arg) < 0; -} - -/** - * \brief Button event - * - * \param lw: LwBTN instance - * \param btn: Button instance - * \param evt: Button event - */ -void -prv_btn_event(struct lwbtn* lw, struct lwbtn_btn* btn, lwbtn_evt_t evt) { - const char* s; - uint32_t color; - HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE); - uint32_t* diff_time_ptr = &last_time_keys[(*(int*)btn->arg) - '0']; - uint32_t 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 == LWBTN_EVT_KEEPALIVE) { - s = "KEEPALIVE"; - color = FOREGROUND_RED; - } else if (evt == LWBTN_EVT_ONPRESS) { - s = " ONPRESS"; - color = FOREGROUND_GREEN; - } else if (evt == LWBTN_EVT_ONRELEASE) { - s = "ONRELEASE"; - color = FOREGROUND_BLUE; - } else if (evt == LWBTN_EVT_ONCLICK) { - s = " ONCLICK"; - color = FOREGROUND_RED | FOREGROUND_GREEN; - } else { - s = " UNKNOWN"; - color = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE; - } - SetConsoleTextAttribute(hConsole, color); - printf("[%7u][%6u] CH: %c, evt: %s, keep-alive cnt: %3u, click cnt: %3u\r\n", (unsigned)get_tick(), - (unsigned)diff_time, *(int*)btn->arg, s, (unsigned)btn->keepalive.cnt, (unsigned)btn->click.cnt); - SetConsoleTextAttribute(hConsole, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE); - (void)lw; -} +extern int example_win32(void); int main(void) { - printf("Application running\r\n"); - QueryPerformanceFrequency(&freq); - QueryPerformanceCounter(&sys_start_time); - - /* Define buttons */ - lwbtn_init_ex(NULL, btns, sizeof(btns) / sizeof(btns[0]), prv_btn_get_state, prv_btn_event); - - while (1) { - /* Process forever */ - lwbtn_process_ex(NULL, get_tick()); - - /* Artificial sleep to offload win process */ - Sleep(5); - } -} - -/** - * \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); + example_win32(); + return 0; } diff --git a/docs/conf.py b/docs/conf.py index 3d2351f..718c57c 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -23,7 +23,7 @@ subprocess.call('doxygen doxyfile.doxy', shell=True) # -- Project information ----------------------------------------------------- project = 'LwBTN' -copyright = '2022, Tilen MAJERLE' +copyright = '2023, Tilen MAJERLE' author = 'Tilen MAJERLE' # Try to get branch at which this is running diff --git a/docs/get-started/index.rst b/docs/get-started/index.rst index f372066..5740046 100644 --- a/docs/get-started/index.rst +++ b/docs/get-started/index.rst @@ -62,7 +62,7 @@ Next step is to add the library to the project, by means of source files to comp * Copy ``lwbtn`` folder to your project, it contains library files * Add ``lwbtn/src/include`` folder to `include path` of your toolchain. This is where `C/C++` compiler can find the files during compilation process. Usually using ``-I`` flag -* Add source files from ``lwbtn/src/`` folder to toolchain build. These files are built by `C/C++` compiler +* Add source files from ``lwbtn/src/`` folder to toolchain build. These files are built by `C/C++` compiler. CMake configuration comes with the library, allows users to include library in the project as **subdirectory** and **library**. * Copy ``lwbtn/src/include/lwbtn/lwbtn_opts_template.h`` to project folder and rename it to ``lwbtn_opts.h`` * Copy ``lwbtn/src/include/lwbtn/lwbtn_types_template.h`` to project folder and rename it to ``lwbtn_types.h`` * Build the project diff --git a/docs/index.rst b/docs/index.rst index 7d55164..9697b2a 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -78,3 +78,4 @@ Table of contents LwRB - Ring buffer LwSHELL - Shell LwUTIL - Utility functions + LwWDG - RTOS task watchdog diff --git a/docs/user-manual/index.rst b/docs/user-manual/index.rst index 10c053c..cc1dc72 100644 --- a/docs/user-manual/index.rst +++ b/docs/user-manual/index.rst @@ -19,6 +19,13 @@ User must define buttons array and pass it to the library. Next to that, ``2`` m User shall later periodically call processing function with current system time as simple parameter and get ready to receive various events. +A simple example for win32 is below: + +.. literalinclude:: ../../examples/example_win32.c + :language: c + :linenos: + :caption: Win32 example code + Input events ^^^^^^^^^^^^ diff --git a/examples/example_win32.c b/examples/example_win32.c new file mode 100644 index 0000000..5ed771f --- /dev/null +++ b/examples/example_win32.c @@ -0,0 +1,140 @@ +#include "lwbtn/lwbtn.h" +#include "windows.h" +#include +#include + +static LARGE_INTEGER freq, sys_start_time; +static uint32_t get_tick(void); + +/* User defined settings */ +const int keys[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'}; +uint32_t last_time_keys[sizeof(keys) / sizeof(keys[0])] = {0}; + +/* List of buttons to process with assigned custom arguments for callback functions */ +static lwbtn_btn_t btns[] = { + {.arg = (void*)&keys[0]}, {.arg = (void*)&keys[1]}, {.arg = (void*)&keys[2]}, {.arg = (void*)&keys[3]}, + {.arg = (void*)&keys[4]}, {.arg = (void*)&keys[5]}, {.arg = (void*)&keys[6]}, {.arg = (void*)&keys[7]}, + {.arg = (void*)&keys[8]}, {.arg = (void*)&keys[9]}, +}; + +/** + * \brief Get input state callback + * \param lw: LwBTN instance + * \param btn: Button instance + * \return `1` if button active, `0` otherwise + */ +uint8_t +prv_btn_get_state(struct lwbtn* lw, struct lwbtn_btn* btn) { + (void)lw; + + /* + * Function will return negative number if button is pressed, + * or zero if button is releases + */ + return GetAsyncKeyState(*(int*)btn->arg) < 0; +} + +/** + * \brief Button event + * + * \param lw: LwBTN instance + * \param btn: Button instance + * \param evt: Button event + */ +void +prv_btn_event(struct lwbtn* lw, struct lwbtn_btn* btn, lwbtn_evt_t evt) { + const char* s; + uint32_t color, keepalive_cnt = 0; + HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE); + uint32_t* diff_time_ptr = &last_time_keys[(*(int*)btn->arg) - '0']; + uint32_t 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 (0) { +#if LWBTN_CFG_USE_KEEPALIVE + } else if (evt == LWBTN_EVT_KEEPALIVE) { + s = "KEEPALIVE"; + color = FOREGROUND_RED; +#endif /* LWBTN_CFG_USE_KEEPALIVE */ + } else if (evt == LWBTN_EVT_ONPRESS) { + s = " ONPRESS"; + color = FOREGROUND_GREEN; + } else if (evt == LWBTN_EVT_ONRELEASE) { + s = "ONRELEASE"; + color = FOREGROUND_BLUE; + } else if (evt == LWBTN_EVT_ONCLICK) { + s = " ONCLICK"; + color = FOREGROUND_RED | FOREGROUND_GREEN; + } else { + s = " UNKNOWN"; + color = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE; + } +#if LWBTN_CFG_USE_KEEPALIVE + keepalive_cnt = btn->keepalive.cnt; +#endif + SetConsoleTextAttribute(hConsole, color); + printf("[%7u][%6u] CH: %c, evt: %s, keep-alive cnt: %3u, click cnt: %3u\r\n", (unsigned)get_tick(), + (unsigned)diff_time, *(int*)btn->arg, s, (unsigned)keepalive_cnt, (unsigned)btn->click.cnt); + SetConsoleTextAttribute(hConsole, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE); + (void)lw; +} + +/** + * \brief Example function + */ +int +example_win32(void) { + uint32_t time_last; + printf("Application running\r\n"); + QueryPerformanceFrequency(&freq); + QueryPerformanceCounter(&sys_start_time); + + /* Define buttons */ + lwbtn_init_ex(NULL, btns, sizeof(btns) / sizeof(btns[0]), prv_btn_get_state, prv_btn_event); + + time_last = get_tick(); + while (1) { + /* Process forever */ + lwbtn_process_ex(NULL, get_tick()); + + /* Manually read button state */ +#if LWBTN_CFG_GET_STATE_MODE == LWBTN_GET_STATE_MODE_MANUAL + for (size_t i = 0; i < sizeof(btns) / sizeof(btns[0]); ++i) { + lwbtn_set_btn_state(&btns[i], prv_btn_get_state(NULL, &btns[i])); + } +#endif /* LWBTN_CFG_GET_STATE_MODE == LWBTN_GET_STATE_MODE_MANUAL */ + + /* Check if specific button is active and do some action */ + if (lwbtn_is_btn_active(&btns[0])) { + if ((get_tick() - time_last) > 200) { + time_last = get_tick(); + printf("Button is active\r\n"); + } + } + + /* Artificial sleep to offload win process */ + Sleep(5); + } + return 0; +} + +/** + * \brief Get current tick in ms from start of program + * \return uint32_t: Tick in ms + */ +static uint32_t +get_tick(void) { + LONGLONG ret; + LARGE_INTEGER now; + + QueryPerformanceFrequency(&freq); + QueryPerformanceCounter(&now); + ret = now.QuadPart - sys_start_time.QuadPart; + return (uint32_t)((ret * 1000) / freq.QuadPart); +} diff --git a/library.json b/library.json index edba3fa..6e8cf76 100644 --- a/library.json +++ b/library.json @@ -1,6 +1,6 @@ { "name": "LwBTN", - "version": "0.0.1", + "version": "0.0.2", "description": "Lightweight button handler for embedded systems", "keywords": "lwbtn, button, input, manager, btn, lightweight, embedded", "repository": { diff --git a/lwbtn/src/include/lwbtn/lwbtn.h b/lwbtn/src/include/lwbtn/lwbtn.h index d209750..fac2734 100644 --- a/lwbtn/src/include/lwbtn/lwbtn.h +++ b/lwbtn/src/include/lwbtn/lwbtn.h @@ -4,7 +4,7 @@ */ /* - * Copyright (c) 2022 Tilen MAJERLE + * Copyright (c) 2023 Tilen MAJERLE * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation @@ -29,7 +29,7 @@ * This file is part of LwBTN - Lightweight button manager. * * Author: Tilen MAJERLE - * Version: v0.0.1 + * Version: v0.0.2 */ #ifndef LWBTN_HDR_H #define LWBTN_HDR_H @@ -75,38 +75,47 @@ typedef enum { LWBTN_EVT_ONPRESS = 0x00, /*!< On press event - sent when valid press is detected (after debounce if enabled) */ LWBTN_EVT_ONRELEASE, /*!< On release event - sent when valid release event is detected (from active to inactive) */ LWBTN_EVT_ONCLICK, /*!< On Click event - sent when valid sequence of on-press and on-release events occurs */ +#if LWBTN_CFG_USE_KEEPALIVE || __DOXYGEN__ LWBTN_EVT_KEEPALIVE, /*!< Keep alive event - sent periodically when button is active */ +#endif /* LWBTN_CFG_USE_KEEPALIVE || __DOXYGEN__ */ } lwbtn_evt_t; /** * \brief Button event function callback prototype - * \param[in] lw: LwBTN instance + * \param[in] lwobj: LwBTN instance * \param[in] btn: Button instance from array for which event occured * \param[in] evt: Event type */ -typedef void (*lwbtn_evt_fn)(struct lwbtn* lw, struct lwbtn_btn* btn, lwbtn_evt_t evt); +typedef void (*lwbtn_evt_fn)(struct lwbtn* lwobj, struct lwbtn_btn* btn, lwbtn_evt_t evt); /** * \brief Get button/input state callback function - * \param[in] lw: LwBTN instance + * \param[in] lwobj: LwBTN instance * \param[in] btn: Button instance from array to read state * \return `1` when button is considered `active`, `0` otherwise */ -typedef uint8_t (*lwbtn_get_state_fn)(struct lwbtn* lw, struct lwbtn_btn* btn); +typedef uint8_t (*lwbtn_get_state_fn)(struct lwbtn* lwobj, struct lwbtn_btn* btn); /** * \brief Button/input structure */ typedef struct lwbtn_btn { - uint16_t flags; /*!< Private button flags management */ - uint8_t old_state; /*!< Old button state - `1` means active, `0` means inactive */ - uint32_t time_change; /*!< Time in ms when button state got changed last time */ + uint16_t flags; /*!< Private button flags management */ +#if LWBTN_CFG_GET_STATE_MODE != LWBTN_GET_STATE_MODE_CALLBACK || __DOXYGEN__ + uint8_t curr_state; /*!< Current button state to be processed. It is used + to keep track when application manually sets the button state */ +#endif /* LWBTN_CFG_GET_STATE_MODE != LWBTN_GET_STATE_MODE_CALLBACK || __DOXYGEN__ */ + uint8_t old_state; /*!< Old button state - `1` means active, `0` means inactive */ + uint32_t time_change; /*!< Time in ms when button state got changed last time after valid debounce */ + uint32_t time_state_change; /*!< Time in ms when button state got changed last time */ +#if LWBTN_CFG_USE_KEEPALIVE || __DOXYGEN__ struct { uint32_t last_time; /*!< Time in ms of last send keep alive event */ uint16_t cnt; /*!< Number of keep alive events sent after successful on-press detection. Value is reset after on-release */ } keepalive; /*!< Keep alive structure */ +#endif /* LWBTN_CFG_USE_KEEPALIVE || __DOXYGEN__ */ struct { uint32_t last_time; /*!< Time in ms of last successfully detected (not sent!) click event */ @@ -114,39 +123,76 @@ typedef struct lwbtn_btn { } click; /*!< Click event structure */ void* arg; /*!< User defined custom argument for callback function purpose */ + +#if LWBTN_CFG_TIME_DEBOUNCE_PRESS_DYNAMIC || __DOXYGEN__ + uint16_t time_debounce; /*!< Debounce time in milliseconds */ +#endif /* LWBTN_CFG_TIME_DEBOUNCE_PRESS_DYNAMIC || __DOXYGEN__ */ +#if LWBTN_CFG_TIME_DEBOUNCE_RELEASE_DYNAMIC || __DOXYGEN__ + uint16_t time_debounce_release; /*!< Debounce time in milliseconds for release event */ +#endif /* LWBTN_CFG_TIME_DEBOUNCE_RELEASE */ +#if LWBTN_CFG_TIME_CLICK_MIN_DYNAMIC || __DOXYGEN__ + uint16_t time_click_pressed_min; /*!< Minimum pressed time for valid click event */ +#endif /* LWBTN_CFG_TIME_CLICK_MIN_DYNAMIC || __DOXYGEN__ */ +#if LWBTN_CFG_TIME_CLICK_MAX_DYNAMIC || __DOXYGEN__ + uint16_t time_click_pressed_max; /*!< Maximum pressed time for valid click event*/ +#endif /* LWBTN_CFG_TIME_CLICK_MAX_DYNAMIC || __DOXYGEN__ */ +#if LWBTN_CFG_TIME_CLICK_MULTI_MAX_DYNAMIC || __DOXYGEN__ + uint16_t time_click_multi_max; /*!< Maximum time between 2 clicks to be considered consecutive click */ +#endif /* LWBTN_CFG_TIME_CLICK_MULTI_MAX_DYNAMIC || __DOXYGEN__ */ +#if LWBTN_CFG_TIME_KEEPALIVE_PERIOD_DYNAMIC || __DOXYGEN__ + uint16_t time_keepalive_period; /*!< Time in ms for periodic keep alive event */ +#endif /* LWBTN_CFG_TIME_KEEPALIVE_PERIOD_DYNAMIC || __DOXYGEN__ */ +#if LWBTN_CFG_CLICK_MAX_CONSECUTIVE_DYNAMIC || __DOXYGEN__ + uint16_t max_consecutive; /*!< Max number of consecutive clicks */ +#endif /* LWBTN_CFG_CLICK_MAX_CONSECUTIVE_DYNAMIC || __DOXYGEN__ */ } lwbtn_btn_t; /** * \brief LwBTN group structure */ typedef struct lwbtn { - lwbtn_btn_t* btns; /*!< Pointer to buttons array */ - uint16_t btns_cnt; /*!< Number of buttons in array */ - lwbtn_evt_fn evt_fn; /*!< Pointer to event function */ + lwbtn_btn_t* btns; /*!< Pointer to buttons array */ + uint16_t btns_cnt; /*!< Number of buttons in array */ + lwbtn_evt_fn evt_fn; /*!< Pointer to event function */ +#if LWBTN_CFG_GET_STATE_MODE != LWBTN_GET_STATE_MODE_MANUAL || __DOXYGEN__ lwbtn_get_state_fn get_state_fn; /*!< Pointer to get state function */ +#endif /* LWBTN_CFG_GET_STATE_MODE != LWBTN_GET_STATE_MODE_MANUAL || __DOXYGEN__ */ } lwbtn_t; -uint8_t lwbtn_init_ex(lwbtn_t* lw, lwbtn_btn_t* btns, uint16_t btns_cnt, lwbtn_get_state_fn get_state_fn, +uint8_t lwbtn_init_ex(lwbtn_t* lwobj, lwbtn_btn_t* btns, uint16_t btns_cnt, lwbtn_get_state_fn get_state_fn, lwbtn_evt_fn evt_fn); -uint8_t lwbtn_process_ex(lwbtn_t* lw, uint32_t mstime); +uint8_t lwbtn_process_ex(lwbtn_t* lwobj, uint32_t mstime); +uint8_t lwbtn_process_btn_ex(lwbtn_t* lwobj, lwbtn_btn_t* btn, uint32_t mstime); +uint8_t lwbtn_set_btn_state(lwbtn_btn_t* btn, uint8_t state); +uint8_t lwbtn_is_btn_active(const lwbtn_btn_t* btn); /** * \brief Initialize LwBTN library with buttons on default button group * \param[in] btns: Array of buttons to process * \param[in] btns_cnt: Number of buttons to process - * \param[in] get_state_fn: Pointer to function providing button state on demand + * \param[in] get_state_fn: Pointer to function providing button state on demand. + * Can be set to `NULL` if \ref LWBTN_CFG_FORCE_MANUAL_STATE_SET is enabled * \param[in] evt_fn: Button event function callback * \sa lwbtn_init_ex */ #define lwbtn_init(btns, btns_cnt, get_state_fn, evt_fn) lwbtn_init_ex(NULL, btns, btns_cnt, get_state_fn, evt_fn) /** - * \brief Periodically read button states and take appropriate actions + * \brief Periodically read button states and take appropriate actions. + * It processes the default buttons instance group. * \param[in] mstime: Current system time in milliseconds * \sa lwbtn_process_ex */ #define lwbtn_process(mstime) lwbtn_process_ex(NULL, mstime) +/** + * \brief Process specific button in a default LwBTN instance + * + * \param[in] btn: Button instance to process + * \param[in] mstime: Current system time in milliseconds + */ +#define lwbtn_process_btn(btn, mstime) lwbtn_process_btn_ex(NULL, (btn), (mstime)) + /** * \} */ diff --git a/lwbtn/src/include/lwbtn/lwbtn_opt.h b/lwbtn/src/include/lwbtn/lwbtn_opt.h index 7d9275b..dfb0853 100644 --- a/lwbtn/src/include/lwbtn/lwbtn_opt.h +++ b/lwbtn/src/include/lwbtn/lwbtn_opt.h @@ -4,7 +4,7 @@ */ /* - * Copyright (c) 2022 Tilen MAJERLE + * Copyright (c) 2023 Tilen MAJERLE * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation @@ -29,10 +29,10 @@ * This file is part of LwBTN - Lightweight button manager. * * Author: Tilen MAJERLE - * Version: v0.0.1 + * Version: v0.0.2 */ -#ifndef LWBTN_HDR_OPT_H -#define LWBTN_HDR_OPT_H +#ifndef LWBTN_OPT_HDR_H +#define LWBTN_OPT_HDR_H /* Uncomment to ignore user options (or set macro in compiler flags) */ /* #define LWBTN_IGNORE_USER_OPTS */ @@ -53,12 +53,67 @@ extern "C" { */ /** - * \brief Minimum debounce time in units of milliseconds + * \brief Enables `1` or disables `0` periodic keep alive events. * - * This is the time input shall have stable level to detect valid *onpress* event + * Default keep alive period is set with \ref LWBTN_CFG_TIME_KEEPALIVE_PERIOD macro */ -#ifndef LWBTN_CFG_TIME_DEBOUNCE -#define LWBTN_CFG_TIME_DEBOUNCE 20 +#ifndef LWBTN_CFG_USE_KEEPALIVE +#define LWBTN_CFG_USE_KEEPALIVE 1 +#endif + +/** + * \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. + * + * This is used to detect initial debounce when button/input is being pressed by the user. + * + * When value is set to `> 0`, input must be in active state for at least + * minimum milliseconds time, before valid *onpress* event is detected + */ +#ifndef LWBTN_CFG_TIME_DEBOUNCE_PRESS +#define LWBTN_CFG_TIME_DEBOUNCE_PRESS 20 +#endif + +/** + * \brief Enables `1` or disables `0` dynamic settable time debounce + * + * When enabled, additional field is added to button structure to allow + * each button setting its very own debounce time for press event. + * + * If not used, \ref LWBTN_CFG_TIME_DEBOUNCE_PRESS is used as default + * debouncing configuration + */ +#ifndef LWBTN_CFG_TIME_DEBOUNCE_PRESS_DYNAMIC +#define LWBTN_CFG_TIME_DEBOUNCE_PRESS_DYNAMIC 0 +#endif + +/** + * \brief Minimum debounce time for release event in units of milliseconds + * + * This is the time when the input shall have 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 + */ +#ifndef LWBTN_CFG_TIME_DEBOUNCE_RELEASE +#define LWBTN_CFG_TIME_DEBOUNCE_RELEASE 0 +#endif + +/** + * \brief Enables `1` or disables `0` dynamic settable time debounce for release event + * + * When enabled, additional field is added to button structure to allow + * each button setting its very own debounce time for release event. + * + * If not used, \ref LWBTN_CFG_TIME_DEBOUNCE_RELEASE is used as default + * debouncing configuration + */ +#ifndef LWBTN_CFG_TIME_DEBOUNCE_RELEASE_DYNAMIC +#define LWBTN_CFG_TIME_DEBOUNCE_RELEASE_DYNAMIC 0 #endif /** @@ -71,6 +126,15 @@ extern "C" { #define LWBTN_CFG_TIME_CLICK_MIN 20 #endif +/** + * \brief Enables `1` or disables `0` dynamic settable min time for click + * + * When enabled, additional field is added to button structure + */ +#ifndef LWBTN_CFG_TIME_CLICK_MIN_DYNAMIC +#define LWBTN_CFG_TIME_CLICK_MIN_DYNAMIC 0 +#endif + /** * \brief Maximum active input time for valid click event, in milliseconds * @@ -81,6 +145,15 @@ extern "C" { #define LWBTN_CFG_TIME_CLICK_MAX 300 #endif +/** + * \brief Enables `1` or disables `0` dynamic settable max time for click + * + * When enabled, additional field is added to button structure + */ +#ifndef LWBTN_CFG_TIME_CLICK_MAX_DYNAMIC +#define LWBTN_CFG_TIME_CLICK_MAX_DYNAMIC 0 +#endif + /** * \brief Maximum allowed time between last on-release and next valid on-press, * to still allow multi-click events, in milliseconds @@ -92,12 +165,30 @@ extern "C" { #define LWBTN_CFG_TIME_CLICK_MULTI_MAX 400 #endif +/** + * \brief Enables `1` or disables `0` dynamic settable max time for multi click + * + * When enabled, additional field is added to button structure + */ +#ifndef LWBTN_CFG_TIME_CLICK_MULTI_MAX_DYNAMIC +#define LWBTN_CFG_TIME_CLICK_MULTI_MAX_DYNAMIC 0 +#endif + /** * \brief Maximum number of allowed consecutive click events, * before structure gets reset to default value */ #ifndef LWBTN_CFG_CLICK_MAX_CONSECUTIVE -#define LWBTN_CFG_CLICK_MAX_CONSECUTIVE 1 +#define LWBTN_CFG_CLICK_MAX_CONSECUTIVE 3 +#endif + +/** + * \brief Enables `1` or disables `0` dynamic settable max consecutive clicks + * + * When enabled, additional field is added to button structure + */ +#ifndef LWBTN_CFG_CLICK_MAX_CONSECUTIVE_DYNAMIC +#define LWBTN_CFG_CLICK_MAX_CONSECUTIVE_DYNAMIC 0 #endif /** @@ -108,6 +199,15 @@ extern "C" { #define LWBTN_CFG_TIME_KEEPALIVE_PERIOD 100 #endif +/** + * \brief Enables `1` or disables `0` dynamic settable keep alive period + * + * When enabled, additional field is added to button structure + */ +#ifndef LWBTN_CFG_TIME_KEEPALIVE_PERIOD_DYNAMIC +#define LWBTN_CFG_TIME_KEEPALIVE_PERIOD_DYNAMIC 0 +#endif + /** * \brief Enables `1` or disables `0` immediate onclick event * after on-release event, if number of consecutive @@ -122,6 +222,28 @@ extern "C" { #define LWBTN_CFG_CLICK_MAX_CONSECUTIVE_SEND_IMMEDIATELY 1 #endif +#define LWBTN_GET_STATE_MODE_CALLBACK 0 /*!< Callback-only state mode */ +#define LWBTN_GET_STATE_MODE_MANUAL 1 /*!< Manual-only state mode */ +#define LWBTN_GET_STATE_MODE_CALLBACK_OR_MANUAL 2 /*!< Callback or manual state mode */ + +/** + * \brief Sets the mode how new button state is acquired. + * + * Different modes are availale, set with the level number: + * + * - `LWBTN_GET_STATE_MODE_CALLBACK`: State of the button is checked through *get state* callback function (default mode, legacy) + * - `LWBTN_GET_STATE_MODE_MANUAL`: Only manual state set is enabled. Application must set the button state with API functions. + * Callback API is not used. + * - `LWBTN_GET_STATE_MODE_CALLBACK_OR_MANUAL`: State of the button is checked through *get state* callback function (by default). + * It enables API to manually set the state with approapriate function call. + * Button state is checked with the callback at least until manual state API function is called. + * + * This allows multiple build configurations for various button types + */ +#ifndef LWBTN_CFG_GET_STATE_MODE +#define LWBTN_CFG_GET_STATE_MODE LWBTN_GET_STATE_MODE_CALLBACK +#endif + /** * \} */ @@ -130,4 +252,4 @@ extern "C" { } #endif /* __cplusplus */ -#endif /* LWBTN_HDR_OPT_H */ +#endif /* LWBTN_OPT_HDR_H */ diff --git a/lwbtn/src/include/lwbtn/lwbtn_opts_template.h b/lwbtn/src/include/lwbtn/lwbtn_opts_template.h index c8ca728..81c23d0 100644 --- a/lwbtn/src/include/lwbtn/lwbtn_opts_template.h +++ b/lwbtn/src/include/lwbtn/lwbtn_opts_template.h @@ -4,7 +4,7 @@ */ /* - * Copyright (c) 2022 Tilen MAJERLE + * Copyright (c) 2023 Tilen MAJERLE * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation @@ -29,10 +29,10 @@ * This file is part of LwBTN - Lightweight button manager. * * Author: Tilen MAJERLE - * Version: v0.0.1 + * Version: v0.0.2 */ -#ifndef LWBTN_HDR_OPTS_H -#define LWBTN_HDR_OPTS_H +#ifndef LWBTN_OPTS_HDR_H +#define LWBTN_OPTS_HDR_H /* Rename this file to "lwbtn_opts.h" for your application */ @@ -41,4 +41,4 @@ * copy & replace here settings you want to change values */ -#endif /* LWBTN_HDR_OPTS_H */ +#endif /* LWBTN_OPTS_HDR_H */ diff --git a/lwbtn/src/lwbtn/lwbtn.c b/lwbtn/src/lwbtn/lwbtn.c index b68b444..eaad815 100644 --- a/lwbtn/src/lwbtn/lwbtn.c +++ b/lwbtn/src/lwbtn/lwbtn.c @@ -4,7 +4,7 @@ */ /* - * Copyright (c) 2022 Tilen MAJERLE + * Copyright (c) 2023 Tilen MAJERLE * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation @@ -29,191 +29,216 @@ * This file is part of LwBTN - Lightweight button manager. * * Author: Tilen MAJERLE - * Version: v0.0.1 + * Version: v0.0.2 */ #include #include "lwbtn/lwbtn.h" -#define LWBTN_FLAG_ONPRESS_SENT ((uint16_t)0x0001) +#if LWBTN_CFG_GET_STATE_MODE > 2 +#error "Invalid LWBTN_GET_STATE_MODE_CALLBACK configuration" +#endif -#define LWBTN_TIME_DEBOUNCE_GET_MIN(btn) LWBTN_CFG_TIME_DEBOUNCE +#define LWBTN_FLAG_ONPRESS_SENT ((uint16_t)0x0001) /*!< Flag indicates that on-press event has been sent */ +#define LWBTN_FLAG_MANUAL_STATE \ + ((uint16_t)0x0002) /*!< Flag indicates that user wants to manually set button state. + Do not call "get_state" function */ + +#if LWBTN_CFG_TIME_DEBOUNCE_PRESS_DYNAMIC +#define LWBTN_TIME_DEBOUNCE_GET_MIN(btn) (uint32_t)((btn)->time_debounce) +#else +#define LWBTN_TIME_DEBOUNCE_GET_MIN(btn) (uint32_t) LWBTN_CFG_TIME_DEBOUNCE_PRESS +#endif /* LWBTN_CFG_TIME_DEBOUNCE_PRESS_DYNAMIC */ + +#if LWBTN_CFG_TIME_DEBOUNCE_RELEASE_DYNAMIC +#define LWBTN_TIME_DEBOUNCE_RELEASE_GET_MIN(btn) (uint32_t)((btn)->time_debounce_release) +#else +#define LWBTN_TIME_DEBOUNCE_RELEASE_GET_MIN(btn) (uint32_t) LWBTN_CFG_TIME_DEBOUNCE_RELEASE +#endif /* LWBTN_CFG_TIME_DEBOUNCE_RELEASE_DYNAMIC */ + +#if LWBTN_CFG_TIME_CLICK_MIN_DYNAMIC +#define LWBTN_TIME_CLICK_GET_PRESSED_MIN(btn) ((btn)->time_click_pressed_min) +#else #define LWBTN_TIME_CLICK_GET_PRESSED_MIN(btn) LWBTN_CFG_TIME_CLICK_MIN +#endif /* LWBTN_CFG_TIME_CLICK_MIN_DYNAMIC */ +#if LWBTN_CFG_TIME_CLICK_MAX_DYNAMIC +#define LWBTN_TIME_CLICK_GET_PRESSED_MAX(btn) ((btn)->time_click_pressed_max) +#else #define LWBTN_TIME_CLICK_GET_PRESSED_MAX(btn) LWBTN_CFG_TIME_CLICK_MAX -#define LWBTN_TIME_CLICK_MAX_MULTI(btn) LWBTN_CFG_TIME_CLICK_MULTI_MAX -#define LWBTN_TIME_KEEPALIVE_PERIOD(btn) LWBTN_CFG_TIME_KEEPALIVE_PERIOD -#define LWBTN_CLICK_MAX_CONSECUTIVE(btn) LWBTN_CFG_CLICK_MAX_CONSECUTIVE +#endif /* LWBTN_CFG_TIME_CLICK_MAX_DYNAMIC */ +#if LWBTN_CFG_TIME_CLICK_MULTI_MAX_DYNAMIC +#define LWBTN_TIME_CLICK_MAX_MULTI(btn) ((btn)->time_click_multi_max) +#else +#define LWBTN_TIME_CLICK_MAX_MULTI(btn) LWBTN_CFG_TIME_CLICK_MULTI_MAX +#endif /* LWBTN_CFG_TIME_CLICK_MULTI_MAX_DYNAMIC */ +#if LWBTN_CFG_TIME_KEEPALIVE_PERIOD_DYNAMIC +#define LWBTN_TIME_KEEPALIVE_PERIOD(btn) ((btn)->time_keepalive_period) +#else +#define LWBTN_TIME_KEEPALIVE_PERIOD(btn) LWBTN_CFG_TIME_KEEPALIVE_PERIOD +#endif /* LWBTN_CFG_TIME_KEEPALIVE_PERIOD_DYNAMIC */ +#if LWBTN_CFG_CLICK_MAX_CONSECUTIVE_DYNAMIC +#define LWBTN_CLICK_MAX_CONSECUTIVE(btn) ((btn)->max_consecutive) +#else +#define LWBTN_CLICK_MAX_CONSECUTIVE(btn) LWBTN_CFG_CLICK_MAX_CONSECUTIVE +#endif /* LWBTN_CFG_CLICK_MAX_CONSECUTIVE_DYNAMIC */ + +/* Get button state */ +#if LWBTN_CFG_GET_STATE_MODE == LWBTN_GET_STATE_MODE_CALLBACK +#define LWBTN_BTN_GET_STATE(lwobj, btn) ((lwobj)->get_state_fn((lwobj), (btn))) +#elif LWBTN_CFG_GET_STATE_MODE == LWBTN_GET_STATE_MODE_MANUAL +#define LWBTN_BTN_GET_STATE(lwobj, btn) ((btn)->curr_state) +#elif LWBTN_CFG_GET_STATE_MODE == LWBTN_GET_STATE_MODE_CALLBACK_OR_MANUAL +#define LWBTN_BTN_GET_STATE(lwobj, btn) \ + (((btn)->flags & LWBTN_FLAG_MANUAL_STATE) \ + ? ((btn)->curr_state) \ + : (((lwobj)->get_state_fn != NULL) ? ((lwobj)->get_state_fn((lwobj), (btn))) : 0)) +#endif /* Default button group instance */ static lwbtn_t lwbtn_default; -#define LWBTN_GET_LW(in_lw) ((in_lw) != NULL ? (in_lw) : (&lwbtn_default)) +#define LWBTN_GET_LWOBJ(in_lwobj) ((in_lwobj) != NULL ? (in_lwobj) : (&lwbtn_default)) /** - * \brief Initialize button manager - * \param[in] lw: LwBTN instance. Set to `NULL` to use default one - * \param[in] btns: Array of buttons to process - * \param[in] btns_cnt: Number of 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 + * \brief Process the button information and state + * + * \param[in] lwobj: LwBTN instance. Set to `NULL` to use default one + * \param[in] btn: Button instance to process + * \param[in] mstime: Current milliseconds system time */ -uint8_t -lwbtn_init_ex(lwbtn_t* lw, lwbtn_btn_t* btns, uint16_t btns_cnt, lwbtn_get_state_fn get_state_fn, lwbtn_evt_fn evt_fn) { - lw = LWBTN_GET_LW(lw); +void +prv_process_btn(lwbtn_t* lwobj, lwbtn_btn_t* btn, uint32_t mstime) { + uint8_t new_state; - if (btns == NULL || btns_cnt == 0 || get_state_fn == NULL || evt_fn == NULL) { - return 0; + /* Get button state */ + new_state = LWBTN_BTN_GET_STATE(lwobj, btn); + + /* Button state has just changed */ + if (new_state != btn->old_state) { + btn->time_state_change = mstime; } - memset(lw, 0x00, sizeof(*lw)); - lw->btns = btns; - lw->btns_cnt = btns_cnt; - lw->evt_fn = evt_fn; - lw->get_state_fn = get_state_fn; - return 1; -} -/** - * \brief Button processing function, - * that reads the inputs and makes actions accordingly. - * \param[in] mstime: Current time in milliseconds - * \return `1` on success, `0` otherwise - */ -uint8_t -lwbtn_process_ex(lwbtn_t* lw, uint32_t mstime) { - lwbtn_btn_t* btn = NULL; - uint8_t new_state = 0; - - lw = LWBTN_GET_LW(lw); - - /* Process all buttons */ - for (size_t index = 0; index < lw->btns_cnt; ++index) { - btn = &lw->btns[index]; - - new_state = lw->get_state_fn(lw, btn); /* Get button state */ - - /* - * Button state has changed + /* Button is still pressed */ + else if (new_state) { + /* + * Handle debounce and send on-press event + * + * This is when we detect valid press */ - if (new_state != btn->old_state) { + if (!(btn->flags & LWBTN_FLAG_ONPRESS_SENT)) { /* - * Button just became inactive + * Run if statement when: * - * - Handle on-release event - * - Handle on-click event + * - Runtime mode is enabled -> user sets its own config for debounce + * - Config debounce time for press is more than `0` */ - if (!new_state) { - /* - * We only need to react if on-press event has even been started. - * - * Do nothing if that was not the case - */ - if (btn->flags & LWBTN_FLAG_ONPRESS_SENT) { - /* Handle on-release event */ - btn->flags &= ~LWBTN_FLAG_ONPRESS_SENT; - lw->evt_fn(lw, btn, LWBTN_EVT_ONRELEASE); - - /* Check time validity for click event */ - if ((mstime - btn->time_change) >= LWBTN_TIME_CLICK_GET_PRESSED_MIN(btn) - && (mstime - btn->time_change) <= LWBTN_TIME_CLICK_GET_PRESSED_MAX(btn)) { - - /* - * Increase consecutive clicks if max not reached yet - * and if time between two clicks is not too long - * - * Otherwise we consider click as fresh one - */ - if (btn->click.cnt > 0 && btn->click.cnt < LWBTN_CLICK_MAX_CONSECUTIVE(btn) - && (mstime - btn->click.last_time) < LWBTN_TIME_CLICK_MAX_MULTI(btn)) { - ++btn->click.cnt; - } else { - btn->click.cnt = 1; - } - btn->click.last_time = mstime; - } else { - /* - * There was an on-release event, but timing - * for click event detection is outside allowed window. - * - * If user has some consecutive clicks from previous clicks, - * these will be sent after the timeout window (and after the on-release event) - */ - } - -#if LWBTN_CFG_CLICK_MAX_CONSECUTIVE_SEND_IMMEDIATELY - /* - * Depending on the configuration, - * 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 == LWBTN_CLICK_MAX_CONSECUTIVE(btn)) { - lw->evt_fn(lw, btn, LWBTN_EVT_ONCLICK); - btn->click.cnt = 0; - } -#endif /* LWBTN_CFG_CLICK_MAX_CONSECUTIVE_SEND_IMMEDIATELY */ - } - } - - /* - * Button just pressed - */ - else { - /* Do nothing - things are handled after debounce period */ - } - btn->time_change = mstime; - btn->keepalive.last_time = mstime; - btn->keepalive.cnt = 0; - } - - /* - * Button is still pressed - */ - else if (new_state) { - /* - * Handle debounce and send on-press event - * - * This is when we detect valid press - */ - if (!(btn->flags & LWBTN_FLAG_ONPRESS_SENT)) { - /* Check minimum stable time */ - if ((mstime - btn->time_change) >= LWBTN_TIME_DEBOUNCE_GET_MIN(btn)) { +#if LWBTN_CFG_TIME_DEBOUNCE_PRESS_DYNAMIC || LWBTN_CFG_TIME_DEBOUNCE_PRESS > 0 + if ((mstime - btn->time_state_change) >= LWBTN_TIME_DEBOUNCE_GET_MIN(btn)) +#endif /* LWBTN_CFG_TIME_DEBOUNCE_PRESS_DYNAMIC || LWBTN_CFG_TIME_DEBOUNCE_PRESS> 0 */ + { #if !LWBTN_CFG_CLICK_MAX_CONSECUTIVE_SEND_IMMEDIATELY - /* - * Depending on the configuration, - * this part will send on-click event just before the next on-press release, - * if maximum number of consecutive clicks has been reached. - */ - if (btn->click.cnt > 0 && btn->click.cnt == LWBTN_CLICK_MAX_CONSECUTIVE(btn)) { - lw->evt_fn(lw, btn, LWBTN_EVT_ONCLICK); - btn->click.cnt = 0; - } + /* + * Depending on the configuration, + * this part will send on-click event just before the next on-press release, + * if maximum number of consecutive clicks has been reached. + */ + if (btn->click.cnt > 0 && btn->click.cnt == LWBTN_CLICK_MAX_CONSECUTIVE(btn)) { + lwobj->evt_fn(lwobj, btn, LWBTN_EVT_ONCLICK); + btn->click.cnt = 0; + } #endif /* !LWBTN_CFG_CLICK_MAX_CONSECUTIVE_SEND_IMMEDIATELY */ - /* Start with new on-press */ - btn->flags |= LWBTN_FLAG_ONPRESS_SENT; - lw->evt_fn(lw, btn, LWBTN_EVT_ONPRESS); + /* Start with new on-press */ + btn->flags |= LWBTN_FLAG_ONPRESS_SENT; + lwobj->evt_fn(lwobj, btn, LWBTN_EVT_ONPRESS); - /* Set keep alive time */ - btn->keepalive.last_time = mstime; - } - } - - /* - * Handle keep alive, but only if on-press event has been sent - * - * Keep alive is sent when valid press is being detected - */ - else { - if ((mstime - btn->keepalive.last_time) >= LWBTN_TIME_KEEPALIVE_PERIOD(btn)) { - btn->keepalive.last_time += LWBTN_TIME_KEEPALIVE_PERIOD(btn); - ++btn->keepalive.cnt; - lw->evt_fn(lw, btn, LWBTN_EVT_KEEPALIVE); - } + btn->time_change = mstime; /* Button state has now changed */ +#if LWBTN_CFG_USE_KEEPALIVE + /* Set keep alive time */ + btn->keepalive.last_time = mstime; + btn->keepalive.last_time = mstime; + btn->keepalive.cnt = 0; +#endif /* LWBTN_CFG_USE_KEEPALIVE */ } } /* - * Button is still released + * Handle keep alive, but only if on-press event has been sent + * + * Keep alive is sent when valid press is being detected */ else { +#if LWBTN_CFG_USE_KEEPALIVE + if ((mstime - btn->keepalive.last_time) >= LWBTN_TIME_KEEPALIVE_PERIOD(btn)) { + btn->keepalive.last_time += LWBTN_TIME_KEEPALIVE_PERIOD(btn); + ++btn->keepalive.cnt; + lwobj->evt_fn(lwobj, btn, LWBTN_EVT_KEEPALIVE); + } +#endif /* LWBTN_CFG_USE_KEEPALIVE */ + } + } + + /* 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 & LWBTN_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 LWBTN_CFG_TIME_DEBOUNCE_RELEASE_DYNAMIC || LWBTN_CFG_TIME_DEBOUNCE_RELEASE > 0 + if ((mstime - btn->time_state_change) >= LWBTN_TIME_DEBOUNCE_RELEASE_GET_MIN(btn)) +#endif /* LWBTN_CFG_TIME_DEBOUNCE_RELEASE_DYNAMIC || LWBTN_CFG_TIME_DEBOUNCE_RELEASE > 0 */ + { + /* Handle on-release event */ + btn->flags &= ~LWBTN_FLAG_ONPRESS_SENT; + lwobj->evt_fn(lwobj, btn, LWBTN_EVT_ONRELEASE); + + /* Check time validity for click event */ + if ((mstime - btn->time_change) >= LWBTN_TIME_CLICK_GET_PRESSED_MIN(btn) + && (mstime - btn->time_change) <= LWBTN_TIME_CLICK_GET_PRESSED_MAX(btn)) { + + /* + * Increase consecutive clicks if max not reached yet + * and if time between two clicks is not too long + * + * Otherwise we consider click as fresh one + */ + if (btn->click.cnt > 0 && btn->click.cnt < LWBTN_CLICK_MAX_CONSECUTIVE(btn) + && (mstime - btn->click.last_time) < LWBTN_TIME_CLICK_MAX_MULTI(btn)) { + ++btn->click.cnt; + } else { + btn->click.cnt = 1; + } + btn->click.last_time = mstime; + } else { + /* + * There was an on-release event, but timing + * for click event detection is outside allowed window. + * + * If user has some consecutive clicks from previous clicks, + * these will be sent after the timeout window (and after the on-release event) + */ + } + +#if LWBTN_CFG_CLICK_MAX_CONSECUTIVE_SEND_IMMEDIATELY + /* + * Depending on the configuration, + * 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 == LWBTN_CLICK_MAX_CONSECUTIVE(btn)) { + lwobj->evt_fn(lwobj, btn, LWBTN_EVT_ONCLICK); + btn->click.cnt = 0; + } +#endif /* LWBTN_CFG_CLICK_MAX_CONSECUTIVE_SEND_IMMEDIATELY */ + 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. @@ -224,12 +249,144 @@ lwbtn_process_ex(lwbtn_t* lw, uint32_t mstime) { */ if (btn->click.cnt > 0) { if ((mstime - btn->click.last_time) >= LWBTN_TIME_CLICK_MAX_MULTI(btn)) { - lw->evt_fn(lw, btn, LWBTN_EVT_ONCLICK); + lwobj->evt_fn(lwobj, btn, LWBTN_EVT_ONCLICK); btn->click.cnt = 0; } } } - btn->old_state = new_state; + } + btn->old_state = new_state; +} + +/** + * \brief Initialize button manager + * \param[in] lwobj: LwBTN instance. Set to `NULL` to use default one + * \param[in] btns: Array of buttons to process + * \param[in] btns_cnt: Number of buttons to process + * \param[in] get_state_fn: Pointer to function providing button state on demand. + * May be set to `NULL` when \ref LWBTN_CFG_GET_STATE_MODE is set to manual. + * \param[in] evt_fn: Button event function callback + * \return `1` on success, `0` otherwise + */ +uint8_t +lwbtn_init_ex(lwbtn_t* lwobj, lwbtn_btn_t* btns, uint16_t btns_cnt, lwbtn_get_state_fn get_state_fn, + lwbtn_evt_fn evt_fn) { + lwobj = LWBTN_GET_LWOBJ(lwobj); + + if (btns == NULL || btns_cnt == 0 || evt_fn == NULL +#if LWBTN_CFG_GET_STATE_MODE == LWBTN_GET_STATE_MODE_CALLBACK + || get_state_fn == NULL /* Parameter is a must only in callback-only mode */ +#endif /* LWBTN_CFG_GET_STATE_MODE == LWBTN_GET_STATE_MODE_CALLBACK */ + ) { + return 0; + } + + memset(lwobj, 0x00, sizeof(*lwobj)); + lwobj->btns = btns; + lwobj->btns_cnt = btns_cnt; + lwobj->evt_fn = evt_fn; +#if LWBTN_CFG_GET_STATE_MODE != LWBTN_GET_STATE_MODE_MANUAL + lwobj->get_state_fn = get_state_fn; +#else + (void)get_state_fn; /* May be unused */ +#endif /* LWBTN_CFG_GET_STATE_MODE != LWBTN_GET_STATE_MODE_MANUAL */ + + for (size_t i = 0; i < btns_cnt; ++i) { +#if LWBTN_CFG_TIME_DEBOUNCE_PRESS_DYNAMIC + btns[i].time_debounce = LWBTN_CFG_TIME_DEBOUNCE_PRESS; +#endif /* LWBTN_CFG_TIME_DEBOUNCE_PRESS_DYNAMIC */ +#if LWBTN_CFG_TIME_DEBOUNCE_RELEASE_DYNAMIC + btns[i].time_debounce_release = LWBTN_CFG_TIME_DEBOUNCE_RELEASE; +#endif /* LWBTN_CFG_TIME_DEBOUNCE_RELEASE_DYNAMIC */ +#if LWBTN_CFG_TIME_CLICK_MIN_DYNAMIC + btns[i].time_click_pressed_min = LWBTN_CFG_TIME_CLICK_MIN; +#endif /* LWBTN_CFG_TIME_CLICK_MIN_DYNAMIC */ +#if LWBTN_CFG_TIME_CLICK_MAX_DYNAMIC + btns[i].time_click_pressed_max = LWBTN_CFG_TIME_CLICK_MAX; +#endif /* LWBTN_CFG_TIME_CLICK_MAX_DYNAMIC */ +#if LWBTN_CFG_TIME_CLICK_MULTI_MAX_DYNAMIC + btns[i].time_click_multi_max = LWBTN_CFG_TIME_CLICK_MULTI_MAX; +#endif /* LWBTN_CFG_TIME_CLICK_MULTI_MAX_DYNAMIC */ +#if LWBTN_CFG_TIME_KEEPALIVE_PERIOD_DYNAMIC + btns[i].time_keepalive_period = LWBTN_CFG_TIME_KEEPALIVE_PERIOD; +#endif /* LWBTN_CFG_TIME_KEEPALIVE_PERIOD_DYNAMIC */ +#if LWBTN_CFG_CLICK_MAX_CONSECUTIVE_DYNAMIC + btns[i].max_consecutive = LWBTN_CFG_CLICK_MAX_CONSECUTIVE; +#endif /* LWBTN_CFG_CLICK_MAX_CONSECUTIVE_DYNAMIC */ + } + + return 1; +} + +/** + * \brief Button processing function, that reads the inputs and makes actions accordingly. + * + * It checks state of all the buttons, linked to the specific LwBTN instance (group). + * + * \param[in] lwobj: LwBTN instance. Set to `NULL` to use default one + * \param[in] mstime: Current time in milliseconds + * \return `1` on success, `0` otherwise + */ +uint8_t +lwbtn_process_ex(lwbtn_t* lwobj, uint32_t mstime) { + lwobj = LWBTN_GET_LWOBJ(lwobj); + + /* Process all buttons */ + for (size_t index = 0; index < lwobj->btns_cnt; ++index) { + prv_process_btn(lwobj, &lwobj->btns[index], mstime); } return 1; } + +/** + * \brief Process single button instance from the specific LwOBJ instance (group). + * + * This feature can be used if application wants to process the button events only + * when interrupt hits (as a trigger). It gives user higher autonomy to decide which + * and when it will call specific button processing. + * + * \param[in] lwobj: LwBTN instance. Set to `NULL` to use default one + * \param[in] btn: Button object. Must not be set to `NULL`. + * \param[in] mstime: Current time in milliseconds + * \return `1` on success, `0` otherwise + */ +uint8_t +lwbtn_process_btn_ex(lwbtn_t* lwobj, lwbtn_btn_t* btn, uint32_t mstime) { + if (btn != NULL) { + prv_process_btn(LWBTN_GET_LWOBJ(lwobj), btn, mstime); + return 1; + } + return 0; +} + +/** + * \brief Set button state to either "active" or "inactive". + * \param[in] btn: Button instance + * \param[in] state: New button state. `1` is for active (pressed), `0` is for inactive (released). + * \return `1` on success, `0` otherwise + */ +uint8_t +lwbtn_set_btn_state(lwbtn_btn_t* btn, uint8_t state) { +#if LWBTN_CFG_GET_STATE_MODE != LWBTN_GET_STATE_MODE_CALLBACK + btn->curr_state = state; + btn->flags |= LWBTN_FLAG_MANUAL_STATE; + return 1; +#else /* LWBTN_CFG_GET_STATE_MODE != LWBTN_GET_STATE_MODE_CALLBACK */ + (void)btn; + (void)state; + return 0; +#endif /* LWBTN_CFG_GET_STATE_MODE != LWBTN_GET_STATE_MODE_CALLBACK */ +} + +/** + * \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 + */ +uint8_t +lwbtn_is_btn_active(const lwbtn_btn_t* btn) { + return btn != NULL && (btn->flags & LWBTN_FLAG_ONPRESS_SENT); +}