Realistic expectations of sleep currents on Xiao nrf52840 - HELP

Greetings all,

I am new to the platform and trying to build a battery powered BLE Beacon that just broadcasts a small package of data once every 5 seconds or so. inbetween these broadcasts, I would like to sleep with the lowest possible current. Currently, I am getting about 120 microamps in my onSleep state. If I go lower than that, (5-8 microamps) I cannot get BLE back online without a reset. I have tried multiple approaches but all either resulted in 120 microamps, or needed a reboot.

below is my code in progress, keen if anyone can help me get to sub 10 microamps in a sleep state that I will be able to wake from using a Timer without a reset. (alternate approaches that dont require a direct user interaction are welcome (switch to the sense board and use the accelerometer?)).

Thank you in advance for any help or ideas!!!

#include <Arduino.h>
#include <bluefruit.h>
#include "Adafruit_SPIFlash.h"

// For SoftDevice-aware sleep
extern "C" uint32_t sd_app_evt_wait(void);

// LED pins
int loopCounter = 0;

// Example pressure value - dummy data for now, replace later with real datastream
uint16_t pressureValue = 1000;

// --- Setup external SPI flash ---
// We will set up the QSPI Flash here so it is ready to use later. Right now this
// program does not use the QSPI, but we will build in the low power handeling of
// flash during initial architecture as I am sure we will save something to flash later
#if defined(EXTERNAL_FLASH_USE_QSPI)
Adafruit_FlashTransport_QSPI flashTransport;
#elif defined(EXTERNAL_FLASH_USE_SPI)
Adafruit_FlashTransport_SPI flashTransport(EXTERNAL_FLASH_USE_CS, EXTERNAL_FLASH_USE_SPI);
#else
Adafruit_FlashTransport_SPI flashTransport;
#endif

Adafruit_SPIFlash flash(&flashTransport);

// ---------------------------------------------------------------------
// HELPER FUNCTIONS - Here wwe will gather everythign that we want to
// reuse on a regular basis.
// ---------------------------------------------------------------------

void deepPowerDownFlash() {
  //If the QSPI flash has been used, this will put it into a powered-down
  //state and save some microamps. Every bit counts!
  flash.begin();
  flashTransport.runCommand(0xB9);  // SPI deep power-down
  delay(10);
  flash.end();
}

void blinkLED(int ledPin, int times, int duration) {
  //lets be tidy and use a blink function any time a LED is being flashed
  //more than once. Single LED blinks might be handeled in line later so they don't
  //have additional delays. This could be re-written not using delay, but for this
  //project, the delays are really not impactful.
  for (int i = 0; i < times; i++) {
    digitalWrite(ledPin, LOW);   // ON
    delay(duration);
    digitalWrite(ledPin, HIGH);  // OFF
    delay(duration);
  }
}

void setupLEDs() {
  // This could be in the main setup, don't know why I pulled it out here.
  // but I did.
  pinMode(LED_RED, OUTPUT);
  pinMode(LED_GREEN, OUTPUT);
  pinMode(LED_BLUE, OUTPUT);

  // Turn all LEDs off initially
  digitalWrite(LED_RED, HIGH);
  digitalWrite(LED_GREEN, HIGH);
  digitalWrite(LED_BLUE, HIGH);
}

void updateBLEBeacon() {
  uint8_t manufData[2];
  manufData[0] = pressureValue & 0xFF;
  manufData[1] = (pressureValue >> 8) & 0xFF;

  // Stop first to safely update
  Bluefruit.Advertising.stop();
  Bluefruit.Advertising.clearData();
  Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE);
  Bluefruit.setName("PressurePoint");
  Bluefruit.Advertising.addName();
  Bluefruit.Advertising.addManufacturerData(manufData, sizeof(manufData));
  Bluefruit.Advertising.restartOnDisconnect(true);
  Bluefruit.Advertising.setFastTimeout(0);
  Bluefruit.Advertising.start();
}

// ---------------------------------------------------------------------
// STATES - These are our primary States for this simple Sensor.
// Currently states and helper functions really are not that
// different, just a mental map.
// ---------------------------------------------------------------------

