Skip to content

Commit aa7b1a0

Browse files
committed
release: fix USB timed hibernate in 1.0.14
1 parent 7ec90ae commit aa7b1a0

17 files changed

Lines changed: 491 additions & 59 deletions

File tree

.planning/debug/npm1300-usb-timed-hibernate.md

Lines changed: 173 additions & 0 deletions
Large diffs are not rendered by default.

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -751,7 +751,7 @@ PMIC-controlled cold-boot path; it is not interchangeable with a returning
751751
| Continue after a timed idle | `delayLowPowerIdle(ms)` or normal low-power `delay(ms)` | Returns to the next statement |
752752
| Lowest CPU System OFF with retained RAM banks | `delaySystemOff(ms)` | Cold reset; explicitly retained `.noinit` state may survive |
753753
| System OFF without RAM retention | `delaySystemOffNoRetention(ms)` | Cold reset |
754-
| Lowest timed LM20A board sleep | `npm1300_enter_timed_hibernate_ms(ms)` | PMIC restores power and the board cold-boots |
754+
| Timed LM20A cold boot (lowest-current path on VBAT) | `npm1300_enter_timed_hibernate_ms(ms)` | PMIC wake on VBAT or GRTC System OFF wake on USB; both cold-reset |
755755

756756
For reproducible measurements, power from VBAT through a PPK2/Joulescope/Otii,
757757
disconnect USB/VBUS, close serial/debug sessions, keep voltage and temperature
@@ -811,7 +811,7 @@ Examples:
811811

812812
- `npm1300_charger_set_current(ma)` sets the battery charge-current target and now also updates the nPM1300 VBUS input-current limiter so the default 100 mA input limit does not throttle higher charge currents.
813813
- `npm1300_vbus_set_input_current_limit_ma(ma)` and `npm1300_vbus_get_input_current_limit_ma()` expose the VBUS limiter directly. This limit is the allowed USB/VBUS input draw, not the measured battery charge or discharge current.
814-
- `npm1300_enter_timed_hibernate_ms(ms)` programs the nPM1300 hibernate wake timer and enters PMIC hibernate mode. On XIAO nRF54LM20A this is the lowest-current timed sleep path; wake is a cold boot after the PMIC restores power. The nPM1300 requires VBUS to be absent before Ship/Hibernate entry, so this helper returns `false` while USB/VBUS is present; test it from VBAT/battery power with USB/debug disconnected.
814+
- `npm1300_enter_timed_hibernate_ms(ms)` provides one timed cold-boot API for either supply path. With VBUS absent it programs the nPM1300 wake timer and enters true PMIC Hibernate, the lowest-current LM20A path. With USB/VBUS present it uses nRF54 GRTC System OFF because the nPM1300 requires VBUS to be disconnected before Hibernate entry. Both paths accept 1 through `NPM1300_HIBERNATE_TIMER_MAX_MS`, normally do not return after successful entry, and restart from `setup()` on wake. Disconnect USB/debug wiring and power from VBAT when measuring the sub-uA PMIC path.
815815
- Use `File > Examples > Nrf54L15-Clean-Implementation > PMIC > nPM1300_ChargerControl` or `File > Examples > Power > nPM1300_BatteryCurrent` to compare `IBAT` with the configured `VBUS_ILIM`.
816816

817817
---

docs/RELEASE_1.0.13.md

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,16 @@
11
# nRF54 Arduino Core 1.0.13
22

