CAF: Buttons module (ISR)
The HID Add-on provides an alternative implementation of the CAF buttons module.
It emits a button_event on button press and release with minimal latency.
The module uses GPIO interrupts and applies debouncing after press and release detection.
Use this module for directly connected buttons when low button press latency is required.
It can reduce the time from a physical button press to a button_event submission.
For keyboard matrices or when minimal latency is not required, use the standard CAF buttons module from the nRF Connect SDK (CONFIG_CAF_BUTTONS).
Configuration
To use the module, you must enable the following Kconfig options:
CONFIG_CAF_BUTTONS_ISR- This option enables the low-latency buttons module. You cannot enable it together withCONFIG_CAF_BUTTONS.CONFIG_GPIO- This option enables Zephyr’s General-Purpose Input/Output (GPIO) driver, which is required for interacting with the GPIO pins.
Configuration file
When you define how the buttons are connected, you must create a configuration file with the following arrays:
rowcontains GPIO pins to which buttons are directly connected (one button for each pin). The array can contain up to 32 buttons.colmust be defined as an empty array. Keyboard matrix is not supported.Each GPIO port used by a button must have an associated GPIOTE instance.
For example, the file contents should look as follows:
#include <caf/gpio_pins.h>
static const struct gpio_pin col[] = {};
static const struct gpio_pin row[] = {
{ .port = 1, .pin = 26 },
{ .port = 1, .pin = 9 },
};
You must define both arrays in this configuration file, and specify its location using the CONFIG_CAF_BUTTONS_DEF_PATH Kconfig option.
Note
The configuration file should be included only by the configured module. Do not include the configuration file in other source files.
Configuration options
The following Kconfig options are available for this module:
CONFIG_CAF_BUTTONS_DEF_PATH- Location of the module configuration file. The default value isbuttons_def.h.CONFIG_CAF_BUTTONS_PM_EVENTS- Power management events support. See Power management configuration for details.CONFIG_CAF_BUTTONS_PM_KEEP_ALIVE- Generatekeep_alive_eventon button press (requiresCONFIG_CAF_POWER_MANAGER). The feature is implemented by the CAF power manager keep alive module for buttons in the nRF Connect SDK, which reacts tobutton_eventsubmitted by this module.CONFIG_CAF_BUTTONS_POLARITY_INVERSED- Inverse button polarity. By default, a button press is indicated by a pin switch from the low to the high state. When this option is enabled, the application treats a switch from the high to the low state as a button press.CONFIG_CAF_BUTTONS_DEBOUNCE_INTERVAL- Debounce interval in milliseconds. The default value is 2 ms. The module waits for this interval before enabling the listener for the opposite pin state after press or release detection.CONFIG_CAF_BUTTONS_WQ- Use a dedicated workqueue for button debouncing works. When enabled, debouncing works run on a dedicated workqueue instead of the system workqueue, which can reduce interference with other work. When disabled, the system workqueue is used and memory footprint is lower.CONFIG_CAF_BUTTONS_WQ_STACK_SIZE- Stack size of the dedicated workqueue (ifCONFIG_CAF_BUTTONS_WQis enabled). The default value is 1024 bytes.CONFIG_CAF_BUTTONS_WQ_PRIO- Priority of the dedicated workqueue (ifCONFIG_CAF_BUTTONS_WQis enabled). The default value is-1. Only cooperative priorities are supported.
Power management configuration
If the CONFIG_CAF_BUTTONS_PM_EVENTS Kconfig option is enabled, the module interacts with power management events (power_down_event, power_off_event, and wake_up_event).
You can use the module to trigger application wakeup.
By default, pressing any button wakes up the application.
In the configuration file, you can specify which buttons should not trigger an application wakeup.
Mark such buttons in the row array by setting gpio_pin.wakeup_blocked to true.
See an example of the modified configuration file:
#include <caf/gpio_pins.h>
static const struct gpio_pin col[] = {};
static const struct gpio_pin row[] = {
{ .port = 0, .pin = 9, .wakeup_blocked = true },
{ .port = 0, .pin = 8, .wakeup_blocked = true },
{ .port = 1, .pin = 15 },
{ .port = 1, .pin = 14 },
};
After you apply the change, the module still handles a marked button but no longer triggers the application wakeup.
Implementation details
Depending on the configuration, the module-level state machine uses from two to five states.
The following figure shows all states and transitions.
Paths that depend on CONFIG_CAF_BUTTONS_PM_EVENTS are marked accordingly.
If CONFIG_CAF_BUTTONS_PM_EVENTS is disabled, the module operates in STATE_ACTIVE after initialization.
Only STATE_ACTIVE and STATE_ERROR are used during normal runtime (STATE_DISABLED and STATE_INIT occur briefly at boot).
If CONFIG_CAF_BUTTONS_PM_EVENTS is enabled, the module can also enter STATE_GOING_TO_OFF and STATE_OFF while handling power management events.
See Power management states for details.
State transitions of the CAF buttons ISR module
Each button is handled by a dedicated state machine. The module uses level-triggered GPIO interrupts to detect both press and release.
State transitions of a single button
After initialization, the module switches to STATE_ACTIVE and enables press listeners on all configured buttons.
When a button press is detected, the module immediately submits a button_event with button_event.pressed set to true.
The module then disables the GPIO interrupt for that button and starts the press debounce interval handling.
The module applies the debounce interval in two steps
Scheduling a delayed work item requires GRTC register access, which can take a long time right after the CPU wakes up from idle. To avoid that access in the GPIO interrupt handler, the module first submits the debounce work with no delay.
When that work runs, it switches the button from
BTN_STATE_LISTEN_PRESS_CONFIRMEDtoBTN_STATE_LISTEN_PRESS_WAIT_DEBOUNCEand reschedules the same work with a timeout equal toCONFIG_CAF_BUTTONS_DEBOUNCE_INTERVAL. After this second step completes, the module enables a release listener on the button.
When a button release is detected, the module immediately submits a button_event with button_event.pressed set to false.
The module then disables the GPIO interrupt for that button and applies the release debounce interval using the same two-step approach (BTN_STATE_LISTEN_RELEASE_CONFIRMED followed by BTN_STATE_LISTEN_RELEASE_WAIT_DEBOUNCE).
After the release debounce interval expires, the module re-enables the press listener for that button.
The debounce interval prevents the module from enabling the listener for the opposite pin state too early, which filters signal bouncing. Button events are submitted immediately after the pin state change is detected.
If a button is already pressed when listeners are enabled, the level-triggered press interrupt fires and the press is handled in the interrupt callback.
If CONFIG_CAF_BUTTONS_WQ is enabled, debouncing works run on a dedicated workqueue instead of the system workqueue.
This can reduce interference with other work submitted to the system workqueue.
Key ID
The button_event.key_id is a unique 2-byte value that depends on the module configuration described in the configuration file.
The following table breaks down the button_event.key_id bits.
key_id |
|||
|---|---|---|---|
0 .. 6 |
7 .. 13 |
14 |
15 |
Row idx |
Col idx |
Reserved |
|
In this button_event.key_id value, the bits are distributed as follows:
The first seven least significant bits (
Row idx) encode the index of therowarray that corresponds to the GPIO port and the pin to which the button is connected.The next seven bits (
Col idx) are set to zero for directly connected buttons.The last two bits are reserved for application-specific usage.
For example, if the configuration file defines a button at index 3 in the row array (port 1, pin 14), a button_event related to that button has the row index encoded in the least significant bits of the button_event.key_id.
Power management states
If the CONFIG_CAF_BUTTONS_PM_EVENTS Kconfig option is enabled, the module can react to power management events and submit a wake_up_event.
In that case, the following additional states are available:
STATE_GOING_TO_OFFSTATE_OFF
The power management events that the module can react to are the following:
power_down_eventpower_off_eventwake_up_event
If a power_down_event is received while the module is in the STATE_ACTIVE state and no button interaction is in progress, the module immediately switches to STATE_OFF.
If any button is still pressed or debouncing is in progress, the module switches to STATE_GOING_TO_OFF and rejects the event.
The module remains in STATE_GOING_TO_OFF until all buttons complete the current press/release cycle, then switches to STATE_OFF.
In STATE_OFF, GPIO interrupts are enabled only for buttons configured to wake up the application.
Pressing any of these buttons submits a wake_up_event.
Receiving a wake_up_event switches the module back to STATE_ACTIVE.
Note
If the system enters the off state (indicated by power_off_event), a button press triggers an instant system reboot (sys_reboot()).
This is done to prevent entering the system off state with GPIO interrupts disabled.
System reboot is anyway necessary to wake up from the system off state.