Conversation
The previous nRF54LM20A flash algorithm enabled only CONFIG.WEN and copied words straight into RRAM with no write-buffer configuration and no buffer commit. Every CPU store to RRAM then faulted (HardFault, IPSR=3) — while erase kept working because EraseChip only pokes RRAMC task registers — so `pyocd flash -t nrf54lm20a` faulted at the first programming store, on any chip state (pyocd#2016). This blob programs through the RRAMC write buffer, matching Nordic's own nRF54LM20A flash algorithm (the same one shipped in probe-rs targets): set CONFIG = WEN | WRITEBUFSIZE(32) = 0x2001, copy the page, flush with TASKS_COMMITWRITEBUF, wait READY, clear WEN. Pages are 4 KiB so each program call covers whole 128-bit RRAM codewords (4-byte pages were observed dropping stray bytes, e.g. 2 bytes/flash in an independent OpenOCD verify). The previous blob's flash algorithm was byte-identical to the official one merged in pyocd#1889 (shipped in v0.44.0), so this fix lands on top of that and also applies to any fork/pin that carried it. Hardware verified on a Seeed XIAO nRF54LM20A (on-board CMSIS-DAP): - mass-erase + flash on a blank chip: exit 0, 24576 bytes programmed - independent OpenOCD verify_image: zero byte differences - board resets and runs the image Built with arm-zephyr-eabi-gcc -mcpu=cortex-m33 -mthumb -Os from this C: #include <stdint.h> #define RRAMC 0x5004E000u #define TASKS_CWB (*(volatile uint32_t *)(RRAMC + 0x008)) #define READY (*(volatile uint32_t *)(RRAMC + 0x400)) #define CONFIG_REG (*(volatile uint32_t *)(RRAMC + 0x500)) #define ERASEALL (*(volatile uint32_t *)(RRAMC + 0x540)) #define CFG (1u | (0x20u << 8)) /* WEN | WRITEBUFSIZE=32 */ static void wait(void){ while((READY & 1u)==0){} } int Init(uint32_t a){ (void)a; wait(); return 0; } int UnInit(void){ return 0; } int EraseChip(void){ CONFIG_REG=1; wait(); ERASEALL=1; wait(); CONFIG_REG=0; return 0; } int EraseSector(uint32_t a){ CONFIG_REG=CFG; wait(); *(volatile uint32_t*)a=0xFFFFFFFFu; TASKS_CWB=1; wait(); CONFIG_REG=0; return 0; } int ProgramPage(uint32_t a, uint32_t sz, const uint32_t *s){ CONFIG_REG=CFG; wait(); for (uint32_t n=sz&~3u; n; n-=4) *(volatile uint32_t*)a++=*s++; TASKS_CWB=1; wait(); CONFIG_REG=0; return 0; } Fixes pyocd#2016. Co-Authored-By: Claude <noreply@anthropic.com>
3 tasks
cumin777
added a commit
to cumin777/platform-seeedboards
that referenced
this pull request
Sep 22, 2026
The StarSphere-1024/pyOCD@lm20_stable fork carried exactly three changes over upstream, all now in official releases, so the fork adds nothing: its nRF54LM20A target support was merged upstream as pyocd/pyOCD#1889 (shipped in v0.44.0, flash algorithm byte-identical), its JLink serial-number fix was superseded by the version-aware fix in v0.45.0 (pyocd/pyOCD#1927), plus a trivial .gitignore entry. - Pin pyocd==0.45.1 in the PlatformIO upload path and the factory_reset venvs (requirements.txt). - Make _installed_pyocd_is_expected() version-aware: the old check only tested for the nrf54lm20a target, which the retired fork also exposes, so machines that had the fork installed would silently keep it. Now `pyocd --version` is compared against the pin first, making the switch self-healing; the install banner prints the version. - Bump the factory_reset DEP_MARKER (.deps_forked_pyocd_v1 -> .deps_official_pyocd_v1) so cached venvs reinstall from the new pin. - Correct the misleading comments that claimed the fork carried working nRF54LM20A flash support while stock pyOCD faults: the algorithms are byte-identical, so `pyocd flash` faults either way. The fault is root-caused (RRAM programmed with the RRAMC write buffer unconfigured, pyocd/pyOCD#2016) with a fix pending in pyocd/pyOCD#2033; bump this pin once that ships. Until then the LM20A upload path uses OpenOCD (nrf54lm20a-load); pyocd serves list/rtt/erase, and the default cmsis-dap upload is unaffected. Hardware verified on a XIAO nRF54LM20A (CMSIS-DAP): pyocd 0.45.1 list / erase --mass work; `pyocd flash` faults exactly as under the fork (zero regression); the OpenOCD path is untouched. Co-Authored-By: Claude <noreply@anthropic.com>
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #2016.
Root cause
The nRF54LM20A builtin target (#1889) enabled only
CONFIG.WENin its flash algorithm and copied words straight into RRAM, with the write buffer left unconfigured (CONFIG.WRITEBUFSIZE=0, "Unbuffered") andTASKS_COMMITWRITEBUFnever issued. Every CPU store to RRAM was rejected by the RRAMC (EVENTS_ACCESSERRORset,ACCESSERRORADDR= store target, precise BusFault → HardFault, IPSR=3). Erase kept working becauseEraseChiponly pokes RRAMC task registers — exactly the "erase fine, flash faults" signature reported in #2016.Fix
Program through the RRAMC write buffer, matching Nordic's own nRF54LM20A flash algorithm (the FLM-derived one shipped in probe-rs targets):
CONFIG = WEN | WRITEBUFSIZE(32)(0x2001), waitREADYTASKS_COMMITWRITEBUF = 1to flush the last partial 128-bit line, waitREADYCONFIG = 0, waitREADYPages are 4 KiB (was 4 bytes) so each
ProgramPagecall covers whole 128-bit RRAM codewords — with 4-byte pages the flow completed but an independent verify showed stray bytes never landed (e.g. 2 wrong bytes per flash).page_buffers/begin_stackwere relocated accordingly (algo code occupies 0x004–0x0B4; buffers at 0x20000100/0x20001100, stack at 0x20003000).The full C source of the blob (built with
arm-zephyr-eabi-gcc -mcpu=cortex-m33 -mthumb -Os, freestanding,-Wl,-Ttext=0x20000004) is embedded in the commit message.Hardware verification
Seeed XIAO nRF54LM20A, on-board CMSIS-DAP, Windows 11:
pyocd erase --mass -t nrf54lm20apyocd flash -t nrf54lm20a firmware_lm20a_blink.hexon the blank (mass-erased) chip — the exact repro from #2016programmed 24576 bytes (6 pages)openocd verify_image(different toolchain)reset runBefore this change, the same sequence faults with
target was not halted as expected after calling flash algorithm routine (IPSR=3).For reference, the nRF54L15 (
nrf54l) target uses a different controller (MRAMC) where unbuffered CPU stores are accepted, which is why L15 programs fine with an otherwise identical algorithm shape.🤖 Generated with Claude Code