void sendBLE() {
  //in this state we will simply get/maintain the value we want to
  //send to the BLE Beacon. in the future, this will get dynamic
  //i2c data from a sensor and fowrad it onto the update BLE Beacon function
  Serial.println("Entering State: BLE advertising");

  // Turn on Blue LED while updating BLE (comment this out later to save power)
  digitalWrite(LED_BLUE, LOW);

  // Increment dummy pressure value for testing
  pressureValue++;
  if (pressureValue > 1099) pressureValue = 1000;

  Serial.print("Updating BLE beacon: pressure = ");
  Serial.println(pressureValue);

  updateBLEBeacon();

  delay(250);  // allow a short broadcast period (can probably be shorter)
  digitalWrite(LED_BLUE, HIGH);
}

void onSleep() {
  //We will use onSleep() to try to put the system into the lowest
  //possible sleep state that we can while still being able to wake
  //back up with a time of some form and birng BLE back onlilne without
  //a reset. TARGET for this mode is 5-8 microamps but currently seeing
  //~125 microamps.

  // Blink green LED just before entering low-power
  digitalWrite(LED_GREEN, LOW);
  delay(150);
  digitalWrite(LED_GREEN, HIGH);

  // make sure all LEDs are off
  digitalWrite(LED_RED, HIGH);
  digitalWrite(LED_BLUE, HIGH);
  digitalWrite(LED_GREEN, HIGH);

  // IF SPI Flash was running, Put SPI flash into deep power down
  deepPowerDownFlash();

  // Stop BLE advertising
  Bluefruit.Advertising.stop();

  // Disable the SoftDevice
  sd_softdevice_disable();

  // Sleep loop — no Serial, no delays (There is probably a better way to do this, fix later)
  const uint32_t STATE2_DURATION_MS = 5000;  // 5 seconds
  uint32_t start = millis();
  while (millis() - start < STATE2_DURATION_MS) {
    __WFI();  // CPU sleeps until an event occurs
  }

  // Blink green LED on wake (debug purposes, comment out later)
  digitalWrite(LED_GREEN, LOW);
  delay(150);
  digitalWrite(LED_GREEN, HIGH);

  // Re-enable BLE at the end of sleep. This should probably be pulled out elsewhere, like a general wakeup routine.
  Bluefruit.begin();
  Bluefruit.setTxPower(-8);
  Bluefruit.setName("PressurePoint");
  updateBLEBeacon();
}

void powerDown() {
  //we will use this as our lowest possible powerdown. You will need to
  //use the reset button to wake from this state, RAM will be lost.
  //this is currently measuring ~ 2.5 microamps. Which is good enough.
  Serial.println("Turning System OFF");

  //Blink the RED LED for debug purposes
  digitalWrite(LED_RED, LOW);
  delay(2000);
  digitalWrite(LED_RED, HIGH);

  // IF SPI Flash was running, Put SPI flash into deep power down (might not be neededd)
  deepPowerDownFlash();

  // make sure all LEDs are off
  digitalWrite(LED_RED,   HIGH);
  digitalWrite(LED_GREEN, HIGH);
  digitalWrite(LED_BLUE,  HIGH);

  //turn the processor off
  Serial.flush();
  NRF_POWER->SYSTEMOFF = 1;  // sub-”A sleep
  while (1) { }
}

// ---------------------------------------------------------------------
// SETUP & Main LOOP
// This is the main porition of our program, nice and simple as most of it
// is already worked out in the functions above.
// ---------------------------------------------------------------------

void setup() {
  Serial.begin(115200);
  delay(500);
  Serial.println("Setup start...");

  deepPowerDownFlash(); // its odd that this is here, but needed it for my first sleep cycle for some reason.
  setupLEDs();

  // Initialize BLE
  Bluefruit.autoConnLed(false);   // Disable default Blue LED for BLE - I don't want this behaviour right now
  Bluefruit.begin();
  Bluefruit.setTxPower(-8);
  Bluefruit.setName("PressurePoint");

  // Startup blink
  blinkLED(LED_RED, 1, 200);
  blinkLED(LED_GREEN, 1, 200);
  blinkLED(LED_BLUE, 1, 200);
  Serial.println("Setup complete.");
}

void loop() {
  Serial.print("Loop counter: ");
  Serial.println(loopCounter);
  sendBLE();  // BLE advertising
  onSleep();  // Ultra-low-power sleep
  loopCounter++; // just have this here for debug purposes and see when the RAM is reset
}

