nRF9161 to lowest power mode

I am trying to make the nrf9161 go to very low power mode for 10 seconds and then come back online. 

Actually I would not mind if it were to start all over again when coming back around but currently on my nrf9161dk I cannot achieve < 3.2mA. 

I could not find any right source with an example but if someone has a good example source, please let me know. Otherwise, why doesn't the code that I provide here bring me anywhere below 3.2mA? 

#include <zephyr/kernel.h>
#include <zephyr/sys/printk.h>
#include <hal/nrf_power.h>
#include <hal/nrf_clock.h>
#include <hal/nrf_rtc.h>
#include <modem/lte_lc.h>

#define WAKEUP_SECONDS 10U  // Sleep duration in seconds

// Start LFCLK (needed for RTC)
static void start_lfclk(void)
{
    if (!NRF_CLOCK->LFCLKRUN) {
        NRF_CLOCK->LFCLKSRC = 2U; // LFXO (external crystal)
        NRF_CLOCK->TASKS_LFCLKSTART = 1;
        while (!NRF_CLOCK->EVENTS_LFCLKSTARTED) {}
    }
}

// Configure RTC0 as wake timer
static void setup_rtc_wakeup(uint32_t seconds)
{
    // RTC0 runs at 32.768 kHz / (prescaler + 1)
    nrf_rtc_prescaler_set(NRF_RTC0, 0); 
    nrf_rtc_cc_set(NRF_RTC0, 0, seconds * 32768UL);
    nrf_rtc_int_enable(NRF_RTC0, NRF_RTC_INT_COMPARE0_MASK);
    nrf_rtc_task_trigger(NRF_RTC0, NRF_RTC_TASK_START);
}

// Enter System OFF (deep sleep)
static void enter_system_off(void)
{
    // Must power down LTE modem first
    lte_lc_power_off();

    // Ensure all memory operations completed
    __DSB();
    __WFI(); // Wait for interrupt; chip powers down
}

// Check if we woke from System OFF
static bool woke_from_system_off(void)
{
    return (NRF_POWER->RESETREAS & (1U << 2)) != 0; // Bit 2 = OFF
}

// Clear wake reason
static void clear_wake_reason(void)
{
    NRF_POWER->RESETREAS = NRF_POWER->RESETREAS;
}

