Skip to content

fix(esp32): define the RTC backup of ESP32RTCClock once (note 0012) - #39

Merged
marcelverdult merged 15 commits into
devfrom
refactor/esp32-rtc-backup
Oct 26, 2026
Merged

marcelverdult merged 15 commits into
devfrom
refactor/esp32-rtc-backup

Conversation

@marcelverdult

@marcelverdult marcelverdult commented Oct 23, 2026 •

Copy link
Copy Markdown
Owner

CI only, not for merge into integration. Stacked on #34 (cut D2 of note 0011), so the diff against dev shows the earlier cuts as well. This PR's own commits are the last six (12e37302, 836678d2, e6072261, a1549849, 24e3ead4, plus the note update).

Note 0012: ESP32Board.h defined the RTC backup of ESP32RTCClock (_rtc_backup_time, _rtc_backup_magic) as static RTC_NOINIT_ATTR in the header, so every translation unit got its own pair while the inline members begin(), setCurrentTime() and tick() use the pair of the unit they are compiled in. Today exactly one unit per image uses them; a second one could make begin() restore a pair setCurrentTime() never wrote. The header now declares them extern, ESP32Board.cpp defines them once.

Evidence:

  • Host suite native_esp32_rtc_backup: the real ESP32Board.h and ESP32Board.cpp against ESP-IDF stand-ins, members called from two translation units. 5 of 9 fail on the test commit, 9 of 9 pass on the code commit. 8 mutations: 6 caught, 1 equivalent, 1 does not link. All 23 native environments: 716 cases pass.
  • 64 environments built from 12e37302 and 836678d2, same worktree, SOURCE_DATE_EPOCH=1760000000 (one per ESP32 variant folder, all roles of heltec_v3, more C6, nRF52/RP2040/STM32 controls). objdump -t: every ESP32 image after has exactly one _rtc_backup_time and one _rtc_backup_magic; in 55 of 57 at the same address as before, with all of .rtc_noinit unchanged. In ThinkNode_M7 and ThinkNode_M9 the time moves from 0x50000000 to 0x50000004: these never call fallback_clock.begin(), so the backup is never read there and nothing is lost (the compiler had dropped the unread static magic).
  • nRF52, RP2040, STM32 images byte for byte the same. ESP32/S3/C6 the same outside the ELF hash; the three C3 builds differ only in ESP32RTCClock::tick() addressing (same length). Flash +28/+24 on M7/M9 only.
  • The two C6 board files are the one user unit in their images and need no change.
  • A first version with its own ESP32RTCBackup.cpp was measured and dropped: the extra empty object reorders RP2040 linker veneers.
  • No hardware evidence (without board).

…the board description

Inventory of the radio blocks in the 89 target.cpp (tools/refactoring/
variant_target_globals.py), why objects cannot be weak, the opt-in macro
RADIOLIB_TARGET_GLOBALS in target.h, the split into D1 (31 nRF52) and
D2 (22 ESP32 with the shared radio_init()), rejected alternatives and the
open upstream pull requests that touch the same lines.
…riants

A new host suite (native_variant_target_globals) builds the shared variant
code against the target.h of a variant with the real AutoDiscoverRTCClock,
with the radio block of the 31 nRF52 variants of cut C copied verbatim: the
radio is built from a Module on the board pins in SX126x order with SPI
passed, radio_driver wraps it and the board, rtc_clock falls back to and
ticks fallback_clock, and the shared radio_init() starts that radio. The
variant stand-ins move into variant_stubs.h and gain a Module stand-in.
… variants

RadioLibTarget.cpp defines radio, radio_driver, fallback_clock and
rtc_clock for the variants whose target.h defines the new opt-in macro
RADIOLIB_TARGET_GLOBALS, built from the P_LORA_* pins: on nRF52 the radio
is a Module on SPI, the fallback clock a VolatileRTCClock. Not weak: a weak
object would still be constructed into a variant's own one. Other platforms
and the SX1276 pin order stop the build with an #error until described.

The 31 nRF52 variants of cut C delete their block from target.cpp
(deletions only) and add the macro line to target.h (additions only).
The host suite now runs its cases against the shared globals.
…riants

A host suite in four environments, one per board description that occurs
among the 22 ESP32 variants of cut D2 (SX1262 on an own bus on the default
host, without P_LORA_SCLK, SX1276, own bus on another host), with their
blocks and radio_init() bodies copied verbatim: the Module's pins in the
chip's order, its bus (own, on which host, or none), what radio_driver
wraps, the fallback clock, and that radio_init() starts the fallback clock,
the RTC and then the radio on its bus, passing the result through. The SPI
stand-in gets bus numbers, the radio stand-in tells the module's bus apart.
…r 22 ESP32 variants

RadioLibTarget.cpp describes the radio globals for ESP32 as well: the pins
in the chip's order (SX1276: DIO0, RESET, DIO1), an own SPIClass where the
board names P_LORA_SCLK, on P_LORA_SPI_HOST if set, otherwise no bus so that
RadioLib starts its default one, and an ESP32RTCClock as fallback clock. For
those variants with an SX126x or SX1276 it adds a weak radio_init(): start
the fallback clock and the RTC, then the radio on its bus.

The 22 ESP32 variants left by cut C delete their block and radio_init()
from target.cpp (deletions only) and add the opt-in, and five of them
P_LORA_SPI_HOST (FSPI, or 0 on the C6), to target.h (additions only).
…on units

The real ESP32Board.h and ESP32Board.cpp on the host, against stand-ins for the
ESP-IDF headers and the variant's target.h, used from two translation units.
Fails on this commit: the header defines the backup as static, so the test's
unit has its own copy (5 of 9 tests).
ESP32Board.h defined _rtc_backup_time and _rtc_backup_magic as static in the
header, so every translation unit that includes it has its own copy, and the
inline members of ESP32RTCClock read and write the copy of the unit they are
compiled in. Today one unit per image uses them; a second one could restore a
copy that setCurrentTime() never wrote. The header now declares them, and
ESP32Board.cpp defines them once.
It needs the ESP-IDF stand-ins of its own environment, native_esp32_rtc_backup.
ESP32RTCClock calls time(), which the ESP32 core declares through Arduino.h.
On macOS <sys/time.h> brings it in, with glibc it does not (CI: 'time' was not
declared in this scope).
@marcelverdult
marcelverdult merged commit d7c5227 into dev Oct 26, 2026
15 checks passed
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.

1 participant