π§° Hardware Abstraction Layer (HAL)
The HAL is the set of TypeScript objects you use to talk to the board β each pin, bus, and built-in feature is an object, and the HAL checks that you're using them in ways the hardware actually supports. A wrong pin assignment, a missing setup step, or two features fighting over the same pin is caught before the firmware reaches the board.
Areas
Every class follows one shape: construction carries the configuration (pin flags, baud, period, broker URI β all set where the object is created). Units are what the hardware talks: nanoseconds for PWM, raw counts for ADC, milliseconds throughout.
Start here
Peripherals & I/O
Networking & data
UI
Language & toolchain
Test & verify
Compile-time directives
Three functions that exist purely for code generation β the TypeScript never executes.
rawCpp / rawCppExpr
Inject raw C++ at the call site β for features the HAL doesn't cover, such as mDNS or OTA.
Example first
import { rawCpp, rawCppExpr } from '@typecad/hal';
rawCpp('PORTB |= (1 << PB5)'); // statement injected at the call site
const pb5 = rawCppExpr('PORTB'); // expression form β where a value is expectedinclude
Adds a C++ #include to the generated firmware.
Example first
import { include } from '@typecad/hal';
include('<Wire.h>'); // adds a C++ #include to the output; no-op while type-checkingboard
Resolves a board definition value at compile time β the generated board module's facts, by path.
Example first
import { board } from '@typecad/hal';
const resolution = board('peripherals.pwm.resolution');Pin model
Pins are typed objects, not integer constants. Everything imports from @typecad/hal, including the named board pins. Using a pin for something it cannot do
(analog on a digital-only pad, PWM on a non-PWM pin) is caught at build time with the valid pins
named.
import { GPIO, ADC, PWM, LED, A1, PB6 } from '@typecad/hal';
const led = new GPIO(LED, GPIO.OUTPUT | GPIO.OUTPUT_INIT_LOW); // set / get / toggle
const sense = new ADC(A1); // read / readMillivolts
const motor = new PWM(PB6, { periodNs: 20_000_000 }); // setPulse / setDutyTiming, threads, async
One Time object with a blocking sleep, monotonic clocks, and a sub-millisecond spin:
import { Time } from '@typecad/hal';
Time.sleep(1000); // blocks at the top level
await Time.sleep(50); // inside an async function: other tasks run while you wait
const ms = Time.now(); // milliseconds since boot, monotonic, no wrap
Time.busyWaitUs(3); // a sub-millisecond spinPeriodic and concurrent work is a Thread β a real background thread, not a callback
timer. Construction carries the stack size and
priority; start(fn) schedules it; join() blocks until it exits. For
deterministic sub-millisecond periodic execution use the hardware Counter instead.
import { Thread } from '@typecad/hal';
const blinker = new Thread(0, { stackKb: 2 }); // 0 = the first thread slot
blinker.start(() => {
while (true) {
led.toggle();
Time.sleep(500);
}
});
blinker.join(); // park main here β the thread runs foreverLong waits β Time.sleep, network requests β can yield, so the rest of your program
keeps running while you wait. When you need to wait without blocking other tasks, Async gives you the cooperative forms:
import { Async } from '@typecad/hal';
async function heartbeat() {
while (true) {
led.toggle();
await Time.sleep(500); // other tasks run while you wait
}
}
await Async.sleep(1000); // non-blocking sleep
await Async.yield(); // let other tasks run onceBoard singletons
Bus singletons are ready to use β UART0.writeLine(...) needs no construction, and I2C0.device(0x44) hands back a working target. A board that lacks a controller does
not export its name β that is a module-resolution error at build time, not a runtime surprise.
import { LED, UART0, I2C0, GPIO } from '@typecad/hal';
const led = new GPIO(LED, GPIO.OUTPUT);
UART0.writeLine('hello');
const dev = I2C0.device(0x44); // register verbs callable immediatelySomething wrong or missing? Open an issue β we'd rather hear about it.