NRF52840 DONGLE NCS 2.7.0 LOW POWER MODE

I'm trying to activate low power mode without success.
I found samples but it appears not to work with the last NCS version.

I want to get basic GPIO INPUT values at 32khz timer resolution. I get values with GPIO interruption during 100ms and send an average values on ESB radio, then the device get back to sleep for  1 second and read the signal again.

I'm lost in CONFIG_PM / CONFIG_PM_DEVICES and all parameters i should set in order to get less then 1mA during passive GPIO READS and then get less then 10uA during sleep mode.

-- CMake version: 3.21.0
-- Cache files will be written to: /Users/lilianbrun/Library/Caches/zephyr
-- Zephyr version: 3.6.99 (/opt/nordic/ncs/v2.7.0/zephyr)

In particular i can't use 
pm_state_force function, even with 

CONFIG_PM_DEVICE=y in prj.conf and 

#include <zephyr/pm/pm.h>
#include <zephyr/pm/policy.h>
#include <zephyr/pm/state.h>
#include <zephyr/pm/device.h>

I Power the device with PPK2 as 3.3V on VDD OUT pin, i cut SB2 and soldier SB1






Parents
  • As i understood, CONFIG_PM is deprecated and pm_state_* functions too.

    So i start a new simple project : 

    #include <zephyr/kernel.h>
    #include <zephyr/device.h>
    #include <hal/nrf_rtc.h>
    #include <zephyr/pm/device.h>
    #include <zephyr/logging/log.h>
    
    LOG_MODULE_REGISTER(main, LOG_LEVEL_INF);
    
    // Configuration RTC
    #define RTC NRF_RTC1
    #define RTC_FREQUENCY 32768    // Fréquence de l'horloge RTC (32.768 kHz)
    #define TIMEOUT_MS 900         // Temps d'attente en millisecondes
    #define TIMEOUT_TICKS (TIMEOUT_MS * RTC_FREQUENCY / 1000)
    
    volatile bool rtc_timeout = false;
    
    // Liste des périphériques à désactiver
    const char *devices_to_disable[] = {
        "UART_0",
        "UART_1",
        "SPI_0",
        "SPI_1",
        "I2C_0",
        "I2C_1",
        "PWM_0",
        "PWM_1",
        "ADC_0",
    };
    
    // Gestionnaire d'interruption RTC
    void rtc_handler(void)
    {
        if (nrf_rtc_event_check(RTC, NRF_RTC_EVENT_COMPARE_0)) {
            nrf_rtc_event_clear(RTC, NRF_RTC_EVENT_COMPARE_0);
            rtc_timeout = true;
            LOG_INF("RTC timeout reached");
        }
    }
    
    void RTC1_IRQHandler(void)
    {
        rtc_handler();
    }
    
    // Configurer le RTC pour générer une interruption après TIMEOUT_MS
    static void configure_rtc(void)
    {
        nrf_rtc_prescaler_set(RTC, 0);  // Pas de division, fréquence à 32.768 kHz
        nrf_rtc_cc_set(RTC, 0, TIMEOUT_TICKS);
        nrf_rtc_event_enable(RTC, NRF_RTC_INT_COMPARE0_MASK);
        nrf_rtc_int_enable(RTC, NRF_RTC_INT_COMPARE0_MASK);
        nrf_rtc_task_trigger(RTC, NRF_RTC_TASK_CLEAR);
        nrf_rtc_task_trigger(RTC, NRF_RTC_TASK_START);
    
        NVIC_ClearPendingIRQ(RTC1_IRQn);
        NVIC_SetPriority(RTC1_IRQn, 1);
        NVIC_EnableIRQ(RTC1_IRQn);
    
        LOG_INF("RTC configured for timeout of %d ms", TIMEOUT_MS);
    }
    
    // Désactiver les périphériques inutiles pour réduire la consommation
    static void disable_unused_devices(void)
    {
        for (int i = 0; i < ARRAY_SIZE(devices_to_disable); i++) {
            const struct device *dev = device_get_binding(devices_to_disable[i]);
            if (!dev) {
                LOG_WRN("Device %s not found", devices_to_disable[i]);
                continue;
            }
    
            if (pm_device_action_run(dev, PM_DEVICE_ACTION_SUSPEND) == 0) {
                LOG_INF("Device %s suspended", devices_to_disable[i]);
            } else {
                LOG_WRN("Failed to suspend device %s", devices_to_disable[i]);
            }
        }
    }
    
    void main(void)
    {
        LOG_INF("Starting low-power mode with RTC wakeup");
    
        // Configurer le RTC
        configure_rtc();
    
        // Désactiver les périphériques inutilisés
        disable_unused_devices();
    
        while (1) {
            rtc_timeout = false;
    
            LOG_INF("Entering low-power mode for 900 ms...");
            k_sleep(K_MSEC(900));  // Le système entre en System ON Idle ici
    
            // Attendre que le RTC atteigne son timeout
            while (!rtc_timeout) {
                k_sleep(K_MSEC(1));
            }
    
            LOG_INF("Woke up from low-power mode");
        }
    }
    


    # Enable GPIO and RTC
    CONFIG_GPIO=y
    
    # Enable power management
    CONFIG_PM_DEVICE=y
    
    # Optimize stack sizes
    CONFIG_MAIN_STACK_SIZE=1024
    CONFIG_IDLE_STACK_SIZE=256
    
    # Disable unnecessary logging to save power
    CONFIG_PRINTK=n
    CONFIG_CONSOLE=n
    CONFIG_SERIAL=n
    CONFIG_LOG=y
    


    And i works a bit, but i get a constant average consumption of 214uA, which is far more than expected, regarding what it does.

  • Hi krypton76,

    I recommend first comment out all of your code and just let main() returns, and measure the current there, see how much current you have. If you have above 10uA, that's indicative that you have other subsystems running that is consuming power.

    Secondly, I am a little confused if you want to actively polling GPIO every second, or if you want to passively get GPIO interrupt. If you use GPIO interrupt, you are using GPIOTE, and with that comes a little more current consumption. It should only be under 1uA increase, however. See GPIOTE — GPIO tasks and events.

    Thirdly, do you need tight timing? If you don't, I recommend using the Zephyr RTOS system timing instead of running a separate peripheral. While you are using RTC, and don't introduce a new active clock source, it is still more current consumed there.

    CONFIG_PM isn't deprecated. I am not certain about pm_state_* functions, but I haven't exactly followed it. Could you share the source that gives you that impression so I can look further into it?
    Otherwise, for your requirement, I don't think you need to touch PM or PM device to achieve sub 10uA. As long as all your thread goes into sleep, you should achieve System ON Idle, which should get you within the target.

    Hieu

  • Hello and thanks for you reply.

    The fact that i think it's deprectated is discussed here :
    devzone.nordicsemi.com/.../can-t-enable-has_pm-to-support-power-management-notification

    And in the NRF KCONFIG GUI i can't activate it : i can't check system power managment checkbox

    And in the IDE i have the message : CONFIG_PM was assigned the value y, but got the value n. Missing dependencies:

    (SOC_SERIES_EFR32BG22 && SOC_VENDOR_SILABS) || (SOC_SERIES_EFR32BG27 && SOC_VENDOR_SILABS) || (SOC_SERIES_EFR32MG24 && SOC_VENDOR_SILABS) || (SYS_CLOCK_EXISTS && HAS_PM)

    My full functionnal goal is :

    Get GPIO state on/off on a pin by interrupt reading. I need to get the time spent between 2 high state interrupts, in order to calculate the frequency of the input signal.
    It is from 2000hz to 5000hz, for what i count tics spent between 2 interrupts events on the 32khz clock
    Id do that for 100ms, i get at the end the average of frequencies, i send it by radio to third application then i go to deep sleep for 900ms and need a wake up by a timer.

    // Include necessary Zephyr and Nordic headers
    #include <zephyr/drivers/gpio.h>
    #include <zephyr/kernel.h>
    #include <zephyr/logging/log.h>
    #include <esb.h>
    #include <stdio.h>
    
    LOG_MODULE_REGISTER(esb_ptx, CONFIG_ESB_PTX_APP_LOG_LEVEL);
    
    #define INTERRUPT_PIN 15               // GPIO pin for the interrupt
    #define MAX_INTERRUPTS 500             // Max number of interrupts to capture
    
    // Define GPIO configuration for the interrupt pin
    static const struct gpio_dt_spec interrupt_pin = {
        .port = DEVICE_DT_GET(DT_NODELABEL(gpio0)),
        .pin = INTERRUPT_PIN,
        .dt_flags = GPIO_INPUT | GPIO_INT_EDGE_RISING,
    };
    
    // GPIO callback structure
    static struct gpio_callback interrupt_cb_data;
    
    // Variables to store interrupt data
    volatile int interrupt_count = 0;
    volatile uint32_t timestamps[MAX_INTERRUPTS];  // Array to store interrupt timestamps
    
    // ESB payload for data transmission
    static struct esb_payload tx_payload = ESB_CREATE_PAYLOAD(0);
    
    // Function prototypes
    static void interrupt_handler(const struct device *dev, struct gpio_callback *cb, uint32_t pins);
    static int esb_initialize(void);
    static void calculate_and_transmit(void);
    
    // Interrupt handler
    static void interrupt_handler(const struct device *dev, struct gpio_callback *cb, uint32_t pins) {
        if (interrupt_count < MAX_INTERRUPTS) {
            timestamps[interrupt_count++] = k_cycle_get_32();  // Capture the timestamp
        }
    }
    
    // Initialize Enhanced ShockBurst (ESB)
    static int esb_initialize(void) {
        int err;
        uint8_t base_addr_0[4] = {0xE7, 0xE7, 0xE7, 0xE7};
        uint8_t base_addr_1[4] = {0xC2, 0xC2, 0xC2, 0xC2};
        uint8_t addr_prefix[8] = {0xE7, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8};
    
        struct esb_config config = ESB_DEFAULT_CONFIG;
        config.protocol = ESB_PROTOCOL_ESB_DPL;
        config.bitrate = ESB_BITRATE_1MBPS;
        config.mode = ESB_MODE_PTX;
        config.tx_output_power = ESB_TX_POWER_0DBM;
    
        err = esb_init(&config);
        if (err) {
            return err;
        }
    
        err = esb_set_base_address_0(base_addr_0);
        if (err) {
            return err;
        }
    
        err = esb_set_base_address_1(base_addr_1);
        if (err) {
            return err;
        }
    
        err = esb_set_prefixes(addr_prefix, ARRAY_SIZE(addr_prefix));
        return err;
    }
    
    // Calculate the average frequency and send via ESB
    static void calculate_and_transmit(void) {
        if (interrupt_count < 2) {
            LOG_INF("Not enough interrupts to calculate frequency.");
            return;
        }
    
        uint32_t total_ticks = 0;
        for (int i = 1; i < interrupt_count; i++) {
            total_ticks += (timestamps[i] - timestamps[i - 1]);
        }
    
        uint32_t average_ticks = total_ticks / (interrupt_count - 1);
    
        // Format the payload as "XX_YYY"
        char payload[8];
        snprintf(payload, sizeof(payload), "%02d_%03d", average_ticks, interrupt_count);
    
        // Copy the formatted string into the ESB payload
        strncpy((char *)tx_payload.data, payload, sizeof(tx_payload.data));
        tx_payload.length = strlen(payload);
    
        // Send the payload
        if (esb_write_payload(&tx_payload)) {
            LOG_ERR("Failed to send payload.");
        } else {
            LOG_INF("Data sent: %s", payload);
        }
    }
    
    void main(void) {
        int err;
    
        // Configure the interrupt pin
        gpio_pin_configure(interrupt_pin.port, interrupt_pin.pin, GPIO_INPUT);
        gpio_pin_interrupt_configure(interrupt_pin.port, interrupt_pin.pin, GPIO_INT_EDGE_RISING);
    
        gpio_init_callback(&interrupt_cb_data, interrupt_handler, BIT(interrupt_pin.pin));
        gpio_add_callback(interrupt_pin.port, &interrupt_cb_data);
    
        // Initialize ESB
        err = esb_initialize();
        if (err) {
            LOG_ERR("Failed to initialize ESB, error: %d", err);
            return;
        }
    
        LOG_INF("System initialized. Waiting for interrupts...");
    
        while (1) {
            interrupt_count = 0;
    
            // Monitor interrupts for 100 ms
            k_sleep(K_MSEC(100));
    
            // Calculate and send the result
            calculate_and_transmit();
    
            // Wait before the next sampling
            k_sleep(K_MSEC(900));
        }
    }
    


    prj.conf

    CONFIG_NCS_SAMPLES_DEFAULTS=y
    CONFIG_ESB=y
    CONFIG_DK_LIBRARY=y
    
    # Power management
    CONFIG_PM_DEVICE=y
    CONFIG_PM_DEVICE_RUNTIME=y
    
    


    i get an average power consumption of 520uA

    For some reason if i plug in the dongle in an USB port the consomption raise up to 2mA.
    SB2 is cut, CB1 soldiered. If i only put the device in USB port (without power it wit PKK) it can't put the device in bootloader mode.

    If i just put 

    void main(void) {
    while (1) {
    }
    }

    i get 2.91 mA

