button: HW timer-based driver for GPIO buttons
The driver debounces the contact, detects short and long presses and can repeat a click while the button is held. Every button is an opaque handle with its own timer, so several buttons work independently.
Events
Event |
When |
|---|---|
|
The button is pressed |
|
The button is released |
|
A short press ends, or an autorepeat step |
|
The button is held longer than |
Without autorepeat a short press produces PRESSED, RELEASED,
CLICKED, and a long press produces PRESSED, PRESSED_LONG,
RELEASED. With autorepeat the long press is disabled and holding the button
produces PRESSED, a series of CLICKED, then RELEASED.
Modes
BUTTON_MODE_POLL starts a periodic timer and reads the pin every
poll_interval_us microseconds. It needs no interrupt service and works with
any pin, but it wakes the CPU on every period even when nobody touches the
button.
BUTTON_MODE_INTERRUPT waits for an edge on the pin; the interrupt then
disables itself and starts the same timer, which confirms the press and follows
it until the release. The interrupt service must be installed with
gpio_install_isr_service() before the first such button is created. A button
that is already held at boot is not detected until it is released and pressed
again.
A pin can serve one button at a time, in either mode: the GPIO driver keeps a
single interrupt handler per pin, so a second button on the same pin would take
the handler over and the first one would stop seeing presses. The second
button_create() fails with ESP_ERR_INVALID_STATE, and the pin becomes
free again after button_delete().
Debouncing
The samples taken during dead_time_us after a press are ignored, so the
contact bounce of the press does not produce extra events. The bounce of the
release is covered by the same window when it is shorter than the dead time;
otherwise increase dead_time_us.
Callback
The event callback runs in the ESP timer task while the button mutex is held. It must be short: a delay in it delays every other ESP timer in the system. It also must not call the driver back, because the mutex is not recursive: deleting the button from the callback deadlocks.
Reference
Defines
- BUTTON_DEFAULT_CONFIG()
Typedefs
- typedef struct button *button_handle_t
Opaque button handle.
- typedef void (*button_event_cb_t)(const button_event_t *event, void *ctx)
Callback prototype.
The callback is invoked from the ESP timer task context, while the internal mutex of the button is held.
It must not block, and it must not call button_delete() or button_create(): the mutex is not recursive, so such a call deadlocks. Any delay in the callback also delays every other ESP timer in the system, because they share one task.
- Param event:
Button event
- Param ctx:
User context
Enums
- enum button_mode_t
Button detection mode.
Values:
- enumerator BUTTON_MODE_POLL
Periodic timer polling (default)
- enumerator BUTTON_MODE_INTERRUPT
GPIO interrupt with debounce timer.
- enum button_state_t
Button states/events.
When autorepeat is disabled, the event sequence for a short press is: PRESSED -> RELEASED -> CLICKED and for a long press: PRESSED -> PRESSED_LONG -> RELEASED
When autorepeat is enabled, long press detection is disabled. Instead, holding the button generates repeated CLICKED events: PRESSED -> CLICKED -> CLICKED -> … -> RELEASED
Values:
- enumerator BUTTON_PRESSED
- enumerator BUTTON_RELEASED
- enumerator BUTTON_CLICKED
- enumerator BUTTON_PRESSED_LONG
Functions
- esp_err_t button_create(const button_config_t *config, button_handle_t *handle)
Create a new button.
The configuration is validated before the GPIO is touched. On failure
handleis set to NULL and the pin is left as it was found.One pin can serve one button at a time: the GPIO driver stores a single interrupt handler per pin, so a second button on the same pin would take the handler over and the first one would stop seeing presses. A second attempt on a pin that is already taken fails with
ESP_ERR_INVALID_STATE; the pin becomes free again after button_delete().When using BUTTON_MODE_INTERRUPT, the GPIO ISR service must be installed by the caller before creating any interrupt-mode buttons (via
gpio_install_isr_service()), otherwise the call fails withESP_ERR_INVALID_STATE.
- Parameters:
config – Button configuration
handle – [out] Button handle, set to NULL on failure
- Returns:
ESP_OKon success
ESP_ERR_INVALID_ARGif the configuration is invalid
ESP_ERR_NO_MEMif the handle or the timer cannot be allocated
ESP_ERR_INVALID_STATEif the GPIO ISR service is not installed or the pin already serves another button
- esp_err_t button_delete(button_handle_t handle)
Delete a button.
The interrupt handler is removed, the timer is stopped and the pin is released back to a floating input, so the button no longer consumes the GPIO and another button may use the pin.
On ESP-IDF 6.1 and later the function waits for a timer callback that has already been dispatched. On older versions
esp_timer_stop()cannot do that: the callback may still be running while the handle is freed, so the button must not be deleted concurrently with its own events. Either delete the button from a task that is not the callback, or make sure that no event can arrive at that moment (for example, by disabling the button beforehand).
- Parameters:
handle – Button handle
- Returns:
ESP_OKon success
ESP_ERR_INVALID_ARGifhandleis NULL
- struct button_event_t
#include <button.h>Button event.
- struct button_config_t
#include <button.h>Button configuration.
button_create() validates the fields: the GPIO must exist, the mode must be one of BUTTON_MODE_POLL or BUTTON_MODE_INTERRUPT, pressed_level must be 0 or 1, poll_interval_us must be non-zero and, when autorepeat is enabled, autorepeat_interval_us must be non-zero as well.
Public Members
- gpio_num_t gpio
GPIO pin number.
- button_mode_t mode
Detection mode (poll or interrupt)
- uint8_t pressed_level
Logic level when button is pressed (0 or 1)
- bool enable_internal_pull
Enable internal pull-up/pull-down resistor.
- bool autorepeat
Enable autorepeat (mutually exclusive with long press)
- uint32_t dead_time_us
Debounce time: samples taken right after a press are ignored (microseconds)
- uint32_t long_press_time_us
Long press threshold in microseconds (ignored when autorepeat is enabled)
- uint32_t autorepeat_timeout_us
Time before autorepeat starts, in microseconds.
- uint32_t autorepeat_interval_us
Autorepeat interval in microseconds.
- uint32_t poll_interval_us
Period of the internal timer and the unit of the timings, in microseconds.
- button_event_cb_t callback
Event callback (required)
- void *callback_ctx
User context passed to callback.