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.

Parents
  • Hi,

    The LPOP options should not have been included in the product specifications, as this is an internal-only option. The LPOP is by default set to LowPower, and it should not be necessary to change this in the application. This will be fixed in the next release of the nRF5340 PS.

    Have you changed the application core's clock from 128 MHz to 64 MHz, as described under "Voltage and frequency scaling" in  System ON mode? Not doing this will increase the System ON sleep current significantly, as you can see in Sleep.

    When you say that the sampling is triggered by an external clock, do you use GPIOTE for triggering this? Have you tried triggering it using the internal RTC?

    Best regards,
    Jørgen

  • Hi,

    Rory Morrison said:
    We haven't changed the core clock, although from what I've found this morning it looks like it should default to 64 MHz.

    Yes, looks like this is correct. As long as you have verified that the application does not change it, this should not be the problem.

    Rory Morrison said:
    We have an external RTC with a 32.0 kHz crystal to give us an exact 1kHz clock signal, which we're using via a GPIOTE interrupt to trigger the PPI task for sampling.

    I assume you then are using the GPIOTE IN event, and not the PORT event? Due to this errata, it is not possible to use the LowPower setting for the GPIOTE IN event: GPIOTE with low-power latency setting does not register IN events in System ON IDLE. The LowLatency option will give higher current consumption, but according to ION_IDLE4 in Sleep current, this should not account for all the high current you are seeing. If you do not use the PORT event for any other GPIOs (like buttons, etc), it would also be possible to connect this to the PPI channel for a 1 kHz signal.

    Rory Morrison said:
    The logic is that an internal RTC would either run on the HF clock thus requiring the HF oscillator to be on at all times, burning more power, or it would run on the LF clock at 32,768 Hz and we'd need to apply a correction to get exactly 1000 samples a second instead of 1024.

    Yes, you are correct here. The RTC runs off the 32.768 kHz LFCLK and you will only be able to get 1024 or ~993 samples per second using this clock.

    Rory Morrison said:
    Is it possible to run an internal RTC from an external clock source on the Nrf53, and would that be more power efficient than using a GPIOTE interrupt?

    The LFCLK or RTCs does not have support for external clock sources in nRF5340.

    Rory Morrison said:
    Or might it be that for some reason the SAADC/PPI is keeping EasyDMA on between samples, and therefore just taking a single sample in an ISR would be better, as we could turn everything off between samples?

    This was the case in nRF52 series, but I'm not 100% sure if this have changed in nRF5340. I will try to write a low power sample using PPI (without any stopping of the SAADC between sampling) to verify this.

    Best regards,
    Jørgen

  • Hi Jørgen

    I've not got much further with this, but from experimenting a bit with sampling in an interrupt, I've found that our current draw is mostly from the interrupt firing - even with the ADC off and an empty function for the ISR the current still jumps by about 1 mA. (~350 uA goes to ~1.33 mA)

    I assume this is because either way the interrupt wakes the MCU to take a sample or generate a PPI event so we get more or less the same current draw. Is there an alternative way to generate a PPI event from a pin toggle without waking the MCU?

    My other thought is perhaps there are settings in zephyr governing sleep that I could tweak so the MCU goes back to sleep quicker?

    I did look at changing to a PORT event - am I right in thinking that's set by the hi_accuracy config option in the 

    nrfx_gpiote_in_config_t struct? I tried setting that to false and it didn't make much of a difference, in fact increasing the current draw by 5-10 uA if I remember correctly.
    Thanks,
    Rory

  • Rory Morrison said:
    I assume this is because either way the interrupt wakes the MCU to take a sample or generate a PPI event so we get more or less the same current draw. Is there an alternative way to generate a PPI event from a pin toggle without waking the MCU?

    Can you post the code you used to configure the GPIOTE pin? Interrupt should only happen if interrupt for the GPIOTE channel is enabled in the code.

    Rory Morrison said:

    I did look at changing to a PORT event - am I right in thinking that's set by the hi_accuracy config option in the 

    nrfx_gpiote_in_config_t struct? I tried setting that to false and it didn't make much of a difference, in fact increasing the current draw by 5-10 uA if I remember correctly.

    If the CPU wakes up to handle an interrupt for every GPIO toggle, this will likely cause the majority of the current draw. When the GPIOTE PORT event is used with the GPIOTE driver, the driver will have to check the state of each pin in SW, which will likely cause the interrupt to take a bit longer to complete. If this should give any improvement, you would have to connect the PORT event directly to the PPI channel, and disable any interrupts for the PORT event in the GPIOTE peripheral and driver. Like I said before, this will prevent you from using the PORT event for other GPIOs, and you should only enable GPIO sense for the GPIO that should trigger the sampling.

  • 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.

Reply Children
  • 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

Related