The CAN backplane
Almost the only thing that crosses a subsystem boundary is an 8-byte CAN frame. This page covers addressing, function codes, the two multi-frame transports that ride the bus, and how provenance survives the trip to the ground.
The exception is bulk science, which crosses a second bus: the payload pushes captured files straight into the DHS’s mass memory over a point-to-point SpaceWire mission data link. Nothing else — no command, housekeeping report or telecommand — travels on that bus, so this one remains the spacecraft’s command-and-control backplane.
Source: include/shared_can.h (frame, addressing, UDP transport — shared
by the hub and the nodes, no FreeRTOS), common/can_drv.c (the node-side
driver, where the Posix port workarounds live), and
hub/can_hub.c (the backplane process).
1. Addressing
An 11-bit identifier:
[10:8] function | [7:4] destination | [3:0] sourceAddresses are 4-bit and live in both the destination and the source field: DHS 0x1, TTC 0x2,
ADCS 0x3, EPS 0x4, PAYLOAD 0x5, broadcast 0xF (CAN_ADDR_*). The CAN_SYS_* constants are
the same values pre-shifted into the destination position — CAN_SYS_DHS is 0x10, not what
CAN_ID_DST(id) returns, which is 0x1.
All eight function codes:
| Function | Code | Carries |
|---|---|---|
CMD | 0x0 | Ground/DHS commands, highest priority |
ACK | 0x1 | Command acknowledgements |
HK | 0x2 | Housekeeping |
SCI | 0x3 | Science telemetry |
XFER_FF | 0x4 | Payload file readout, first frame |
XFER_CF | 0x5 | Payload file readout, consecutive frame |
MGMT | 0x6 | Bus management — HELLO, heartbeat |
PKT | 0x7 | A whole PUS space packet, segmented (DHS↔TTC) |
Function codes sit in the high bits, so a lower code yields a lower ID — CMD outranks HK
exactly as CAN arbitration would decide it.
2. The hub
can_hub is a plain POSIX process — deliberately no FreeRTOS — that receives every frame over
UDP :20000 and forwards it to the destination’s registered socket, or to all of them for a
broadcast. The routing table is learned at runtime: a node’s first frame registers its address,
and a logical address migrates to whichever node last announced it. That migration is what would
make a future warm-redundancy upgrade (two real OBC binaries) a node-side change with no protocol
change. The hub logs every frame it routes, collapsing bulk readouts to a count — the first place
to look when a subsystem goes quiet.
3. Two multi-frame transports
Nothing but 8-byte frames crosses a node boundary on this bus, so anything larger is segmented. Two independent transports do this, distinguished by function code — do not confuse them:
- Payload readout (
XFER_FF/XFER_CF, PAYLOAD→DHS) is a simplified ISO-TP with no flow control: the sender streams at the requested rate. Frame layouts, pacing and what the DHS does with the reassembled file are in The file transport. - Whole-packet transport (
PKT, function0x7, DHS↔TTC) carries a complete PUS space packet: a first segment (start marker + 2-byte length + 5 bytes) then continuations (7-bit rolling sequence + 7 bytes), reassembled bycan_drv’scanPktSend/canPktFeed. This is how the DHS’s packets reach TTC for downlink and how uplinked telecommands reach the DHS.
A third transport exists and is not on this bus: RMAP over the mission data link, which needs no segmentation at all because it writes directly into the receiver’s memory. It is also why the payload readout above is now the fallback route for a science file rather than the only one — but it stays the default, and the eight function codes here are unchanged.
4. Provenance is carried by APID
The ground must be able to attribute every packet to the subsystem that produced it. The DHS does
this at packetisation: it reads each incoming CAN frame’s source address and uses it as the
PUS APID when it builds the downlink packet (pusApidFromAddr(CAN_ID_SRC(...))).
So a TTC housekeeping frame becomes an APID-2 packet, a payload science frame an APID-5 packet — provenance rides the APID, not the DHS’s own address. Get this wrong and telemetry is misattributed on the ground.
The ADCS and the EPS are attributed the same way but not frame by frame: their five and eight
housekeeping frames are ingested into the DHS’s parameter pool and leave as one APID-3 and one
APID-4 report built on the DHS’s own cadence
(dhs.md §6). The APID still names the producer; the packet
boundary is the subsystem’s, not the bus’s.
canSendFrom(), which preserves an originator’s CAN address on a raw relay, still exists but is no
longer used by flight code now that the DHS builds packets instead of relaying frames.
5. The driver
common/can_drv.c is the node-side virtual driver: a UDP socket to the hub,
a receive task, and a frame queue. It is also where the Posix-port workarounds live — SO_RCVTIMEO,
the EINTR retry and CAN_RX_BATCH, all three required and none of them optional. See
porting.md §1.
TODO — document the driver API surface (
canInit,canSend,canSendFrom,canPktSend,canPktFeed, the receive queue depth) and the frame struct itself.