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'm pretty confident the CONFIG_PM_DEVICE_RUNTIME works well in the nrfx_twim driver, disabling the peripheral as it should.

    To avoid the X-Y problem, here's what I want to achieve. My external sensors, like LEDs, need to be powered off when not being used. The Zephyr features currently handle the CPU itself - and my application needs to know when it can power sensors off and on. I've picked the CONFIG_PM as a possible solution.

    Using the INA219 again as my example, it has some built-in averaging that gives better readings so I want to enable that and let the sensor run for a bit. Once that's all done I then take the reading and can put the sensor back to sleep. Using the CONFIG_PM, the module gets a notification (based on the power policy) about when the system enters and exits various modes. When the CPU is going to sleep for a while, the sensor can know to just power down completely. The module using that sensor doesn't even have to know about this, as it's all handled by Zephyr and the driver itself.

    Another option is to wait for the APP_EVT_DATA_GET event, power up the sensor and schedule a timer to fire when the INA219 readings are ready. Once I've got that, I pass the reading to the data module and put the INA219 back to sleep. This is not much extra work and also ensures the sensor is only powered on when in use (not every time the CPU is in use). The only API available to the application for this is pm_device_action_run() and needs CONFIG_PM and CONFIG_PM_DEVICE to be accessible. I also requires we either have a NULL power policy or we only use PM_STATE_RUNTIME_IDLE to avoid automated control from the PM suspend loop.

    Do you have a recommendation on how I can power my external I2C sensors up and down?

  • Hi Dan

    Thanks for providing more context, it is indeed easy to end up in the XY trap Slight smile

    The problem of power management with sensor drivers in Zephyr is that every driver is different, and some of them don't implement any power management features whatsoever. 

    The idea behind the PM system is good on paper, but because it is not implemented across the board it becomes ineffective in practice. 

    I believe the best approach is to have the application keep track of when the sensors are needed and when they can be put to sleep, and control this manually, rather than depending on the PM system to do it for you. 

    Even with this approach some of the sensor drivers might need to be modified in order to properly support low power operation, as this hasn't necessarily been a priority when the drivers where implemented. 

    pm_device_action_run() won't work if the driver doesn't implement any power management. Also, be aware that the pm_device_action_run() function don't do reference counting, so if you need to share a sensor between multiple threads/modules then you would have to handle this separately. 

    And if you really want to go the extra mile you can improve the driver and issue a PR to have it included in upstream Zephyr Slight smile

    Best regards
    Torbjørn

  • Thanks, Torbjørn, for your advice on this. I think you're right that the best way forward is having each module handle when they need the sensor and disabling it when they do not. For some sensors this might require introducing delays, but the event architecture is already available to handle this from the data module point of view - so I don't foresee any issues.

    Yes, absolutely, we've got some drivers to contribute upstream. This will be another case of minor tweaks we can push back to help out the Zephyr Project (and the downstream NCS port as a result!).

    As an added side-benefit of doing it in the modules that use the sensors, any hacks needed to make it work (to circumvent API for example) feels less bad when it's contained in a small part of the code base :). A concrete example would be accessing the I2C bus directly (device->config->bus) to push the device into sleep mode. This was how I prototyped by savings the first time around.

  • Hi Dan

    Delays shouldn't be a problem as long as you handle it in an asynchronous manner. One advantage of using an RTOS is that (seemingly) blocking delays are fine since only the current thread is actually halted, and the system itself will go to sleep, or service other threads, in the mean time. 

    Generally the application should know when sensors are needed and when they are not, allowing you to optimize sensor usage in order to get a good compromise between power consumption and performance. 

    It is much appreciated if you contribute back upstream the improvements you make, typically that is not something that gets prioritized when time is short ;)

    dan_opito said:
    As an added side-benefit of doing it in the modules that use the sensors, any hacks needed to make it work (to circumvent API for example) feels less bad when it's contained in a small part of the code base :). A concrete example would be accessing the I2C bus directly (device->config->bus) to push the device into sleep mode.

    If you modify the driver directly why would accessing the I2C bus directly be a hack? 

    By modifying the drivers directly you should be able to add support for power management at the right level, without having to hack it in at a higher level.  

    Best regards
    Torbjørn

Related