Step 20 of 56
TWAI: CAN 2.0, with the bus-off trap
Standard and extended frames as separate types so a 29-bit identifier cannot reach an 11-bit frame — and an error state that a single-node bench setup walks straight into.
One controller, three modes
TWAI — Two-Wire Automotive Interface — is CAN 2.0, on an SJA1000-compatible controller. A real bus needs an external transceiver on the TX/RX pins.
type Bus_Mode is (Normal, Listen_Only, Self_Test);
Normaldrives a real bus.Listen_Onlynever transmits and never acknowledges — a passive sniffer that cannot perturb traffic.Self_Testtransmits and self-receives without needing an external acknowledgement. WithEnable_Loopbacktying TX back to RX through one pad, that gives a complete wiring-free self-test.
Identifiers that cannot be mixed up
subtype Standard_Id is Interfaces.Unsigned_32 range 0 .. 16#7FF#; -- 11-bit
subtype Extended_Id is Interfaces.Unsigned_32 range 0 .. 16#1FFF_FFFF#; -- 29-bit
type Standard_Frame is record
Id : Standard_Id := 0;
Remote : Boolean := False;
Length : Data_Length := 0; -- 0 .. 8
Data : Data_Bytes := (others => 0);
end record;
-- Extended_Frame is the same shape with an Extended_Id.
Each frame type carries its own identifier subtype, so the identifier is
range-checked against the standard it belongs to, and Send is
overloaded on the frame type. You cannot put a 29-bit identifier in a
standard frame — it is a compile-time or constraint error, not a
malformed frame on the wire.
Remote => True makes it a remote-transmission request: it
carries the identifier and the requested length but no data, and a node owning
that identifier is expected to answer with a data frame.
Receiving, two ways
Polled, where the sender chooses the width so you must ask:
function Available (S : Session) return Boolean;
function Is_Extended (S : Session) return Boolean; -- ask BEFORE choosing an overload
procedure Receive (S : Session; F : out Standard_Frame; Got : out Boolean);
procedure Receive (S : Session; F : out Extended_Frame; Got : out Boolean);
Or interrupt-driven, which is the shape you want for a real bus:
procedure Enable_Rx_Interrupt (S : Session); -- needs the Session
procedure Get (F : out Queued_Frame); -- does NOT -- call it from another task
function Rx_Overruns return Natural;
A Queued_Frame carries its own width, so the consumer can tell a
standard frame from an extended one without asking the controller. Note the
asymmetry: Enable_Rx_Interrupt touches the controller and so needs
the held session, but Get deliberately does not — it is meant
to be called from a different task, typically a decode task separate
from the one that owns the port. Rx_Overruns counts what the queue
dropped, which is the number to watch when you suspect your decoder is too
slow.
The bus-off trap
type Bus_State is (Active, Warning, Bus_Off);
function Health (S : Session) return Bus_State;
procedure Recover (S : Session);
A single node on a bench goes bus-off.
Active is normal and Warning means an error counter has
passed the warning limit, but Bus_Off means the node took
itself off the bus after too many transmit errors — and the
classic way to cause that is transmitting in Normal mode with no
other node present to acknowledge. A bus-off node neither sends nor receives
until Recover rejoins it. If your first CAN experiment goes deaf
after a few frames, check Health before suspecting wiring, and use
Self_Test mode when there is nothing else on the bus.