nrf: Fix I2C regression introduced by nrfx v3 update.#19220
Conversation
In nrfx v3, when both SPIM0 and TWIM0 are enabled (PRS_BOX_0_ENABLED), nrfx_prs.c and nrfx_twim.c emit the same IRQ handler symbol via macro aliasing in nrfx_irqs_nrf52840.h. LTO resolves this to the PRS dispatcher, which never reaches twi_event_handler, causing I2C to stall indefinitely. Fix by switching to blocking mode (NULL handler), where nrfy_twim_tx_start polls the STOPPED/SUSPENDED event internally without IRQ dependency. Also fix the NOSTOP flag: the old code passed (flags & FLAG_STOP) == 0, evaluating to 0 or 1. In nrfx v3 TWIM, flag 1 is NRFX_TWIM_FLAG_TX_POSTINC (buffer post-increment), not no-stop. The correct flag is NRFX_TWIM_FLAG_TX_NO_STOP (1 << 5). Fixes: micropython#19214 Signed-off-by: Andrew Leech <andrew@alelec.net> Signed-off-by: Andrew Leech <andrew.leech@planetinnovation.com.au>
|
I can confirm I2C works with this update on my XIAO nrf52840 with expansion board. Both I2C(1) - the intermal IMU bus and I2C(0) work. |
|
Tested with a Arduino Nano 33 BLE. I2C works. No SPI connected or enabled, besides the internal Flash of the UBLOX module. But I do not see if and where the supplied value for timeout is applied. |
|
This fix does not look correct. If non-blocking (interrupt based) I2C worked before, it should still work with nrfx v3. The PRS (peripheral resource sharing) should be able to route the IRQ to the correct peripheral. |
|
EDIT2: Switching to SPI(1) fixes the interference. EDIT: Is there a right way for a user to select I2C/SPI IDs? I see there are shared internal drivers for SPI and I2C on the 52840. I tried a simple experiment using Peter Hinch's With version 1.27 - if SPI is created first, the display tests run okay. An I2C can be invoked and the ssd1306 works. If this is repeated (no reset) then the SPI display no longer displays (but does not hang.) With this PR, both the testnrf_xxx programs complete with no hang. But if the I2C is created before the SPI then the SPI display does not refesh. The setup was:
# testitdual.py
import gc
import machine
from color_setup import ssd # Create a display instance
import ssd1306
print("ssd imported mem {}".format(gc.mem_free()))
# seems to be slow import for tinys3
from gui.core.colors import RED, BLUE, GREEN
# note seems to work without refresh also
from gui.core.nanogui import refresh
def doit(earlyi2c=False):
print("refresh")
i2c = None
if earlyi2c:
i2c = machine.I2C(0, scl=machine.Pin("P5"), sda=machine.Pin("P4"))
refresh(ssd, True) # Initialise and clear display.
# Uncomment for ePaper displays
# ssd.wait_until_ready()
ssd.fill(0)
ssd.line(
0, 0, ssd.width - 1, ssd.height - 1, GREEN
) # Green diagonal corner-to-corner
ssd.rect(0, 0, 15, 15, RED) # Red square at top left
ssd.rect(
ssd.width - 15, ssd.height - 15, 15, 15, BLUE
) # Blue square at bottom right
ssd.show()
print(gc.mem_free())
if not i2c:
i2c = machine.I2C(0, scl=machine.Pin("P5"), sda=machine.Pin("P4"))
si2c = ssd1306.SSD1306_I2C(128, 32, i2c, addr=60)
si2c.fill(0)
si2c.text(" First Line", 0, 10)
si2c.line(0, 0, 127, 31, 1)
si2c.show()
print("All done")
>pwd
/work/domicropy/micropython-nano-gui
>mpremote mount . run testnrf_i2c.py
Local directory . is mounted at /remote
3.4.0; MicroPython v1.27.0 on 2025-12-09
# hangs here does no return, power off board.
# power reset
>pwd
/work/domicropy/micropython-nano-gui
>mpremote mount . run testnrf_spi.py
Local directory . is mounted at /remote
3.4.0; MicroPython v1.27.0 on 2025-12-09
Completed SSD startup
>#testnrf_i2c.py
import sys
from machine import Pin, I2C, SPI
from drivers.st7789.st7789_4bit import ST7789, LANDSCAPE, TDISPLAY
print(sys.version)
i2 = I2C(0, scl=Pin("P5"), sda=Pin("P4"))
i2.scan()
SSD = ST7789
spi = SPI(0, 30_000_000, sck=Pin(45), mosi=Pin(47))
pdc = Pin(28, Pin.OUT, value=0) # Arbitrary pins
pcs = Pin(29, Pin.OUT, value=1)
prst = Pin(3, Pin.OUT, value=1)
pbl = Pin(2, Pin.OUT, value=1)
ssd = SSD(
spi,
height=135,
width=240,
dc=pdc,
cs=pcs,
rst=prst,
disp_mode=LANDSCAPE,
display=TDISPLAY,
)
print("Completed SSD startup")testnrf_spi.py # spi initialized first, then I2C #testnrf_spi.py
import sys
from machine import Pin, I2C, SPI
from drivers.st7789.st7789_4bit import ST7789, LANDSCAPE, TDISPLAY
print(sys.version)
SSD = ST7789
spi = SPI(0, 30_000_000, sck=Pin(45), mosi=Pin(47))
pdc = Pin(28, Pin.OUT, value=0) # Arbitrary pins
pcs = Pin(29, Pin.OUT, value=1)
prst = Pin(3, Pin.OUT, value=1)
pbl = Pin(2, Pin.OUT, value=1)
ssd = SSD(
spi,
height=135,
width=240,
dc=pdc,
cs=pcs,
rst=prst,
disp_mode=LANDSCAPE,
display=TDISPLAY,
)
i2 = I2C(0, scl=Pin("P5"), sda=Pin("P4"))
i2.scan()
print("Completed SSD startup") |
Summary
Fixes a regression introduced in #19125 where I2C stopped working on nRF52840 when SPI is also enabled (issue #19214).
The root cause is an IRQ handler symbol conflict in nrfx v3: both
nrfx_prs_box_0_irq_handler(PRS dispatcher) andnrfx_twim_0_irq_handlermap to the same final symbolSPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0_IRQHandlervia the nrfx irq alias headers. With LTO enabled, the PRS dispatcher wins and the TWIM event handler is never called, so I2C transfers stall indefinitely waiting for axfer_doneflag that never gets set.The fix is to use nrfx blocking mode (NULL event handler) where
nrfy_twim_tx_startpolls the STOPPED/SUSPENDED peripheral register directly without relying on the NVIC interrupt. This is appropriate for MicroPython's synchronousmachine.I2CAPI.Also fixes the NOSTOP flag: the old code passed
(flags & MP_MACHINE_I2C_FLAG_STOP) == 0as the xfer flags argument, which evaluates to 0 or 1. The value 1 isNRFX_TWIM_FLAG_TX_POSTINCin nrfx v3, not the no-stop flag. Now usesNRFX_TWIM_FLAG_TX_NO_STOPexplicitly.Testing
Tested on PCA10059 (nRF52840-Dongle) via SWD with probe-rs.
machine.I2C(0, scl=Pin(26), sda=Pin(24)).scan()completes without crash and returns an empty list as expected with no devices on the bus, confirming the blocking-mode fix resolves the indefinite stall. Floating-point operations also work correctly after boot.Build tested for nRF52840 only (the only nRF target with TWIM in nrfx v3).
Generative AI
I used generative AI tools when creating this PR, but a human has checked the code and is responsible for the code and the description above.