diff --git a/ports/zephyr-cp/boards/adafruit/itsybitsy_nrf52840_zephyr/autogen_board_info.toml b/ports/zephyr-cp/boards/adafruit/itsybitsy_nrf52840_zephyr/autogen_board_info.toml new file mode 100644 index 00000000000..4fe80b75c11 --- /dev/null +++ b/ports/zephyr-cp/boards/adafruit/itsybitsy_nrf52840_zephyr/autogen_board_info.toml @@ -0,0 +1,135 @@ +# This file is autogenerated when a board is built. Do not edit. Do commit it to git. Other scripts use its info. +name = "Adafruit Industries LLC ItsyBitsy nRF52840 Express" + +[modules] +__future__ = true +_bleio = true # Zephyr board has _bleio +_eve = false +_pew = false +_pixelmap = true # Zephyr board has neopixel_write +_stage = false +adafruit_bus_device = true +adafruit_pixelbuf = true # Zephyr board has neopixel_write +aesio = true +alarm = false +analogbufio = false +analogio = true # Zephyr board has analogio +atexit = false +audiobusio = false +audiocore = false +audiodelays = false +audiofilewriter = false +audiofilters = false +audiofreeverb = false +audioi2sin = false +audioio = false +audiomixer = false +audiomp3 = false +audiopwmio = false +audiospeed = false +aurora_epaper = false +bitbangio = true +bitmapfilter = true # Zephyr board has busio +bitmaptools = true # Zephyr board has busio +bitops = false +board = true # Always enabled +busdisplay = true # Zephyr board has busio +busio = true # Zephyr board has busio +camera = false +canio = false +codeop = false +countio = false +digitalio = true +displayio = true # Zephyr board has busio +dotclockframebuffer = false +dualbank = false +emmcio = false +epaperdisplay = true # Zephyr board has busio +floppyio = false +fontio = true # Zephyr board has busio +fourwire = true # Zephyr board has busio +framebufferio = true # Zephyr board has busio +frequencyio = false +getpass = true +gifio = true # Zephyr board has busio +gnss = false +hardwarekey = false +hashlib = true +hmac = false +hostnetwork = false +i2cdisplaybus = true # Zephyr board has busio +i2cioexpander = false +i2ctarget = false +imagecapture = false +ipaddress = false +is31fl3741 = false +jpegio = true # Zephyr board has busio +keypad = false +keypad_demux = false +locale = false +lvfontio = true # Zephyr board has busio +math = true +max3421e = false +mcp4822 = false +mdns = false +memorymap = false +memorymonitor = false +microcontroller = true +mii = false +mipidsi = false +msgpack = true +neopixel_write = true # Zephyr board has neopixel_write +nvm = true # Board has NVM partition +onewireio = false +os = true +paralleldisplaybus = false +picogame = false +ps2io = false +pulseio = false +pwmio = true # Zephyr board has pwmio +qrio = false +qspibus = false +rainbowio = true +random = true +rclcpy = false +rgbmatrix = false +rotaryio = true # Zephyr board has rotaryio +rtc = false +sdcardio = true # Zephyr board has busio +sdioio = false +sharpdisplay = true # Zephyr board has busio +socketpool = false +spitarget = false +ssl = false +storage = true # Zephyr board has flash +struct = true +supervisor = true +synthio = false +terminalio = true # Zephyr board has busio +tilepalettemapper = true # Zephyr board has busio +time = true +touchio = false +traceback = true +uheap = false +usb = false +usb_audio = false +usb_cdc = true +usb_hid = true +usb_host = false +usb_host_bulk = false +usb_midi = false +usb_video = false +ustack = false +vectorio = true # Zephyr board has busio +warnings = true +watchdog = true # Zephyr board has watchdog +wifi = false +zephyr_display = false +zephyr_kernel = false +zlib = true +# extmod modules shared with MicroPython +asyncio = true +binascii = true +json = true +re = true +select = true diff --git a/ports/zephyr-cp/boards/adafruit/itsybitsy_nrf52840_zephyr/board.conf b/ports/zephyr-cp/boards/adafruit/itsybitsy_nrf52840_zephyr/board.conf new file mode 100644 index 00000000000..86beb3879a2 --- /dev/null +++ b/ports/zephyr-cp/boards/adafruit/itsybitsy_nrf52840_zephyr/board.conf @@ -0,0 +1,17 @@ +CONFIG_BOARD_SERIAL_BACKEND_CDC_ACM=n + +# No Zephyr UART console by default (see board.overlay); a CONSOLE=1 build turns +# it back on in console.conf. +CONFIG_UART_CONSOLE=n +CONFIG_LOG_BACKEND_UART=n + +# The ItsyBitsy uses the MDBT50Q-1MV2 module (aQFN73 inside), so pin numbers +# are module pins. +CONFIG_IOBROKER_PACKAGE_MDBT50Q_1MV2=y + +# PWM instances are enabled in board.overlay with disconnected outputs so +# iobroker can allocate them at runtime for neopixel_write and pwmio. +CONFIG_PWM=y + +# SAADC for analogio testing/use (pads resolve to AIN through iobroker) +CONFIG_ADC=y diff --git a/ports/zephyr-cp/boards/adafruit/itsybitsy_nrf52840_zephyr/board.overlay b/ports/zephyr-cp/boards/adafruit/itsybitsy_nrf52840_zephyr/board.overlay new file mode 100644 index 00000000000..2ec1700c2b0 --- /dev/null +++ b/ports/zephyr-cp/boards/adafruit/itsybitsy_nrf52840_zephyr/board.overlay @@ -0,0 +1,344 @@ +/* Partition layout is owned by the mcuboot fork + * (dts/adafruit/adafruit_itsybitsy_nrf52840.dtsi) and applied to this image by + * sysbuild. This overlay only adds board/app config. */ +/* No Zephyr console by default, so TX/RX are free for busio.UART. A CONSOLE=1 + * build puts the console back on UART0 (console.overlay). */ +/ { + chosen { + /delete-property/ zephyr,console; + /delete-property/ zephyr,shell-uart; + /delete-property/ zephyr,uart-mcumgr; + /delete-property/ zephyr,bt-mon-uart; + /delete-property/ zephyr,bt-c2h-uart; + }; + + aliases { + /delete-property/ led-strip; + }; + + // The upstream board has no header connector. Its pins name the board + // pins, including the SCL/SDA, SCK/MOSI/MISO and TX/RX of board.I2C(), + // board.SPI() and board.UART(). See CONNECTORS in cptools/zephyr2cp.py. + itsybitsy_header: connector { + compatible = "adafruit-itsybitsy-header"; + #gpio-cells = <2>; + gpio-map-mask = <0xffffffff 0xffffffc0>; + gpio-map-pass-thru = <0 0x3f>; + gpio-map = <0 0 &gpio0 4 0>, /* A0 */ + <1 0 &gpio0 30 0>, /* A1 */ + <2 0 &gpio0 28 0>, /* A2 */ + <3 0 &gpio0 31 0>, /* A3 */ + <4 0 &gpio0 2 0>, /* A4 */ + <5 0 &gpio0 3 0>, /* A5 */ + <6 0 &gpio0 13 0>, /* SCK */ + <7 0 &gpio0 15 0>, /* MOSI */ + <8 0 &gpio0 20 0>, /* MISO */ + <9 0 &gpio0 25 0>, /* D0/RX */ + <10 0 &gpio0 24 0>, /* D1/TX */ + <11 0 &gpio1 2 0>, /* D2 */ + <12 0 &gpio0 16 0>, /* SDA */ + <13 0 &gpio0 14 0>, /* SCL */ + <14 0 &gpio0 27 0>, /* D5 */ + <15 0 &gpio1 8 0>, /* D7 */ + <16 0 &gpio0 7 0>, /* D9 */ + <17 0 &gpio0 5 0>, /* D10 */ + <18 0 &gpio0 26 0>, /* D11 */ + <19 0 &gpio0 11 0>, /* D12 */ + <20 0 &gpio0 12 0>; /* D13 */ + }; +}; + +&zephyr_udc0 { + /delete-node/ board_cdc_acm_uart; +}; + +// The upstream board calls the user LED red, but it is blue. The label +// becomes a board pin name. +&led0 { + label = "Blue LED"; +}; + +// The upstream board clocks QSPI at 32 MHz, which the GD25Q16C doesn't do +// reliably here: the filesystem fails with I/O errors. Use 16 MHz, like the +// Feather nRF52840 and ports/nordic (adafruit/circuitpython#1418). +&gd25q16 { + sck-frequency = <16000000>; +}; + +// Bus instances enabled with all pins disconnected and marked +// zephyr,deferred-init so nothing is claimed at boot: the iobroker +// initializes and routes a device to arbitrary pins when a busio object is +// constructed, and de-initializes it on release: I2C0, I2C1, SPI2, SPI3, +// UART0 and UART1. +// +// See "nRF serial instances" in internal-modules/iobroker/README.md for how +// the instances are shared and allocated. + +&pinctrl { + i2c0_dyn_default: i2c0_dyn_default { + group1 { + psels = , + ; + }; + }; + + i2c0_dyn_sleep: i2c0_dyn_sleep { + group1 { + psels = , + ; + }; + }; + + i2c1_dyn_default: i2c1_dyn_default { + group1 { + psels = , + ; + }; + }; + + i2c1_dyn_sleep: i2c1_dyn_sleep { + group1 { + psels = , + ; + }; + }; + + pwm0_dyn_default: pwm0_dyn_default { + group1 { + psels = , + , + , + ; + }; + }; + + pwm0_dyn_sleep: pwm0_dyn_sleep { + group1 { + psels = , + , + , + ; + }; + }; + + pwm1_dyn_default: pwm1_dyn_default { + group1 { + psels = , + , + , + ; + }; + }; + + pwm1_dyn_sleep: pwm1_dyn_sleep { + group1 { + psels = , + , + , + ; + }; + }; + + pwm2_dyn_default: pwm2_dyn_default { + group1 { + psels = , + , + , + ; + }; + }; + + pwm2_dyn_sleep: pwm2_dyn_sleep { + group1 { + psels = , + , + , + ; + }; + }; + + pwm3_dyn_default: pwm3_dyn_default { + group1 { + psels = , + , + , + ; + }; + }; + + pwm3_dyn_sleep: pwm3_dyn_sleep { + group1 { + psels = , + , + , + ; + }; + }; + + spi2_dyn_default: spi2_dyn_default { + group1 { + psels = , + , + ; + }; + }; + + spi2_dyn_sleep: spi2_dyn_sleep { + group1 { + psels = , + , + ; + }; + }; + + spi3_dyn_default: spi3_dyn_default { + group1 { + psels = , + , + ; + }; + }; + + spi3_dyn_sleep: spi3_dyn_sleep { + group1 { + psels = , + , + ; + }; + }; + + uart0_dyn_default: uart0_dyn_default { + group1 { + psels = , + ; + }; + }; + + uart0_dyn_sleep: uart0_dyn_sleep { + group1 { + psels = , + ; + }; + }; + + uart1_dyn_default: uart1_dyn_default { + group1 { + psels = , + ; + }; + }; + + uart1_dyn_sleep: uart1_dyn_sleep { + group1 { + psels = , + ; + }; + }; +}; + +// The upstream board fixes I2C0 to SCL/SDA with the legacy TWI driver. Use +// TWIM like I2C1 so iobroker can route it. +&i2c0 { + compatible = "nordic,nrf-twim"; + status = "okay"; + zephyr,deferred-init; + pinctrl-0 = <&i2c0_dyn_default>; + pinctrl-1 = <&i2c0_dyn_sleep>; + pinctrl-names = "default", "sleep"; +}; + +&i2c1 { + status = "okay"; + zephyr,deferred-init; + pinctrl-0 = <&i2c1_dyn_default>; + pinctrl-1 = <&i2c1_dyn_sleep>; + pinctrl-names = "default", "sleep"; +}; + +// PWM instances, same scheme: all four outputs disconnected and deferred +// init, so iobroker can hand an instance to neopixel_write and pwmio and +// route its outputs at runtime. + +&pwm0 { + status = "okay"; + zephyr,deferred-init; + pinctrl-0 = <&pwm0_dyn_default>; + pinctrl-1 = <&pwm0_dyn_sleep>; + pinctrl-names = "default", "sleep"; +}; + +&pwm1 { + status = "okay"; + zephyr,deferred-init; + pinctrl-0 = <&pwm1_dyn_default>; + pinctrl-1 = <&pwm1_dyn_sleep>; + pinctrl-names = "default", "sleep"; +}; + +&pwm2 { + status = "okay"; + zephyr,deferred-init; + pinctrl-0 = <&pwm2_dyn_default>; + pinctrl-1 = <&pwm2_dyn_sleep>; + pinctrl-names = "default", "sleep"; +}; + +&pwm3 { + status = "okay"; + zephyr,deferred-init; + pinctrl-0 = <&pwm3_dyn_default>; + pinctrl-1 = <&pwm3_dyn_sleep>; + pinctrl-names = "default", "sleep"; +}; + +// SPI1 is the same peripheral instance as I2C1, so leave it disabled. +&spi1 { + status = "disabled"; +}; + +// The upstream board puts the DotStar on SPI2 with the legacy SPI driver. +// The status LED code bit-bangs the DotStar instead, so drop the node and use +// SPIM like SPI3. +/delete-node/ &apa102; + +&spi2 { + compatible = "nordic,nrf-spim"; + status = "okay"; + zephyr,deferred-init; + pinctrl-0 = <&spi2_dyn_default>; + pinctrl-1 = <&spi2_dyn_sleep>; + pinctrl-names = "default", "sleep"; +}; + +&spi3 { + status = "okay"; + zephyr,deferred-init; + pinctrl-0 = <&spi3_dyn_default>; + pinctrl-1 = <&spi3_dyn_sleep>; + pinctrl-names = "default", "sleep"; +}; + +&uart0 { + status = "okay"; + zephyr,deferred-init; + current-speed = <115200>; + pinctrl-0 = <&uart0_dyn_default>; + pinctrl-1 = <&uart0_dyn_sleep>; + pinctrl-names = "default", "sleep"; +}; + +&uart1 { + status = "okay"; + zephyr,deferred-init; + current-speed = <115200>; + pinctrl-0 = <&uart1_dyn_default>; + pinctrl-1 = <&uart1_dyn_sleep>; + pinctrl-names = "default", "sleep"; +}; + +#include "../../../app.overlay" + +/* Enable the SAADC for analogio (AIN0..AIN7 are on P0.02..P0.05 and P0.28..P0.31). */ +&adc { + status = "okay"; +}; diff --git a/ports/zephyr-cp/boards/adafruit/itsybitsy_nrf52840_zephyr/circuitpython.toml b/ports/zephyr-cp/boards/adafruit/itsybitsy_nrf52840_zephyr/circuitpython.toml new file mode 100644 index 00000000000..36bab2cb499 --- /dev/null +++ b/ports/zephyr-cp/boards/adafruit/itsybitsy_nrf52840_zephyr/circuitpython.toml @@ -0,0 +1,24 @@ +CIRCUITPY_BUILD_EXTENSIONS = ["elf", "uf2"] +USB_VID=0x239A +USB_PID=0x8052 +NAME="ItsyBitsy nRF52840 Express" + +# Non-Zephyr build of the same board; nvm and CIRCUITPY must sit where it puts them. +counterpart = "nordic/itsybitsy_nrf52840_express" + +# Status DotStar, driven with bit-banged SPI; names from [pins]. +STATUS_DOTSTAR_DATA = "DOTSTAR_DATA" +STATUS_DOTSTAR_CLOCK = "DOTSTAR_CLOCK" + +[pins] +# Pins not on the header connector (see board.overlay), with names copied +# from the ports/ counterpart board's pins.c and resolved to MDBT50Q-1MV2 +# module pin numbers through the package pin map selected in board.conf. +SWITCH = 10 +APA102_MOSI = 24 +DOTSTAR_DATA = 24 +APA102_SCK = 26 +DOTSTAR_CLOCK = 26 +L = 22 +LED = 22 +BLUE_LED = 22 diff --git a/ports/zephyr-cp/boards/adafruit/itsybitsy_nrf52840_zephyr/console.overlay b/ports/zephyr-cp/boards/adafruit/itsybitsy_nrf52840_zephyr/console.overlay new file mode 100644 index 00000000000..095c4aa1d2d --- /dev/null +++ b/ports/zephyr-cp/boards/adafruit/itsybitsy_nrf52840_zephyr/console.overlay @@ -0,0 +1,18 @@ +/* Applied after board.overlay in CONSOLE=1 and DEBUG=1 builds: the Zephyr + * console on UART0, fixed to the TX/RX pins. TX/RX are then not available to + * busio.UART. */ +/ { + chosen { + zephyr,console = &uart0; + zephyr,shell-uart = &uart0; + zephyr,uart-mcumgr = &uart0; + zephyr,bt-mon-uart = &uart0; + zephyr,bt-c2h-uart = &uart0; + }; +}; + +&uart0 { + /delete-property/ zephyr,deferred-init; + pinctrl-0 = <&uart0_default>; + pinctrl-1 = <&uart0_sleep>; +}; diff --git a/ports/zephyr-cp/boards/board_aliases.cmake b/ports/zephyr-cp/boards/board_aliases.cmake index e46027c9505..a13e2031f7c 100644 --- a/ports/zephyr-cp/boards/board_aliases.cmake +++ b/ports/zephyr-cp/boards/board_aliases.cmake @@ -30,6 +30,7 @@ cp_board_alias(pca10056 nrf52840dk/nrf52840) cp_board_alias(adafruit_feather_nrf52840_zephyr adafruit_feather_nrf52840/nrf52840/uf2) cp_board_alias(adafruit_clue_nrf52840_zephyr adafruit_clue_nrf52840/nrf52840/uf2) cp_board_alias(adafruit_feather_nrf52840_sense_zephyr adafruit_feather_nrf52840/nrf52840/sense/uf2) +cp_board_alias(adafruit_itsybitsy_nrf52840_zephyr adafruit_itsybitsy/nrf52840) cp_board_alias(adafruit_feather_rp2040_zephyr adafruit_feather_rp2040/rp2040) cp_board_alias(renesas_ek_ra6m5 ek_ra6m5) cp_board_alias(renesas_ek_ra8d1 ek_ra8d1) diff --git a/ports/zephyr-cp/cptools/tests/test_zephyr2cp.py b/ports/zephyr-cp/cptools/tests/test_zephyr2cp.py index c4208ad692a..1099ea461bd 100644 --- a/ports/zephyr-cp/cptools/tests/test_zephyr2cp.py +++ b/ports/zephyr-cp/cptools/tests/test_zephyr2cp.py @@ -29,6 +29,7 @@ _fastest_first, _named_pins, _standard_bus_pins, + _toml_status_dotstar, _toml_status_pin, _used_by_zephyr, ) @@ -980,3 +981,32 @@ def test_stable_for_equal_and_missing(self): "i2c1", "i2c0", ] + + +class TestTomlStatusDotstar: + """Resolution of circuitpython.toml STATUS_DOTSTAR_DATA/CLOCK keys.""" + + BOARD_NAMES = { + ("gpio0", 8): ["DOTSTAR_DATA"], + ("gpio1", 9): ["DOTSTAR_CLOCK"], + ("gpio0", 16): ["NEOPIXEL"], + } + DOTSTAR = {"STATUS_DOTSTAR_DATA": "DOTSTAR_DATA", "STATUS_DOTSTAR_CLOCK": "DOTSTAR_CLOCK"} + + def test_resolves_data_and_clock(self): + assert _toml_status_dotstar(self.DOTSTAR, self.BOARD_NAMES) == ( + ("gpio0", 8), + ("gpio1", 9), + ) + + def test_absent_keys_are_none(self): + assert _toml_status_dotstar({}, self.BOARD_NAMES) is None + assert _toml_status_dotstar(None, self.BOARD_NAMES) is None + + def test_one_key_only_raises(self): + with pytest.raises(RuntimeError, match="needs both"): + _toml_status_dotstar({"STATUS_DOTSTAR_DATA": "DOTSTAR_DATA"}, self.BOARD_NAMES) + + def test_with_status_neopixel_raises(self): + with pytest.raises(RuntimeError, match="both STATUS_NEOPIXEL and a status DotStar"): + _toml_status_dotstar({**self.DOTSTAR, "STATUS_NEOPIXEL": "NEOPIXEL"}, self.BOARD_NAMES) diff --git a/ports/zephyr-cp/cptools/zephyr2cp.py b/ports/zephyr-cp/cptools/zephyr2cp.py index e80f542f086..bd5d0c60b38 100644 --- a/ports/zephyr-cp/cptools/zephyr2cp.py +++ b/ports/zephyr-cp/cptools/zephyr2cp.py @@ -58,20 +58,102 @@ AUDIOBUSIO_CLASSES = {"i2s": "I2SOut"} +# Board pin names for each connector compatible, in gpio-map order. Keep the +# entries in alphabetical order. CONNECTORS = { - "mikro-bus": [ - "AN", - "RST", - "CS", + "adafruit-clue": [ + ["P0", "D0", "A2", "RX"], + ["P1", "D1", "A3", "TX"], + ["P2", "D2", "A4"], + ["P3", "D3", "A5"], + ["P4", "D4", "A6"], + ["P5", "D5", "BUTTON_A"], + ["P6", "D6"], + ["P7", "D7"], + ["P8", "D8"], + ["P9", "D9"], + ["P10", "D10", "A7"], + ["P11", "D11", "BUTTON_B"], + ["P12", "D12", "A0"], + ["P13", "D13", "SCK"], + ["P14", "D14", "MISO"], + ["P15", "D15", "MOSI"], + ["P16", "D16", "A1"], + ["P17", "D17", "L", "LED"], + ["P18", "D18", "NEOPIXEL"], + ["P19", "D19", "SCL"], + ["P20", "D20", "SDA"], + ], + "adafruit-feather-header": [ + "A0", + "A1", + "A2", + "A3", + "A4", + "A5", "SCK", - "MISO", "MOSI", - "PWM", - "INT", + "MISO", "RX", "TX", + "D4", + "SDA", + "SCL", + "D5", + "D6", + "D9", + "D10", + "D11", + "D12", + "D13", + ], + # Our own binding (dts/bindings/adafruit-itsybitsy-header.yaml): the + # ItsyBitsy nRF52840 header pins only. + # TODO: figure out how to handle the extra pins on other ItsyBitsy + # boards, such as D3 and D4 on the ItsyBitsy M4 and RP2040. We may want + # separate connectors for the different board families. + "adafruit-itsybitsy-header": [ + "A0", + "A1", + "A2", + "A3", + "A4", + "A5", + "SCK", + "MOSI", + "MISO", + ["D0", "RX"], + ["D1", "TX"], + "D2", + "SDA", + "SCL", + "D5", + "D7", + "D9", + "D10", + "D11", + "D12", + "D13", + ], + "arducam,dvp-20pin-connector": [ "SCL", "SDA", + "VS", + "HS", + "PCLK", + "XCLK", + "D7", + "D6", + "D5", + "D4", + "D3", + "D2", + "D1", + "D0", + "PEN", + "PDN", + "GPIO0", + "GPIO1", ], # The standard bus pin names are those on Arduino Uno R3 boards: TX/RX on # D1/D0, SPI on D11-D13 (and the 2x3 SPI header), and SDA/SCL on the two @@ -100,51 +182,19 @@ ["D14", "SDA"], ["D15", "SCL"], ], - "adafruit-feather-header": [ - "A0", - "A1", - "A2", - "A3", - "A4", - "A5", + "mikro-bus": [ + "AN", + "RST", + "CS", "SCK", - "MOSI", "MISO", + "MOSI", + "PWM", + "INT", "RX", "TX", - "D4", - "SDA", "SCL", - "D5", - "D6", - "D9", - "D10", - "D11", - "D12", - "D13", - ], - "adafruit-clue": [ - ["P0", "D0", "A2", "RX"], - ["P1", "D1", "A3", "TX"], - ["P2", "D2", "A4"], - ["P3", "D3", "A5"], - ["P4", "D4", "A6"], - ["P5", "D5", "BUTTON_A"], - ["P6", "D6"], - ["P7", "D7"], - ["P8", "D8"], - ["P9", "D9"], - ["P10", "D10", "A7"], - ["P11", "D11", "BUTTON_B"], - ["P12", "D12", "A0"], - ["P13", "D13", "SCK"], - ["P14", "D14", "MISO"], - ["P15", "D15", "MOSI"], - ["P16", "D16", "A1"], - ["P17", "D17", "L", "LED"], - ["P18", "D18", "NEOPIXEL"], - ["P19", "D19", "SCL"], - ["P20", "D20", "SDA"], + "SDA", ], "nordic,expansion-board-header": [ "P1_04", @@ -159,26 +209,6 @@ "P1_13", "P1_14", ], - "arducam,dvp-20pin-connector": [ - "SCL", - "SDA", - "VS", - "HS", - "PCLK", - "XCLK", - "D7", - "D6", - "D5", - "D4", - "D3", - "D2", - "D1", - "D0", - "PEN", - "PDN", - "GPIO0", - "GPIO1", - ], "nxp,cam-44pins-connector": ["CAM_RESETB", "CAM_PWDN"], "nxp,lcd-8080": [ "TOUCH_SCL", @@ -234,6 +264,34 @@ "I2C_SCL", "I2C_SDA", ], + "raspberrypi,pico-header": [ + "GP0", + "GP1", + "GP2", + "GP3", + "GP4", + "GP5", + "GP6", + "GP7", + "GP8", + "GP9", + "GP10", + "GP11", + "GP12", + "GP13", + "GP14", + "GP15", + "GP16", + "GP17", + "GP18", + "GP19", + "GP20", + "GP21", + "GP22", + ["GP26_A0", "GP26", "A0"], + ["GP27_A1", "GP27", "A1"], + ["GP28_A2", "GP28", "A2"], + ], "renesas,ra-gpio-mipi-header": [ "IIC_SDA", "DISP_BLEN", @@ -295,34 +353,6 @@ "D1", "D0", ], - "raspberrypi,pico-header": [ - "GP0", - "GP1", - "GP2", - "GP3", - "GP4", - "GP5", - "GP6", - "GP7", - "GP8", - "GP9", - "GP10", - "GP11", - "GP12", - "GP13", - "GP14", - "GP15", - "GP16", - "GP17", - "GP18", - "GP19", - "GP20", - "GP21", - "GP22", - ["GP26_A0", "GP26", "A0"], - ["GP27_A1", "GP27", "A1"], - ["GP28_A2", "GP28", "A2"], - ], # STEMMA QT connectors, and Qwiic connectors, which use the same binding. # board.STEMMA_I2C is on these pins (see STEMMA_I2C_PINS). "stemma-qt-connector": ["STEMMA_SCL", "STEMMA_SDA"], @@ -885,6 +915,29 @@ def _used_by_zephyr(device_tree, node): return False +def _toml_status_dotstar(mpconfigboard, board_names): + """Resolve the status DotStar's ``STATUS_DOTSTAR_DATA`` and + ``STATUS_DOTSTAR_CLOCK`` keys to a ``(data, clock)`` pair of pins. + + Returns None when neither key is present. Raises when only one is, or + when ``STATUS_NEOPIXEL`` is also set: the status LED code would use the + NeoPixel and ignore the DotStar. + """ + data = _toml_status_pin(mpconfigboard, board_names, "STATUS_DOTSTAR_DATA") + clock = _toml_status_pin(mpconfigboard, board_names, "STATUS_DOTSTAR_CLOCK") + if data is None and clock is None: + return None + if data is None or clock is None: + raise RuntimeError( + "circuitpython.toml needs both STATUS_DOTSTAR_DATA and STATUS_DOTSTAR_CLOCK" + ) + if (mpconfigboard or {}).get("STATUS_NEOPIXEL") is not None: + raise RuntimeError( + "circuitpython.toml sets both STATUS_NEOPIXEL and a status DotStar; use one" + ) + return data, clock + + @cpbuild.run_in_thread def zephyr_dts_to_cp_board(board_id, portdir, builddir, zephyrbuilddir, mpconfigboard=None): # noqa: C901 board_dir = builddir / "board" @@ -1361,6 +1414,9 @@ def zephyr_dts_to_cp_board(board_id, portdir, builddir, zephyrbuilddir, mpconfig standard_bus_pins = _standard_bus_pins(board_names) stemma_i2c_pins = _named_pins(board_names, STEMMA_I2C_PINS) pin_object_of = {} + # Status DotStar: ``STATUS_DOTSTAR_DATA`` and ``STATUS_DOTSTAR_CLOCK``, + # driven by the status LED code with bit-banged SPI. + status_dotstar = _toml_status_dotstar(mpconfigboard, board_names) # GPIO pad number -> pad: every (label, num) name and alias the # devicetree walk produced keys into one pin object per pad. pad_of_gpio = {} @@ -1421,6 +1477,10 @@ def zephyr_dts_to_cp_board(board_id, portdir, builddir, zephyrbuilddir, mpconfig if status_neopixel_power and (ioport, num) == status_neopixel_power: status_neopixel_power = pin_object_name pin_object_of[(ioport, num)] = pin_object_name + if status_dotstar: + status_dotstar = tuple( + pin_object_name if pin == (ioport, num) else pin for pin in status_dotstar + ) pin_defs.append( f"const mcu_pin_obj_t pin_{pin_object_name} = {{ .base.type = &mcu_pin_type, .package_pin = {package_pin_init}}};" ) @@ -1919,6 +1979,14 @@ def zephyr_dts_to_cp_board(board_id, portdir, builddir, zephyrbuilddir, mpconfig ) else: status_neopixel_power = "" + if status_dotstar: + status_dotstar = ( + f"#define MICROPY_HW_APA102_MOSI (&pin_{status_dotstar[0]})\n" + f"#define MICROPY_HW_APA102_SCK (&pin_{status_dotstar[1]})\n" + "#define CIRCUITPY_BITBANG_APA102 (1)\n" + ) + else: + status_dotstar = "" ram_list = [] ram_externs = [] max_size = 0 @@ -1953,6 +2021,7 @@ def zephyr_dts_to_cp_board(board_id, portdir, builddir, zephyrbuilddir, mpconfig {status_neopixel} {status_neopixel_power} {board_bus_defines} +{status_dotstar} """ if not header.exists() or header.read_text() != new_header_content: header.write_text(new_header_content) diff --git a/ports/zephyr-cp/dts/bindings/adafruit-itsybitsy-header.yaml b/ports/zephyr-cp/dts/bindings/adafruit-itsybitsy-header.yaml new file mode 100644 index 00000000000..553bf7443e4 --- /dev/null +++ b/ports/zephyr-cp/dts/bindings/adafruit-itsybitsy-header.yaml @@ -0,0 +1,25 @@ +# SPDX-FileCopyrightText: 2026 Dan Halbert for Adafruit Industries +# SPDX-License-Identifier: MIT + +description: | + GPIO pins exposed on the Adafruit ItsyBitsy nRF52840 headers. + + This binding provides a nexus mapping for 21 pins. The positions are in + the order of the CONNECTORS["adafruit-itsybitsy-header"] names in + cptools/zephyr2cp.py, not in physical header order: + + 0 A0 7 MOSI 14 D5 + 1 A1 8 MISO 15 D7 + 2 A2 9 D0/RX 16 D9 + 3 A3 10 D1/TX 17 D10 + 4 A4 11 D2 18 D11 + 5 A5 12 SDA 19 D12 + 6 SCK 13 SCL 20 D13 + + TODO: figure out how to handle the extra pins on other ItsyBitsy boards, + such as D3 and D4 on the ItsyBitsy M4 and RP2040. We may want separate + connectors for the different board families. + +compatible: "adafruit-itsybitsy-header" + +include: [gpio-nexus.yaml, base.yaml]