int main(void)
{
    while (1) {
        if (woke_from_system_off()) {
            printk("Woke from System OFF!\n");
            clear_wake_reason();
        } else {
            printk("Power-on or other reset\n");
        }

        // Start LFCLK for RTC
        start_lfclk();

        // Set RTC wakeup
        setup_rtc_wakeup(WAKEUP_SECONDS);
        printk("Entering System OFF for %d seconds...\n", WAKEUP_SECONDS);

        // Enter deep sleep
        enter_system_off();

        // Execution never reaches here in System OFF
        while (1) {
            k_sleep(K_MSEC(1000));
        }
    }
    return 0;
}

  • I got to this code from the example but this doesn't include restarting and/or waking up;

    #include <modem/lte_lc.h>
    void main(void)
    {
     lte_lc_power_off();
     k_sleep(K_MSEC(1000));
     NRF_REGULATORS->SYSTEMOFF = 1;
    }


    I tried this code but didn't wake up / restart either;

    #define WAKEUP_MS 5000
    
    int main(void)
    {
            // Turn off LTE modem
        lte_lc_power_off();
    
        // Wait for LTE to properly power off
        k_sleep(K_MSEC(1000));
    
        // Configure the RTC0 to wakeup from SYSTEMOFF
        NRF_RTC0->TASKS_STOP = 1;          // Stop RTC0
        NRF_RTC0->PRESCALER = 327;         // 327 => 1 tick = 1ms (32.768kHz / (327+1) = 100Hz -> 10ms per tick, adjust if needed)
        NRF_RTC0->CC[0] = WAKEUP_MS;       // Set compare value in milliseconds
        NRF_RTC0->INTENSET = RTC_INTENSET_COMPARE0_Msk;  // Enable compare interrupt
        NRF_RTC0->EVTENSET = RTC_EVTENSET_COMPARE0_Msk;  // Enable event
        NRF_RTC0->TASKS_CLEAR = 1;          // Clear the counter
        NRF_RTC0->TASKS_START = 1;          // Start RTC0
    
        // Configure wakeup source: RTC0 COMPARE0
        NRF_REGULATORS->SYSTEMOFF = 1;
        return 0;
    }

  • In general:

    if you want to know the very lowest current of your board, just switch all off and don't care about the wake-up.

    If you then want to achieve a more "real world" behavior, check, if PSM is available in your network and enable that.

    In zephyr it's not common to try to do such things as sleeping and wake up on a low level. The modem will already sleep with PSM, the application MCU may also put in sleep with k_sleep. If you want to wake up on an external input, you may setup a GPIO as input interrupt.

    Once your solution works with that, it may be possible to reduce the power consumption of the modem using RAl, but that also depends on the network and used protocol. If you want to use TLS/TCP, RAI may turn out to be not too realistic, but that's left to your own experience.   

  • Hi Mart,

    We have some documentation on power management (link) and also for cellular focused application (link). If you haven't done it yet, I would also recommend checking the Dev Academy, where we have a course on cellular iot (link). It contains a chapter on power saving techniques.

    For a more complete sample, I would recommend checking the simple tracker solution from the Cellular iot course on Dev Academy.

    If you need more information, feel free to ask!

    Best regards,

    Simon D-M

  • Thank you for the answer, indeed I had to take a deeper look. 

    This now is what I achieved and it gets me running to uA levels, very awesome!

    prj.conf

    ########################################
    # Core Zephyr / Kernel
    ########################################
    CONFIG_MAIN_STACK_SIZE=4096
    CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=2048
    
    ########################################
    # Modem / LTE
    ########################################
    CONFIG_NRF_MODEM_LIB=y
    CONFIG_LTE_LINK_CONTROL=y
    
    # Request Power Saving Mode (PSM)
    CONFIG_LTE_PSM_REQ=y
    
    # Requested PSM timers (network may override)
    # TAU ≈ 1 hour
    CONFIG_LTE_PSM_REQ_RPTAU="00100100"
    
    # Active time ≈ 2 seconds
    CONFIG_LTE_PSM_REQ_RAT="00000101"
    
    ########################################
    # Logging (DEBUG ONLY)
    ########################################
    CONFIG_LOG=y
    CONFIG_LOG_MODE_DEFERRED=y
    CONFIG_LOG_DEFAULT_LEVEL=3
    CONFIG_PRINTK=y
    
    ########################################
    # Power optimization
    ########################################
    CONFIG_PM=y
    CONFIG_PM_DEVICE=y
    
    ########################################
    # Disable unused features (important!)
    ########################################
    CONFIG_USB_DEVICE_STACK=n
    CONFIG_SERIAL=n
    CONFIG_UART_CONSOLE=n
    
    

    main.c

    #include <zephyr/kernel.h>
    #include <modem/lte_lc.h>
    #include <modem/nrf_modem_lib.h>
    #include <zephyr/logging/log.h>
    
    LOG_MODULE_REGISTER(psm_example, LOG_LEVEL_INF);
    
    static void lte_handler(const struct lte_lc_evt *const evt)
    {
        switch (evt->type) {
    
        case LTE_LC_EVT_NW_REG_STATUS:
            if ((evt->nw_reg_status == LTE_LC_NW_REG_REGISTERED_HOME) ||
                (evt->nw_reg_status == LTE_LC_NW_REG_REGISTERED_ROAMING)) {
                LOG_INF("LTE registered");
            }
            break;
    
        case LTE_LC_EVT_RRC_UPDATE:
            LOG_INF("RRC mode: %s",
                    evt->rrc_mode == LTE_LC_RRC_MODE_CONNECTED ?
                    "Connected" : "Idle");
            break;
    
        default:
            break;
        }
    }
    
    static int modem_configure(void)
    {
        int err;
    
        LOG_INF("Initializing modem library");
    
        err = nrf_modem_lib_init();
        if (err) {
            LOG_ERR("Failed to initialize the modem library, error: %d", err);
            return err;
        }
    
        LOG_INF("Connecting to LTE network");
        err = lte_lc_connect_async(lte_handler);
        if (err) {
            LOG_ERR("Error in lte_lc_connect_async, error: %d", err);
            return err;
        }
    
        return 0;
    }
    
    int main(void)
    {
        int err;
    
        LOG_INF("Starting PSM example");
    
        err = modem_configure();
        if (err) {
            LOG_ERR("Failed to configure the modem");
            return 0;
        }
    
        while (1) {
            // Disconnect from LTE to save power
            LOG_INF("Disconnecting from LTE");
            err = lte_lc_func_mode_set(LTE_LC_FUNC_MODE_DEACTIVATE_LTE);
            if (err) {
                LOG_ERR("Failed to deactivate LTE, error: %d", err);
                return 0;
            }
    
            // Wait for 20 seconds
            k_msleep(20000);
    
            // Reconnect to LTE
            LOG_INF("Reconnecting to LTE");
            err = lte_lc_func_mode_set(LTE_LC_FUNC_MODE_NORMAL);
            if (err) {
                LOG_ERR("Failed to activate LTE, error: %d", err);
                return 0;
            }
    
            // Wait for 1 second
            k_msleep(1000);
        }
    
        return 0;
    }
    
    

    The only thing that I am not sure about is why having CONFIG_ASSERT=y in my prj.conf drastically increase the power to over mA levels, even during sleep conditions. 

  • Hi,

    I haven't looked into details, but I think that when you enable the asserts, it enables some more debugging stuff which can maybe prevent the chip from going into sleep or also maybe enable some peripherals that were disabled before.

    Best regards,

    Simon D-M

Related