From 342e978316dd8bac954934c591446aa45c30733e Mon Sep 17 00:00:00 2001 From: Tilen Majerle Date: Tue, 25 Oct 2022 19:52:34 +0200 Subject: [PATCH] Add code skeleton --- dev/main.c | 70 +++++++++++-- lwbtn/src/include/lwbtn/lwbtn.h | 65 +++++++++++- lwbtn/src/lwbtn/lwbtn.c | 174 +++++++++++++++++++++++++++++++- 3 files changed, 293 insertions(+), 16 deletions(-) diff --git a/dev/main.c b/dev/main.c index e78134c..053f961 100644 --- a/dev/main.c +++ b/dev/main.c @@ -3,22 +3,72 @@ #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'}; -int state[sizeof(keys) / sizeof(keys[0])]; -int state_prev[sizeof(keys) / sizeof(keys[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]}}; + +uint8_t +prv_btn_get_state(struct lwbtn* lw, struct lwbtn_btn* btn) { + return GetAsyncKeyState(*(int*)btn->arg) < 0; +} + +void +prv_btn_event(struct lwbtn* lw, struct lwbtn_btn* btn, lwbtn_evt_t evt) { + (void)lw; + printf("[%7u] State !!!. CH: %c, evt: ", (unsigned)get_tick(), *(int*)btn->arg); + switch (evt) { + case LWBTN_EVT_KEEPALIVE: { + printf("KEEPALIVE"); + break; + } + case LWBTN_EVT_ONPRESS: { + printf("ONPRESS"); + break; + } + case LWBTN_EVT_ONRELEASE: { + printf("ONRELEASE"); + break; + } + case LWBTN_EVT_ONCLICK: { + printf("ONCLICK"); + break; + } + default: { + break; + } + } + printf("\r\n"); +} int main(void) { printf("Application running\r\n"); + QueryPerformanceFrequency(&freq); + QueryPerformanceCounter(&sys_start_time); + + lwbtn_init_ex(NULL, btns, sizeof(btns) / sizeof(btns[0]), prv_btn_get_state, prv_btn_event); while (1) { - /* Simple simulator */ - for (size_t i = 0; i < sizeof(keys) / sizeof(keys[0]); ++i) { - state[i] = GetAsyncKeyState(keys[i]) < 0; - if (state[i] != state_prev[i]) { - printf("State changed for key %c to: %d\r\n", keys[i], state[i]); - } - state_prev[i] = state[i]; - } + lwbtn_process_ex(NULL, get_tick()); + Sleep(10); } } + +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/lwbtn/src/include/lwbtn/lwbtn.h b/lwbtn/src/include/lwbtn/lwbtn.h index 02b0516..cf26461 100644 --- a/lwbtn/src/include/lwbtn/lwbtn.h +++ b/lwbtn/src/include/lwbtn/lwbtn.h @@ -48,8 +48,69 @@ extern "C" { * \{ */ -void lwbtn_init(void); -void lwbtn_process(uint32_t mstime); +/** + * \brief User argument data structure + * + * This is structure user can use to define + * GPIO port, pin and potential active state (high or low) + */ +typedef struct { + void* port; /*!< User defined GPIO port information */ + void* pin; /*!< User defined GPIO pin information */ + uint8_t state; /*!< User defined GPIO state level when considered active */ +} lwbtn_argdata_port_pin_state_t; + +struct lwbtn_btn; +struct lwbtn; + +/** + * \brief List of button events + * + */ +typedef enum { + LWBTN_EVT_ONPRESS = 0x00, + LWBTN_EVT_ONRELEASE, + LWBTN_EVT_ONCLICK, + LWBTN_EVT_KEEPALIVE, +} lwbtn_evt_t; + +typedef void (*lwbtn_evt_fn)(struct lwbtn* lw, struct lwbtn_btn* btn, lwbtn_evt_t evt); +typedef uint8_t (*lwbtn_get_state_fn)(struct lwbtn* lw, struct lwbtn_btn* btn); + +/** + * \brief Button/input structure + */ +typedef struct lwbtn_btn { + uint16_t flags; + uint8_t old_state; + uint32_t time_change; + + struct { + uint32_t last_time; + uint16_t cnt; + } keepalive; + + struct { + uint32_t last_time; + uint8_t consecutive_cnt; + } click; + + void* arg; +} lwbtn_btn_t; + +/** + * \brief LwBTN group structure + */ +typedef struct lwbtn { + lwbtn_btn_t* btns; + uint16_t btns_cnt; + lwbtn_evt_fn evt_fn; + lwbtn_get_state_fn get_state_fn; +} 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, + lwbtn_evt_fn evt_fn); +uint8_t lwbtn_process_ex(lwbtn_t* lw, uint32_t mstime); /** * \} diff --git a/lwbtn/src/lwbtn/lwbtn.c b/lwbtn/src/lwbtn/lwbtn.c index 2ce716d..f1764c4 100644 --- a/lwbtn/src/lwbtn/lwbtn.c +++ b/lwbtn/src/lwbtn/lwbtn.c @@ -34,16 +34,182 @@ #include #include "lwbtn/lwbtn.h" +#define LWBTN_FLAG_ONPRESS_SENT ((uint16_t)0x0001) + +#define LWBTN_TIME_DEBOUNCE_GET_MIN(btn) 20 /*!< Minimum debounce time to stabilize input */ +#define LWBTN_TIME_CLICK_GET_PRESSED_MIN(btn) 20 /*!< Minimum pressed time to consider it as a potential click event */ +#define LWBTN_TIME_CLICK_GET_PRESSED_MAX(btn) 300 /*!< Maximal pressed time to consider "click" event */ +#define LWBTN_TIME_CLICK_MAX_MULTI(btn) 500 /*!< Maximum allowed time for user to handle consecutive click events */ +#define LWBTN_TIME_KEEPALIVE_PERIOD(btn) 100 /*!< Keep alive time period in milliseconds */ +#define LWBTN_TIME_CLICK_SEND_TIMEOUT(btn) 300 /*!< Minimum time in ms to wait to send click event in timeout */ +#define LWBTN_CLICK_MAX_CONSECUTIVE(btn) 3 /*!< Max consecutive clicks allowed */ + +/* Default button group instance */ +static lwbtn_t lwbtn_default; +#define LWBTN_GET_LW(in_lw) ((in_lw) != NULL ? (in_lw) : (&lwbtn_default)) + /** * \brief Initialize button manager + * \return `1` on success, `0` otherwise */ -void -lwbtn_init(void) {} +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); + + 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 */ -void -lwbtn_process(uint32_t mstime) {} +uint8_t +lwbtn_process_ex(lwbtn_t* lw, uint32_t mstime) { + lwbtn_btn_t* b; + uint8_t new_state; + + lw = LWBTN_GET_LW(lw); + + /* Process all buttons */ + for (size_t i = 0; i < lw->btns_cnt; ++i) { + b = &lw->btns[i]; + + new_state = lw->get_state_fn(lw, b); /* Get button state */ + + /* + * Button state has changed + */ + if (new_state != b->old_state) { + /* + * Button just became inactive + * + * - Handle on-release event + * - Handle on-click event + */ + 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 (b->flags & LWBTN_FLAG_ONPRESS_SENT) { + /* Handle on-release event */ + b->flags &= ~LWBTN_FLAG_ONPRESS_SENT; + lw->evt_fn(lw, b, LWBTN_EVT_ONRELEASE); + + /* Check time validity for click event */ + if ((mstime - b->time_change) >= LWBTN_TIME_CLICK_GET_PRESSED_MIN(b) + && (mstime - b->time_change) <= LWBTN_TIME_CLICK_GET_PRESSED_MAX(b)) { + + /* + * Increase consecutive clicks if max not reached yet + * and if time between two clicks is not too long + */ + if (b->click.consecutive_cnt > 0 && b->click.consecutive_cnt < LWBTN_CLICK_MAX_CONSECUTIVE(b) + && (mstime - b->click.last_time) < LWBTN_TIME_CLICK_MAX_MULTI(b)) { + ++b->click.consecutive_cnt; + } else { + /* Start over - set as first click */ + b->click.consecutive_cnt = 1; + } + b->click.last_time = mstime; + } else { + /* TODO: Shall we check previous clicks and send to user (??) */ + + /* + * Button released with invalid click event + * + * TODO: Consider sending all data immediately from previous clicks? + * + * Clear then counter + */ + b->click.consecutive_cnt = 0; + } + } + } + + /* + * Button just pressed + */ + else { + /* Do nothing - things are handled after debounce period */ + } + b->time_change = mstime; + b->keepalive.last_time = mstime; + b->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 (!(b->flags & LWBTN_FLAG_ONPRESS_SENT)) { + /* Check minimum stable time */ + if ((mstime - b->time_change) >= LWBTN_TIME_DEBOUNCE_GET_MIN(b)) { + /* + * Immediately send click event if number of + * previous consecutive clicks reached maximum level. + * + * Handle this on on-press state + */ + if (b->click.consecutive_cnt > 0 && b->click.consecutive_cnt == LWBTN_CLICK_MAX_CONSECUTIVE(b)) { + lw->evt_fn(lw, b, LWBTN_EVT_ONCLICK); + b->click.consecutive_cnt = 0; + } + + /* Now start with new on-press */ + b->flags |= LWBTN_FLAG_ONPRESS_SENT; + lw->evt_fn(lw, b, LWBTN_EVT_ONPRESS); + + /* Set keep alive time */ + b->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 - b->keepalive.last_time) >= LWBTN_TIME_KEEPALIVE_PERIOD(b)) { + b->keepalive.last_time += LWBTN_TIME_KEEPALIVE_PERIOD(b); + ++b->keepalive.cnt; + lw->evt_fn(lw, b, LWBTN_EVT_KEEPALIVE); + } + } + } + + /* + * Button is still released + */ + else { + /* + * This is the part to process "timeout" after release event + * + * - Send click event with delay - to send it once for multi-click setup + * This is sent after last valid click has been detected for OnRelease event + * - Reset consecutive clicks counter if time after last click is longer than maximum one + */ + if (b->click.consecutive_cnt > 0) { + if ((mstime - b->click.last_time) >= LWBTN_TIME_CLICK_SEND_TIMEOUT(b)) { + lw->evt_fn(lw, b, LWBTN_EVT_ONCLICK); + b->click.consecutive_cnt = 0; + } + } + } + b->old_state = new_state; + } + return 1; +}