Implements button handling

- DTS bindings
- Debouncing logic
- New `input` for testing and interrupt power profiling
This commit is contained in:
rbaron 2023-03-29 21:28:24 +02:00
parent 02b3970ffc
commit e82c1b6bc1
12 changed files with 190 additions and 17 deletions

View file

@ -11,4 +11,4 @@ add_library(prstlib STATIC
)
target_include_directories(prstlib PRIVATE include)
target_link_libraries(prstlib PUBLIC zephyr_interface)
target_link_libraries(prstlib PUBLIC zephyr_interface kernel)

View file

@ -28,9 +28,9 @@
buttons {
compatible = "gpio-keys";
button0: button_0 {
// P0.12.
gpios = <&gpio0 12 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>;
label = "Push button switch 0";
// P0.30.
gpios = <&gpio0 30 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>;
label = "Push button SW1";
};
};
@ -62,6 +62,9 @@
&gpio0 {
status = "okay";
// For low-power EDGE interrupts.
// See github.com/zephyrproject-rtos/zephyr/issues/28499.
sense-edge-mask = <0xffffffff>;
};
&gpio1 {

View file

@ -28,9 +28,9 @@
buttons {
compatible = "gpio-keys";
button0: button_0 {
// P0.12.
gpios = <&gpio0 12 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>;
label = "Push button switch 0";
// P0.30.
gpios = <&gpio0 30 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>;
label = "Push button SW1";
};
};
@ -62,6 +62,9 @@
&gpio0 {
status = "okay";
// For low-power EDGE interrupts.
// github.com/zephyrproject-rtos/zephyr/issues/28499
sense-edge-mask = <0xffffffff>;
};
&gpio1 {

View file

@ -1,7 +1,24 @@
#ifndef _PRST_BUTTON_H_
#define _PRST_BUTTON_H_
// Inits button driver and registers callback.
#include <stdbool.h>
typedef enum {
PRST_BUTTON_SW1 = 0,
} prst_button_t;
typedef void (*prst_button_callback_t)(prst_button_t button, bool is_active);
// Inits button driver.
int prst_button_init();
// Configures ISR and calls callback on debounced button press/release.
int prst_button_register_callback(prst_button_callback_t callback);
// Returns:
// 1 if button is active
// 0 if button is inactive
// -1 on error
int prst_button_poll(prst_button_t prst_button);
#endif // _PRST_BUTTON_H_

View file

@ -1,6 +1,7 @@
#include "prstlib/button.h"
#include <zephyr/drivers/gpio.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include "prstlib/led.h"
@ -8,23 +9,60 @@
LOG_MODULE_REGISTER(button, CONFIG_PRSTLIB_LOG_LEVEL);
static struct k_work_delayable button_pressed_delayable;
static struct gpio_dt_spec button =
GPIO_DT_SPEC_GET(DT_NODELABEL(button0), gpios);
static struct gpio_callback cb_data;
static void button_pressed(const struct device *dev, struct gpio_callback *cb,
uint32_t pins) {
LOG_INF("Button pressed");
prst_led_toggle();
static prst_button_callback_t user_callback = NULL;
static void maybe_call_user_callback(prst_button_t button, bool is_active) {
if (user_callback != NULL) {
user_callback(button, is_active);
} else {
LOG_WRN("No user callback registered for button %d", button);
}
}
static void button_pressed_cb(struct k_work *work) {
int button_state = prst_button_poll(PRST_BUTTON_SW1);
if (button_state < 0) {
LOG_ERR("Failed to poll button");
return;
}
return maybe_call_user_callback(PRST_BUTTON_SW1, button_state);
}
static void button_pressed_isr(const struct device *dev, struct gpio_callback *cb,
uint32_t pins) {
k_work_reschedule(&button_pressed_delayable, K_MSEC(10));
}
int prst_button_init() {
RET_IF_ERR(!device_is_ready(button.port));
RET_IF_ERR(gpio_pin_configure_dt(&button, GPIO_INPUT));
// EDGE interrupts consume more power! Just use a LEVEL one.
RET_IF_ERR(gpio_pin_interrupt_configure_dt(&button, GPIO_INT_LEVEL_ACTIVE));
gpio_init_callback(&cb_data, button_pressed, BIT(button.pin));
RET_IF_ERR(gpio_add_callback(button.port, &cb_data));
return 0;
}
int prst_button_register_callback(prst_button_callback_t callback) {
k_work_init_delayable(&button_pressed_delayable, button_pressed_cb);
// EDGE interrupts seem to consume more power than LEVEL ones.
// For GPIO_INT_EDGE_BOTH: 16 uA idle.
// For GPIO_INT_LEVEL_ACTIVE: 3 uA idle.
// Related issue:
// https://github.com/zephyrproject-rtos/zephyr/issues/28499
// Apparently sense-edge-mask brings the power consumption down to
// 3 uA for EDGE interrupts too.
RET_IF_ERR(gpio_pin_interrupt_configure_dt(&button, GPIO_INT_EDGE_BOTH));
gpio_init_callback(&cb_data, button_pressed_isr, BIT(button.pin));
RET_IF_ERR(gpio_add_callback(button.port, &cb_data));
user_callback = callback;
return 0;
}
int prst_button_poll(prst_button_t prst_button) {
RET_CHECK(prst_button == PRST_BUTTON_SW1, "Invalid button");
return gpio_pin_get_dt(&button);
}

View file

@ -23,4 +23,5 @@ CONFIG_NEWLIB_LIBC_FLOAT_PRINTF=y
CONFIG_PRST_BLE_ENCODING_BTHOME_V2=y
# CONFIG_PRST_SLEEP_DURATION_SEC=1
CONFIG_PRSTLIB_LOG_LEVEL_DBG=y
# prstlib config - ser all options in prstlib/Kconfig.
# CONFIG_PRSTLIB_LOG_LEVEL_DBG=y

View file

@ -0,0 +1,2 @@
build
build_*

View file

@ -0,0 +1,15 @@
cmake_minimum_required(VERSION 3.20.0)
# Pull in the dts/ and boards/ from prstlib.
set(DTS_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/../../prstlib)
set(BOARD_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/../../prstlib)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(input)
target_sources(app PRIVATE src/main.c)
add_subdirectory(../../prstlib prstlib)
target_include_directories(app PRIVATE ../../prstlib/include)
target_link_libraries(app PUBLIC prstlib)

View file

@ -0,0 +1,3 @@
source "Kconfig.zephyr"
rsource "../../prstlib/Kconfig"

View file

@ -0,0 +1,16 @@
{
"folders": [
{
"path": "."
},
{
"path": "../../prstlib"
}
],
"settings": {
"C_Cpp.autoAddFileAssociations": false,
"nrf-connect.applications": [
"${workspaceFolder}"
]
}
}

View file

@ -0,0 +1,18 @@
CONFIG_LOG=y
CONFIG_PWM=y
CONFIG_I2C=y
CONFIG_ADC=y
CONFIG_GPIO=y
CONFIG_CBPRINTF_FP_SUPPORT=y
CONFIG_SERIAL=n
CONFIG_PM=y
CONFIG_PM_DEVICE=y
CONFIG_USE_SEGGER_RTT=y
CONFIG_NEWLIB_LIBC=y
CONFIG_NEWLIB_LIBC_FLOAT_PRINTF=y
CONFIG_PRSTLIB_LOG_LEVEL_DBG=y

View file

@ -0,0 +1,57 @@
// #include <prstlib/button.h>
// #include <prstlib/led.h>
// #include <prstlib/sensors.h>
// #include <zephyr/logging/log.h>
// #include <zephyr/pm/device.h>
// #include <zephyr/pm/pm.h>
// #include <zephyr/pm/policy.h>
#include <prstlib/adc.h>
#include <prstlib/button.h>
#include <prstlib/led.h>
#include <prstlib/macros.h>
#include <prstlib/sensors.h>
#include <prstlib/shtc3.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include <zephyr/pm/device.h>
#include <zephyr/pm/pm.h>
#include <zephyr/pm/policy.h>
LOG_MODULE_REGISTER(main, LOG_LEVEL_DBG);
void button_pressed_cb(prst_button_t button, bool is_active) {
if (is_active) {
LOG_INF("Button pressed (debounced)");
prst_led_on();
} else {
LOG_INF("Button released (debounced)");
prst_led_off();
}
}
int main(void) {
RET_IF_ERR(prst_adc_init());
RET_IF_ERR(prst_led_init());
RET_IF_ERR(prst_button_init());
RET_IF_ERR(prst_led_flash(2));
prst_sensors_t sensors;
// Read the sensors just to ensure they'll be put to a low
// power mode afterward.
RET_IF_ERR(prst_sensors_read_all(&sensors));
int initial_button_state = prst_button_poll(PRST_BUTTON_SW1);
RET_CHECK(initial_button_state >= 0, "Failed to poll button");
LOG_INF("Initial button state: %s", initial_button_state ? "active" : "inactive");
RET_IF_ERR(prst_button_register_callback(button_pressed_cb));
RET_IF_ERR(prst_led_flash(2));
while (true) {
LOG_INF("Main loop.");
k_sleep(K_FOREVER);
}
return 0;
}