Skip to content

zephyr-cp: make buses dynamic and add board buses to the Nordic dev boards - #11618

Open
dhalbert wants to merge 11 commits into
adafruit:mainfrom
dhalbert:zephyr-cp-nrf-dk-board-buses
Open

dhalbert wants to merge 11 commits into
adafruit:mainfrom
dhalbert:zephyr-cp-nrf-dk-board-buses

Conversation

@dhalbert

@dhalbert dhalbert commented Oct 16, 2026 •

Copy link
Copy Markdown
Collaborator

Note by @dhalbert. Written with Claude. This is a stacked PR on #11576.

  • Add appropriate board buses and make some devices dynamic on the Nordic dev boards we are building.
  • nRF54L15 Tag: the onboard sensor buses (i2c21, spi22) are now dynamic, with board.I2C() and board.SPI() on them.

Some omissions:

  • nRF54L15 DK: the expansion header's SPI pins conflict with the VCOM1 port, which we need for the console (no USB)
  • nRF5340 DK: I2S is on the Arduino SPI pins (!). I2S isn't dynamic; we need some modifications to the upstream driver.
  • No I2C on the nRF54L DKs: nothing on the boards or in the upstream devicetrees marks I2C pins.

Some of these are boards we are not so interested in, like nRF5340 and nRF54H20, so I didn't spend a lot of time trying to get the assignments perfect on these. Also the nRF54L15 tag has very limited pins and console and UART can conflict with I2C, so I left the default pins from upstream alone for now.

Testing

  • nRF54L15 DK and nRF54LM20 DK: board.UART() and busio.UART both directions with the debugger's VCOM0 port. nRF54LM20 DK: board.SPI() and busio.SPI MOSI-MISO loopback, 250 kHz to 16 MHz.
  • nRF5340 DK, nRF54H20 DK and nRF54L15 Tag: build only.

dhalbert and others added 11 commits October 12, 2026 18:39
The Feather nRF52840 and Feather Bluefruit Sense put the Zephyr console
on UART0, which fixed TX/RX and kept them from busio.UART. UART0 is now
a dynamic iobroker instance like UART1, and the console comes back only
in CONSOLE=1 builds through the board's console.overlay and
console.conf. DEBUG=1 implies CONSOLE=1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The upstream Feather nRF52840 and Feather Bluefruit Sense boards fix I2C0
to SCL/SDA, so busio.I2C() on those pins failed with "SCL in use" and
the pins could not be used as GPIO. I2C0 is now a dynamic TWIM instance
like I2C1. The Sense drops its SHT3XD node, which sat on the fixed bus.

The pinctrl entries and device nodes in both overlays are now in
alphabetical order; the compiled devicetree is otherwise unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
zephyr2cp.py named a board's main bus after the single enabled devicetree
instance of each kind, which doesn't work once the instances are dynamic
iobroker pools. A board now lists its buses as [[board_i2c]],
[[board_spi]] and [[board_uart]] tables in circuitpython.toml, each with
its board names and pins, and zephyr2cp.py generates the
CIRCUITPY_BOARD_* pin tables for shared-module/board. As in the other
ports, each bus is constructed on its pins on first use.

The Feather nRF52840 and Feather Bluefruit Sense define I2C, SPI and UART
this way.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
iobroker hands out the first free instance in a pool, and the pools were
in devicetree order, so busio.SPI() on the nRF52840 got the 8 MHz SPIM2
before the 32 MHz SPIM3. zephyr2cp.py now orders each pool by the
instances' max-frequency, highest first, keeping devicetree order among
equals.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add an "nRF serial instances" section to the iobroker README, the
counterpart of the comments in ports/nordic's nrfx_config.h: I2C, SPI and
UART modes share an instance, the board overlay picks each instance's
mode, the Feather split matches ports/nordic, and pools are allocated
fastest first. Each nRF board overlay points to it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Feather nRF52840 and Feather Bluefruit Sense had identical
console.conf and console.overlay files, and board.overlay files that
differed only in deleting the Sense's SHT3XD node. console.conf is now
one port-level file, which the Makefile applies in CONSOLE=1 builds of
any board that has a console.overlay. The Sense's console.overlay and
board.overlay include the Feather's; its board.overlay then only deletes
SHT3XD. The compiled devicetree is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…console

# Conflicts:
#	ports/zephyr-cp/boards/nordic/nrf7002dk/board.overlay
Replace the [[board_i2c]], [[board_spi]] and [[board_uart]] tables in
circuitpython.toml with the devicetree as the source of truth. A board
whose devicetree has a feather_i2c, feather_spi or feather_serial label
gets board.I2C(), board.SPI() or board.UART() on the header pins named
SCL/SDA, SCK/MOSI/MISO or TX/RX. The labeled node only marks that the
board has the bus; iobroker picks the instance when the bus is
constructed. The Feather nRF52840 boards' circuitpython.toml files are
back to their main versions, and the generated board buses are
unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Constructing a board bus on its pins needs iobroker's runtime routing,
which only nRF SoCs have. On other SoCs, such as the Feather RP2040,
the labeled buses would raise NotImplementedError. Find board buses
from devicetree labels only when CONFIG_PINCTRL_NRF is on, and leave
other boards as before. The .config scan moves earlier so the board
bus code can use it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
BOARD_BUS_LABELS now knows only board_i2c, board_spi and board_serial.
Boards add them in their board.overlay: the Feather nRF52840 overlay
aliases the upstream feather_* connector labels, and boards without
upstream labels (ItsyBitsy, CLUE) can label the bus nodes directly.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Give the Nordic DKs board.I2C(), board.SPI() and board.UART() through
board_* devicetree labels. The buses are routed at runtime, like the
other dynamic nRF buses, and their pins are named in [pins].

- nRF54L15 DK: board.UART() on P0.00/P0.01, the debugger's VCOM0
  serial port. No board.SPI(): the expansion header's SPI pins share
  lines with the console's VCOM1 port.
- nRF54LM20 DK: board.SPI() on the expansion header's SPI pins, and
  board.UART() on P0.06/P0.07 (VCOM0).
- nRF5340 DK: i2c1 and spi4 are now dynamic. board.I2C() is on Arduino
  D14/D15 and board.UART() on D0/D1. No board.SPI(): the fixed I2S
  uses the Arduino SPI pins.
- nRF54H20 DK: i2c130 is now dynamic, with board.I2C() on P2.10/P2.11.
  gpio2 is enabled so that P2 pins exist as pin objects.
- nRF54L15 Tag: i2c21 and spi22, the onboard sensor buses, are now
  dynamic, with board.I2C() on P1.11/P1.12 and board.SPI() on
  P1.08/P1.06/P1.05.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@dhalbert
dhalbert requested a review from tannewt October 16, 2026 13:37
@mikeysklar

Copy link
Copy Markdown
Collaborator

Not tested on hardware yet. From reading: on nRF54H20 the pin routing check only runs on nRF54L, so H20 routing isn't checked.

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.

zephyr: Do dynamic bus allocation on Nordic DK boards

2 participants