ports: Add RTC.memory() backed by hardware registers.#19084
ports: Add RTC.memory() backed by hardware registers.#19084andrewleech wants to merge 10 commits into
Conversation
|
Code size report: |
|
This would be cleaner as a new api |
|
See related #7133. |
|
@andrewleech - Please use the STM32 backup RAM versus the RTC register. You should aim to use the largest possible backup-related memory on the device (also initialize the clocks, etc.). The IMXRT doesn't have a lot of backup RAM, so, it's the odd duck here. But, Alif and ST do. For the IMXRT, you are correct though with that those registers have to be accessed as 32-bit at a time. Byte writes don't work. |
|
Here's usign the backup RAM (4KB) on the STM32H7. The N6 has 8KB. Here's how I contolled the backup ram (32-bytes on the RT1062) |
|
I've reworked this PR as Thanks @kwagyeman for the suggestion to use STM32 backup SRAM instead of the BKP registers. The stm32 commit now auto-detects BKPSRAM availability at compile time via the CMSIS header macros. On F4/F7/H5/H7/U5/N6 this gives 4-8KB of byte-addressable battery-backed SRAM (itemsize=1). Families without BKPSRAM (L0, L1, L4, G0, G4, WB, WL) fall back to the RTC BKP registers with word-level access (itemsize=4). BKPSRAM clock enable and backup regulator init are done during boot after The Alif backup SRAM at 0x4902C000 turned out to be in peripheral space, not real SRAM, so byte writes don't work there either. It's exposed as itemsize=4 with a comment explaining why. During hardware testing I found a pre-existing issue in The last commit fixes both functions by routing big-int values through Changes from the original PR:
Original commits preserved at Tested on:
On H7 and N6, BKPSRAM falls in the 0x20000000-0x3FFFFFFF SRAM region which is cacheable by default, so an MPU region marks it non-cacheable during init. This was validated on the H747I-DISCO. Other families (F4, F7, H5, U5) have BKPSRAM in the peripheral address space where caching isn't an issue. |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #19084 +/- ##
==========================================
+ Coverage 98.46% 98.51% +0.05%
==========================================
Files 176 178 +2
Lines 22811 23074 +263
==========================================
+ Hits 22460 22731 +271
+ Misses 351 343 -8
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
d9fc7c1 to
8b0a588
Compare
|
@andrewleech - Need an N6/H7? Email me. |
Thanks for the offer! I'll get my openmv N6 back on Tuesday, I just loaned it for testing some other image quality stuff at work. |
a7c7e6a to
c2e1b6d
Compare
|
I tried that with a Arch Mix board and battery backup. Works in that the content survives a power off period, when a battery is connected. A few questions:
|
|
Note: The SAMD51 RTC has 8 words of backup memory as well, which survives hard reset. Address 0x40002480, or edit: A suitable code snippet for Tested with ITSYBITSY_M4 board. |
39d543f to
3c8d730
Compare
|
Thanks for testing @robert-hh, good to hear it survives power cycling with battery backup. On your questions:
Re SAMD51, I've added it as a new commit using your suggested config with the 8KB backup RAM. Builds clean on ADAFRUIT_ITSYBITSY_M4_EXPRESS though I don't have hardware to test on, so it'd be great if you could verify it on your board. |
Thanks. I tested that before posting. Tested again with your last commit. The content of the backup memory survives hard reset. Since no board I have has the MCU battery pin exposed, testing with a small backup battery like a coin cell is not possible. The battery pin on the boards is designed to completely feed the board. |
|
Thanks @robert-hh for the feedback. I'd originally thought boards might want to override individual values to avoid certain registers used by bootloaders etc, but on reflection it's probably better for the app to just avoid those as needed. I've simplified all ports to just gate the block on Thanks for retesting on SAMD51. |
|
|
||
| .. note:: | ||
|
|
||
| On esp32 and rp2, data persists across soft resets but is lost on |
There was a problem hiding this comment.
From #17521 -
For, now I am suggesting that the docs point out that EN will clear RTC memory, just a heads-up for beginners.
[Nice RAG tool Andrew ]
|
Got the N6 BKPSRAM tested via OpenMV N6 (STM32N657X0), 44/44 pass. Byte writes, struct.pack_into across the full uint32 range, uctypes overlays, persistence across soft_reset, all good. Couple of things this confirmed: The MPU non-cacheable region works correctly on Cortex-M55, not just M7. Writes reach BKPSRAM and survive soft_reset. N6 BKPSRAM is real byte-addressable SRAM so itemsize=1 works fine, no sub-word write hazard like we saw on Alif. The Alif region at 0x4902C000 is in peripheral space and zeros unaddressed bytes of the containing word, which is why those two ports use different itemsize values despite both being called "backup SRAM" on paper. The auto-detect in Full hardware test list now:
|
| // Whether to support memoryview.itemsize attribute | ||
| #ifndef MICROPY_PY_BUILTINS_MEMORYVIEW_ITEMSIZE | ||
| #define MICROPY_PY_BUILTINS_MEMORYVIEW_ITEMSIZE (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EVERYTHING) | ||
| #define MICROPY_PY_BUILTINS_MEMORYVIEW_ITEMSIZE (MICROPY_PY_MACHINE_BACKUP_MEMORY || MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EVERYTHING) |
There was a problem hiding this comment.
Suggest changing this to MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_BASIC_FEATURES. It's a pretty basic feature, and since 6443130 mpremote will take advantage of it.
Add a static const memoryview object that exposes battery-backed or persistent hardware memory as machine.backup_memory. Each port provides four config macros (enable, byte count, itemsize, address) and the shared implementation handles the rest. Automatically enables MICROPY_PY_BUILTINS_MEMORYVIEW_ITEMSIZE so users can discover access granularity at runtime. Signed-off-by: Andrew Leech <[email protected]>
Expose SNVS LP General Purpose Registers as machine.backup_memory with word-level access (itemsize=4). Register count varies by chip (4 on RT1011/RT1176, 8 on RT1015/1021/1052/1062/1064). Signed-off-by: Andrew Leech <[email protected]>
On families with dedicated backup SRAM (F4, F7, H5, H7, U5, N6), expose 4-8 KB of byte-addressable battery-backed SRAM as machine.backup_memory with itemsize=1. The BKPSRAM clock and backup regulator are enabled during boot after RTC init. On H7 and N6, an MPU region marks the BKPSRAM non-cacheable since its address falls in the default-cacheable SRAM range. On families without BKPSRAM (L0, L1, L4, G0, G4, WB, WL), fall back to RTC backup registers (BKPxR / TAMP BKPxR) with word-level access (itemsize=4, 20-128 bytes). Gated on MICROPY_HW_ENABLE_RTC. Signed-off-by: Andrew Leech <[email protected]>
Expose the 8 watchdog scratch registers as machine.backup_memory with word-level access (itemsize=4, 32 bytes). Data persists across soft resets but not power-off (no battery backing). Signed-off-by: Andrew Leech <[email protected]>
Expose the 4KB battery-backed backup SRAM at 0x4902C000 as machine.backup_memory with word-level access (itemsize=4). The region lives in peripheral space and does not support sub-word writes, so the memoryview is exposed as uint32 to enforce word-aligned access from Python. Signed-off-by: Andrew Leech <[email protected]>
Expose the RTC user memory (2048 bytes default) as machine.backup_memory with byte-level access alongside the existing RTC.memory() method. The two APIs share the same backing buffer but have independent semantics: backup_memory is a raw memoryview while RTC.memory() tracks written length. Signed-off-by: Andrew Leech <[email protected]>
Expose the 8KB backup RAM at 0x47000000 as machine.backup_memory with byte-level access (itemsize=1) on SAMD51 boards. Signed-off-by: Andrew Leech <[email protected]>
Add documentation for the machine.backup_memory memoryview attribute including per-port storage sizes, reserved register table, uctypes integration example, and availability. Signed-off-by: Andrew Leech <[email protected]>
When writing a big-int value to an array, memoryview, or struct buffer, mp_binary_set_val_array and mp_binary_set_val previously used mp_obj_int_to_bytes_impl which writes individual bytes. On hardware registers and peripheral-backed memory that only supports word-sized stores (e.g. STM32 RTC backup registers, NXP SNVS LPGPR, RP2 watchdog scratch), byte-wise writes silently corrupt the target word. For element sizes that fit in mp_int_t, route big-int values through mp_obj_int_get_truncated and then through the same typed store paths that small-int values already use. This is also slightly faster since it avoids the byte decomposition loop in mpz_as_bytes. The byte-wise path is retained for element sizes exceeding mp_int_t (e.g. int64 on 32-bit targets). The bug only manifests on 32-bit targets where values >= 0x40000000 exceed the small-int range and the destination rejects sub-word stores. On 64-bit hosts all uint32 values are small ints and already take the typed-store path. Signed-off-by: Andrew Leech <[email protected]>
The coverage_32bit job was building with coverage flags but never running gcov or uploading to codecov, so all reported coverage was from the 64-bit build only. Add the same gcov + codecov-action steps that the 64-bit job has, with distinct flags so codecov can merge the data correctly. This closes coverage gaps in code paths that only execute on 32-bit targets (e.g. py/binary.c byte-write fallbacks for typecodes where size > sizeof(mp_int_t)). Signed-off-by: Andrew Leech <[email protected]>
|
@dpgeorge - Any progress on this? |
Summary
Adds
RTC.memory()to mimxrt, stm32, rp2, and alif ports. On read it returns a writable uint32memoryviewbacked directly by the hardware registers / memory, so users get direct register access, slicing, and uctypes integration without any copying. On write,RTC.memory(data)does a word-aligned bulk write that only touches the registers covered by the input data.Each port auto-sizes to the available hardware: SNVS LPGPR on mimxrt (16-32 bytes), RTC backup registers on stm32 (20-128 bytes), watchdog scratch on rp2 (32 bytes), and 4KB battery-backed SRAM on alif. Boards can override
MICROPY_HW_RTC_USER_MEM_MAXif needed.Some registers are used by system firmware (UF2 bootloader on mimxrt, CLK_LAST_FREQ / mboot / rfcore on stm32, watchdog_reboot on rp2). These are documented in the RST but not hidden, the full register array is always exposed so users can address specific registers for purposes like mboot status reporting or bootloader flags.
On rp2 the watchdog scratch registers aren't battery-backed, data only survives soft resets.
Testing
Tested on hardware across all four ports:
Tests cover direct memoryview register write/read, bulk write with untouched register preservation, partial trailing word read-modify-write, buffer-too-long ValueError, and persistence across soft reset.
Also build-tested on MIMXRT1170_EVK, NUCLEO_G0B1RE, NUCLEO_H7A3ZI_Q, and PYBV10.
Trade-offs and Alternatives
The read path returns a uint32
memoryviewrather thanbytes(as esp32/esp8266 do) or abytearray. Abytescopy is safe but doesn't allow direct register writes. Abytearrayby reference allows byte-level access but STM32 backup registers require 32-bit aligned writes, so byte stores through the bytearray silently corrupt data. The uint32 memoryview forces word-aligned access at the type level which matches the hardware constraint, and works well with uctypes for structured register layouts.The full register array is exposed including system-reserved locations, rather than hiding them with skip logic (an earlier revision did skip LPGPR[3] on mimxrt for the UF2 bootloader). Hiding registers creates confusing offset mapping when users need to address specific registers for things like mboot status or bootloader flags, and the same approach would be impractical on stm32 where reserved registers are scattered across the array.
The write path only modifies registers covered by the input data rather than zero-filling the remainder. This is important when the register space is shared with system firmware, a bulk write of a couple of bytes shouldn't clobber the clock frequency register at the other end of the array.
The esp32/esp8266 ports still return
byteswith length tracking. Updating those to match the memoryview approach would be a separate change.Closes #18960.
Generative AI
I used generative AI tools when creating this PR, but a human has checked the code and is responsible for the description above.