Getting low power consumption, with external sensors

Hi,

Our application is based on the asset tracker, and has a similar overall process of waking up, taking sensor readings and going back to sleep. This application is used in a number of our products, some of which need to run on small batteries. Over the last week I've been working on getting power consumption down - and I'm currently at 20 uA including the other devices on our board (like high-side switches and external flash memory). 20 uA is awesome!

However, I now need to introduce back the functionality - like measuring external sensors using I2C. I see some sensor drivers, for example the INA219 that I'm using to start with, supports Zephyr's CONFIG_PM_DEVICE option and handles power down. When asked, the INA219 driver will set the device into a power down mode - and I have tested this and found that the INA219 itself uses very little current when in this mode.

How do we go about making this work with the nRF9160? For that option to work, we need the Zephyr Power Manager to be running with a power policy - and I note the CAF Power Manager actually disables that by requiring CONFIG_PM_POLICY_CUSTOM and returning NULL back to the Zephyr Power Manager. Instead, I'm using the residency-based policy (see here: System Power Management — Zephyr Project Documentation) and I've added a couple of power states to my device tree:

/ {
    power-states {
        active: active {
            compatible = "zephyr,power-state";
            power-state-name = "active";
            min-residency-us = < 1000 >;
        };
        idle: idle {
            compatible = "zephyr,power-state";
            power-state-name = "suspend-to-idle";
            min-residency-us = <2000000>;
            exit-latency-us = < 1000 >;
        };
    };

    cpus {
        cpu@0 {
            cpu-power-states = <&active &idle>;
        };
    };
};
When I run the debugger, I can see Zephyr actually runs the function with an initial tick value of 7FFFFFFF (-1, or K_TICKS_FOREVER). Strangely, that doesn't seem to select my first policy (active) and decides to shut down the INA219 on boot - potentially before the initialisation even runs.
Before I spend too much time going down this route, I wanted to ask and see what the Nordic-recommended approach was. How should I be preparing by devices for power down? Is there an event I can capture (like the power down event from CAF), and then somehow ask the external sensors to power down using their PM integration? Does Nordic have any example code where they use a high-current sensor (600 uA is considered high in my application!) and shut it down before sleeping?
I notice there's no nRF91-series example code (in my NCS 2.2.0) showing how those CPU power states are used in the device tree - but I also don't see any using that CAF Power Manager (machine_learning targets the nRF53, and nrf_desktop targets the nRF52).

Searching for power savings, I find a number of references to this article: Measuring PSM idle current on the nRF91 DK - Blog Archives - Blog - Nordic DevZone (nordicsemi.com). This article was really useful in achieving my 20 uA figure (I was able to get even less by removing all the other components on my PCB) - but that relies on disabling a lot of things at compile-time and doesn't explain the power management of external devices.
I'm planning on continuing to investigate these cpu-power-states in Zephyr but I would appreciate any advice you can provide!
Kind regards,
Dan
  • I should note, the machine_learning sample also targets the nRF52, and the nrf_desktop sample also targets the nRF53. It would be more appropriate to suggest these two examples target both nRF52 and nRF53 :)

  • I also just realised 0x7fffffff is /not/ -1 - it's the largest value we can store in a signed 32-bit integer. Not sure why this is so large but it explains why I'm getting the lowest power state selected.

  • Hi Dan

    Essentially the CAF Power Manager module was introduced at a time when the power management functionality in Zephyr was quite limited, and as you say there is a limited number of applications that use it. 

    In general it is better to use standardized Zephyr API's over more specialized API's where possible, and my recommendation would be to use the PM_DEVICE_RUNTIME feature if you want to put I2C sensors into and out of sleep from the application. 

    Using the Zephyr power state mechanism doesn't really apply to Nordic devices because of the way power management is handled on a low level. Essentially all you have to do to enable low power in most nRF devices is to enter system on idle mode using WFI or WFE, and make sure not to keep peripherals enabled that don't need to be. 

    This was a bit of a short answer to a long question. Please let me know if anything is still unclear and I will do my best to help Slight smile

    Best regards
    Torbjørn

  • An answer is an answer, Torbjørn Smiley! You've given me some confidence that this is a beaten track - using the Zephyr power management at least.

    What I have right now is a custom policy that will return the PM_STATE_RUNTIME_IDLE state when we want to go to sleep (which is arguable still residency based). The issue I have, even with CONFIG_PM_DEVICE_RUNTIME enabled is the system crashes somewhere (having trouble actually debugging this, but it seems related to a K_FOREVER wait getting killed perhaps by the WDT?).

    Does Nordic have any examples using this runtime, and using some mechanism to signal to modules that their drivers need to be powered down?

    In my pm_notfifier callbacks, I get this same crash with i2c-based devices (like INA219) but also if I try a simple uart_poll_tx() when we enter and exit the PM_STATE_RUNTIME_IDLE. Seems like this could be an order of execution issue - perhaps the peripheral is already asleep and I'm waiting too long for a semaphore?

    I notice in later commits to the Zephyr branch, Nordic seems to have deprecated CONFIG_PM? See here for what I'm referencing: [nrf fromtree] soc: arm: nordic_nrf: drop PM hooks · nrfconnect/sdk-zephyr@15f47a6 (github.com).

    I've got a couple of other changes I need to get out early this week, but I'll be following along with any replies and hope to get back to this later this week or early next.

  • Hi Dan

    It is true that CONFIG_PM is being deprecated, since it doesn't really have any use for Nordic devices. All Nordic devices practically only have one sleep mode (not including system OFF mode, which can only be used in very special cases/applications), and this sleep mode will be entered automatically by the idle thread whenever there is nothing to do. 

    In other words you only need to enable CONFIG_PM_DEVICE_RUNTIME (and by extension CONFIG_PM_DEVICE) in order to ensure that drivers disable the underlying hardware peripherals when not in use, to ensure low sleep power. 

    With PM_DEVICE_RUNTIME enabled the I2C driver should make sure to automatically disable the TWIM module in between transactions, and for the higher level sensor driver there is nothing else needed to be done in order to ensure low sleep current. 

    Could you elaborate why you feel you need to use a power policy? 
    Using a power policy doesn't really add any value when developing for Nordic devices, for the reasons mentioned above. 

    Best regards
    Torbjørn

Related