File syn_button.c¶
File List > input > syn_button.c
Go to the documentation of this file
#if __has_include("syn_config.h")
#include "syn_config.h"
#endif
#if !defined(SYN_USE_BUTTON) || SYN_USE_BUTTON
#if defined(SYN_USE_FSM) && !SYN_USE_FSM
#error "syn_button requires SYN_USE_FSM=1 (table-driven FSM)"
#endif
#include "../drivers/syn_gpio.h"
#include "../util/syn_assert.h"
#include "syn_button.h"
#include <string.h>
/* ── Internal FSM events ───────────────────────────────────────────────── */
enum {
BTN_EV_RAW_PRESS = 0,
BTN_EV_RAW_RELEASE,
BTN_EV_DEBOUNCE_OK,
BTN_EV_LONG_PRESS,
};
/* ── Helpers ────────────────────────────────────────────────────────────── */
static bool button_read_raw(const SYN_Button *btn)
{
if (btn == NULL)
return false;
SYN_GPIO_State level = syn_gpio_read(btn->pin);
if (btn->polarity == (uint8_t)SYN_BUTTON_ACTIVE_LOW) {
return level == SYN_GPIO_LOW;
}
return level == SYN_GPIO_HIGH;
}
static void button_fire_event(SYN_Button *btn, uint8_t evt, SYN_ButtonCallback cb, void *ctx)
{
btn->events |= evt;
if (cb != NULL) {
cb(btn, ctx);
}
}
/* ── FSM transition actions ─────────────────────────────────────────────── */
static void action_start_debounce(void *ctx)
{
SYN_Button *btn = (SYN_Button *)ctx;
btn->state_tick = syn_port_get_tick_ms();
btn->raw_pressed = true;
}
static void action_bounce_back(void *ctx)
{
SYN_Button *btn = (SYN_Button *)ctx;
btn->state_tick = syn_port_get_tick_ms();
btn->raw_pressed = false;
}
static void action_confirm_press(void *ctx)
{
SYN_Button *btn = (SYN_Button *)ctx;
btn->state_tick = syn_port_get_tick_ms();
btn->pressed = true;
btn->repeat_tick = syn_port_get_tick_ms();
btn->click_count++;
button_fire_event(btn, SYN_BUTTON_EVT_PRESS, btn->on_press, btn->on_press_ctx);
}
static void action_release(void *ctx)
{
SYN_Button *btn = (SYN_Button *)ctx;
btn->state_tick = syn_port_get_tick_ms();
btn->click_tick = syn_port_get_tick_ms();
btn->pressed = false;
btn->raw_pressed = false;
btn->in_combo = false;
button_fire_event(btn, SYN_BUTTON_EVT_RELEASE, btn->on_release, btn->on_release_ctx);
}
static void action_long_press(void *ctx)
{
SYN_Button *btn = (SYN_Button *)ctx;
btn->state_tick = syn_port_get_tick_ms();
btn->repeat_tick = syn_port_get_tick_ms();
btn->click_count = 0;
button_fire_event(btn, SYN_BUTTON_EVT_LONG_PRESS, btn->on_long_press, btn->on_long_press_ctx);
}
/* ── FSM transition table ───────────────────────────────────────────────── */
static const SYN_FSM_Transition g_button_transitions[] = {
/* IDLE: wait for press */
{SYN_BUTTON_STATE_IDLE, BTN_EV_RAW_PRESS, SYN_BUTTON_STATE_DEBOUNCING, NULL,
action_start_debounce},
/* DEBOUNCING: bounce back or confirm */
{SYN_BUTTON_STATE_DEBOUNCING, BTN_EV_RAW_RELEASE, SYN_BUTTON_STATE_IDLE, NULL,
action_bounce_back},
{SYN_BUTTON_STATE_DEBOUNCING, BTN_EV_DEBOUNCE_OK, SYN_BUTTON_STATE_PRESSED, NULL,
action_confirm_press},
/* PRESSED: release or escalate to held */
{SYN_BUTTON_STATE_PRESSED, BTN_EV_RAW_RELEASE, SYN_BUTTON_STATE_IDLE, NULL, action_release},
{SYN_BUTTON_STATE_PRESSED, BTN_EV_LONG_PRESS, SYN_BUTTON_STATE_HELD, NULL, action_long_press},
/* HELD: release */
{SYN_BUTTON_STATE_HELD, BTN_EV_RAW_RELEASE, SYN_BUTTON_STATE_IDLE, NULL, action_release},
SYN_FSM_END};
/* ── Initialization ─────────────────────────────────────────────────────── */
void syn_button_init(SYN_Button *btn, SYN_GPIO_Pin pin, SYN_ButtonPolarity polarity,
uint16_t debounce_ms)
{
SYN_ASSERT(btn != NULL);
memset(btn, 0, sizeof(*btn));
btn->pin = pin;
btn->polarity = (uint8_t)polarity;
btn->debounce_ms = debounce_ms;
btn->double_click_ms = 250;
btn->pressed = false;
btn->raw_pressed = false;
btn->click_count = 0;
btn->state_tick = syn_port_get_tick_ms();
syn_fsm_init(&btn->fsm, g_button_transitions, (SYN_FSM_State)SYN_BUTTON_STATE_IDLE, "btn");
syn_fsm_set_context(&btn->fsm, btn);
}
/* ── Configuration ──────────────────────────────────────────────────────── */
void syn_button_set_click_window(SYN_Button *btn, uint16_t window_ms)
{
SYN_ASSERT(btn != NULL);
btn->double_click_ms = window_ms;
}
/* ── Callback registration ──────────────────────────────────────────────── */
void syn_button_on_press(SYN_Button *btn, SYN_ButtonCallback cb, void *ctx)
{
btn->on_press = cb;
btn->on_press_ctx = ctx;
}
void syn_button_on_release(SYN_Button *btn, SYN_ButtonCallback cb, void *ctx)
{
btn->on_release = cb;
btn->on_release_ctx = ctx;
}
void syn_button_on_long_press(SYN_Button *btn, SYN_ButtonCallback cb, uint16_t hold_ms, void *ctx)
{
btn->on_long_press = cb;
btn->on_long_press_ctx = ctx;
btn->long_press_ms = hold_ms;
}
void syn_button_on_repeat(SYN_Button *btn, SYN_ButtonCallback cb, uint16_t interval_ms, void *ctx)
{
btn->on_repeat = cb;
btn->on_repeat_ctx = ctx;
btn->repeat_ms = interval_ms;
}
void syn_button_on_single_click(SYN_Button *btn, SYN_ButtonCallback cb, void *ctx)
{
btn->on_single_click = cb;
btn->on_single_click_ctx = ctx;
}
void syn_button_on_double_click(SYN_Button *btn, SYN_ButtonCallback cb, void *ctx)
{
btn->on_double_click = cb;
btn->on_double_click_ctx = ctx;
}
void syn_button_on_triple_click(SYN_Button *btn, SYN_ButtonCallback cb, void *ctx)
{
btn->on_triple_click = cb;
btn->on_triple_click_ctx = ctx;
}
void syn_button_on_multi_click(SYN_Button *btn, SYN_ButtonCallback cb, void *ctx)
{
btn->on_multi_click = cb;
btn->on_multi_click_ctx = ctx;
}
/* ── Combo button functions ─────────────────────────────────────────────── */
void syn_button_combo_init(SYN_ButtonCombo *combo, const SYN_Button **buttons, size_t count,
SYN_ButtonCallback cb, void *ctx)
{
SYN_ASSERT(combo != NULL);
SYN_ASSERT(buttons != NULL || count == 0);
memset(combo, 0, sizeof(*combo));
combo->buttons = buttons;
combo->count = count;
combo->on_combo = cb;
combo->on_combo_ctx = ctx;
combo->active = false;
}
void syn_button_combo_update(SYN_ButtonCombo *combo)
{
SYN_ASSERT(combo != NULL);
if (combo->count == 0 || combo->buttons == NULL) {
return;
}
bool all_pressed = true;
for (size_t i = 0; i < combo->count; i++) {
if (combo->buttons[i] == NULL || !combo->buttons[i]->pressed) {
all_pressed = false;
break;
}
}
if (all_pressed && !combo->active) {
combo->active = true;
for (size_t i = 0; i < combo->count; i++) {
if (combo->buttons[i] != NULL) {
((SYN_Button *)combo->buttons[i])->click_count = 0;
((SYN_Button *)combo->buttons[i])->in_combo = true;
}
}
if (combo->on_combo != NULL) {
combo->on_combo(NULL, combo->on_combo_ctx);
}
} else if (!all_pressed && combo->active) {
combo->active = false;
}
}
/* ── State machine update ───────────────────────────────────────────────── */
void syn_button_update(SYN_Button *btn)
{
SYN_ASSERT(btn != NULL);
bool raw = button_read_raw(btn);
uint32_t now = syn_port_get_tick_ms();
uint32_t elapsed = now - btn->state_tick;
SYN_ButtonState current = (SYN_ButtonState)syn_fsm_state(&btn->fsm);
/* Generate edge events from GPIO reading */
if (current == SYN_BUTTON_STATE_IDLE && raw) {
syn_fsm_dispatch(&btn->fsm, BTN_EV_RAW_PRESS);
} else if (current == SYN_BUTTON_STATE_DEBOUNCING) {
if (!raw) {
syn_fsm_dispatch(&btn->fsm, BTN_EV_RAW_RELEASE);
} else if (elapsed >= btn->debounce_ms) {
syn_fsm_dispatch(&btn->fsm, BTN_EV_DEBOUNCE_OK);
}
} else if (current == SYN_BUTTON_STATE_PRESSED) {
if (!raw) {
syn_fsm_dispatch(&btn->fsm, BTN_EV_RAW_RELEASE);
} else if (!btn->in_combo) {
/* Check long press threshold */
if (btn->long_press_ms > 0 && elapsed >= btn->long_press_ms) {
syn_fsm_dispatch(&btn->fsm, BTN_EV_LONG_PRESS);
}
/* Check repeat (stays in same state, no FSM transition) */
if (btn->repeat_ms > 0 && (now - btn->repeat_tick) >= btn->repeat_ms) {
btn->repeat_tick = now;
btn->click_count = 0;
button_fire_event(btn, SYN_BUTTON_EVT_REPEAT, btn->on_repeat, btn->on_repeat_ctx);
}
}
} else if (current == SYN_BUTTON_STATE_HELD) {
if (!raw) {
syn_fsm_dispatch(&btn->fsm, BTN_EV_RAW_RELEASE);
} else if (!btn->in_combo) {
/* Check repeat in held state */
if (btn->repeat_ms > 0 && (now - btn->repeat_tick) >= btn->repeat_ms) {
btn->repeat_tick = now;
btn->click_count = 0;
button_fire_event(btn, SYN_BUTTON_EVT_REPEAT, btn->on_repeat, btn->on_repeat_ctx);
}
}
}
/* Evaluate multi-click timeout window when button is back in IDLE */
current = (SYN_ButtonState)syn_fsm_state(&btn->fsm);
if (current == SYN_BUTTON_STATE_IDLE && btn->click_count > 0) {
uint32_t idle_elapsed = now - btn->click_tick;
if (btn->double_click_ms == 0 || idle_elapsed >= btn->double_click_ms) {
uint8_t count = btn->click_count;
btn->click_count = 0;
if (count == 1) {
button_fire_event(btn, SYN_BUTTON_EVT_SINGLE_CLICK, btn->on_single_click,
btn->on_single_click_ctx);
} else if (count == 2) {
button_fire_event(btn, SYN_BUTTON_EVT_DOUBLE_CLICK, btn->on_double_click,
btn->on_double_click_ctx);
} else if (count == 3) {
button_fire_event(btn, SYN_BUTTON_EVT_TRIPLE_CLICK, btn->on_triple_click,
btn->on_triple_click_ctx);
} else if (count >= 4) {
button_fire_event(btn, SYN_BUTTON_EVT_MULTI_CLICK, btn->on_multi_click,
btn->on_multi_click_ctx);
}
}
}
}
void syn_button_service(SYN_Button *buttons, size_t count)
{
SYN_ASSERT(buttons != NULL || count == 0);
for (size_t i = 0; i < count; i++) {
syn_button_update(&buttons[i]);
}
}
#endif /* SYN_USE_BUTTON */