CAN Bus
One controller per board — the harvested can@ devicetree node, whatever silicon
carries it (ESP32 TWAI, STM32 bxCAN, NXP FlexCAN) — with Zephyr's verbs: configure and start,
send a frame, and a receive filter. Classic CAN: 11-bit ids (29-bit extended), payloads up to
8 bytes.
CAN
A bus handle constructed with its facts — instance, bitrate, mode — and three verbs. begin() applies the constructed facts in the order classic CAN requires (stopped for mode/bitrate changes, then start); send() builds one can_frame; onReceive() installs an accept-all filter.
Example first
import { CAN } from '@typecad/hal';
const bus = new CAN(); // 500 kbit/s — the automotive middle
bus.begin(); // mode + bitrate, then start
bus.send(0x123, [0x11, 0x22]);
bus.onReceive((id: number, len: number, b0: number, b1: number): void => {
// fires from interrupt context for every frame on the wire
});| Constructor option | Type | Meaning |
|---|---|---|
| instance | number | Controller index — 0 is the board’s first, the only one on virtually every board |
| hz | number | Bus bitrate (default 500 kbit/s) |
| loopback | boolean | Route every sent frame back to this controller’s own filters — the bench-test mode (default false) |
All methods
| Method | What it does |
|---|---|
| begin() | Configure and start — applies the constructed mode + bitrate, then starts once. Idempotent |
| send(id, data, extended?) | One frame: 11-bit standard id (29-bit with extended), up to 8 bytes. Blocks briefly until the controller accepts it |
| onReceive(handler) | Accept-all filter — the handler fires per frame as scalars (id, len, b0…b7), unused bytes zero |
loopback — the bench test
The zero-hardware verification: constructing with loopback: true routes every sent frame back to this controller's own filters. No transceiver, no wiring, no second node — and the round trip exercises the whole path: devicetree enable, driver init, frame build, tx submission, filter callback.
Example first
import { CAN, Time } from '@typecad/hal';
// no transceiver, no bus — the bench test
const bus = new CAN(0, { loopback: true, hz: 500_000 });
let gotId = -1;
bus.onReceive((id: number, len: number): void => {
if (gotId < 0) gotId = id; // ISR discipline: copy, don't busy-work
});
bus.begin();
bus.send(0x123, [0x11, 0x22]); // loops back to your own filter
Time.sleep(100);
// gotId === 0x123 — the whole path, proven