Skip to content

fix(nrf): move the panel SPI off SPIM3 onto SPIM2 (anomalies 195/198) - #137

Merged
jonasniesner merged 1 commit into
OpenDisplay:mainfrom
davelee98:fix/nrf-spim2-panel
Aug 2, 2026
Merged

fix(nrf): move the panel SPI off SPIM3 onto SPIM2 (anomalies 195/198)#137
jonasniesner merged 1 commit into
OpenDisplay:mainfrom
davelee98:fix/nrf-spim2-panel

Conversation

@davelee98

Copy link
Copy Markdown
Contributor

Single-commit nRF52840 change: move the panel SPI off SPIM3 and onto SPIM2. Independent of #135 and #136.

Why

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. Worse, 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.

How

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.

The flag is scoped to [env:nrf52840custom], so it does not reach the ESP32 environments through [env] inheritance.

Cost

None 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_* untouched). No I2C conflict: Wire is TWIM0 and Wire1 is TWIM1, while SPIM2's block is shared only with SPIS2/SPI2, neither used.

Verification

The macro is consumed by the framework's SPI.cpp, not by our sources, so it only works while -D reaches the framework build — and if it silently stops doing so we are back on SPIM3 with no error. Confirmed for this commit:

$ pio run -e nrf52840custom -v | grep 'SPI_32MHZ_INTERFACE=1.*SPI\.cpp'
-DSPI_32MHZ_INTERFACE=1 … framework-arduinoadafruitnrf52-seeed/libraries/SPI/SPI.cpp
  -o .pio/build/nrf52840custom/lib85a/SPI/SPI.cpp.o

The check is recorded in an inline comment so it survives a platform update. Builds clean; not yet exercised on hardware — it wants a panel-refresh soak before merge.

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.
@davelee98
davelee98 requested a review from jonasniesner as a code owner August 1, 2026 21:26
@jonasniesner
jonasniesner merged commit 0c816b5 into OpenDisplay:main Aug 2, 2026
13 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.

2 participants