Hi TheBrownHobbit,

I think #31 in the link below will be helpful.

THANK YOU so much. This is basically a perfect example for what I need to do and also appears to be kind/tolerant to i2c traffic (so far)

If I need/want to use flash, can I wake the flash, write to flash and then put the flash back to sleep? ( I will experiment with this later)

THANK YOU again!

Please also read this.

Hi there,

And Welcome here


So we see this A lot , you got the best reply possible from the One guy on here that as you can read has brought the receipts. Follow his path. :+1:
@msfujino is the Power Ninja :ninja: Look at All of the Charts you can see each step.

Now as far as You code posted, Well let’s just say Yeah, this one’s familiar. Your basically doing three things that fight the chip instead of letting it help You. :index_pointing_at_the_viewer:

1. Your turning the SoftDevice off, then trying to “DIY low power”

Bluefruit.Advertising.stop();
sd_softdevice_disable();
// while(millis() - start < 5000) { __WFI(); }
Bluefruit.begin();

That’s heavy and leaky. Nordic’s low-power story is built around leaving the SoftDevice on and using sd_app_evt_wait() (or waitForEvent() in the Adafruit core) so the stack can park the CPU and clocks properly between BLE events. Toggling the SoftDevice every 5 seconds is the opposite of low power.

For sub-10 ”A with timer wake and BLE still usable, the pattern is:

  • Keep SoftDevice running
  • Use sd_app_evt_wait() in the idle loop
  • Use an RTC / app timer to tell you when 5 s has passed
  • Just update the adv payload; don’t tear BLE down every cycle

120 ”A smells like “peripherals left on”, not a true sleep state
In your onSleep():

  • Serial is still active → UARTE + HFCLK burning current.
  • SoftDevice has been yanked out from under the core instead of being allowed to idle.
  • you’re using a while (millis() - start < 5000) loop, which keeps SysTick / timers going and never actually transitions into a proper deep idle mode.

SYSTEMOFF + timer = great current, but you will reboot
Your powerDown() is the only truly low-power path you have now:

NRF_POWER->SYSTEMOFF = 1;  // ~sub-”A

That’s exactly why you see ~2.5 ”A there. But waking from SYSTEMOFF is a reset, by design. You don’t “return” to loop(). You boot again: run setup(), re-init BLE, and go. For a simple beacon, that’s often perfectly fine as a design, as long as you accept “fresh boot every 5 seconds” and stash any state you care about.

Right now your code is sitting awkwardly in the middle, so you get the worst of both worlds. :face_with_open_eyes_and_hand_over_mouth:

If you want to stay with Adafruit/Bluefruit and keep it simple:

  • Option A – Let the SoftDevice do its job (recommended)
    • Don’t call sd_softdevice_disable() at all.
    • Don’t re-begin() Bluefruit every loop.
    • Set a long advertising interval (e.g. several seconds) and just sit in:
extern "C" uint32_t sd_app_evt_wait(void);

void loop() {
  static uint32_t last = 0;
  uint32_t now = millis();

  if (now - last >= 5000) {
    pressureValue = nextValue();
    updateBLEBeacon();     // just update adv payload
    last = now;
  }

  sd_app_evt_wait();       // real low-power idle
}

or

*Option B – Go all-in on SYSTEMOFF and embrace rebooted beacons

  • Use SYSTEMOFF + RTC / GPIO wake every 5 s.
  • Treat every wake as a fresh boot: init Bluefruit, update advertisement once, then go back to SYSTEMOFF. That’s how you hit a couple of ”A average.

HTH
GL :slight_smile: PJ :v:

Thanks for the thoughts! super helpful on all counts!

I will revisit the “option B” for sure. I am at 5 microamps at the moment and that will be good enough for prototyping. I need to warp my heads around this and can clean it up for the next round of prototypes.

1 Like

Hello everyone.

May I conquer this thread? Unfortunately I am not (yet) able to create threads myself as I am new. Would someone be so kind to check my code and why power consumption does not drop below 4,5 mA even though connection interval is set to approx. 1s and in between the Peripheral should be in light sleep in between.

Peripheral measures voltage once a second and sends it via BLE to the central. In between the SoC should be in light sleep.

I don’t know why I cant get to sub 1 mA :frowning:

