MultiButton Library

repository·master·Indexed 25 days ago

https://github.com/0x1abin/multibutton

A compact, memory-efficient state machine library for embedded systems to handle complex button input patterns. It supports event types including single/double clicks, long presses, and repeated presses. Features include callback registration with user data context, polling utilities, configurable debounce and timing thresholds, and optional thread safety for RTOS environments via MULTIBUTTON_THREAD_SAFE.

Tokens
3.2K
Snippets
10
Records
16
Agent score
31%

What's inside MultiButton

  1. Thread Safety and RTOS usage

    master

    To use MultiButton in a multi-threaded RTOS environment, define MULTIBUTTON_THREAD_SAFE and provide implementation for MULTIBUTTON_LOCK() and MULTIBUTTON_UNLOCK() using your RTOS mutexes.

    Important: Callbacks are executed outside of the lock. This means it is safe to call button_stop() or button_start() from within a callback without causing a deadlock. You can use standard mutexes (no need for recursive locks).

    #define MULTIBUTTON_THREAD_SAFE
    #define MULTIBUTTON_LOCK()   osMutexAcquire(btn_mutex, osWaitForever)
    #define MULTIBUTTON_UNLOCK() osMutexRelease(btn_mutex)
    #include "multi_button.h"
  2. Using user_data context in callbacks

    master

    Every callback function receives a void* user_data pointer. This pointer is set during button_attach() and is shared by all callbacks registered to the same button. This allows you to pass application-specific context (like LED pins or counters) into your handlers.

    typedef struct {
        int led_pin;
        int count;
    } MyContext;
    
    MyContext ctx = { .led_pin = 13, .count = 0 };
    
    void on_click(Button* btn, void* user_data)
    {
        MyContext* ctx = (MyContext*)user_data;
        toggle_led(ctx->led_pin);
        ctx->count++;
    }
    
    // Pass &ctx as the user_data argument
    button_attach(&btn1, BTN_SINGLE_CLICK, on_click, &ctx);
  3. Enable Thread Safety for RTOS

    master

    To use MultiButton in an RTOS environment, define the following macros before including multi_button.h. This enables optional lock hooks with zero overhead on bare-metal systems.

    #define MULTIBUTTON_THREAD_SAFE
    #define MULTIBUTTON_LOCK()   osMutexAcquire(btn_mutex, osWaitForever)
    #define MULTIBUTTON_UNLOCK() osMutexRelease(btn_mutex)
    #include "multi_button.h"

    Note: Callbacks are executed outside the lock. This means it is safe to call button_stop() or button_start() from within a callback without risking a deadlock. A regular (non-recursive) mutex is sufficient.

  4. Implement N-Click Patterns (e.g., Triple Click)

    master

    While the library natively supports BTN_SINGLE_CLICK and BTN_DOUBLE_CLICK, you can implement higher N-click patterns by using the BTN_PRESS_REPEAT event and checking button_get_repeat_count().

    • BTN_SINGLE_CLICK fires when repeat == 1 after the timeout.
    • BTN_DOUBLE_CLICK fires when repeat == 2 after the timeout.
    • For repeat >= 3, only BTN_PRESS_REPEAT fires during the sequence. You must check the count in a callback to resolve the pattern.
    void on_click_resolve(Button* btn, void* user_data)
    {
        uint8_t count = button_get_repeat_count(btn);
        if (count == 3) {
            // Triple click!
        }
    }
    
    // Register for single click (fires after timeout with final repeat count)
    button_attach(&btn, BTN_SINGLE_CLICK, on_click_resolve, NULL);
  5. Quick Start with MultiButton

    master

    To use MultiButton in your project, follow these steps:

    1. Include the header: #include "multi_button.h".
    2. Define a button instance: static Button btn1;.
    3. Implement a GPIO read function: Create a function that takes a button_id and returns the current pin level.
    4. Initialize the button: Use button_init() specifying the GPIO function, active level (0 for low, 1 for high), and ID.
    5. Register callbacks: Use button_attach() to link specific ButtonEvent types to handler functions.
    6. Start processing: Call button_start().
    7. Run the background task: Call button_ticks() periodically (recommended every 5ms) in a timer interrupt or main loop.
    #include "multi_button.h"
    
    static Button btn1;
    
    // 1. Implement GPIO reading
    uint8_t read_button_gpio(uint8_t button_id)
    {
        switch (button_id) {
            case 1:
                return HAL_GPIO_ReadPin(BUTTON1_GPIO_Port, BUTTON1_Pin);
            default:
                return 0;
        }
    }
    
    // 2. Define callback
    void btn1_single_click_handler(Button* btn, void* user_data)
    {
        printf("Button 1: Single Click\n");
    }
    
    void setup() {
        // 3. Initialize
        button_init(&btn1, read_button_gpio, 0, 1);
        
        // 4. Attach event
        button_attach(&btn1, BTN_SINGLE_CLICK, btn1_single_click_handler, NULL);
        
        // 5. Start
        button_start(&btn1);
    }
    
    // 6. Periodic call (e.g., in a 5ms timer ISR)
    void timer_5ms_interrupt_handler(void)
    {
        button_ticks();
    }
  6. Handle BTN_LONG_PRESS_HOLD Throttling

    master

    The BTN_LONG_PRESS_HOLD event fires on every tick (e.g., every 5ms) while the button is held after the long press threshold is reached. If your callback performs expensive operations, you should implement manual throttling.

    void on_long_hold(Button* btn, void* user_data)
    {
        static uint16_t throttle = 0;
        if (++throttle < 20) return;  // fire every 100ms instead (if tick is 5ms)
        throttle = 0;
        // ... do work ...
    }
  7. How to implement N-Click (e.g., Triple Click)

    master

    The library natively supports single and double clicks. For three or more clicks, use the BTN_PRESS_REPEAT event in combination with button_get_repeat_count().

    Note: BTN_SINGLE_CLICK triggers when repeat == 1, and BTN_DOUBLE_CLICK triggers when repeat == 2. For repeat >= 3, only BTN_PRESS_REPEAT will trigger during the press sequence.

    void on_repeat(Button* btn, void* user_data)
    {
        uint8_t count = button_get_repeat_count(btn);
        if (count == 3) {
            // Triple click detected!
        }
    }
    
    button_attach(&btn, BTN_PRESS_REPEAT, on_repeat, NULL);
  8. Configure Timing and Debounce

    master

    Timing thresholds and debounce depth are configured via defines in multi_button.h:

    • TICKS_INTERVAL: The timer tick interval in milliseconds (default 5).
    • DEBOUNCE_TICKS: The debounce filter depth (max 7).
    • SHORT_TICKS: The threshold for short press/click (calculated as SHORT_TICKS_MS / TICKS_INTERVAL).
    • LONG_TICKS: The threshold for long press (calculated as LONG_TICKS_MS / TICKS_INTERVAL).
    • PRESS_REPEAT_MAX_NUM: The maximum number of repeat presses to track.
    #define TICKS_INTERVAL       5     // timer tick interval (ms)
    #define DEBOUNCE_TICKS       3     // debounce filter depth (max 7)
    #define SHORT_TICKS          (300  / TICKS_INTERVAL)  // short press threshold
    #define LONG_TICKS           (1000 / TICKS_INTERVAL)  // long press threshold
    #define PRESS_REPEAT_MAX_NUM 15    // max repeat counter
  9. Configure MultiButton parameters

    master

    Timing and debounce parameters can be customized in multi_button.h using the following macros:

    • TICKS_INTERVAL: Timer interrupt interval in ms (default 5).
    • DEBOUNCE_TICKS: Debounce depth (max 7).
    • SHORT_TICKS: Threshold for short press.
    • LONG_TICKS: Threshold for long press.
    • PRESS_REPEAT_MAX_NUM: Maximum repeat count (default 15).
  10. Passing User Data to Callbacks

    master

    You can pass a custom context pointer to callbacks using button_attach(). This pointer is stored per-button and is passed to every callback associated with that button.

    typedef struct {
        int led_pin;
        int beep_count;
    } ButtonContext;
    
    ButtonContext ctx = { .led_pin = 13, .beep_count = 0 };
    
    void on_click(Button* btn, void* user_data)
    {
        ButtonContext* ctx = (ButtonContext*)user_data;
        toggle_led(ctx->led_pin);
        ctx->beep_count++;
    }
    
    button_attach(&btn1, BTN_SINGLE_CLICK, on_click, &ctx);
  11. Utility Functions for Polling and State

    master

    If you prefer polling over callbacks, use these utility functions:

    • ButtonEvent button_get_event(Button* handle): Returns the current event for the button.
    • uint8_t button_get_repeat_count(Button* handle): Returns the number of repeated presses detected.
    • int button_is_pressed(Button* handle): Returns 1 if pressed, 0 if released, or -1 on error.
    • void button_reset(Button* handle): Resets the button to the IDLE state.
  12. Core API Functions

    master

    The following functions are used to manage button lifecycle and event registration:

    • void button_init(Button* handle, uint8_t(*pin_level)(uint8_t), uint8_t active_level, uint8_t button_id): Initializes a button instance.
    • void button_attach(Button* handle, ButtonEvent event, BtnCallback cb, void* user_data): Attaches a callback to a specific event.
    • void button_detach(Button* handle, ButtonEvent event): Removes a callback for a specific event.
    • int button_start(Button* handle): Starts the button state machine. Returns 0 for success, -1 if the ID is a duplicate, or -2 if the handle is invalid.
    • void button_stop(Button* handle): Stops the button state machine.
    • void button_ticks(void): Drives the state machine logic. Must be called periodically (e.g., every 5ms).