Repository navigation
zephyr-cp: dynamic buses on the Feather nRF52840 boards - #11576
Conversation
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>
tannewt
left a comment
There was a problem hiding this comment.
Thanks for working on this. A couple Qs.
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>
262d0e1 to
13cd72c
Compare
…console # Conflicts: # ports/zephyr-cp/boards/nordic/nrf7002dk/board.overlay
tannewt
left a comment
There was a problem hiding this comment.
I think we still need to settle on where the buses are defined.
CONSOLE=1 is fine with me. The intent is to not change the log level though right? I know changing the log level can require stack size increases too.
|
Right, no log level changes for CONSOLE=1. |
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>
tannewt
left a comment
There was a problem hiding this comment.
I really want to minimize the DTS that we maintain in CP versus in our zephyr upstream because it's harder to reuse then.
|
Tested on Feather nRF52840. Works: Doesn't work: after Suggestion: with |
reset_port() calls i2sout_reset() on every board with audiobusio.I2SOut, but zephyr2cp only generated it when the board had an I2SOut object, so a board with I2S but no board object failed to link. Generate it with an empty body in that case. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Drop the board_* devicetree labels. board.I2C(), board.SPI() and board.UART() now use the board's standard pins: the pins named SCL/SDA, SCK/MOSI/MISO and TX/RX by a connector (see CONNECTORS) or the circuitpython.toml [pins] table. - On nRF, a fixed instance on the standard pins is the board object. Otherwise, if the board has a routable instance, the board object is a bus on the standard pins, and iobroker picks the instance at runtime. - Other SoCs keep the old choice of main instance. - Only that object is exported, under its standard name (I2C, SPI, UART, I2SOUT). Instance and label names, and other fixed instances, are no longer board objects. - The zephyr,console and zephyr,shell-uart UARTs are never board objects. - The arduino-header-r3 connector names D0/D1 RX/TX, D11-D13 MOSI/MISO/SCK and D14/D15 SDA/SCL, as on Arduino Uno R3 boards. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The upstream board uses the legacy UART driver for UART0. It has no de-init, so once a busio.UART on UART0 was released, iobroker could not route it again, and the next busio.UART failed with "All UART peripherals are in use" when UART1 was busy. Use UARTE, like UART1. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…console # Conflicts: # ports/zephyr-cp/cptools/zephyr2cp.py
The console UART is no longer a board object, so native_sim had no board UART, and test_busio_uart.py's board.UART0() failed. Enable the second PTY UART (uart1) on native_sim, which becomes board.UART(), and use it in the test. uart1 also announces its pseudotty on stdout, so after a reboot the test harness reattached to it instead of the console. Reconnect only to the console UART's pseudotty. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
3708432 to
db682b7
Compare
|
@tannewt Good for another look now. In 1050db3, I took out the
I added TX/RX, SCK/MOSI/MISO and SDA/SCL pin name aliases to the I didn't need to do anything with board overlays for this. The Feather's pin names come from the upstream Feather connector. There will be further fixes for other boards in the stacked PR's if this scheme is OK. |
setup-python's "3.x" now picks Python 3.15.0. minify_html, which requirements-dev.txt installs, has no 3.15 wheel, and building it fails because PyO3 supports Python only up to 3.14. So pre-commit, the Build CI scheduler and the non-Zephyr board builds fail before building anything. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
b7d3463 to
33b39d6
Compare
tannewt
left a comment
There was a problem hiding this comment.
One comment about the nrf specific fixed peripheral stuff. I think we can simplify it by erroring if a static peripheral exists where IOBroker support does. It should either be all static and we don't know the pins or all dynamic via IOBroker.
Make i2c1 and spi4 dynamic iobroker instances, so board.I2C(), board.SPI() and board.UART() are routed at runtime on the Arduino header pins, like the other buses. Remove the fixed i2s0 for the Audio Codec Shield, which used the Arduino SPI pins (D10-D13), until audiobusio.I2SOut can be routed dynamically. Turn off the upstream board's GPIO forwarder, which handed the Arduino D0/D1 pins (and P0.10/P0.11) to the network core for its UART console, so the application core can use them for board.UART(). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Make i2c130 a dynamic iobroker instance instead of fixed to P2.10/P2.11, and name those pins SCL and SDA in circuitpython.toml, so board.I2C() is routed there at runtime. Enable gpio2, which the SoC dtsi leaves disabled, so the P2 pins have pin objects. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A board either routes its I2C, SPI and UART buses with iobroker or has only fixed buses from the devicetree. Drop the nRF pinctrl matching that chose a fixed instance on the standard pins as the board object. On a board with routable instances, board.I2C(), board.SPI() and board.UART() are always buses on the standard pins, and fixed bus instances are never board objects. The build fails if one is not used by Zephyr itself: a /chosen node, a node with child devices, or a node that another enabled node refers to by phandle. Boards with only fixed buses keep the existing choice of main instance. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
From @mikeysklar:
I used the UARTE driver instead of UART; this fixes the deinit problem. For |
The upstream board fixes I2C0 to SCL/SDA with the legacy TWI driver, which the board-bus rule no longer allows on a board that uses iobroker. Make it a dynamic TWIM instance; the onboard sensors on SCL/SDA have no devicetree nodes, so it doesn't need to be fixed. The upstream board also uses the legacy UART driver for UART0, the CLUE's only UART instance. It can't be de-initialized, so after the first busio.UART was released, no UART could be created again. Use UARTE. Add a console.overlay, so a CONSOLE=1 build puts the Zephyr console on TX/RX, as on the Feathers. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
tannewt
left a comment
There was a problem hiding this comment.
Thanks for working on this! I think we may want to do the Kconfig checking somehow differently but this is fine for now.
busio.I2C(),busio.UART(), busio.SPI()` on boards where it disappeared #11598 (@dhalbert)Code and tests by Claude. EDIT: This note was rewritten completely by @dhalbert on Oct 19.
Zephyr UART console took over TX/RX pins
busio.UART()could not use those pins. Fixed: stop assigning UART0 in a fixed way to the console. The console is now a build option: setCONSOLE=1(implied byDEBUG=1) to put the console on TX/RX. The console shows Zephyr's boot banner,printk()and warning/error logs. The CircuitPython REPL stays on USB.Note that with
CONSOLE=1,board.UART()raises "TX in use", because the TX/RX pins are already taken for the console.Don't allocate I2C0 in a fixed way
busio.I2C(board.SCL, board.SDA)failed with "SCL in use". Now, make I2C0 a dynamic TWIM instance, allocated through iobroker.UART0 reuse
Make
board.I2C(),board.SPI(),board.UART()exist againProblem: In 10.3.x, zephyr-cp boards had
board.I2C(),board.SPI()andboard.UART()only when a board's devicetree had exactly one device of that kind. This was true on the nRF Feathers, so the names existed. Since #11420 (the iobroker PR, in 11.0.0-alpha.1), these boards also enabled dynamic I2C, SPI and UART instances, which causedboardto only have the nonstandard namesI2C0,FEATHER_I2C,UART0andFEATHER_SERIAL.Fix: Have each
boardbus use the board's standard pins: SCL/SDA, TX/RX, etc. These names come from theCONNECTORStable inzephyr2cp.pyor from the[pins]table incircuitpython.toml. A board either routes its buses with iobroker, or has only fixed buses from the devicetree:boardobject is a bus on the standard pins. iobroker picks the instance when it is constructed. The build will fail if a fixed bus instance isn't used by Zephyr itself: like a UART console, SPI flash chip, or a node that another node refers to, such as a display.boardobjects are fixed instances, chosen as before.Don't include extra non-standard names defined by the device tree names.
Other SoCs keep their existing choice of main device, and will be fixed in PRs stacked on this one. This restores the three names on the Feather and Sense.
Change the
arduino-header-r3connector. It now also names D0/D1RX/TX, D11-D13MOSI/MISO/SCKand D14/D15SDA/SCL, as on Arduino Uno R3 boards, so boards with an Arduino header have standard pins too.nRF5340 DK and nRF54H20 DK
i2c130is now dynamic, andboard.I2C()is on P2.10/P2.11.I2S reset
zephyr2cp.pynow always generatesi2sout_reset(), whichreset_port()calls. Without aboardI2SOut object, it was missing and the build failed to link.Python 3.14 in CI
minify_htmlhas a Python 3.15 wheel. Otherwise CI fails before building anything.Allocate 32 MHz SPIM3 on nRF first
zephyr2cp.pyorders each pool bymax-frequency, highest first, so SPIM3 is allocated first, as inports/nordic.Documentation
The iobroker README has a new "nRF serial instances" section: I2C, SPI and UART share an instance, the board overlay chooses each instance's mode, and pools are allocated fastest first. The nRF board overlays point to it.
Testing
board.I2C()with an MSA301,busio.I2C()on SCL/SDA and A2/A3 at once, SCL as GPIO afterdeinit(),board.SPI()loopback at 1 to 16 MHz,board.UART()loopback on TX/RX and to an FTDI adapter both ways,CONSOLE=1output on TX.busio.UARTreleased and reused on both UART instances while the other is busy.CONSOLE=1:board.UART()raises "TX in use".board.I2C()andbusio.I2C()find all five onboard sensors.cptools/testsand the native_sim tests pass. (test_spi_loopbackfails on my machine, onmaintoo, but passes in CI.)