Peripheral

#include <bluefruit.h>

#include <Adafruit_SPIFlash.h>

#include “nrf_sdm.h” //for sd_app_evt_wait() instead of __WFI()

#include “nrf_soc.h” //for sd_app_evt_wait() instead of __WFI()

//Issues

//HFCLK muss noch deaktiviert werden

//Weiteres?

//--------------------------------------

// USER SETTINGS

//--------------------------------------

#define MEASURE_INTERVAL_SEC 1 // Time between measurements

#define ANALOG_PIN A0 // Pin from which the battery voltage is measured

String device_name = “seeednrf52”; // Name the GUI connects to

int var_txpower = 0; // Sendeleistung, Werte: -40, -30, -20, -16, -12, -8, -4, 0, +2, +3, +4, +5, +6, +7, +8

//-8 bis -12 vermutlich guter Kompromiss zwischen Energieverbrauch und Reichweite

// Sendung dauert nur ms, deshalb hat var_txpower vermutlich keinen großen Einfluss auf den Energieverbauch

int interval_lower = 800; // Unteres Ende *1,25 = [ms] des BLE VERBINDUNGSintervalls

int interval_upper = 840; // Oberes Ende *1,25 = [ms] des BLE VERBINDUNGSintervalls

//Advertising Interval kann hoch/lange sein, da es nur relevant ist, so lange keine Verbindung besteht.

int interval_adv_lower = 100; // Unteres Ende [ms] des BLE ADVERTISINGinterval

int interval_adv_upper = 200; // Oberes Ende [ms] des BLE ADVERTISINGinterval

//--------------------------------------

// CONFIGURATION

//--------------------------------------

#define RTC_COMPARE_VALUE (32768 * MEASURE_INTERVAL_SEC) //RealTimeClock Zeit bis zum nÀchsten Aufwecken der CPU

#define DATA_LENGTH 2 // 2 bytes for uint16_t or 4 bytes for float voltage

//--------------------------------------

// GLOBALS

//--------------------------------------

volatile bool rtcWakeFlag = false;

bool connectedFlag = false;

//--------------------------------------

// Payload union for BLE notify

//--------------------------------------

union DataUnion {

float voltage;

uint16_t u16[DATA_LENGTH];

};

DataUnion txData;

//--------------------------------------

// BLE SERVICE + CHARACTERISTIC

//--------------------------------------

#define SERVICE_UUID “19B10001-E8F2-537E-4F6C-D104768A1214”

#define VOLT_CHAR_UUID “19B10002-E8F2-537E-4F6C-D104768A1214”

//--------------------------------------

// Create the BLE service and characteristic

//--------------------------------------

BLEService voltageService(SERVICE_UUID); // You can also define a separate service UUID if needed

BLECharacteristic voltageCharacteristic(VOLT_CHAR_UUID, CHR_PROPS_NOTIFY | CHR_PROPS_READ);

//--------------------------------------

// RTC2 Interrupt Handler

//--------------------------------------

extern “C” void RTC2_IRQHandler(void)

{

if (NRF_RTC2->EVENTS_COMPARE[0])

{

NRF_RTC2->EVENTS_COMPARE\[0\] = 0;

NRF_RTC2->TASKS_CLEAR = 1;



rtcWakeFlag = true;

}

}

//--------------------------------------

// RTC2 Initialization

//--------------------------------------

void initRTC(uint32_t countValue)

{

NRF_CLOCK->LFCLKSRC = 1; // LFXO

NRF_CLOCK->TASKS_LFCLKSTART = 1;

while ((NRF_CLOCK->LFCLKSTAT & 0x10001) != 0x10001);

NRF_RTC2->TASKS_STOP = 1;

NRF_RTC2->TASKS_CLEAR = 1;

NRF_RTC2->PRESCALER = 0;

NRF_RTC2->CC[0] = countValue;

NRF_RTC2->INTENSET = RTC_INTENSET_COMPARE0_Msk;

NVIC_SetPriority(RTC2_IRQn, 7); // safe priority for SoftDevice

NVIC_ClearPendingIRQ(RTC2_IRQn);

NVIC_EnableIRQ(RTC2_IRQn);

NRF_RTC2->TASKS_START = 1;

}

//--------------------------------------

// BLE Callbacks

