Unable to obtain low power use when idle

We have designed and built a battery-powered 'IoT' measurement device, based on the nRF9160 (SICA-B1A, MFW v1.3.3), currently in prototype field-testing phase.

The application is developed in Rust (all our green-field embedded development is done in Rust from 2019 onward) and the application functionally nearly finished. One of the things we're still chasing is the high idle use of the device, measuring ~1.7mA in the MCU power domain. We have been working quite hard to tackle this, but haven't found the culprit yet.

In order to eliminate any parasitic power use of the hardware components in our product, we ended up making a custom board that the is only populated with the nRF9160, a couple of decoupling caps and a SIM slot (no DC/DC psu, no sensors etc..) The goal is to have a "nothing" application, doing nothing expect having an "application" running with the lowest power possible to set the baseline power usage target for when all peripherals are connected in a fully populated/functional product.

When we run the nRF9160 with a 'hello world' type application the power use is (static, flat) 2.7mA @3.3V (the program ends in a WFI(), so CPU should be idle). We tried disabling as much MCU peripherals as possible, with no result: draw remains a steady 2.7mA. What we did find is, that when the modem is initialized, but not enabled (calling `nrf_modem_init` in the nrfxlib), the power use drops by about 1 mA (to 1.7mA). Still way too high, but better... unexpected(!), but better..

Although there's next-to-nothing on our board, we started to doubt our purpose-created hardware; how can it be that a bare-as-bone hardware and software is still drawing a whopping 2.7 mA's.. there has to be something wrong with the hardware.. But lo and behold when we flashed a 'do nothing' (single `printk` in the `main()`) sample using the Zephyr based connect SDK, the power use dropped to 4µA.. that's more like it! (in our final product anything <50µA idle use would be acceptable). At least now we know our hardware is capable of a very low power usage. We fruitlessly invested another couple of hours trying to find what is the culprit, no smoking gun was found why we weren't able to get to this level in our programming environment.

Here's the summary of power uses in different setups, maybe something obvious can be distilled from this(?):

  • Without modem initialized
    • Idle loop (WFI()): 2.7mA
    • REGULATOR System off: 1.5mA


  • With modem initialized
    • Idle loop (WFI()): 1.7mA
    • REGULATOR System off:  <100µA

With the modem is initialized and "System OFF mode" (REGULATORS_NS) is enabled we get closer to low power territory at sub 100µA draw, but still nowhere near the 4µA that should be obtainable. Furthermore using the "system off" mode for this is undesirable.

The application is running in Non-Secure mode. Too boot into our Non-Secure app, we use a precompiled SPM binary (from Connect SDK). Another line of thought is that the SPM does something that prevents us to get into the low power modes. The "hello world" example that is running at 4µA uses the TFM, unfortunately I wasn't able to get our application to run by using this TFM image, so I could not test that loader (possibly TFM need some more secure parameters set or in flash?).

All in all there must be some initialization or some disabling we're missing.. Hope anyone can shed some light on this..

Thanks in advance!

Ps. All power measurements are made using a PPK2 as power supply/analyzer. Power supply voltage was set to 3.3 Volt. The DUT was disconnected from the debugger and power-cycled before any measurements were taken.

For completeness the (part of the) schematics which was is populated on the board:

/resized-image/__size/2080x2070/__key/communityserver-discussions-components-files/4/pastedimage1693836927606v3.png

