From 9a561fd7c93c680e4f225d0fe64a14d2ed67fed5 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 2c8753d46..c83f40bf0 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 96f67dc95..604aea14b 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 1ea33e1f2..c2e828cca 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 6a30a4d18..2ac89f652 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 48244e172..994c301e4 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 be20cfb43..13585499a 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 4f6befc60..03f7a1b5e 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 85f11232a..2053cdf06 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 12d222ff7..238fa8365 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 40a94526e..c926ea309 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 6fabb3174..ff3e8f8bb 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 9188db174..6965ed75d 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 fcec19419..554dfbf83 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 ea7383e25..271777a58 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 bd4e9b480..8fd1f57c3 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 e4a4442ab..7a5609f85 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 77fb0e5f2..fa22f8b08 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 ac1ac7cac..a775e6000 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 d42c0d58e..7bbc351e2 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 9f5495155..82dae512e 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 da8fa48bb..9f9d88a5b 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 d4e3c02c5..2bd86993e 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 ec2438d40..24f29b9cc 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 7303eb4ca..a2b2f02eb 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 36ca86180..798d8f31b 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 4575a76c8..a23df3532 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 457d5bda2..731265728 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 8712a0ef0..d6bc0e4a1 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 a8f4162ea..61430acdb 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;