//--------------------------------------

void connect_callback(uint16_t conn_handle)

{

connectedFlag = true; // ← Wichtig

// Turn off all LEDs by setting pins to INPUT if a connection has been established

pinMode(LED_RED, OUTPUT);

pinMode(LED_GREEN, OUTPUT);

pinMode(LED_BLUE, OUTPUT);

digitalWrite(LED_RED, HIGH);

digitalWrite(LED_GREEN, HIGH);

digitalWrite(LED_BLUE, HIGH);

}

void disconnect_callback(uint16_t conn_handle, uint8_t reason)

{

connectedFlag = false;

pinMode(LED_BLUE, OUTPUT);

digitalWrite(LED_BLUE, LOW);

}

//--------------------------------------

// ADC Reading

//--------------------------------------

uint16_t readVoltage()

{

uint16_t raw = analogRead(ANALOG_PIN);

// Disable SAADC to avoid approx0.4 mA drain

NRF_SAADC->TASKS_STOP = 1;

while (NRF_SAADC->STATUS == (SAADC_STATUS_STATUS_Busy << SAADC_STATUS_STATUS_Pos));

NRF_SAADC->ENABLE = 0;

//Serial.println(raw);

return raw;

}

// ------------------------------------------------------

// Power down QSPI flash on Seeed XIAO nRF52840

// ------------------------------------------------------

// Deep power-down the on-board QSPI flash

void flashPowerDownXIAO() {

static Adafruit_FlashTransport_QSPI flashTransport; // uses PIN_QSPI_* (CS=P0.25) on XIAO

flashTransport.begin();

flashTransport.runCommand(0xB9); // 0xB9 = Deep Power-Down

flashTransport.end();

}

// Optional: wake the flash later if you need to use it again in the same run

void flashWakeXIAO() {

static Adafruit_FlashTransport_QSPI flashTransport;

flashTransport.begin();

flashTransport.runCommand(0xAB); // 0xAB = Release from Deep Power-Down

flashTransport.end();

// Some parts need a short tRES1 delay after 0xAB; if reads fail, add delay(1) here.

}

///////////////////TEST

void flashPowerDownXIAO_fast() {

static Adafruit_FlashTransport_QSPI qspi;

qspi.begin();

qspi.runCommand(0xB9); // Deep Power-Down

qspi.end();

}

//--------------------------------------

// SETUP

//--------------------------------------

void setup()

{

//Serial for debugging

//Serial.begin(115200);

//while(!Serial);

//delay(500);

// LED setup

pinMode(LED_RED, OUTPUT);

pinMode(LED_GREEN, OUTPUT);

pinMode(LED_BLUE, OUTPUT);

digitalWrite(LED_RED, HIGH);

digitalWrite(LED_GREEN, HIGH);

digitalWrite(LED_BLUE, HIGH);

pinMode(ANALOG_PIN, INPUT);

// Enable DCDC converter for lower power

NRF_POWER->DCDCEN = 1; //Immer aktiv lassen!

//Power saving

//flashPowerDownXIAO();

flashPowerDownXIAO();

// RTC2 setup

initRTC(RTC_COMPARE_VALUE);

// BLE initialization

Bluefruit.configPrphBandwidth(BANDWIDTH_MAX);

Bluefruit.begin();

Bluefruit.setName(device_name.c_str());

Bluefruit.setTxPower(var_txpower);

Bluefruit.Periph.setConnInterval(interval_lower, interval_upper);

Bluefruit.Periph.setConnectCallback(connect_callback);

Bluefruit.Periph.setDisconnectCallback(disconnect_callback);

// BLE Service + Characteristic

voltageService.begin();

voltageCharacteristic.setProperties(CHR_PROPS_NOTIFY | CHR_PROPS_READ);

voltageCharacteristic.setPermission(SECMODE_OPEN, SECMODE_NO_ACCESS);

voltageCharacteristic.setFixedLen(DATA_LENGTH);

voltageCharacteristic.begin();

// BLE Advertising

Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE);

Bluefruit.Advertising.addService(voltageService);

Bluefruit.ScanResponse.addName();

Bluefruit.Advertising.setIntervalMS(interval_adv_lower, interval_adv_upper);

Bluefruit.Advertising.restartOnDisconnect(true);

Bluefruit.Advertising.start();

}

