nRF54L15 CPU idle thread hooks

Hi,

I would like to be able to hook into the system idle thread before/after the CPU is put to sleep to do some more custom power management things. Is there a way to do that easily (using NCS v2.9.0)?

For context, as a baseline I would like to have the serial console/logging interface active while the CPU is awake for debug and QA purposes, but disable everything to get into the lowest power state possible when the CPU is asleep. So simply disabling the CONFIG_LOG and CONFIG_SERIAL at build time simply isn't good enough for my use case.

Thanks!

Bryan

Parents
  • Using logging while awake will also increase your current consumption, and particularly if your device is battery operated, I guess this is something that you don't want. The UART in itself also generates events when it is done processing the logs, which will again wake the device. 

    Can you please specify what you mean by sleep? Do you mean the System On Mode (light sleep) that occurs every time you you use e.g. k_sleep(), which will occur between the connection events (the events that occurs every connection interval), and between every advertisement? Or do you mean System Off Mode, which will occur only if you call sys_poweroff() or nrf_power_system_off(), going into the lowest power state, where it can only be woken up by an external interrupt.

    Best regards,

    Edvin

  • By sleep I mean system ON IDLE. I was able to achieve a solution/workaround by patching the upstream ncs v2.9.0. These changes dynamically disable the console UART after all logging has been sent over the interface. It also, "wakes up" the shell/UART interface to accept commands on RX activity, and the disables the UART when a inactivity timeout happens.

    Source modifications:

    diff --git a/subsys/shell/backends/Kconfig.backends b/subsys/shell/backends/Kconfig.backends
    index 4524d03b9a4..1321f6453d1 100644
    --- a/subsys/shell/backends/Kconfig.backends
    +++ b/subsys/shell/backends/Kconfig.backends
    @@ -655,4 +655,23 @@ config SHELL_BACKEND_ADSP_MEMORY_WINDOW_POLL_INTERVAL

    endif # SHELL_BACKEND_ADSP_MEMORY_WINDOW

    +if SHELL
    +
    +config SHELL_LOW_POWER
    + bool "Enable the low power shell support"
    + depends on DT_HAS_SERIAL_WAKEUP_ENABLED
    + select PM_DEVICE
    + default n
    + help
    + Enable the low power shell support.
    +
    +config SHELL_RX_INACTIVITY_TIMEOUT
    + int "RX inactivity timeout"
    + depends on SHELL_LOW_POWER
    + default 30
    + help
    + RX inactivity timeout (in seconds).
    +
    +endif
    +
    endif # SHELL_BACKENDS
    diff --git a/subsys/shell/backends/shell_uart.c b/subsys/shell/backends/shell_uart.c
    index 1d19ce19a31..0b1ee06e0af 100644
    --- a/subsys/shell/backends/shell_uart.c
    +++ b/subsys/shell/backends/shell_uart.c
    @@ -14,6 +14,9 @@
    #include <zephyr/logging/log.h>
    #include <zephyr/net_buf.h>

    +#include <zephyr/drivers/gpio.h>
    +#include <zephyr/pm/device.h>
    +
    #define LOG_MODULE_NAME shell_uart
    LOG_MODULE_REGISTER(shell_uart);

    @@ -28,6 +31,62 @@ NET_BUF_POOL_DEFINE(smp_shell_rx_pool, CONFIG_MCUMGR_TRANSPORT_SHELL_RX_BUF_COUN
    SMP_SHELL_RX_BUF_SIZE, 0, NULL);
    #endif /* CONFIG_MCUMGR_TRANSPORT_SHELL */

    +#ifdef CONFIG_SHELL_LOW_POWER
    +
    +#define RX_INACTIVITY_EXPIRY (CONFIG_SHELL_RX_INACTIVITY_TIMEOUT * 1000)
    +
    +static volatile bool rx_session_active = false;
    +static struct gpio_callback rx_gpio_cb;
    +static struct k_timer rx_inactivity_timer;
    +static struct uart_config shell_uart_config;
    +
    +static int shell_power_up(const struct device *dev)
    +{
    + return pm_device_action_run(dev, PM_DEVICE_ACTION_RESUME);
    +}
    +
    +static void shell_rx_receieved(const struct device *dev, struct gpio_callback *cb, uint32_t pins)
    +{
    + const struct device *uart = DEVICE_DT_GET(DT_CHOSEN(zephyr_shell_uart));
    +
    + rx_session_active = true;
    + uart_configure(uart, &shell_uart_config);
    + shell_power_up(uart);
    + k_timer_start(&rx_inactivity_timer,
    + K_MSEC(RX_INACTIVITY_EXPIRY), K_NO_WAIT);
    +}
    +
    +static int shell_power_down(const struct device *dev)
    +{
    + const struct gpio_dt_spec rx_pin =
    + GPIO_DT_SPEC_GET_OR(DT_NODELABEL(shell_rx), gpios, {0});
    +
    + // We have the console enabled
    + if (rx_session_active) {
    + return 0;
    + }
    +
    + int status = pm_device_action_run(dev, PM_DEVICE_ACTION_SUSPEND);
    +
    + // Configure RX pin as an interrupt
    + gpio_pin_configure_dt(&rx_pin, GPIO_INPUT);
    + gpio_pin_interrupt_configure_dt(&rx_pin, GPIO_INT_EDGE_BOTH);
    + gpio_init_callback(&rx_gpio_cb, shell_rx_receieved,
    + BIT(rx_pin.pin));
    + gpio_add_callback(rx_pin.port, &rx_gpio_cb);
    +
    + return status;
    +}
    +
    +static void rx_inactivity_expiry(struct k_timer *timer_id)
    +{
    + const struct device *dev = DEVICE_DT_GET(DT_CHOSEN(zephyr_shell_uart));
    + rx_session_active = false;
    + shell_power_down(dev);
    +}
    +
    +#endif
    +
    static void async_callback(const struct device *dev, struct uart_event *evt, void *user_data)
    {
    struct shell_uart_async *sh_uart = (struct shell_uart_async *)user_data;
    @@ -181,6 +240,9 @@ static void uart_tx_handle(const struct device *dev, struct shell_uart_int_drive
    if (len) {
    int err;

    +#ifdef CONFIG_SHELL_LOW_POWER
    + shell_power_up(dev);
    +#endif
    len = uart_fifo_fill(dev, data, len);
    err = ring_buf_get_finish(&sh_uart->tx_ringbuf, len);
    __ASSERT_NO_MSG(err == 0);
    @@ -188,6 +250,9 @@ static void uart_tx_handle(const struct device *dev, struct shell_uart_int_drive
    } else {
    uart_irq_tx_disable(dev);
    sh_uart->tx_busy = 0;
    +#ifdef CONFIG_SHELL_LOW_POWER
    + shell_power_down(dev);
    +#endif
    }

    sh_uart->common.handler(SHELL_TRANSPORT_EVT_TX_RDY, sh_uart->common.context);
    @@ -551,6 +616,12 @@ static int enable_shell_uart(void)

    shell_init(&shell_uart, dev, cfg_flags, log_backend, level);

    +#ifdef CONFIG_SHELL_LOW_POWER
    + uart_config_get(dev, &shell_uart_config);
    + k_timer_init(&rx_inactivity_timer, rx_inactivity_expiry, NULL);
    + shell_power_down(dev);
    +#endif
    +
    return 0;
    }

    Defined a new device tree binding for the RX pin (this may be unnecessary):

    description: GPIO (RX) pin to enable the serial console to remain active

    compatible: "serial-wakeup"

    properties:
      gpios:
        type: phandle-array
        required: true
        description: |
          The GPIO (RX) pin to wake up the serial interface.

    And corresponding overlay:

    / {

        shell_rx: shell_rx {
            status = "okay";
            compatible = "serial-wakeup";
            gpios = <&gpio0 1 GPIO_ACTIVE_LOW>;
        };

    ...

    There is probably a more robust way of doing this at a lower layer, but this works for my purposes. Hopefully, this helps someone would hopes to achieve similar.

Reply
  • By sleep I mean system ON IDLE. I was able to achieve a solution/workaround by patching the upstream ncs v2.9.0. These changes dynamically disable the console UART after all logging has been sent over the interface. It also, "wakes up" the shell/UART interface to accept commands on RX activity, and the disables the UART when a inactivity timeout happens.

    Source modifications:

    diff --git a/subsys/shell/backends/Kconfig.backends b/subsys/shell/backends/Kconfig.backends
    index 4524d03b9a4..1321f6453d1 100644
    --- a/subsys/shell/backends/Kconfig.backends
    +++ b/subsys/shell/backends/Kconfig.backends
    @@ -655,4 +655,23 @@ config SHELL_BACKEND_ADSP_MEMORY_WINDOW_POLL_INTERVAL

    endif # SHELL_BACKEND_ADSP_MEMORY_WINDOW

    +if SHELL
    +
    +config SHELL_LOW_POWER
    + bool "Enable the low power shell support"
    + depends on DT_HAS_SERIAL_WAKEUP_ENABLED
    + select PM_DEVICE
    + default n
    + help
    + Enable the low power shell support.
    +
    +config SHELL_RX_INACTIVITY_TIMEOUT
    + int "RX inactivity timeout"
    + depends on SHELL_LOW_POWER
    + default 30
    + help
    + RX inactivity timeout (in seconds).
    +
    +endif
    +
    endif # SHELL_BACKENDS
    diff --git a/subsys/shell/backends/shell_uart.c b/subsys/shell/backends/shell_uart.c
    index 1d19ce19a31..0b1ee06e0af 100644
    --- a/subsys/shell/backends/shell_uart.c
    +++ b/subsys/shell/backends/shell_uart.c
    @@ -14,6 +14,9 @@
    #include <zephyr/logging/log.h>
    #include <zephyr/net_buf.h>

    +#include <zephyr/drivers/gpio.h>
    +#include <zephyr/pm/device.h>
    +
    #define LOG_MODULE_NAME shell_uart
    LOG_MODULE_REGISTER(shell_uart);

    @@ -28,6 +31,62 @@ NET_BUF_POOL_DEFINE(smp_shell_rx_pool, CONFIG_MCUMGR_TRANSPORT_SHELL_RX_BUF_COUN
    SMP_SHELL_RX_BUF_SIZE, 0, NULL);
    #endif /* CONFIG_MCUMGR_TRANSPORT_SHELL */

    +#ifdef CONFIG_SHELL_LOW_POWER
    +
    +#define RX_INACTIVITY_EXPIRY (CONFIG_SHELL_RX_INACTIVITY_TIMEOUT * 1000)
    +
    +static volatile bool rx_session_active = false;
    +static struct gpio_callback rx_gpio_cb;
    +static struct k_timer rx_inactivity_timer;
    +static struct uart_config shell_uart_config;
    +
    +static int shell_power_up(const struct device *dev)
    +{
    + return pm_device_action_run(dev, PM_DEVICE_ACTION_RESUME);
    +}
    +
    +static void shell_rx_receieved(const struct device *dev, struct gpio_callback *cb, uint32_t pins)
    +{
    + const struct device *uart = DEVICE_DT_GET(DT_CHOSEN(zephyr_shell_uart));
    +
    + rx_session_active = true;
    + uart_configure(uart, &shell_uart_config);
    + shell_power_up(uart);
    + k_timer_start(&rx_inactivity_timer,
    + K_MSEC(RX_INACTIVITY_EXPIRY), K_NO_WAIT);
    +}
    +
    +static int shell_power_down(const struct device *dev)
    +{
    + const struct gpio_dt_spec rx_pin =
    + GPIO_DT_SPEC_GET_OR(DT_NODELABEL(shell_rx), gpios, {0});
    +
    + // We have the console enabled
    + if (rx_session_active) {
    + return 0;
    + }
    +
    + int status = pm_device_action_run(dev, PM_DEVICE_ACTION_SUSPEND);
    +
    + // Configure RX pin as an interrupt
    + gpio_pin_configure_dt(&rx_pin, GPIO_INPUT);
    + gpio_pin_interrupt_configure_dt(&rx_pin, GPIO_INT_EDGE_BOTH);
    + gpio_init_callback(&rx_gpio_cb, shell_rx_receieved,
    + BIT(rx_pin.pin));
    + gpio_add_callback(rx_pin.port, &rx_gpio_cb);
    +
    + return status;
    +}
    +
    +static void rx_inactivity_expiry(struct k_timer *timer_id)
    +{
    + const struct device *dev = DEVICE_DT_GET(DT_CHOSEN(zephyr_shell_uart));
    + rx_session_active = false;
    + shell_power_down(dev);
    +}
    +
    +#endif
    +
    static void async_callback(const struct device *dev, struct uart_event *evt, void *user_data)
    {
    struct shell_uart_async *sh_uart = (struct shell_uart_async *)user_data;
    @@ -181,6 +240,9 @@ static void uart_tx_handle(const struct device *dev, struct shell_uart_int_drive
    if (len) {
    int err;

    +#ifdef CONFIG_SHELL_LOW_POWER
    + shell_power_up(dev);
    +#endif
    len = uart_fifo_fill(dev, data, len);
    err = ring_buf_get_finish(&sh_uart->tx_ringbuf, len);
    __ASSERT_NO_MSG(err == 0);
    @@ -188,6 +250,9 @@ static void uart_tx_handle(const struct device *dev, struct shell_uart_int_drive
    } else {
    uart_irq_tx_disable(dev);
    sh_uart->tx_busy = 0;
    +#ifdef CONFIG_SHELL_LOW_POWER
    + shell_power_down(dev);
    +#endif
    }

    sh_uart->common.handler(SHELL_TRANSPORT_EVT_TX_RDY, sh_uart->common.context);
    @@ -551,6 +616,12 @@ static int enable_shell_uart(void)

    shell_init(&shell_uart, dev, cfg_flags, log_backend, level);

    +#ifdef CONFIG_SHELL_LOW_POWER
    + uart_config_get(dev, &shell_uart_config);
    + k_timer_init(&rx_inactivity_timer, rx_inactivity_expiry, NULL);
    + shell_power_down(dev);
    +#endif
    +
    return 0;
    }

    Defined a new device tree binding for the RX pin (this may be unnecessary):

    description: GPIO (RX) pin to enable the serial console to remain active

    compatible: "serial-wakeup"

    properties:
      gpios:
        type: phandle-array
        required: true
        description: |
          The GPIO (RX) pin to wake up the serial interface.

    And corresponding overlay:

    / {

        shell_rx: shell_rx {
            status = "okay";
            compatible = "serial-wakeup";
            gpios = <&gpio0 1 GPIO_ACTIVE_LOW>;
        };

    ...

    There is probably a more robust way of doing this at a lower layer, but this works for my purposes. Hopefully, this helps someone would hopes to achieve similar.

Children
No Data
Related