From 6e73cbcedc311fbd5841345a4cd30f8198c7cb91 Mon Sep 17 00:00:00 2001 From: David Lee <247393336+davelee98@users.noreply.github.com> Date: Sat, 1 Aug 2026 17:17:39 -0400 Subject: [PATCH] fix(nrf): move the panel SPI off SPIM3 onto SPIM2 SPIM3 is the only nRF52840 SPIM instance carrying anomalies 195 and 198, and both bite this firmware: - 195: the instance keeps drawing current after disable. The nrfx workaround pokes 0x4002F004 on every uninit and never undoes it on re-init. - 198: transmit data may be corrupted when another bus master touches the TX buffer's RAM block mid-transfer. The Arduino per-byte path makes that buffer a one-byte STACK local, contended by SoftDevice interrupts on every byte -- and 198's workaround is compiled out here entirely, because NRFX_SPIM3_NRF52840_ANOMALY_198_WORKAROUND_ENABLED is never defined. Both nrfx workarounds are gated on `p_spim == NRF_SPIM3`, so moving to SPIM2 makes the anomalies inapplicable rather than worked around. The Adafruit core binds the global `SPI` object to an instance chosen by SPI_32MHZ_INTERFACE (framework SPI.cpp): 0 -- its default -- gives SPIM3, 1 gives SPIM2. Only `SPI` matters: bb_epaper drives the panel through that global and nothing here ever calls SPI1.begin(), so the instance handed to SPI1 is left uninitialised. Free at our clock. SPIM2 tops out at 8 MHz and bbepInitIO already asks for exactly 8000000, which setClockDivider maps to NRF_SPIM_FREQ_8M on either instance. It forfeits the 16/32 MHz only SPIM3 offers -- deliberately, since raising the panel clock increases the DMA bus pressure this change exists to avoid. No pin change; PIN_SPI_* are untouched. No I2C conflict: Wire is TWIM0 and Wire1 is TWIM1, while SPIM2's block is shared only with SPIS2/SPI2. The macro is consumed by the framework's SPI.cpp, not by our sources, so it only works while -D reaches the framework build -- verified for this commit: pio run -e nrf52840custom -v | grep 'SPI_32MHZ_INTERFACE=1.*SPI\.cpp' Not yet exercised on hardware. --- platformio.ini | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/platformio.ini b/platformio.ini index a36cc82..41bc6ea 100644 --- a/platformio.ini +++ b/platformio.ini @@ -51,6 +51,39 @@ build_flags = ; -DOPENDISPLAY_ZLIB_WINDOW_BITS=15 ; nRF keeps the zlib history window in static storage for deterministic allocation. -DOPENDISPLAY_ZLIB_USE_HEAP_WINDOW=0 + ; Move the panel SPI off SPIM3 and onto SPIM2. + ; + ; The Adafruit core binds the global `SPI` object to an instance chosen by this + ; macro (framework SPI.cpp): 0 (its default) gives SPIM3, 1 gives SPIM2. Only + ; `SPI` matters here -- bb_epaper drives the panel through that global + ; (arduino_io.inl `SPI.begin()`), and nothing in this firmware ever calls + ; SPI1.begin(), so the instance this hands to SPI1 is left uninitialised. + ; + ; Why: SPIM3 is the only nRF52840 SPIM instance carrying anomalies 195 (keeps + ; drawing current after disable; the nrfx workaround pokes 0x4002F004 on every + ; uninit and never undoes it on re-init) and 198 (transmit data may be corrupted + ; when another bus master touches the TX buffer's RAM block mid-transfer -- and + ; the Arduino per-byte path makes that buffer a one-byte STACK local, contended + ; by SoftDevice interrupts on every byte). Both workarounds in nrfx_spim.c are + ; gated on `p_spim == NRF_SPIM3`, and 198's is compiled out entirely here because + ; NRFX_SPIM3_NRF52840_ANOMALY_198_WORKAROUND_ENABLED is never defined. Moving to + ; SPIM2 makes both inapplicable rather than worked around. + ; + ; Free of cost at our clock: SPIM2 tops out at 8 MHz and bbepInitIO already asks + ; for exactly 8000000 (display_service.cpp), which setClockDivider maps to + ; NRF_SPIM_FREQ_8M on either instance. It does forfeit the 16/32 MHz that only + ; SPIM3 offers -- deliberately, since raising the panel clock increases the DMA + ; bus pressure this flag exists to avoid. + ; + ; No pin change: the instance is swapped, PIN_SPI_* are not. + ; No I2C conflict: Wire is TWIM0 and Wire1 is TWIM1 (Wire_nRF52.cpp), while + ; SPIM2's block is shared only with SPIS2/SPI2, neither of which is used. + ; + ; VERIFY AFTER ANY PLATFORM UPDATE. This macro is consumed by the framework's + ; SPI.cpp, not by our sources, so it only works if -D reaches the framework + ; build. If it silently stops doing so we are back on SPIM3 with no error. Check + ; with: pio run -e nrf52840custom -v 2>&1 | grep -c 'SPI_32MHZ_INTERFACE=1.*SPI\.cpp' + -DSPI_32MHZ_INTERFACE=1 platform = https://github.com/maxgerhardt/platform-nordicnrf52 framework = arduino board_build.variants_dir = variants