//--------------------------------------

// LOOP — Low Power

//--------------------------------------

void loop()

{

// Sleep until RTC or BLE wakes CPU

sd_app_evt_wait();

// Execution resumes here after an interrupt

if (rtcWakeFlag)

{

rtcWakeFlag = false;



// Read analog voltage

txData.voltage = readVoltage();



// Send via BLE only if connected

if (connectedFlag)

{

  voltageCharacteristic.notify(txData.u16, DATA_LENGTH);

}

}

}

Central

#include <bluefruit.h>

#include “nrf_sdm.h”

#include “nrf_soc.h”

//--------------------------------------

// USER SETTINGS

//--------------------------------------

int interval_lower = 800; // Unteres Ende *1,25 = [ms] des BLE VERBINDUNGSintervalls

int interval_upper = 840; // Oberes Ende *1,25 = [ms] des BLE VERBINDUNGSintervalls

// Target peripheral name and UUIDs (must match the peripheral)

static const char* DEVICE_NAME = “seeednrf52”;

static const char* SERVICE_UUID_STR = “19B10001-E8F2-537E-4F6C-D104768A1214”;

static const char* CHAR_UUID_STR = “19B10002-E8F2-537E-4F6C-D104768A1214”;

BLEUuid svcUuid(SERVICE_UUID_STR);

BLEUuid chrUuid(CHAR_UUID_STR);

BLEClientService clientVoltageService(svcUuid);

BLEClientCharacteristic clientVoltageChar(chrUuid);

volatile uint16_t g_conn_handle = BLE_CONN_HANDLE_INVALID;

// Convert raw 10-bit ADC to volts (3.3V reference)

static inline float rawToVoltage(uint16_t raw)

{

Serial.println(“rawtovoltage”);

//return (float)raw * 3.3f / 1023.0f;

return (float)raw * 3.6f / 1023.0f;

}

void notify_callback(BLEClientCharacteristic* chr, uint8_t* data, uint16_t len)

{

Serial.println(“notify_callback”);

if (len < 2) return;

uint16_t raw = (uint16_t)data[0] | ((uint16_t)data[1] << 8);

Serial.println(raw);

float v = rawToVoltage(raw);

Serial.print("Voltage (V): ");

Serial.println(v, 3);

}

// Choose your desired intervals in ms (example values)

static const uint16_t interval_min = interval_lower;

static const uint16_t interval_max = interval_upper;

void connect_callback(uint16_t conn_handle)

{

Serial.println(“connect_callback”);

//Prints the current connection interval in ms

BLEConnection* conn = Bluefruit.Connection(conn_handle);

if (conn) {

uint16_t ci_units = conn->getConnectionInterval();   // units of 1.25 ms

float    ci_ms    = ci_units \* 1.25f;

Serial.printf("Central: Conn Interval = %u units (%.2f ms)\\n", ci_units, ci_ms);

} else {

Serial.println("Central: Connection pointer is null");

}

g_conn_handle = conn_handle;

Serial.println(“Central: Connected”);

if ( clientVoltageService.discover(conn_handle))

{

Serial.println("Service discovered");



// Use the 0-arg discover() with your library version

if ( clientVoltageChar.discover() )

{

  Serial.println("Characteristic discovered");



  clientVoltageChar.setNotifyCallback(notify_callback);

  if ( clientVoltageChar.enableNotify() )

  {

    Serial.println("Notifications enabled");



    ble_gap_conn_params_t cp = {};

    cp.min_conn_interval = interval_min; // 800 -> \~1000 ms

    cp.max_conn_interval = interval_max; // 840 -> \~1050 ms

    cp.slave_latency     = 0;

    cp.conn_sup_timeout  = 400;          // 4 s



    uint32_t err = sd_ble_gap_conn_param_update(conn_handle, &cp);

    Serial.print("sd_ble_gap_conn_param_update -> "); Serial.println(err);

  }

  else

  {

    Serial.println("Failed to enable notifications");

  }

} else {

  Serial.println("Characteristic discovery failed");

}

} else {

Serial.println("Service discovery failed");

}

}

void disconnect_callback(uint16_t conn_handle, uint8_t reason)

