Low power with Xiao nrf52840 on Zephyr RTOS

I stumbled onto a solution that gives reasonable idle mode power with the Xiao nrf52840 on Zephyr RTOS. I’m currently getting 1.0uA in system off sleep and 12uA in idle mode. I thought others might be interested.

I was stuck for a long time with idle power between 190-570 uA. The solution I stumbled into gets close to the 1uA idle, and 3uA system_off sleep that was achievable with the Arduino code base as described in Getting lower power consumption on Seeed XIAO nRF52840.

I was originally working on Zephyr 3.2 and nrf Connect for desktop. When I couldn’t make headway I upgraded to zephyr 3.3. In zephyr 3.3 the flash device was upgraded to the QSPI interface. However, there is a bug where it fails to come out of deep sleep (Exiting Deep Power Down on a p25q16h not working), so I ported the SPI flash configuration in 3.2 to the 3.3 environment. In the process I moved the device to spi2 from spi3. This solved a major idle mode power issue. I went back to 3.2 and confirmed that this also addressed the problem in that release.

I’ve tried to disabled non-critical devices, but haven’t gotten any closer to the 3uA achieved on the Arduino code base. Next I might try to explicitly put the flash chip in deep power down mode while in idle.

I’d be interested if anyone has info on this topic or other ideas to try.

Attached code snippets demonstrate system on sleep (idle), system off sleep, and wake from system off. This should work with both Zephyr 3.2 and 3.3.

power_test.zip (3.4 KB)

Power measurements with PPK2:

  • Starting point: idle 190uA, system off sleep 186uA
  • Enable default flash device on sp3: idle 570uA, system off sleep 41uA
  • CONFIG_PM_DEVICE=y: idle 566uA, system off sleep 1.0 uA
  • Put flash on spi2: idle 22uA, system off sleep 1.0 uA
  • Switch to sense interrupts: idle 11.5uA, system off sleep 1.0uA
5 Likes

This is cool. I am also trying to get low-power mode working, but I am stuck at 730uA following some of the other threads in this forum. I was going to try Zephyr next to gain total control, looks like I’m not the only one :slight_smile: . I’m still new to zephyr/embedded systems and learning as I go. Could you share the board file you are using?

I’m using the plain Xiao nrf52840.

I found it easier to optimize for memory size with Zephyr since it is highly configurable. I found it harder to optimize for power with Zephyr because of the abstraction and indirection. My target application is using Zephyr on a nrf52810 which I needed to optimized for size. I experimented with arduino to help figure out baseline power.

I was able to get 1uA system_off sleep and 15uA system_on idle with unmodified arduino. With modified arduino interrupt code I was able to get 3uA system_on. The code is posted here Getting lower power consumption on Seeed XIAO nRF52840. The topic also has links to other pages on nrf52 power optimization.

The board file I’m using is the default file that comes with zephyr: xiao_ble

I didn’t see that file originally and created my own, but lately I’ve been using the above.

I played around with this today. I verified with the debugger that the flash is put into deep sleep before the call to the main() entry point.

I don’t have another idea on what device to look at next. Maybe I’ll browse the various nrf52840 threads and see if I can find a case where someone changed power usage by 7uA.

7uA might not matter to most use cases, but I feel like I’ve been challenged with a quest. I’ll try a few more options before letting it drop.

Success!

The attached zip is a zephyr application that uses .92 uA in system_off sleep and idles at 2.65 uA on the Xiao nrf52840 (not sense). It demonstrates low power gpio interrupts and wake from system_off sleep.
power_test.zip (5.8 KB)

If using GPIO interrupts, the solution with Zephyr out of the box saves 9-10 uA at idle over Arduino. If you modify the Arduino interrupt code as described here Getting lower power consumption on Seeed XIAO nRF52840, the performance is about the same.

Since it looked like the flash was in dpd with my previous solution, I wasn’t sure what was consuming the extra power. I decided to sacrifice my Xiao for the quest. I took off the RF shielding and then planned to pull of parts one-by-one until I found where the extra current was going. I pulled of the flash first and current went to the low power target.

I didn’t have new ideas on how to change the Zephyr spi/qspi solutions, so I disabled the Zephyr spi/qspi functionality and implemented a simple module that follows from the Adafruit_FlashTransport_QSPI. Both Zephyr and Adafruit_FlashTransport use nrfx/nrf under the hood. The flash could probably be made functional by porting over the rest of the functionality.

Xiao nrf52840 with RF shielding removed

3 Likes

Congratulations! That is some impressive investigation, very much appreciated. This experiment is probably too difficult for most weekend coders but your sharing is going to inspire some capable readers for sure.

1 Like

Hey @daCoder,

I have been following your answers for a few weeks now, and could really use your help with the following:

What I need to do: Using the Xiao nRF52840 Sense board, I want to put the system into deep sleep (lowest current consumption possible), and have it wake up and start advertising (as a beacon) when the IMU detects movement.

My setup: Seeed studio nRG52840 Sense board with board firmware v1.0 (for deep sleep support), non-embedded version.

What I’ve achieved so far: I am able to implement BLE advertising along with deep sleep. For example, I can make the board advertise for 30 seconds, then put it to sleep with a digital pin interrupt for wake-up. During sleep it consumes ~2-4 uA. I can successfully wake the device up by providing input (LOW in my case) to a Digital I/O pin.

