Wake up after sys_poweroff() via GPIO inconsistent

I was doing a demo code to implement a wake up triggered by motion. I was able to make it work with the integrated low power accelerometer on thingy52 but it only worked 1 time after turning off and on the board. After some research I found out that the problem might be in the internal latch of the accelerometer and it was suggested to read the register to clear that latch. I started doing that before configuring the interrupt, but the behavior is not consistent. Sometimes it works, other times not.

Here is the code I amusing:

main.c

#include "bt_service.h"
#include "accelerometer.h"

#include <zephyr/sys/poweroff.h>
#include <zephyr/logging/log.h>

LOG_MODULE_REGISTER(STATE_MACHINE, LOG_LEVEL_INF);

int main(void)
{
    init_bt_service();
    init_advertise();
    k_sleep(K_SECONDS(10));

    LOG_INF("%s system off demo\n", CONFIG_BOARD);

    init_accelerometer();
   

    k_sleep(K_SECONDS(10));
   
    sys_poweroff();

    return 0;
}
accelerometer.c
#include <zephyr/drivers/gpio.h>
#include "accelerometer.h"

LOG_MODULE_REGISTER(ACCELEROMETER, LOG_LEVEL_INF);

const struct device *accelerometer = DEVICE_DT_GET_ANY(st_lis2dh12);
const struct gpio_dt_spec accelerometer_gpio = GPIO_DT_SPEC_GET(DT_NODELABEL(lis2dh12), irq_gpios);
const struct i2c_dt_spec accelerometer_i2c = I2C_DT_SPEC_GET(DT_NODELABEL(lis2dh12));


static void lis2dh_trigger_handler(const struct device *accelerometer, const struct sensor_trigger *trig)
{
    struct sensor_value xyz[3];

    if (sensor_sample_fetch(accelerometer)) {
        LOG_ERR("Failed to fetch sensor sample");
        return;
    }

    if (sensor_channel_get(accelerometer, SENSOR_CHAN_ACCEL_XYZ, xyz)) {
        LOG_ERR("Failed to get sensor channel: accel");
        return;
    }

    LOG_INF("Movement detected: X=%d mg, Y=%d mg, Z=%d mg",
        (int)(sensor_value_to_double(&xyz[0]) * 1000),
        (int)(sensor_value_to_double(&xyz[1]) * 1000),
        (int)(sensor_value_to_double(&xyz[2]) * 1000));

    stop_accelerometer();
}

static void clear_accelerometer_interrupt_line()
{
    uint8_t src;
    if (!device_is_ready(accelerometer_i2c.bus)) {
        LOG_ERR("I2C bus not ready to clear LIS2DH interrupt");
        return;
    }

    if (i2c_reg_read_byte(accelerometer_i2c.bus, LIS2DH_ADDR, LIS2DH_REG_INT1_SRC, &src)) {
        LOG_ERR("Failed to read INT1_SRC");
    }

    LOG_INF("Cleared LIS2DH interrupt, INT1_SRC=0x%02X", src);
}

void configure_accelerometer_wakeup_gpio() {
    if(gpio_pin_configure_dt(&accelerometer_gpio, GPIO_INPUT)) {
        LOG_INF("Could not configure accelerometer GPIO");
        return;
    }

    if (gpio_pin_interrupt_configure_dt(&accelerometer_gpio, GPIO_INT_LEVEL_ACTIVE)) {
        LOG_INF("Could not configure accelerometer GPIO interrupt");
        return;
    }
    NRF_GPIO->PIN_CNF[accelerometer_gpio.pin] |= (GPIO_PIN_CNF_SENSE_High << GPIO_PIN_CNF_SENSE_Pos);
}

void init_accelerometer(void)
{
    if (accelerometer == NULL) {
        LOG_ERR("Accelerometer device not found");
        return;
    }

    if (!device_is_ready(accelerometer)) {
        LOG_ERR("Accelerometer not ready");
        return;
    }

    clear_accelerometer_interrupt_line();
    k_sleep(K_SECONDS(1));

    double sensitivity_value = ACCELEROMETER_TRIGGER_SENSITIVITY_VALUE * GRAVITY_CONSTANT;
    struct sensor_value attr;

    attr.val1 = (int32_t)sensitivity_value;
    attr.val2 = (int32_t)((double)(sensitivity_value - (int32_t)(sensitivity_value)) * 1000000);
   
    if(sensor_attr_set(accelerometer, SENSOR_CHAN_ACCEL_XYZ, SENSOR_ATTR_SLOPE_TH, &attr)){
        LOG_ERR("Failed to set slope");
        return;
    }

    struct sensor_trigger trig = {
        .type = SENSOR_TRIG_DELTA,
        .chan = SENSOR_CHAN_ACCEL_XYZ,
    };

    if (IS_ENABLED(CONFIG_LIS2DH_ODR_RUNTIME)) {
        struct sensor_value odr = {
            .val1 = SAMPLE_FREQUENCY,
            .val2 = 0,
        };

        if (sensor_attr_set(accelerometer, trig.chan, SENSOR_ATTR_SAMPLING_FREQUENCY, &odr)) {
            LOG_ERR("Failed to set odr");
            return;
        }
    }

    if (sensor_trigger_set(accelerometer, &trig, lis2dh_trigger_handler)) {
        LOG_ERR("Failed to set trigger");
        return;
    }

    configure_accelerometer_wakeup_gpio();

    LOG_INF("Accelerometer device is ready");
}

void stop_accelerometer() {
    struct sensor_value odr = {
        .val1 = 0,
        .val2 = 0
    };

    if (sensor_attr_set(accelerometer, SENSOR_CHAN_ACCEL_XYZ, SENSOR_ATTR_SAMPLING_FREQUENCY, &odr)) {
        LOG_ERR("Failed to set ODR to 0");
    }

    LOG_INF("Accelerometer stopped");
}

The Bluetooth initialization is standard and it's just to see when the device wakes up without needing the terminal.

Related