{

Serial.println(“disconnect_callback”);

(void)conn_handle; (void)reason;

g_conn_handle = BLE_CONN_HANDLE_INVALID;

Serial.println(“Central: Disconnected, restarting scan”);

Bluefruit.Scanner.start(0); // scan forever

}

void scan_callback(ble_gap_evt_adv_report_t* report)

{

Serial.print(“scan_callback rssi=”);

Serial.println(report->rssi);

bool will_connect = false;

char name[32] = {0};

int len = Bluefruit.Scanner.parseReportByType(

          report, BLE_GAP_AD_TYPE_COMPLETE_LOCAL_NAME,

          (uint8_t\*)name, sizeof(name)-1);

if (len <= 0) {

len = Bluefruit.Scanner.parseReportByType(

          report, BLE_GAP_AD_TYPE_SHORT_LOCAL_NAME,

          (uint8_t\*)name, sizeof(name)-1);

}

if (len > 0) {

name\[len\] = 0;

Serial.print("Seen name: "); Serial.println(name);

if (strcmp(name, DEVICE_NAME) == 0) {

  Serial.print("Found target: "); Serial.println(name);

  Bluefruit.Central.connect(report);

}

}

//Bluefruit.Scanner.resume() ist wichtig, da er sonst nicht sucht bis GerÀt gefunden

if (!will_connect) {

Bluefruit.Scanner.resume();

}

}

void setup()

{

Serial.begin(115200);

//while (!Serial);

delay(500);

Serial.println(“setup”);

Bluefruit.configCentralBandwidth(BANDWIDTH_MAX); // recommended for central

Bluefruit.begin(0, 1);

Bluefruit.setName(“XIAO nRF52 Central”);

// Register client objects (no arguments in this library version)

clientVoltageService.begin();

clientVoltageChar.begin();

// Central callbacks

Bluefruit.Central.setConnectCallback(connect_callback);

Bluefruit.Central.setDisconnectCallback(disconnect_callback);

// Scanner: 100ms interval, 50ms window (units of 0.625ms)

Bluefruit.Scanner.setInterval(160, 80);

Bluefruit.Scanner.useActiveScan(true);

//Bluefruit.Scanner.filterUuid(svcUuid);

Bluefruit.Scanner.clearFilters();

Bluefruit.Scanner.setRxCallback(scan_callback);

Bluefruit.Scanner.start(0);

Serial.println(“Central: Scanning for seeednrf52 
”);

}

/*

void loop()

{

sd_app_evt_wait(); // optional during low-power

}

*/

/*

static uint32_t lastCiPrintMs = 0;

static uint32_t lastReqMs = 0;

static uint8_t paramReqRetries = 0;

static const uint8_t kMaxParamReqRetries = 3;

void loop()

{

uint32_t now = millis();

// Print current interval every 1 s when connected

if (g_conn_handle != BLE_CONN_HANDLE_INVALID && (now - lastCiPrintMs) >= 1000)

{

BLEConnection\* conn = Bluefruit.Connection(g_conn_handle);

if (conn)

{

  uint16_t ci_units = conn->getConnectionInterval(); // 1.25 ms units

  float    ci_ms    = ci_units \* 1.25f;

  Serial.printf("Central: Conn Interval = %u units (%.2f ms)\\n", ci_units, ci_ms);



  // If outside desired range, try to re-request (up to N times, every \~2 s)

  if (paramReqRetries < kMaxParamReqRetries &&

      (ci_units < interval_min || ci_units > interval_max) &&

      (now - lastReqMs) >= 2000)

  {

    ble_gap_conn_params_t cp = {};

    cp.min_conn_interval = interval_min; // e.g. 800 (=1000 ms)

    cp.max_conn_interval = interval_max; // e.g. 840 (=1050 ms)

    cp.slave_latency     = 0;

    cp.conn_sup_timeout  = 400;          // 4 s



    uint32_t err = sd_ble_gap_conn_param_update(g_conn_handle, &cp);

    Serial.print("Re-request conn params -> "); Serial.println(err);



    lastReqMs = now;

    paramReqRetries++;

  }

}

lastCiPrintMs = now;

}

// Your normal event wait or other logic

sd_app_evt_wait();

}

*/