Problem: I am facing two problems when adding the IMU component to it: a) The board is consuming ~800uA even when it’s supposed to be in the sleep state, and b) something keeps waking up the device instantly. I have provided a basic code example below. Can you please help me add the IMU interrupt to it so that it consumes low power in sleep state and successfully wakes up when IMU detects ‘x’ threshold of movement?

Thank you so much!!
Sheep Boi

Code:

#include <Adafruit_FlashTransport.h>

#include <bluefruit.h>

#include “LSM6DS3.h”

#include “Wire.h”

#include <Arduino.h>

#include <Adafruit_SPIFlash.h>

#define MANUFACTURER_ID 0x0059

Adafruit_FlashTransport_QSPI flashTransport;

int ble_enabled = 0;

int work_LED_status = HIGH;

uint8_t beaconUuid[16] =

{

0x01, 0x12, 0x23, 0x34, 0x45, 0x56, 0x67, 0x78,

0x89, 0x9a, 0xab, 0xbc, 0xcd, 0xde, 0xef, 0xf0

};

// A valid Beacon packet consists of the following information:

// UUID, Major, Minor, RSSI @ 1M

BLEBeacon beacon(beaconUuid, 0x0102, 0x0304, -4);

void setup() {

Bluefruit.begin();

Bluefruit.setName(“board1”);

Bluefruit.setTxPower(0);

Bluefruit.autoConnLed(false);

beacon.setManufacturer(MANUFACTURER_ID);

delay(3000);

flashTransport.begin();

flashTransport.runCommand(0xB9);

flashTransport.end();

//nrf_gpio_cfg_sense_input(g_ADigitalPinMap[D3], NRF_GPIO_PIN_PULLUP, NRF_GPIO_PIN_SENSE_LOW);

Bluefruit.Advertising.setBeacon(beacon);

Bluefruit.ScanResponse.addName();

// Start Advertising

void loop(){

delay(20000);

//

sd_power_system_off();

}

1 Like

Hi @Sheep_Boi

Unfortunately, I have only used the non-Sense version of the Xiao, which doesn’t have the IMU, and have only worked with Bluetooth using Zephyr. However, someone else recently asked a question similar to yours on the page where I posted the low power Arduino code, so I just purchased a Xiao Sense version with the idea to add a low power example for that version.

I won’t have solid play time until this weekend. In the meantime, I suggest the following:

  • If you haven’t already, check out this page XIAO BLE Sense in deep sleep mode. The solution to your problem may already be posted there. I remember seeing IMU wake up code there.
  • In your own solution, I would recommend to first get the IMU working with deep sleep without Bluetooth. Then add Bluetooth. This would reduce the number of variables.

I plan to do as I describe in the second bullet as soon as I have time to play. I will update this page with any results.

Hi @Sheep_Boi

I played around with the accelerometer and posted some examples and power measurements. It is a different topic then what is on this page, so I started a new topic: Xiao Sense Accelerometer Examples and Low Power

1 Like

Are those values measuring from VBAT or 3.3V? I was wondering what is the LDO quiescent current

@ldab Measured from 3.3V

1 Like

By chance I came across the below info when browsing another topic: XIAO BLE Sense in deep sleep mode.

cut-n-paste
"Better to use the 3V3 pin for lowest power consumption.

Seems like powering through the battery adds ~2uA (guessing quiescent from the 3V3 LDO, no part number given in schematics).

Powering through the USB-C port adds an additional 750uA, due to the active supply current of the BQ25100 Lipo Battery Charger IC.
"

1 Like

Thank you for your research and sharing. I struggled all night yesterday with my board idling using like 1ma
I confirm with your project I can idle with ~3.38ua current and deep sleep with ~1.34 ua. Amazing.

Thanks a lot for this post @daCoder ! It definitely put me in the right track.

Using the latest SDK from nordic (2.7.0) this can be greatly simplified to:

xiao_ble.dts:

// buttons: Use sense mechanism for edge detection (sense-edge-mask) instead of GPIOTE
// to save energyc consumption.

&gpio0 {
    status = "okay";
    sense-edge-mask = <0xffffffff>;
};

&gpio1 {
    status = "okay";
    sense-edge-mask = <0xffffffff>;
};

// Fix pinning of quad spi flash so that it doesn't consume power on suspend
// this won't be needed once https://github.com/zephyrproject-rtos/zephyr/pull/79222 is merged
// and released by Nordic
&qspi {
    // Do not change the pins during sleep otherwise a 6mA consumption happens
    pinctrl-1 = <&qspi_default>;
};

Initialization code:

#include <zephyr/pm/device.h>
#define XIAO_QSPI DT_ALIAS(spi_flash0)
static const struct device *qspi_dev = DEVICE_DT_GET(XIAO_QSPI);

static void init_peripherals(void)
{
	int err = pm_device_action_run(qspi_dev, PM_DEVICE_ACTION_SUSPEND);
	if (err) {
		printk("cannot suspend qspi device\n");
	}
}

proj.conf

# set to 'y' for debugging:
CONFIG_LOG=n
CONFIG_SERIAL=n
CONFIG_CONSOLE=n
CONFIG_USB_CDC_ACM_LOG_LEVEL_OFF=n
CONFIG_USB_DEVICE_STACK=n

CONFIG_PM_DEVICE=y