Setting Up Peripherals Before You Use Them
A peripheral (a serial port, an I2C device, a timer) has to be configured before it’s used. In Cuttlefish the configuration is the setup — and for the common case the board has already done it: the bus singletons it exports (UART0, I2C0.device(...)) are ready the moment you import them. When you need different facts, you pass them to the constructor and the object that comes back is ready. There is no begin() step to forget, no order to get right, and no uninitialized state to track.
import { UART0, UART, I2C0, I2CTarget } from '@typecad/board';
UART0.writeLine('ready'); // no setup at all
const gps = new UART('UART0', { baud: 9600, rxBufferBytes: 128 }); // ready
const sensor = new I2CTarget(I2C0, 0x44, { hz: 400000 }); // readyThe constructor’s arguments become part of the generated code — devicetree nodes, Kconfig, or one-time guarded calls — whichever is the honest form for that peripheral on the target.
Why There Is No begin()
The begin() pattern has two failure modes Cuttlefish removes by construction:
- Using a peripheral before it’s configured cannot happen — the configuration exists before the object does.
- Configuring it twice with different arguments cannot happen — there is one constructor call per instance, and the facts it captured are the ones every method uses.
⚙️ Advanced details — where the facts go
Construction facts never become runtime state. They become devicetree nodes (sensors, SPI targets, PWM channels), usage-gated Kconfig, per-instance state blocks in the generated C++, or a guarded one-time call (i2c_configure, uart_configure). The methods you call later carry the facts implicitly — each lowers to one driver call with the construction values folded in.
Uses the Board Rejects at Build Time
The checks that used to guard the lifecycle now guard the construction itself:
- A peripheral your board does not have (a second I2C controller on a one-controller chip) is a build error naming what the board declares.
- Two peripherals claiming the same pins is a build error naming the conflict. See Conflict Detection.
- A pin used for something it cannot do (analog on a digital-only pad) is a build error naming the valid pins.
Runtime Reconfiguration
A few peripherals have runtime verbs where the platform genuinely supports them — PWM.setPeriod, WiFi.leave. These are methods on the object, not a second initialization step, and their docs say what resets when you use them (for example, setPeriod resets the pulse to idle).
Peripheral Ownership with take() / release()
For programs that share one bus across tasks, the optional exclusive-claim markers add compile-time ownership validation:
I2C0.take(); // claim — a compile-time marker
I2C0.device(0x76).readReg(0x00); // I/O between take and release
I2C0.release(); // releaseNothing is emitted for the markers themselves; the ownership pass checks the discipline — no double-take(), no release() without a take(), no bus taken and never released, and no I/O outside the claimed region. See The Ownership Model.
Complete Example: I2C Sensor
One construction, one read — the whole lifecycle:
import { I2C0 } from '@typecad/board';
const sensor = I2C0.device(0x44);
sensor.writeReg(0xF4, 0x27);
const id = sensor.readReg(0xD0);On This Page