Reply
  • Hello and thanks for you reply.

    The fact that i think it's deprectated is discussed here :
    devzone.nordicsemi.com/.../can-t-enable-has_pm-to-support-power-management-notification

    And in the NRF KCONFIG GUI i can't activate it : i can't check system power managment checkbox

    And in the IDE i have the message : CONFIG_PM was assigned the value y, but got the value n. Missing dependencies:

    (SOC_SERIES_EFR32BG22 && SOC_VENDOR_SILABS) || (SOC_SERIES_EFR32BG27 && SOC_VENDOR_SILABS) || (SOC_SERIES_EFR32MG24 && SOC_VENDOR_SILABS) || (SYS_CLOCK_EXISTS && HAS_PM)

    My full functionnal goal is :

    Get GPIO state on/off on a pin by interrupt reading. I need to get the time spent between 2 high state interrupts, in order to calculate the frequency of the input signal.
    It is from 2000hz to 5000hz, for what i count tics spent between 2 interrupts events on the 32khz clock
    Id do that for 100ms, i get at the end the average of frequencies, i send it by radio to third application then i go to deep sleep for 900ms and need a wake up by a timer.

    // Include necessary Zephyr and Nordic headers
    #include <zephyr/drivers/gpio.h>
    #include <zephyr/kernel.h>
    #include <zephyr/logging/log.h>
    #include <esb.h>
    #include <stdio.h>
    
    LOG_MODULE_REGISTER(esb_ptx, CONFIG_ESB_PTX_APP_LOG_LEVEL);
    
    #define INTERRUPT_PIN 15               // GPIO pin for the interrupt
    #define MAX_INTERRUPTS 500             // Max number of interrupts to capture
    
    // Define GPIO configuration for the interrupt pin
    static const struct gpio_dt_spec interrupt_pin = {
        .port = DEVICE_DT_GET(DT_NODELABEL(gpio0)),
        .pin = INTERRUPT_PIN,
        .dt_flags = GPIO_INPUT | GPIO_INT_EDGE_RISING,
    };
    
    // GPIO callback structure
    static struct gpio_callback interrupt_cb_data;
    
    // Variables to store interrupt data
    volatile int interrupt_count = 0;
    volatile uint32_t timestamps[MAX_INTERRUPTS];  // Array to store interrupt timestamps
    
    // ESB payload for data transmission
    static struct esb_payload tx_payload = ESB_CREATE_PAYLOAD(0);
    
    // Function prototypes
    static void interrupt_handler(const struct device *dev, struct gpio_callback *cb, uint32_t pins);
    static int esb_initialize(void);
    static void calculate_and_transmit(void);
    
    // Interrupt handler
    static void interrupt_handler(const struct device *dev, struct gpio_callback *cb, uint32_t pins) {
        if (interrupt_count < MAX_INTERRUPTS) {
            timestamps[interrupt_count++] = k_cycle_get_32();  // Capture the timestamp
        }
    }
    
    // Initialize Enhanced ShockBurst (ESB)
    static int esb_initialize(void) {
        int err;
        uint8_t base_addr_0[4] = {0xE7, 0xE7, 0xE7, 0xE7};
        uint8_t base_addr_1[4] = {0xC2, 0xC2, 0xC2, 0xC2};
        uint8_t addr_prefix[8] = {0xE7, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8};
    
        struct esb_config config = ESB_DEFAULT_CONFIG;
        config.protocol = ESB_PROTOCOL_ESB_DPL;
        config.bitrate = ESB_BITRATE_1MBPS;
        config.mode = ESB_MODE_PTX;
        config.tx_output_power = ESB_TX_POWER_0DBM;
    
        err = esb_init(&config);
        if (err) {
            return err;
        }
    
        err = esb_set_base_address_0(base_addr_0);
        if (err) {
            return err;
        }
    
        err = esb_set_base_address_1(base_addr_1);
        if (err) {
            return err;
        }
    
        err = esb_set_prefixes(addr_prefix, ARRAY_SIZE(addr_prefix));
        return err;
    }
    
    // Calculate the average frequency and send via ESB
    static void calculate_and_transmit(void) {
        if (interrupt_count < 2) {
            LOG_INF("Not enough interrupts to calculate frequency.");
            return;
        }
    
        uint32_t total_ticks = 0;
        for (int i = 1; i < interrupt_count; i++) {
            total_ticks += (timestamps[i] - timestamps[i - 1]);
        }
    
        uint32_t average_ticks = total_ticks / (interrupt_count - 1);
    
        // Format the payload as "XX_YYY"
        char payload[8];
        snprintf(payload, sizeof(payload), "%02d_%03d", average_ticks, interrupt_count);
    
        // Copy the formatted string into the ESB payload
        strncpy((char *)tx_payload.data, payload, sizeof(tx_payload.data));
        tx_payload.length = strlen(payload);
    
        // Send the payload
        if (esb_write_payload(&tx_payload)) {
            LOG_ERR("Failed to send payload.");
        } else {
            LOG_INF("Data sent: %s", payload);
        }
    }
    
    void main(void) {
        int err;
    
        // Configure the interrupt pin
        gpio_pin_configure(interrupt_pin.port, interrupt_pin.pin, GPIO_INPUT);
        gpio_pin_interrupt_configure(interrupt_pin.port, interrupt_pin.pin, GPIO_INT_EDGE_RISING);
    
        gpio_init_callback(&interrupt_cb_data, interrupt_handler, BIT(interrupt_pin.pin));
        gpio_add_callback(interrupt_pin.port, &interrupt_cb_data);
    
        // Initialize ESB
        err = esb_initialize();
        if (err) {
            LOG_ERR("Failed to initialize ESB, error: %d", err);
            return;
        }
    
        LOG_INF("System initialized. Waiting for interrupts...");
    
        while (1) {
            interrupt_count = 0;
    
            // Monitor interrupts for 100 ms
            k_sleep(K_MSEC(100));
    
            // Calculate and send the result
            calculate_and_transmit();
    
            // Wait before the next sampling
            k_sleep(K_MSEC(900));
        }
    }
    


    prj.conf

    CONFIG_NCS_SAMPLES_DEFAULTS=y
    CONFIG_ESB=y
    CONFIG_DK_LIBRARY=y
    
    # Power management
    CONFIG_PM_DEVICE=y
    CONFIG_PM_DEVICE_RUNTIME=y
    
    


    i get an average power consumption of 520uA

    For some reason if i plug in the dongle in an USB port the consomption raise up to 2mA.
    SB2 is cut, CB1 soldiered. If i only put the device in USB port (without power it wit PKK) it can't put the device in bootloader mode.

    If i just put 

    void main(void) {
    while (1) {
    }
    }

    i get 2.91 mA

Children
No Data
Related