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:

Configuration file

When you define how the buttons are connected, you must create a configuration file with the following arrays:

  • row contains GPIO pins to which buttons are directly connected (one button for each pin). The array can contain up to 32 buttons.

  • col must 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:

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 buttons module in ISR mode

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 in ISR mode

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_CONFIRMED to BTN_STATE_LISTEN_PRESS_WAIT_DEBOUNCE and reschedules the same work with a timeout equal to CONFIG_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 the row array 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_OFF

  • STATE_OFF

The power management events that the module can react to are the following:

  • power_down_event

  • power_off_event

  • wake_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.