void loop() {

sd_app_evt_wait(); // sleep until an event

static uint32_t lastMs = 0;

uint32_t now = millis();

if (g_conn_handle != BLE_CONN_HANDLE_INVALID && (now - lastMs) >= 1000) {

BLEConnection\* conn = Bluefruit.Connection(g_conn_handle);

if (conn) {

  uint16_t ci = conn->getConnectionInterval();

  Serial.printf("Conn Interval: %u (%.2f ms)\\n", ci, ci \* 1.25f);

}

lastMs = now;

}

}

platformio.ini

[env:seeed_xiao_nrf52840]

platform = GitHub - Seeed-Studio/platform-seeedboards: Seeed Boards: development platform for PlatformIO

board = seeed-xiao-afruitnrf52-nrf52840

framework = arduino

monitor_speed = 115200

;lib_ldf_mode = deep+

lib_deps =

adafruit/Adafruit SPIFlash

adafruit/SdFat - Adafruit Fork

Hi SteveB,

The sample project at the link below may be helpful. It communicates every second and then enters LightSleep using the delay() function. The average current consumption over one second is 97uA.

1 Like

Hi there,

And welcome here


SO I see you have a very good grasp on what the flow should be, a couple things I would check
 :grin: After you read the Power Ninja’s :ninja: Threads he Posted, @msfujino has the 411 so definitely check those.

From the Peripheral side, You do know The peripheral can request 1s interval, but the central decides. If the central keeps it at 7.5–30 ms, current will look “stuck”.
double check that, also you said " the HFCLK needs to be disabled ". It does for sub Ma. power save. In BLE connected mode the radio will wake periodically, but between events you should still be in the tens/hundreds of ”A if everything is right.

BLE config is set to max bandwidth:
Bluefruit.configPrphBandwidth(BANDWIDTH_MAX);
This can drive aggressive scheduling / higher duty behavior. If you want low power, then should not ask for “max”. :+1:

My AI is suggesting this as well.

  • Print the negotiated conn interval + latency on connect. If it’s not ~1000 ms and latency > 0, he won’t get low power.
  • Set slave latency (huge for power): example: conn interval 1000 ms, latency 4–9, timeout ~6–10 s.
  • Remove Bluefruit.configPrphBandwidth(BANDWIDTH_MAX); for a power test.
  • Make LEDs truly off:
  • set LED pins to INPUT (not output), or ensure correct polarity for the board.
  • Confirm LFCLK source: he manually forces NRF_CLOCK->LFCLKSRC = 1; // LFXO which is fine if you actually have a crystal, but don’t fight the SoftDevice. SoftDevice normally owns LFCLK config. Manually poking clocks while SoftDevice is active can produce “works but not low-power” behavior.
  • Measure correctly: use a meter like PPK2 and measure VBAT rail, not USB.

Most of the time the central is the one silently keeping the interval short, and the peripheral looks guilty even though it isn’t.

HTH
GL :santa_claus: :PJ :v:

1 Like

Hi there,

thanks for taking your time. I forgot to write down the debugging I already performed.

According to my output via Serial:

  • HFCLK = NO, so should be fine
  • Connection interval is definitely 1.000 ms to 1.050 ms (client sets it to 1.050 ms) after the connection has been established, checked via build in functions on both ends (central/periph)
  • I use Xiao Studio nrf52840 at both ends for ease of use

I will take your suggestions and check:

  • Remove Bandwith_max
  • Set slave latency
  • LEDs INPUT/OUTPUT
  • Remove NRF_Clock→LFCLK

Lets see how it turns out :slight_smile: I use a Rhode&Schwarz HCM 8015 at work which is really nice in measuring low currents at a high measuring frequency. Source is a LiFePo4 3,2 VDC/700 mAh with a TPS63802 Buck/Boost Converter set to 3,3 VDC output (not the cheapest converter but 3,334 VDC output is pretty good regarding tolerance). I measure at the converter output side to neglect losses from transformation. Power supply is directly via 3,3V/GND pins. As far as I understood this should avoid the internal DCDC-converter.

I also started to take a look at VSCode + nrf connect + nrf sdk (Zephyr) which msfujinos code seems to be based on but if your tipps help fix my code I would like to stick to bluefruit/nrf5 sdk for now as all of this is new to me (python ftw :D) and I feel quiet comfortable with VSCode+PlatformIO for now.

But we will see :slight_smile: Again, thanks, I will report back.

1 Like