Communication Buses
One thin class per target: a UART port, a USBConsole, an I2CTarget at one address, an SPITarget behind one chip-select. Construction carries the configuration — baud, buffer sizes, bus speed, SPI mode — and every verb maps to one Zephyr driver call. There is no begin(), no transaction dance, and no Wire vocabulary.
The board module exports each controller as a ready-to-use singleton, so the common case needs no construction at all:
import { UART0, I2C0, SPI0, PA4 } from '@typecad/board';
UART0.writeLine('hello'); // the controller, default 115200
const dev = I2C0.device(0x44); // working I2CTarget at 0x44
const id = dev.readReg(0xD0);
const flash = SPI0.device(PA4); // working SPITarget behind CSA board without the controller does not export the name — using it is a build error at import, not a runtime surprise. Explicit construction (below) is for non-default facts: a different baud, a bigger receive ring, a faster bus.
For managed sensor drivers over these buses, see Sensors — one generic class covers every Zephyr sensor driver from a generated part catalog.
UART — UART
The UART0 / UART1 … singletons are the direct surface. Construction names the controller and carries the baud (applied once on first use) and the receive-ring size — the first argument may be the board instance or its name:
import { UART, UART0 } from '@typecad/board';
UART0.writeLine("hello"); // default 115200 — no construction
const gps = new UART(UART0, { baud: 9600, rxBufferBytes: 128 });
gps.write("$PMTK220,1000*2F\r\n"); // no newline appended
gps.writeLine("hello"); // with a newline
if (gps.available() > 0) { // bytes waiting in the ring
const b = gps.read(); // pop the oldest byte (-1 when empty)
const next = gps.peek(); // look without consuming
}read() and peek() return one byte or −1. There is no line API — frame messages in your program.
USB Serial — USBConsole
On boards with a USB device connector, USBConsole is the CDC port the host sees as a COM/tty device. One CDC-specific fact matters: output written before the host opens the port is dropped by most hosts. Gate early output on linked(), or block once with waitLinked():
import { USB0 } from '@typecad/board';
const usb = USB0; // the board exports it ready to use
if (!usb.linked()) {
usb.waitLinked(3000); // bounded poll — returns once the host opens it
}
usb.writeLine('hello, host');USB0 is board-gated: the board must declare a USB device controller, otherwise usb.* operations fail at build time with a diagnostic naming the missing board data.
I2C — I2CTarget
One address on a bus, Zephyr’s register verbs verbatim. I2C0.device(0x44) is the common form — it hands back a working target with no further construction, and the same object is what new Sensor(...) takes:
import { I2C0 } from '@typecad/board';
const sensor = I2C0.device(0x44);
sensor.writeReg(0xF4, 0x27); // one-byte register write
const id = sensor.readReg(0xD0); // one-byte register read
sensor.updateReg(0xF5, 0x0F, 0x02); // read-modify-write, no read-back race
sensor.write([0x2C, 0x06]); // raw bytesThe address is the 7-bit form (0x44) — never the left-shifted 8-bit form. A custom bus speed rides the explicit constructor — new I2CTarget(I2C0, 0x44, { hz: 400000 }) — applied once; 100k/400k/1M map to Zephyr’s speed tiers. Explicit construction takes the board instance or its name and is otherwise equivalent to device(...).
To attach a managed sensor driver, pass the same target — new Sensor(SENSOR.sensirion_sht3xd, I2C0.device(0x44)) — which generates the devicetree node and typed channels. I2CTarget is for register-level access to parts the catalog does not cover.
SPI — SPITarget
One chip-select on a bus. The configuration (CS pin, frequency, mode bits) becomes a devicetree spec at build time; CS asserts and deasserts around every operation, so there is no transaction API:
import { SPI0, PA4 } from '@typecad/board';
const display = SPI0.device(PA4, { hz: 10000000, mode: 0 });
const rx = new Uint8Array(4);
display.transceive([0x42, 0x00, 0x00, 0x00], rx); // full duplex
display.write([0xAA, 0xBB]); // write only
const id = display.readReg(0x00); // one-byte register readMultiple targets on one bus with different CS pins is the normal shape — one SPI0.device(...) per chip. Explicit construction is equivalent: new SPITarget(SPI0, PA4, { hz, mode }).
⚙️ Advanced details — bus conflicts
Two peripherals claiming the same pins fail the build with the conflict named. The optional exclusive-claim markers (I2C0.take() / I2C0.release()) add compile-time ownership validation for programs that share a bus across tasks — see Ownership.
API Reference
| Member | Description |
|---|---|
UART0 / UART1 … | Board singletons — verbs callable with no construction. |
new UART(UART0, opts?) | baud (default 115200), rxBufferBytes (default 64); takes the instance or its name. |
write(v) / writeLine(v) / available() / read() / peek() | Poll TX (v is text, a number, or a boolean); IRQ-backed RX ring (byte or −1). |
USB0 | The CDC console singleton, ready to use; the board must declare USB. |
write(v) / writeLine(v) / linked() / waitLinked(ms) / read() / available() | CDC port verbs (v is text, a number, or a boolean). |
I2C0.device(address) | Working I2CTarget at a 7-bit address. |
new I2CTarget(I2C0, address, opts?) | Explicit equivalent; hz sets the bus speed once. |
writeReg(r, v) / readReg(r) / updateReg(r, mask, v) / write(bytes) | Zephyr register verbs. |
SPI0.device(cs, opts?) | Working SPITarget behind one chip-select; hz, mode (0–3) become the devicetree spec. |
new SPITarget(SPI0, cs, opts?) | Explicit equivalent. |
transceive(tx, rx?) / write(tx) / readReg(r) | Full-duplex and write transfers; one-byte register read. |
On This Page