3-
`1.0.13` fixes the nPM1300 timed-hibernate helper behavior reported in
4-
[issue #107](https://github.com/lolren/nrf54-arduino-core/issues/107).
3+
`1.0.13` corrected the battery-powered nPM1300 timer sequence reported in
4+
[issue #107](https://github.com/lolren/nrf54-arduino-core/issues/107), but its
5+
VBUS guard left the issue's USB-powered case unresolved.
6+
7+
## Corrected in 1.0.14
8+
9+
Release `1.0.14` keeps true PMIC Hibernate for VBUS-absent battery power and
10+
uses the nRF54 GRTC System OFF wake-reset path while VBUS is present. This is
11+
required because Nordic documents VBUS-disconnected as a precondition for
12+
`TASKENTERHIBERNATE`, while GRTC compare is a valid nRF54 System OFF wake
13+
source. Both paths cold-reset after the requested delay.
514

615
## nPM1300 timed hibernate
716

docs/RELEASE_1.0.14.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# nRF54 Arduino Core 1.0.14
2+
3+
`1.0.14` completes the timed-hibernate fix reported in
4+
[issue #107](https://github.com/lolren/nrf54-arduino-core/issues/107).
5+
6+
## nPM1300 timed cold boot
7+
8+
- Keep true nPM1300 timed Hibernate when the XIAO nRF54LM20A is powered from
9+
VBAT with VBUS absent.
10+
- When USB/VBUS is present, use the nRF54 GRTC timed System OFF wake-reset
11+
path. Nordic requires VBUS to be disconnected before the PMIC can enter
12+
Hibernate, so the SoC fallback provides the requested USB-powered behavior.
13+
- Preserve one `1` through `268435440` ms API range on both supply paths by
14+
retaining the full millisecond delay in the GRTC calculation.
15+
- Accept the LM20A RADIO EasyDMA cleared-pointer readback during the guarded
16+
System OFF shutdown sequence, while retaining the existing idle, disabled,
17+
and zero-length DMA checks.
18+
- Both successful paths cold-boot and restart the sketch from `setup()`.
19+
20+
## Validation
21+
22+
- Core I/O regression suite passes with explicit battery/USB path contracts.
23+
- `nPM1300_TimedHibernate` compiles for XIAO nRF54LM20A.
24+
- An attached USB-powered XIAO nRF54LM20A completed three consecutive public
25+
API cycles: each entered timed System OFF and cold-booted from the GRTC
26+
compare event with no shutdown abort.
27+
28+
## Install or upgrade
29+
30+
Use Arduino Boards Manager after refreshing the package index, or install from
31+
the release archive attached to `v1.0.14`.

hardware/nrf54l15clean/nrf54l15clean/cores/nrf54l15/CoreVersionGenerated.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
#define ARDUINO_NRF54L15_CLEAN_VERSION_MAJOR 1
55
#define ARDUINO_NRF54L15_CLEAN_VERSION_MINOR 0
6-
#define ARDUINO_NRF54L15_CLEAN_VERSION_PATCH 13
6+
#define ARDUINO_NRF54L15_CLEAN_VERSION_PATCH 14
77
#define ARDUINO_NRF54L15_CLEAN_VERSION_PRERELEASE ""
88
#define ARDUINO_NRF54L15_CLEAN_VERSION_IS_PRERELEASE 0
99

@@ -16,6 +16,6 @@
1616
ARDUINO_NRF54L15_CLEAN_VERSION_MINOR, \
1717
ARDUINO_NRF54L15_CLEAN_VERSION_PATCH)
1818

19-
#define ARDUINO_NRF54L15_CLEAN_VERSION_STRING "1.0.13"
19+
#define ARDUINO_NRF54L15_CLEAN_VERSION_STRING "1.0.14"
2020

2121
#endif // NRF54L15_CLEAN_CORE_VERSION_GENERATED_H

hardware/nrf54l15clean/nrf54l15clean/cores/nrf54l15/wiring_time.c

Lines changed: 13 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -708,9 +708,9 @@ static uint32_t systemOffMinimumLatencyUs(void)
708708
kSystemOffMinimumLatencyGuardUs;
709709
}
710710

711-
static uint32_t clampSystemOffDelayUs(uint32_t delayUs)
711+
static uint64_t clampSystemOffDelayUs(uint64_t delayUs)
712712
{
713-
const uint32_t minimumLatencyUs = systemOffMinimumLatencyUs();
713+
const uint64_t minimumLatencyUs = systemOffMinimumLatencyUs();
714714
if (delayUs < minimumLatencyUs) {
715715
return minimumLatencyUs;
716716
}
@@ -805,7 +805,7 @@ typedef enum {
805805
kSystemOffWakeCompareFired = 3U
806806
} system_off_wake_program_status_t;
807807

808-
static system_off_wake_program_status_t programSystemOffWakeUs(uint32_t delayUs)
808+
static system_off_wake_program_status_t programSystemOffWakeUs(uint64_t delayUs)
809809
{
810810
NRF_GRTC_Type* const grtc = NRF_GRTC;
811811
delayUs = clampSystemOffDelayUs(delayUs);
@@ -828,7 +828,7 @@ static system_off_wake_program_status_t programSystemOffWakeUs(uint32_t delayUs)
828828
}
829829

830830
const uint32_t minimumLatencyUs = systemOffMinimumLatencyUs();
831-
uint32_t wakeDelayUs = delayUs;
831+
uint64_t wakeDelayUs = delayUs;
832832

833833
for (uint8_t attempt = 0U; attempt < 2U; ++attempt) {
834834
ensureGrtcReady(grtc);
@@ -865,7 +865,7 @@ static system_off_wake_program_status_t programSystemOffWakeUs(uint32_t delayUs)
865865
grtc->CC[wakeChannel].CCEN =
866866
(GRTC_CC_CCEN_ACTIVE_Disable << GRTC_CC_CCEN_ACTIVE_Pos);
867867
grtc->EVENTS_COMPARE[wakeChannel] = 0U;
868-
if (wakeDelayUs > UINT32_MAX - minimumLatencyUs) {
868+
if (wakeDelayUs > UINT64_MAX - minimumLatencyUs) {
869869
return kSystemOffWakeCompareFired;
870870
}
871871
wakeDelayUs += minimumLatencyUs;
@@ -995,11 +995,11 @@ static bool clearResetReasonsForSystemOff(void)
995995

996996
static void enterSystemOffInternal(bool disableRamRetention,
997997
bool timedWake,
998-
uint32_t delayUs) __attribute__((noreturn));
998+
uint64_t delayUs) __attribute__((noreturn));
999999

10001000
static void enterSystemOffInternal(bool disableRamRetention,
10011001
bool timedWake,
1002-
uint32_t delayUs)
1002+
uint64_t delayUs)
10031003
{
10041004
clearSystemOffAbortDiagnostic();
10051005
__asm volatile("cpsid i" ::: "memory");
@@ -1072,26 +1072,22 @@ void nrf54_core_system_off_timed_us(uint32_t delayUs,
10721072
enterSystemOffInternal(disableRamRetention != 0U, true, delayUs);
10731073
}
10741074

1075-
static void enterTimedSystemOff(bool disableRamRetention, uint32_t delayUs)
1075+
static void enterTimedSystemOff(bool disableRamRetention, uint64_t delayUs)
10761076
__attribute__((noreturn));
10771077

1078-
static void enterTimedSystemOff(bool disableRamRetention, uint32_t delayUs)
1078+
static void enterTimedSystemOff(bool disableRamRetention, uint64_t delayUs)
10791079
{
10801080
enterSystemOffInternal(disableRamRetention, true, delayUs);
10811081
}
10821082

1083-
static uint32_t systemOffDelayMsToUs(unsigned long ms)
1083+
static uint64_t systemOffDelayMsToUs(unsigned long ms)
10841084
{
1085-
uint32_t delayMs = (uint32_t)ms;
1086-
if (delayMs > (0xFFFFFFFFUL / 1000UL)) {
1087-
delayMs = 0xFFFFFFFFUL / 1000UL;
1088-
}
1089-
return delayMs * 1000UL;
1085+
return (uint64_t)ms * 1000ULL;
10901086
}
10911087

1092-
static void enterSystemOffWakeReset(uint32_t delayUs) __attribute__((noreturn));
1088+
static void enterSystemOffWakeReset(uint64_t delayUs) __attribute__((noreturn));
10931089

1094-
static void enterSystemOffWakeReset(uint32_t delayUs)
1090+
static void enterSystemOffWakeReset(uint64_t delayUs)
10951091
{
10961092
enterSystemOffInternal(true, true, delayUs);
10971093
}

hardware/nrf54l15clean/nrf54l15clean/cores/nrf54lm20b/CoreVersionGenerated.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
#define ARDUINO_NRF54L15_CLEAN_VERSION_MAJOR 1
55
#define ARDUINO_NRF54L15_CLEAN_VERSION_MINOR 0
6-
#define ARDUINO_NRF54L15_CLEAN_VERSION_PATCH 13
6+
#define ARDUINO_NRF54L15_CLEAN_VERSION_PATCH 14
77
#define ARDUINO_NRF54L15_CLEAN_VERSION_PRERELEASE ""
88
#define ARDUINO_NRF54L15_CLEAN_VERSION_IS_PRERELEASE 0
99

@@ -16,6 +16,6 @@
1616
ARDUINO_NRF54L15_CLEAN_VERSION_MINOR, \
1717
ARDUINO_NRF54L15_CLEAN_VERSION_PATCH)
1818

19-
#define ARDUINO_NRF54L15_CLEAN_VERSION_STRING "1.0.13"
19+
#define ARDUINO_NRF54L15_CLEAN_VERSION_STRING "1.0.14"
2020

2121
#endif // NRF54L15_CLEAN_CORE_VERSION_GENERATED_H

hardware/nrf54l15clean/nrf54l15clean/cores/nrf54lm20b/wiring_time.c

Lines changed: 13 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -702,9 +702,9 @@ static uint32_t systemOffMinimumLatencyUs(void)
702702
kSystemOffMinimumLatencyGuardUs;
703703
}
704704

705-
static uint32_t clampSystemOffDelayUs(uint32_t delayUs)
705+
static uint64_t clampSystemOffDelayUs(uint64_t delayUs)
706706
{
707-
const uint32_t minimumLatencyUs = systemOffMinimumLatencyUs();
707+
const uint64_t minimumLatencyUs = systemOffMinimumLatencyUs();
708708
if (delayUs < minimumLatencyUs) {
709709
return minimumLatencyUs;
710710
}
@@ -799,7 +799,7 @@ typedef enum {
799799
kSystemOffWakeCompareFired = 3U
800800
} system_off_wake_program_status_t;
801801

802-
static system_off_wake_program_status_t programSystemOffWakeUs(uint32_t delayUs)
802+
static system_off_wake_program_status_t programSystemOffWakeUs(uint64_t delayUs)
803803
{
804804
NRF_GRTC_Type* const grtc = NRF_GRTC;
805805
delayUs = clampSystemOffDelayUs(delayUs);
@@ -822,7 +822,7 @@ static system_off_wake_program_status_t programSystemOffWakeUs(uint32_t delayUs)
822822
}
823823

824824
const uint32_t minimumLatencyUs = systemOffMinimumLatencyUs();
825-
uint32_t wakeDelayUs = delayUs;
825+
uint64_t wakeDelayUs = delayUs;
826826

827827
for (uint8_t attempt = 0U; attempt < 2U; ++attempt) {
828828
ensureGrtcReady(grtc);
@@ -859,7 +859,7 @@ static system_off_wake_program_status_t programSystemOffWakeUs(uint32_t delayUs)
859859
grtc->CC[wakeChannel].CCEN =
860860
(GRTC_CC_CCEN_ACTIVE_Disable << GRTC_CC_CCEN_ACTIVE_Pos);
861861
grtc->EVENTS_COMPARE[wakeChannel] = 0U;
862-
if (wakeDelayUs > UINT32_MAX - minimumLatencyUs) {
862+
if (wakeDelayUs > UINT64_MAX - minimumLatencyUs) {
863863
return kSystemOffWakeCompareFired;
864864
}
865865
wakeDelayUs += minimumLatencyUs;
@@ -989,11 +989,11 @@ static bool clearResetReasonsForSystemOff(void)
989989

990990
static void enterSystemOffInternal(bool disableRamRetention,
991991
bool timedWake,
992-
uint32_t delayUs) __attribute__((noreturn));
992+
uint64_t delayUs) __attribute__((noreturn));
993993

994994
static void enterSystemOffInternal(bool disableRamRetention,
995995
bool timedWake,
996-
uint32_t delayUs)
996+
uint64_t delayUs)
997997
{
998998
clearSystemOffAbortDiagnostic();
999999
__asm volatile("cpsid i" ::: "memory");
@@ -1066,26 +1066,22 @@ void nrf54_core_system_off_timed_us(uint32_t delayUs,
10661066
enterSystemOffInternal(disableRamRetention != 0U, true, delayUs);
10671067
}
10681068

1069-
static void enterTimedSystemOff(bool disableRamRetention, uint32_t delayUs)
1069+
static void enterTimedSystemOff(bool disableRamRetention, uint64_t delayUs)
10701070
__attribute__((noreturn));
10711071

1072-
static void enterTimedSystemOff(bool disableRamRetention, uint32_t delayUs)
1072+
static void enterTimedSystemOff(bool disableRamRetention, uint64_t delayUs)
10731073
{
10741074
enterSystemOffInternal(disableRamRetention, true, delayUs);
10751075
}
10761076

1077-
static uint32_t systemOffDelayMsToUs(unsigned long ms)
1077+
static uint64_t systemOffDelayMsToUs(unsigned long ms)
10781078
{
1079-
uint32_t delayMs = (uint32_t)ms;
1080-
if (delayMs > (0xFFFFFFFFUL / 1000UL)) {
1081-
delayMs = 0xFFFFFFFFUL / 1000UL;
1082-
}
1083-
return delayMs * 1000UL;
1079+
return (uint64_t)ms * 1000ULL;
10841080
}
10851081

1086-
static void enterSystemOffWakeReset(uint32_t delayUs) __attribute__((noreturn));
1082+
static void enterSystemOffWakeReset(uint64_t delayUs) __attribute__((noreturn));
10871083

1088-
static void enterSystemOffWakeReset(uint32_t delayUs)
1084+
static void enterSystemOffWakeReset(uint64_t delayUs)
10891085
{
10901086
enterSystemOffInternal(true, true, delayUs);
10911087
}

hardware/nrf54l15clean/nrf54l15clean/libraries/Nrf54L15-Clean-Implementation/examples/PMIC/nPM1300_TimedHibernate/nPM1300_TimedHibernate.ino

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
11
/*
22
* nPM1300 Timed Hibernate
33
*
4-
* XIAO nRF54LM20A PMIC hibernate example. This programs the nPM1300 wake
5-
* timer, then asks the PMIC to enter hibernate mode. On a battery/PPK2 supply
6-
* this is the sub-uA path reported by msfujino's test sketch.
4+
* XIAO nRF54LM20A timed cold-boot example.
5+
* - On battery/VBAT, the helper programs the nPM1300 wake timer and enters
6+
* true PMIC Hibernate.
7+
* - On USB/VBUS, where the PMIC cannot enter Hibernate, the helper uses the
8+
* nRF54 GRTC System OFF wake-reset path.
79
*
810
* Current measurement notes:
9-
* - Measure from the VBAT/battery pads, not through USB.
10-
* - USB/VBUS must be disconnected before PMIC Hibernate entry; the helper
11-
* returns false while VBUS is present.
12-
* - Wake is a cold boot after the PMIC timer expires.
11+
* - To measure the lowest-current PMIC path, supply the VBAT/battery pads and
12+
* disconnect USB/VBUS plus debug wiring.
13+
* - The USB fallback keeps the board's USB power path active and is not the
14+
* sub-uA PMIC Hibernate measurement case.
15+
* - Both successful paths wake by cold boot and restart setup().
1316
*/
1417

1518
#include <Arduino.h>
@@ -36,8 +39,8 @@ void setup() {
3639
delay(2000);
3740

3841
if (!npm1300_enter_timed_hibernate_ms(kHibernateMs)) {
39-
// PMIC not present or timer setup failed. Blink fast so the failure is
40-
// visible without requiring Serial while measuring current.
42+
// Invalid delay, PMIC status read, or timer setup failed. Blink fast so
43+
// the failure is visible without requiring Serial while measuring current.
4144
while (true) {
4245
digitalWrite(LED_BUILTIN, LOW);
4346
delay(50);

hardware/nrf54l15clean/nrf54l15clean/libraries/Nrf54L15-Clean-Implementation/src/npm1300.cpp

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -705,10 +705,22 @@ bool npm1300_configure_hibernate_timer_ms(uint32_t delay_ms) {
705705
}
706706

707707
bool npm1300_enter_timed_hibernate_ms(uint32_t delay_ms) {
708-
if (!npm1300_vbus_absent_for_ship_hibernate()) {
708+
if (delay_ms == 0UL || delay_ms > NPM1300_HIBERNATE_TIMER_MAX_MS) {
709709
return false;
710710
}
711711

712+
uint8_t vbusStatus = 0U;
713+
if (!npm1300_vbus_status(&vbusStatus)) {
714+
return false;
715+
}
716+
717+
if ((vbusStatus & NPM1300_VBUS_STATUS_PRESENT) != 0U) {
718+
// nPM1300 Hibernate requires VBUS to be absent, and VBUS is itself a
719+
// PMIC wake source. Use the SoC's timed System OFF cold-reset path
720+
// while USB is powering the board.
721+
systemOffWakeReset(delay_ms);
722+
}
723+
712724
if (!npm1300_configure_hibernate_timer_ms(delay_ms)) {
713725
return false;
714726
}

0 commit comments

Comments
 (0)