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.

class

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

can.ts
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 optionTypeMeaning
instancenumberController index — 0 is the board’s first, the only one on virtually every board
hznumberBus bitrate (default 500 kbit/s)
loopbackbooleanRoute every sent frame back to this controller’s own filters — the bench-test mode (default false)

All methods

MethodWhat 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
pattern

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

loopback.ts
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
© 2026 typeCAD — code-defined electronics github