I found good hints on this topic page XIAO BLE Sense in deep sleep mode. But many of the code snippets I saw in the thread were not working as I expected. I dug into the arduino core try to understand the underlying implementation. Based on my digging, I developed the below sketch. The arduino loop runs at 3uA (with a major caveat). It demonstrates system_off sleep with wake up on gpio interrupt, system_on sleep, and a low latency Arduino main loop. More info on my findings below the sketch. (I tried to post this on the other topic, but the post is stuck in a “pending” state.)
#include "Adafruit_SPIFlash.h"
#define DO_WORK_PIN D3
#define SHUTDOWN_PIN D4
Adafruit_FlashTransport_QSPI flashTransport;
SemaphoreHandle_t xSemaphore;
bool gotoSystemOffSleep = false;
int work_LED_status = HIGH;
void QSPIF_sleep(void)
{
flashTransport.begin();
flashTransport.runCommand(0xB9);
flashTransport.end();
}
void setup()
{
pinMode(LED_RED, OUTPUT);
pinMode(LED_GREEN, OUTPUT);
pinMode(LED_BLUE, OUTPUT);
digitalWrite(LED_RED, HIGH);
digitalWrite(LED_GREEN, HIGH);
digitalWrite(LED_BLUE, work_LED_status);
QSPIF_sleep();
pinMode(DO_WORK_PIN, INPUT_PULLUP_SENSE);
attachInterrupt(digitalPinToInterrupt(DO_WORK_PIN), doWorkISR, FALLING);
pinMode(SHUTDOWN_PIN, INPUT_PULLUP_SENSE);
attachInterrupt(digitalPinToInterrupt(SHUTDOWN_PIN), shutdownISR, FALLING);
xSemaphore = xSemaphoreCreateBinary();
// Flash green to see power on, reset, and wake from system_off
digitalWrite(LED_GREEN, LOW);
delay(1000);
digitalWrite(LED_GREEN, HIGH);
}
void doWorkISR()
{
xSemaphoreGive(xSemaphore);
}
void shutdownISR()
{
gotoSystemOffSleep = true;
xSemaphoreGive(xSemaphore);
}
void loop()
{
// FreeRTOS will automatically put the system in system_on sleep mode here
xSemaphoreTake(xSemaphore, portMAX_DELAY);
if (gotoSystemOffSleep)
{
//Flash red to see we are going to system_off sleep mode
digitalWrite(LED_RED, LOW);
delay(1000);
digitalWrite(LED_RED, HIGH);
NRF_POWER->SYSTEMOFF=1; // Execution should not go beyond this
//sd_power_system_off() // Use this instead if using the soft device
}
// Not going to system off sleep mode, so do work
work_LED_status = !work_LED_status;
digitalWrite(LED_BLUE, work_LED_status);
}
To get the complete solution I had to rewrite the adafruit/seeed interrupt handler to use low power interrupts (code posted below). If you use gpio interrupts, the best you can do with out of the box adafruit/seeed code is 15ua. (I saw an open issue to implement low power interrupts on the adafruit site.)
The Arduino code is running FreeRTOS underneath the hood, so the techniques published in the Nordic SDK don’t work as expected. I think the __WFE(), __WFI, and maybe sd_app_evt_wait() are intended to run in your main loop with minimal background interrupts/events to cause the loop to wake. FreeRTOS is using RTC1 as a tick timer, so the calls such as __WFI keep unblocking. __WFI will work as expected if you turn off RTC1.
FreeRTOS will automatically put the system in system_on sleep mode and manage RTC1 if all the threads are idle. Underneath the hood it calls __WFE(). You can idle your main thread by calling delay() or by blocking the main thread with a semaphore or other similar primitive. Use a semaphore if you want low latency response time in your main loop.
Here are the results measured with PPK2:
- System off mode: 1uA
- gpio interrupts with modified interrupt code
- Main loop with semaphore: 3uA
- Main loop with delay(250): 5uA
- Main loop with delay(100): 8uA
- Main loop with delay(50): 14uA
- Gpio interrupts with default interrupt impl: add 12uA to each above