Skip to content

Conversation

@oltaco
Copy link
Contributor

@oltaco oltaco commented Feb 1, 2026

Note: Draft PR, Testing required - Please test this PR on your NRF52 repeaters, it seems to work well from my initial testing and in theory shouldn't cause packets to be dropped but thorough testing is still required.

This PR implements proper low-power sleep for repeater firmware on NRF52 boards, reducing idle power consumption:
Repeater firmware: 6-7mA down to 5mA at 3.6v (after first advert is sent).
Companion firmware: Not yet integrated (see below)

Added NRF52Board::sleep() which uses __WFE() (Wait For Event) to put the CPU into idle whenever possible. CPU will automatically wake on any interrupt so there should be no effect on serial/lora/clock etc. This drops the number of main loops during idle from ~25000+/sec to ~1000/loops per second, because it wakes for every rtc.tick().

Includes the workaround for errata 87 which probably isn't necessary for repeaters but seems to be required for companions to go into idle.

Note on companions: I haven't added board.sleep() to the main loop for companions, although I did test it briefly and found it to drop idle current draw from ~8-9ma to ~6ma at 3.6v. More testing would be welcomed for this as well.

@oltaco oltaco changed the title Add Low-Power Sleep for nRF52 Boards (R Add Low-Power Sleep for nRF52 Boards Feb 1, 2026
@IoTThinks
Copy link
Contributor

IoTThinks commented Feb 1, 2026

You may want to add periodically wakeup as in the repeater needs to do periodically adverts.
No idea if we can keep the same architecture flow as ESP32.

So this is to use main loop to execeute these three commands infinitely.
__SEV(); __WFE(); __WFE();

@oltaco
Copy link
Contributor Author

oltaco commented Feb 1, 2026

You may want to add periodically wakeup as in the repeater needs to do periodically adverts. No idea if we can keep the same architecture flow as ESP32.

It's unnecessary, this will wake to do anything required. It wakes every second on tick as well. This is SYSTEMON "sleep", more accurately it is putting the CPU into low power idle state (not executing) when there is nothing to do, and there is no requirement to schedule wake or anything.

You could achieve slightly more current reduction by disabling "unused" peripherals but it's probably not worth the added complexity and possible trade off in stability.

@liquidraver
Copy link
Contributor

liquidraver commented Feb 2, 2026

I'm running with firmware like this for months now on my companion, I just added an sd_app_evt_wait(); with guarded IF-s for nRF to the bottom of the main loop.

@oltaco told me two calls will work better so I experimented with it:

One time:
[2026-02-02 14:38:35.3038 DEBUG: Sleep stats (10s): slept=1258ms awake=8742ms count=1258 (12.6% sleep)
[2026-02-02 14:38:36.0080 DEBUG: Loops/sec: 20322.0 (20322 loops in 1000ms)

Two times:
[2026-02-02 14:40:36.2427 DEBUG: Sleep stats (10s): slept=2125ms awake=7875ms count=2125 (21.2% sleep)
[2026-02-02 14:40:36.9458 DEBUG: Loops/sec: 18354.0 (18354 loops in 1000ms)

Three times:
[2026-02-02 14:43:11.1596 DEBUG: Sleep stats (10s): slept=2697ms awake=7303ms count=2697 (27.0% sleep)
[2026-02-02 14:43:11.8646 DEBUG: Loops/sec: 16835.0 (16835 loops in 1000ms)

third time adds diminishing returns, so we are maybe taking away time from real work there

indeed two times is the sweet spot.

first call returns instantly maybe because the pending timer event so it adds a small amount of sleep only.

@oltaco
Copy link
Contributor Author

oltaco commented Feb 2, 2026

first call returns instantly maybe because the pending timer event so it adds a small amount of sleep only.

I didn't have a chance to investigate but it could be that the reason sd_app_evt_wait() wakes so soon compared to SEV(); WFE(); WFE(); is that we are still polling for BLE RX instead of being event driven.

sd_app_evt_wait() checks the SoftDevice queue first and if there are pending jobs it does them instead of sleeping. So calling it twice lets it process the SoftDevice queue, and then the next call sleeps straight away which gives us more time asleep before the next SoftDevice event gets queued. At least that's my theory.

That's why I wonder if we could change SerialBLEInterface to be fully event driven for RX like it is for TX maybe we could achieve even more sleep.

I have been running the sleep code in this PR on my companions for a couple of days now and haven't noticed any issues. To do it I just added this to the companion main.cpp loop():

#if defined(NRF52_PLATFORM)
  board.sleep(1800);
#endif

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants