Skip to content

zephyr-cp: add ItsyBitsy nRF52840; add dynamic buses on CLUE and ItsyBitsy - #11612

Open
dhalbert wants to merge 16 commits into
adafruit:mainfrom
dhalbert:zephyr-cp-itsybitsy-nrf52840
Open

dhalbert wants to merge 16 commits into
adafruit:mainfrom
dhalbert:zephyr-cp-itsybitsy-nrf52840

Conversation

@dhalbert

Copy link
Copy Markdown
Collaborator

PR note written by @dhalbert; code and tests by Claude, guided by me.

This PR is stacked on #11576, so merge that first. I merged main into this branch to pick up #11568 (bitbangio), needed for the DotStar on the ItsyBitsy.

Testing

Both boards were tested to verify I2C, SPI, and UART work.

dhalbert and others added 16 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>
STATUS_DOTSTAR_DATA and STATUS_DOTSTAR_CLOCK name board pins and emit
MICROPY_HW_APA102_MOSI/SCK with CIRCUITPY_BITBANG_APA102 into the
generated mpconfigboard.h. Setting only one, or combining them with
STATUS_NEOPIXEL, is an error.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Move the mcuboot pin to adafruit/adaboot 3fcd1c89, which adds the
ItsyBitsy nRF52840 partition layout (adafruit/adaboot#8) and brings in
adafruit/adaboot#5, adafruit#6 and adafruit#7.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Pin names come from the ports/nordic board. All I2C, SPI, UART and PWM
instances are dynamic, and board_* labels give board.I2C(), board.SPI()
and board.UART() on SCL/SDA, SCK/MOSI/MISO and TX/RX. The status
DotStar is bit-banged, so the upstream apa102 node on SPI2 is dropped.

Changes from the upstream board:
- QSPI runs at 16 MHz, not 32 MHz. The GD25Q16C fails with I/O errors at
  32 MHz, as found for ports/nordic in adafruit#1418.
- The user LED is labeled blue, not red.

CONSOLE=1 puts the Zephyr console on TX/RX.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A board_stemma_i2c label on the board_i2c node makes board.STEMMA_I2C
another name for board.I2C(), as on ports/nordic boards where both
names use the same bus. The label on any other node is a build error.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Make I2C0 dynamic TWIM. The onboard sensors on SCL/SDA have no
devicetree nodes, so nothing needs it fixed. board_* labels give
board.I2C() (also board.STEMMA_I2C), board.SPI() and board.UART() on
the ports/nordic pins. SPI2 stays fixed for the display.

CONSOLE=1 puts the Zephyr console on TX/RX.

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

Copy link
Copy Markdown
Collaborator

Tested on CLUE and ItsyBitsy nRF52840.

Works: buses, CIRCUITPY read/write, CLUE display and all 5 sensors.

Doesn't work: on CLUE, busio.UART(TX, RX) fails once board.UART() has been used. Same UART bug as #11576. CLUE only has one UART, so it shows up right away.

Note: the adaboot bump is missing adaboot #9, which #11570 needs for the Picos. Whichever merges second needs both.

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 CLUE boards zephyr: add ItsyBitsy nRF52840

2 participants