nRF5340 SAADC power consumption

I'm profiling the current consumption of our device, on an nRF5340 module, at SDK 2.1.0.

So far as I can tell, the ADC is using approximately 1000 uA. It's sampling via PPI, and is triggered from an external clock signal at 1 kH.

The current consumption figures for the SAADC here show typical current consumption based on different configuration options. It looks like ~1000 uA is on the high end of what is expected, and that setting LPOP=LowPower would save us a considerable amount of current.

However I can't find anything about LPOP or LowPower anywhere else, either in the SAADC documentation or in the SDK.

Any tips as to where This can be enabled? I've seen this for an old version of the SDK, but it seems to come down to setting saadc_config.low_power_mode = true; which isn't part of the config struct in the current SDK.

  • These are the actual SDK calls we're making, I've pulled them together from various files in our project but I think this covers all of it:

    static const nrfx_gpiote_in_config_t pin_interrupt_config = {
        .sense = NRF_GPIOTE_POLARITY_HITOLO,
        .pull = NRF_GPIO_PIN_PULLUP,
        .is_watcher = false,
        .hi_accuracy = true,
        .skip_gpio_setup = false,
    };

    nrfx_gpiote_in_init(KHZ_CLOCK_PIN, &pin_interrupt_config, dummy_handler);
    nrfx_gpiote_in_event_enable(KHZ_CLOCK_PIN, true);

    uint32_t clock_event_address = nrf_gpiote_event_address_get(NRF_GPIOTE, nrfx_gpiote_in_event_get(KHZ_CLOCK_PIN));

    uint32_t task_address = nrf_saadc_task_address_get(NRF_SAADC, NRF_SAADC_TASK_SAMPLE);

    ppi_init_channel_one(clock_event_address, task_address);

    and dummy_handler passed in to the init function is an empty function.

    I did just remember we were previously calling nrfx_gpiote_in_init with NULL instead of a callback handler, but when I updated to SDK 2.1.0 that started causing a hard fault as the SDK was attempting to execute NULL.

  • The second argument to nrfx_gpiote_in_event_enable() determines if the interrupt is enabled or not. If you set that to false, the pin should not generate interrupts.

  • Hi Jørgen,

    I tried doing that last week, and although it stopped the ISR being called, it didn't make much difference to the current (maybe 10 uA or so, but in context of >900 uA of current that's not a lot).

    So I went back through my code just commenting things out bit by bit to try to confirm exactly what part of enabling it was causing all the current draw. It turns out even initialising the interrupt causes a much higher current.

    Today I've been looking at the basic button sample project to confirm if the interrupt is still a problem without any of our code involved. Initially that was drawing 833 uA. I got that down somewhat to 632 uA by removing the LED code and changing it to use GPIO_INT_LEVEL_ACTIVE instead of EDGE_TO_ACTIVE to trigger the interrupt. But while searching for ways to reduce the GPIO interrupt power consumption I found for example this where they're complaining about a 100 uA current. So it still seems way too high.

    I've added a k_msleep call before doing anything in the button sample code, and I can see that's got a current consumption of about 22 uA. So that implies it's not anything on our board drawing excess power. And I've confirmed the interrupt doesn't get triggered, so it's also not that the sample code is connected up to some floating voltage and getting triggered at random, so it seems to be genuinely a sleep current of hundreds of micro amps just from enabling the interrupt.

    The sample code is using different API code as it's using the gpio module, not nrfx_gpiote, and it seems to be using PORT not PIN events. So that might explain why it was at 833 initially not 900+ as in our main project.

    But I think it should be much much lower than that. Looking again at the current consumption figures, it looks to me like it should be in ION_IDLE4 when waiting for an interrupt, which is 48 uA. What I'm wondering is if somehow it gets stuck in ION_IDLE3,128MHz which is 785 uA, and the only thing I can see close to the power consumption I'm getting.

    Thanks,

    Rory

  • Okay, I had the wrong idea a couple of days ago - in the sample button code, it sleeps for 1 ms in a while loop, and if I make that 1000 ms instead, the current consumption drops massively. I think that whole reply can be safely ignored!

    I've now copied over our interrupt code, so I can test it in isolation from the rest of our code. If I initialise the interrupt using a spare unconnected pin, I get current consumption of ~90 uA, and this is with the sense set to NRF_GPIOTE_POLARITY_HITOLO, which I suspect means we're doing edge detection, which adds some extra current. So that's encouraging.

    If I go back to using our external 1000 Hz RTC signal, I get just over 1 mA consumption. But I can set the prescaler on our external RTC to give different interrupt frequencies, and this is where things get weird...

    The pink line is 1000 Hz interrupts, and just over 1 mA current. The blue line is 250 Hz interrupts, and about 220 uA. The purple line is 500 Hz and oscillates between ~90 uA and ~220 uA.

    (The low power at the beginning is 2.5 seconds of sleep, the code to set up our external RTC over spi, and then 2.5 seconds of sleep again)

    All I can think is that the timing of the MCU going to sleep somehow requires > 1 ms for it to kick in, and so at 1000 Hz interrupts, we never get there. Or rather, I guess the idle current still has the HF oscillator on for ~1 ms?

    This is the config/init code I'm using - so the interrupt is disabled, and so button_pressed shouldn't be being executed. And I've done nothing to initialise the ADC or PPI.

        static const nrfx_gpiote_in_config_t pin_interrupt_config = {
            .sense = NRF_GPIOTE_POLARITY_HITOLO,
            .pull = NRF_GPIO_PIN_PULLUP,
            .is_watcher = false,
            .hi_accuracy = true,
            .skip_gpio_setup = false,
        };

        nrfx_gpiote_in_init(pin, &pin_interrupt_config, button_pressed);
        nrfx_gpiote_in_event_enable(pin, false);
  • Hi,

    Have you checked how long the execution time of the interrupt is? You can use GPIO toggling in the interrupt handler to check that, or toggle a GPIO at the end of the handler and compare this to the GPIO input. It is also possible to use DPPI and GPIOTE to toggle a GPIO from the POWER->EVENTS_SLEEPENTER and EVENTS_SLEEPEXIT to see when the CPU is actually in sleep.

    If for instance the interrupt takes 250us to complete, the ~1mA avereage current makes sense as it will make the Application CPU running about 25% of the time. This also makes sense for the 250Hz current. The 500Hz current does not make sense, but there could be some other error with this. Have you checked that the interrupt actually happens at 500Hz with this configuration? Is there anything in your application that corresponds with the rate that it toggles between high and low current consumption?

    Best regards,
    Jørgen

Related