From 4da623c9fe919e6a6d9d9e2e3832a042d1d023ae Mon Sep 17 00:00:00 2001 From: Wessel Nieboer Date: Sat, 7 Mar 2026 16:07:00 +0100 Subject: [PATCH] Preserve time across warm resets on nRF52 (watchdog, soft reset, pin reset) nRF52 boards without I2C RTC will drop back to seed time on every reset. Mirrors fix in #1896 for ESP32. --- src/helpers/NRF52Board.cpp | 4 +++ src/helpers/NRF52Board.h | 41 ++++++++++++++++++++++++++ variants/heltec_mesh_solar/target.cpp | 2 +- variants/heltec_t114/target.cpp | 2 +- variants/ikoka_handheld_nrf/target.cpp | 2 +- variants/ikoka_nano_nrf/target.cpp | 2 +- variants/ikoka_stick_nrf/target.cpp | 2 +- variants/keepteen_lt1/target.cpp | 2 +- variants/lilygo_techo/target.cpp | 2 +- variants/lilygo_techo_lite/target.cpp | 2 +- variants/mesh_pocket/target.cpp | 2 +- variants/meshtiny/target.cpp | 2 +- variants/minewsemi_me25ls01/target.cpp | 2 +- variants/minewsemi_me25ls01/target.h | 2 +- variants/nano_g2_ultra/target.cpp | 2 +- variants/promicro/target.cpp | 2 +- variants/rak3401/target.cpp | 2 +- variants/rak4631/target.cpp | 2 +- variants/rak_wismesh_tag/target.cpp | 2 +- variants/sensecap_solar/target.cpp | 2 +- variants/t1000-e/target.cpp | 2 +- variants/t1000-e/target.h | 2 +- variants/thinknode_m1/target.cpp | 2 +- variants/thinknode_m3/target.cpp | 2 +- variants/thinknode_m6/target.cpp | 2 +- variants/wio-tracker-l1/target.cpp | 2 +- variants/wio_wm1110/target.cpp | 2 +- variants/wio_wm1110/target.h | 2 +- variants/xiao_nrf52/target.cpp | 2 +- 29 files changed, 72 insertions(+), 27 deletions(-) diff --git a/src/helpers/NRF52Board.cpp b/src/helpers/NRF52Board.cpp index 2c8753d464..c83f40bf0d 100644 --- a/src/helpers/NRF52Board.cpp +++ b/src/helpers/NRF52Board.cpp @@ -1,6 +1,10 @@ #if defined(NRF52_PLATFORM) #include "NRF52Board.h" +// Single definitions for noinit backup variables (declared extern in NRF52Board.h) +uint32_t _noinit_backup_time __attribute__((section(".noinit"))); +uint32_t _noinit_backup_magic __attribute__((section(".noinit"))); + #include #include diff --git a/src/helpers/NRF52Board.h b/src/helpers/NRF52Board.h index c9f1e071b8..c2d667fc80 100644 --- a/src/helpers/NRF52Board.h +++ b/src/helpers/NRF52Board.h @@ -5,6 +5,47 @@ #if defined(NRF52_PLATFORM) +// noinit variables survive watchdog, soft, pin, and lockup resets (RAM retained). +// Lost on power-on and System OFF (magic check handles this). +extern uint32_t _noinit_backup_time __attribute__((section(".noinit"))); +extern uint32_t _noinit_backup_magic __attribute__((section(".noinit"))); +#define NRF52_BACKUP_MAGIC 0xAA55CC33 +#define NRF52_TIME_MIN 1772323200 // 1 Mar 2026 + +class NRF52RTCClock : public mesh::RTCClock { + uint32_t base_time; + uint64_t accumulator; + unsigned long prev_millis; +public: + NRF52RTCClock() { + if (_noinit_backup_magic == NRF52_BACKUP_MAGIC && _noinit_backup_time > NRF52_TIME_MIN) { + base_time = _noinit_backup_time; + } else { + base_time = NRF52_TIME_MIN; + } + accumulator = 0; + prev_millis = millis(); + } + uint32_t getCurrentTime() override { return base_time + accumulator / 1000; } + void setCurrentTime(uint32_t time) override { + base_time = time; + accumulator = 0; + prev_millis = millis(); + _noinit_backup_time = time; + _noinit_backup_magic = NRF52_BACKUP_MAGIC; + } + void tick() override { + unsigned long now = millis(); + accumulator += (now - prev_millis); + prev_millis = now; + uint32_t current = base_time + accumulator / 1000; + if (current > NRF52_TIME_MIN && current != _noinit_backup_time) { + _noinit_backup_time = current; + _noinit_backup_magic = NRF52_BACKUP_MAGIC; + } + } +}; + #ifdef NRF52_POWER_MANAGEMENT // Shutdown Reason Codes (stored in GPREGRET before SYSTEMOFF) #define SHUTDOWN_REASON_NONE 0x00 diff --git a/variants/heltec_mesh_solar/target.cpp b/variants/heltec_mesh_solar/target.cpp index d140864cd1..7f96d3f585 100644 --- a/variants/heltec_mesh_solar/target.cpp +++ b/variants/heltec_mesh_solar/target.cpp @@ -9,7 +9,7 @@ RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BU WRAPPER_CLASS radio_driver(radio, board); -VolatileRTCClock fallback_clock; +NRF52RTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1, &rtc_clock); SolarSensorManager sensors = SolarSensorManager(nmea); diff --git a/variants/heltec_t114/target.cpp b/variants/heltec_t114/target.cpp index cb8f75db51..7f1fccf8c9 100644 --- a/variants/heltec_t114/target.cpp +++ b/variants/heltec_t114/target.cpp @@ -17,7 +17,7 @@ RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BU WRAPPER_CLASS radio_driver(radio, board); -VolatileRTCClock fallback_clock; +NRF52RTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); #if ENV_INCLUDE_GPS diff --git a/variants/ikoka_handheld_nrf/target.cpp b/variants/ikoka_handheld_nrf/target.cpp index 6c53c5e324..e041d82e63 100644 --- a/variants/ikoka_handheld_nrf/target.cpp +++ b/variants/ikoka_handheld_nrf/target.cpp @@ -8,7 +8,7 @@ RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BU WRAPPER_CLASS radio_driver(radio, board); -VolatileRTCClock fallback_clock; +NRF52RTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); EnvironmentSensorManager sensors; diff --git a/variants/ikoka_nano_nrf/target.cpp b/variants/ikoka_nano_nrf/target.cpp index baf02c3d12..f1b818baf4 100644 --- a/variants/ikoka_nano_nrf/target.cpp +++ b/variants/ikoka_nano_nrf/target.cpp @@ -13,7 +13,7 @@ RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BU WRAPPER_CLASS radio_driver(radio, board); -VolatileRTCClock fallback_clock; +NRF52RTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); EnvironmentSensorManager sensors; diff --git a/variants/ikoka_stick_nrf/target.cpp b/variants/ikoka_stick_nrf/target.cpp index bb140505a8..007eff097f 100644 --- a/variants/ikoka_stick_nrf/target.cpp +++ b/variants/ikoka_stick_nrf/target.cpp @@ -13,7 +13,7 @@ RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BU WRAPPER_CLASS radio_driver(radio, board); -VolatileRTCClock fallback_clock; +NRF52RTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); EnvironmentSensorManager sensors; diff --git a/variants/keepteen_lt1/target.cpp b/variants/keepteen_lt1/target.cpp index f1879ac569..34a668fce8 100644 --- a/variants/keepteen_lt1/target.cpp +++ b/variants/keepteen_lt1/target.cpp @@ -8,7 +8,7 @@ RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BU WRAPPER_CLASS radio_driver(radio, board); -VolatileRTCClock fallback_clock; +NRF52RTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); #if ENV_INCLUDE_GPS #include diff --git a/variants/lilygo_techo/target.cpp b/variants/lilygo_techo/target.cpp index 278fc76dad..36b633a561 100644 --- a/variants/lilygo_techo/target.cpp +++ b/variants/lilygo_techo/target.cpp @@ -9,7 +9,7 @@ RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BU WRAPPER_CLASS radio_driver(radio, board); -VolatileRTCClock fallback_clock; +NRF52RTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); #ifdef ENV_INCLUDE_GPS diff --git a/variants/lilygo_techo_lite/target.cpp b/variants/lilygo_techo_lite/target.cpp index 95a62fcd25..2236c59acc 100644 --- a/variants/lilygo_techo_lite/target.cpp +++ b/variants/lilygo_techo_lite/target.cpp @@ -9,7 +9,7 @@ RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BU WRAPPER_CLASS radio_driver(radio, board); -VolatileRTCClock fallback_clock; +NRF52RTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); #ifdef ENV_INCLUDE_GPS diff --git a/variants/mesh_pocket/target.cpp b/variants/mesh_pocket/target.cpp index ee8f944de4..b93c03def9 100644 --- a/variants/mesh_pocket/target.cpp +++ b/variants/mesh_pocket/target.cpp @@ -11,7 +11,7 @@ WRAPPER_CLASS radio_driver(radio, board); SensorManager sensors = SensorManager(); -VolatileRTCClock fallback_clock; +NRF52RTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); #ifdef DISPLAY_CLASS diff --git a/variants/meshtiny/target.cpp b/variants/meshtiny/target.cpp index 50d678266e..0e954167f2 100644 --- a/variants/meshtiny/target.cpp +++ b/variants/meshtiny/target.cpp @@ -9,7 +9,7 @@ RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BU WRAPPER_CLASS radio_driver(radio, board); -VolatileRTCClock fallback_clock; +NRF52RTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); EnvironmentSensorManager sensors = EnvironmentSensorManager(); diff --git a/variants/minewsemi_me25ls01/target.cpp b/variants/minewsemi_me25ls01/target.cpp index 9944a38b93..2fced756d0 100644 --- a/variants/minewsemi_me25ls01/target.cpp +++ b/variants/minewsemi_me25ls01/target.cpp @@ -7,7 +7,7 @@ RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BU WRAPPER_CLASS radio_driver(radio, board); -VolatileRTCClock rtc_clock; +NRF52RTCClock rtc_clock; extern EnvironmentSensorManager sensors; #if ENV_INCLUDE_GPS #include diff --git a/variants/minewsemi_me25ls01/target.h b/variants/minewsemi_me25ls01/target.h index f8d42863b2..e7850c467a 100644 --- a/variants/minewsemi_me25ls01/target.h +++ b/variants/minewsemi_me25ls01/target.h @@ -19,7 +19,7 @@ extern MinewsemiME25LS01Board board; extern WRAPPER_CLASS radio_driver; -extern VolatileRTCClock rtc_clock; +extern NRF52RTCClock rtc_clock; extern EnvironmentSensorManager sensors; bool radio_init(); diff --git a/variants/nano_g2_ultra/target.cpp b/variants/nano_g2_ultra/target.cpp index 69a2772ccb..620b1ca873 100644 --- a/variants/nano_g2_ultra/target.cpp +++ b/variants/nano_g2_ultra/target.cpp @@ -10,7 +10,7 @@ RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BU WRAPPER_CLASS radio_driver(radio, board); -VolatileRTCClock fallback_clock; +NRF52RTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1, &rtc_clock); NanoG2UltraSensorManager sensors = NanoG2UltraSensorManager(nmea); diff --git a/variants/promicro/target.cpp b/variants/promicro/target.cpp index b5a74c90dc..f5e9a3350d 100644 --- a/variants/promicro/target.cpp +++ b/variants/promicro/target.cpp @@ -8,7 +8,7 @@ RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BU WRAPPER_CLASS radio_driver(radio, board); -VolatileRTCClock fallback_clock; +NRF52RTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); #if ENV_INCLUDE_GPS #include diff --git a/variants/rak3401/target.cpp b/variants/rak3401/target.cpp index 5309e6b22b..b7ff17359a 100644 --- a/variants/rak3401/target.cpp +++ b/variants/rak3401/target.cpp @@ -21,7 +21,7 @@ RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BU WRAPPER_CLASS radio_driver(radio, board); -VolatileRTCClock fallback_clock; +NRF52RTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); #if ENV_INCLUDE_GPS diff --git a/variants/rak4631/target.cpp b/variants/rak4631/target.cpp index a41ba72075..bb27af4474 100644 --- a/variants/rak4631/target.cpp +++ b/variants/rak4631/target.cpp @@ -21,7 +21,7 @@ RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BU WRAPPER_CLASS radio_driver(radio, board); -VolatileRTCClock fallback_clock; +NRF52RTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); #if ENV_INCLUDE_GPS diff --git a/variants/rak_wismesh_tag/target.cpp b/variants/rak_wismesh_tag/target.cpp index cdf81f4f5b..02669cd11f 100644 --- a/variants/rak_wismesh_tag/target.cpp +++ b/variants/rak_wismesh_tag/target.cpp @@ -17,7 +17,7 @@ RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BU WRAPPER_CLASS radio_driver(radio, board); -VolatileRTCClock fallback_clock; +NRF52RTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); #if ENV_INCLUDE_GPS diff --git a/variants/sensecap_solar/target.cpp b/variants/sensecap_solar/target.cpp index 466415176f..108f04cd62 100644 --- a/variants/sensecap_solar/target.cpp +++ b/variants/sensecap_solar/target.cpp @@ -10,7 +10,7 @@ RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BU WRAPPER_CLASS radio_driver(radio, board); -VolatileRTCClock fallback_clock; +NRF52RTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); #ifdef ENV_INCLUDE_GPS MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1, &rtc_clock); diff --git a/variants/t1000-e/target.cpp b/variants/t1000-e/target.cpp index 4253282708..0b775b34e7 100644 --- a/variants/t1000-e/target.cpp +++ b/variants/t1000-e/target.cpp @@ -9,7 +9,7 @@ RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BU WRAPPER_CLASS radio_driver(radio, board); -VolatileRTCClock rtc_clock; +NRF52RTCClock rtc_clock; MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1, &rtc_clock); T1000SensorManager sensors = T1000SensorManager(nmea); diff --git a/variants/t1000-e/target.h b/variants/t1000-e/target.h index db003cc5fa..7233e9881b 100644 --- a/variants/t1000-e/target.h +++ b/variants/t1000-e/target.h @@ -37,7 +37,7 @@ class T1000SensorManager: public SensorManager { extern T1000eBoard board; extern WRAPPER_CLASS radio_driver; -extern VolatileRTCClock rtc_clock; +extern NRF52RTCClock rtc_clock; extern T1000SensorManager sensors; bool radio_init(); diff --git a/variants/thinknode_m1/target.cpp b/variants/thinknode_m1/target.cpp index 69306fc0e1..f26f3921f5 100644 --- a/variants/thinknode_m1/target.cpp +++ b/variants/thinknode_m1/target.cpp @@ -9,7 +9,7 @@ RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BU WRAPPER_CLASS radio_driver(radio, board); -VolatileRTCClock fallback_clock; +NRF52RTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1, &rtc_clock); ThinkNodeM1SensorManager sensors = ThinkNodeM1SensorManager(nmea); diff --git a/variants/thinknode_m3/target.cpp b/variants/thinknode_m3/target.cpp index 1e6c2ad13c..1102356430 100644 --- a/variants/thinknode_m3/target.cpp +++ b/variants/thinknode_m3/target.cpp @@ -8,7 +8,7 @@ RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BU WRAPPER_CLASS radio_driver(radio, board); -VolatileRTCClock fallback_clock; +NRF52RTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); #ifdef ENV_INCLUDE_GPS MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1, &rtc_clock); diff --git a/variants/thinknode_m6/target.cpp b/variants/thinknode_m6/target.cpp index de167194e4..1ae94d9b2d 100644 --- a/variants/thinknode_m6/target.cpp +++ b/variants/thinknode_m6/target.cpp @@ -9,7 +9,7 @@ RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BU WRAPPER_CLASS radio_driver(radio, board); -VolatileRTCClock fallback_clock; +NRF52RTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); #ifdef ENV_INCLUDE_GPS MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1, &rtc_clock); diff --git a/variants/wio-tracker-l1/target.cpp b/variants/wio-tracker-l1/target.cpp index 7a573258e6..89c99265c5 100644 --- a/variants/wio-tracker-l1/target.cpp +++ b/variants/wio-tracker-l1/target.cpp @@ -9,7 +9,7 @@ RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BU WRAPPER_CLASS radio_driver(radio, board); -VolatileRTCClock fallback_clock; +NRF52RTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); #ifdef ENV_INCLUDE_GPS diff --git a/variants/wio_wm1110/target.cpp b/variants/wio_wm1110/target.cpp index 1bb7cec8cb..0435f2801e 100644 --- a/variants/wio_wm1110/target.cpp +++ b/variants/wio_wm1110/target.cpp @@ -8,7 +8,7 @@ RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BU WRAPPER_CLASS radio_driver(radio, board); -VolatileRTCClock rtc_clock; +NRF52RTCClock rtc_clock; EnvironmentSensorManager sensors; #ifndef LORA_CR diff --git a/variants/wio_wm1110/target.h b/variants/wio_wm1110/target.h index 79504ab8d2..9617cd714b 100644 --- a/variants/wio_wm1110/target.h +++ b/variants/wio_wm1110/target.h @@ -10,7 +10,7 @@ extern WioWM1110Board board; extern WRAPPER_CLASS radio_driver; -extern VolatileRTCClock rtc_clock; +extern NRF52RTCClock rtc_clock; extern EnvironmentSensorManager sensors; bool radio_init(); diff --git a/variants/xiao_nrf52/target.cpp b/variants/xiao_nrf52/target.cpp index ab6fe279ac..0b732749c2 100644 --- a/variants/xiao_nrf52/target.cpp +++ b/variants/xiao_nrf52/target.cpp @@ -12,7 +12,7 @@ RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BU WRAPPER_CLASS radio_driver(radio, board); -VolatileRTCClock fallback_clock; +NRF52RTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); EnvironmentSensorManager sensors;