Parents
  • Hi Rogier, sorry for the delay. I understand that you are in contact with our FAE. Please let us know if you would like to continue parts of the discussion here.

  • Hi Helsing,

    Thanks for getting back! Yes indeed, I got in contact with the very helpful local Application Engineer. I had a nice conversation with him, but unfortunately this didn't materialize into concrete pointers to the issue at hand. (The FAE will be forwarding me some additional information/pointers regarding the issue).

    In the mean time I did some more "fiddling" and got the power down to 1.2mA, not a huge improvement, but nevertheless an improvement. This was the result of disabling the DC/DC converter on the SoC. (peripherals.REGULATORS_NS.dcdcen.write(|w| w.dcdcen().disabled()))

    So, in summary power use comes to this now:

    • Just call `wfi()`: 2.7mA
    • Init modem before: 1.7mA
    • Disable (i.e. don't enable) DC/DC convertor: 1.2mA
    • SYSTEM_OFF: 2-4µA

    Note: the current draw in SYSTEM_OFF mode can be "wandering about" (up to a couple of 100µA). This looks to be caused by dangling GPIO lines, the lowest power in SYSTEM_OFF mode is obtained when all GPIO pins' SENSE setting is disabled.

    For reference disabling most peripherals (code below), does not make seem to make any difference to the power use. Therefore I'd reckon that the peripherals weren't enabled in the first place. Something else must be sucking some power (maybe some peripheral in the _S (secure) partition, would that be a possibility? Or maybe some ARM core peripheral (already tried disabling SYSTICK)...?

    For reference the (not very exciting, Rust) code for disabling most peripherals:

    pub fn disable_periphs(p: &Peripherals) {
        defmt::println!("Disabling periphs!");

        p.UARTE0_NS.enable.write(|w| w.enable().disabled());
        p.UARTE1_NS.enable.write(|w| w.enable().disabled());
        p.UARTE2_NS.enable.write(|w| w.enable().disabled());
        p.UARTE3_NS.enable.write(|w| w.enable().disabled());

        p.TWIM0_NS.enable.write(|w| w.enable().disabled());
        p.TWIM1_NS.enable.write(|w| w.enable().disabled());
        p.TWIM2_NS.enable.write(|w| w.enable().disabled());
        p.TWIM3_NS.enable.write(|w| w.enable().disabled());

        p.TWIS0_NS.enable.write(|w| w.enable().disabled());
        p.TWIS1_NS.enable.write(|w| w.enable().disabled());
        p.TWIS2_NS.enable.write(|w| w.enable().disabled());
        p.TWIS3_NS.enable.write(|w| w.enable().disabled());

        p.SPIM0_NS.enable.write(|w| w.enable().disabled());
        p.SPIM1_NS.enable.write(|w| w.enable().disabled());
        p.SPIM2_NS.enable.write(|w| w.enable().disabled());
        p.SPIM3_NS.enable.write(|w| w.enable().disabled());

        p.SPIS0_NS.enable.write(|w| w.enable().disabled());
        p.SPIS1_NS.enable.write(|w| w.enable().disabled());
        p.SPIS2_NS.enable.write(|w| w.enable().disabled());
        p.SPIS3_NS.enable.write(|w| w.enable().disabled());

        p.PWM0_NS.enable.write(|w| w.enable().disabled());
        p.PWM1_NS.enable.write(|w| w.enable().disabled());
        p.PWM2_NS.enable.write(|w| w.enable().disabled());
        p.PWM3_NS.enable.write(|w| w.enable().disabled());

        p.SAADC_NS.enable.write(|w| w.enable().disabled());

        p.TIMER0_NS.tasks_stop.write(|w| w.tasks_stop().trigger());
        p.TIMER1_NS.tasks_stop.write(|w| w.tasks_stop().trigger());
        p.TIMER2_NS.tasks_stop.write(|w| w.tasks_stop().trigger());

        p.RTC0_NS.tasks_stop.write(|w| w.tasks_stop().trigger());
        p.RTC1_NS.tasks_stop.write(|w| w.tasks_stop().trigger());
    }

Reply
  • Hi Helsing,

    Thanks for getting back! Yes indeed, I got in contact with the very helpful local Application Engineer. I had a nice conversation with him, but unfortunately this didn't materialize into concrete pointers to the issue at hand. (The FAE will be forwarding me some additional information/pointers regarding the issue).

    In the mean time I did some more "fiddling" and got the power down to 1.2mA, not a huge improvement, but nevertheless an improvement. This was the result of disabling the DC/DC converter on the SoC. (peripherals.REGULATORS_NS.dcdcen.write(|w| w.dcdcen().disabled()))

    So, in summary power use comes to this now:

    • Just call `wfi()`: 2.7mA
    • Init modem before: 1.7mA
    • Disable (i.e. don't enable) DC/DC convertor: 1.2mA
    • SYSTEM_OFF: 2-4µA

    Note: the current draw in SYSTEM_OFF mode can be "wandering about" (up to a couple of 100µA). This looks to be caused by dangling GPIO lines, the lowest power in SYSTEM_OFF mode is obtained when all GPIO pins' SENSE setting is disabled.

    For reference disabling most peripherals (code below), does not make seem to make any difference to the power use. Therefore I'd reckon that the peripherals weren't enabled in the first place. Something else must be sucking some power (maybe some peripheral in the _S (secure) partition, would that be a possibility? Or maybe some ARM core peripheral (already tried disabling SYSTICK)...?

    For reference the (not very exciting, Rust) code for disabling most peripherals:

    pub fn disable_periphs(p: &Peripherals) {
        defmt::println!("Disabling periphs!");

        p.UARTE0_NS.enable.write(|w| w.enable().disabled());
        p.UARTE1_NS.enable.write(|w| w.enable().disabled());
        p.UARTE2_NS.enable.write(|w| w.enable().disabled());
        p.UARTE3_NS.enable.write(|w| w.enable().disabled());

        p.TWIM0_NS.enable.write(|w| w.enable().disabled());
        p.TWIM1_NS.enable.write(|w| w.enable().disabled());
        p.TWIM2_NS.enable.write(|w| w.enable().disabled());
        p.TWIM3_NS.enable.write(|w| w.enable().disabled());

        p.TWIS0_NS.enable.write(|w| w.enable().disabled());
        p.TWIS1_NS.enable.write(|w| w.enable().disabled());
        p.TWIS2_NS.enable.write(|w| w.enable().disabled());
        p.TWIS3_NS.enable.write(|w| w.enable().disabled());

        p.SPIM0_NS.enable.write(|w| w.enable().disabled());
        p.SPIM1_NS.enable.write(|w| w.enable().disabled());
        p.SPIM2_NS.enable.write(|w| w.enable().disabled());
        p.SPIM3_NS.enable.write(|w| w.enable().disabled());

        p.SPIS0_NS.enable.write(|w| w.enable().disabled());
        p.SPIS1_NS.enable.write(|w| w.enable().disabled());
        p.SPIS2_NS.enable.write(|w| w.enable().disabled());
        p.SPIS3_NS.enable.write(|w| w.enable().disabled());

        p.PWM0_NS.enable.write(|w| w.enable().disabled());
        p.PWM1_NS.enable.write(|w| w.enable().disabled());
        p.PWM2_NS.enable.write(|w| w.enable().disabled());
        p.PWM3_NS.enable.write(|w| w.enable().disabled());

        p.SAADC_NS.enable.write(|w| w.enable().disabled());

        p.TIMER0_NS.tasks_stop.write(|w| w.tasks_stop().trigger());
        p.TIMER1_NS.tasks_stop.write(|w| w.tasks_stop().trigger());
        p.TIMER2_NS.tasks_stop.write(|w| w.tasks_stop().trigger());

        p.RTC0_NS.tasks_stop.write(|w| w.tasks_stop().trigger());
        p.RTC1_NS.tasks_stop.write(|w| w.tasks_stop().trigger());
    }

Children
  • Ok.. I knew it had to be something trivial...

    I think I have found the leak; somewhere hidden in my code I had the line:

    core::ptr::write_volatile(0x4000_5C04 as *mut u32, 0x02);

    Which is a mitigation for anomaly [17] Debug and Trace: LTE modem stops when debugging through SWD interface This erratum has been long-fixed in the production hardware revisions, so is never needed anymore. Face palm

    Anyway, the mystery seems solved, so very happy with that. :) Now we can properly power-optimize the design further.

    Btw, initializing the modem in my code lowered the current draw by ~1mA and you mentioned that not initializing the modem causes a current draw of "a few" mA's. So this being lower than expected was actually a pointer in this direction. (in hindsight things like this are always much easier to see... ;))

    Anyway, I will close the ticket. Thanks for your time and help!

  • Great to hear that you were able to lower the current.

    helsing said:
    The modem will draw a large current (a few mA) if it has not been initialized. After initialized the current will drop to a few uA. (This is listed as an errata/known issue, I will post the link later.)
    rogierl said:
    Btw, initializing the modem in my code lowered the current draw by ~1mA and you mentioned that not initializing the modem causes a current draw of "a few" mA's. So this being lower than expected was actually a pointer in this direction. (in hindsight things like this are always much easier to see... ;))

    Sorry, I forgot to post the link to the Known Issue in Zephyr, "NCSDK-10106: Elevated current consumption when using applications without Modem library on nRF9160". This should lower the sleep current from 3mA down to a few uA. Check PR #5648 to see how the modem library is enabled when building samples for nRF960.

Related