Chapter 24 — Data Flow Walkthrough

This chapter traces messages through the library end-to-end, from CAN frame arrival to protocol handler response and back out to the bus. Use these traces as a map when debugging or understanding how the pieces fit together.

24.1 The Five State Machines

State MachineLayerFilePurpose
CAN Main SMCAN Transportcan_main_statemachine.cCAN frame dispatch, alias conflict, CAN login orchestration
CAN Login SMCAN Transportcan_login_statemachine.cPer-node CAN alias allocation (CID/RID/AMD)
OpenLCB Login SMCoreopenlcb_login_statemachine.cPer-node OpenLCB login (Init Complete, event announcements)
OpenLCB Main SMCoreopenlcb_main_statemachine.cProtocol message dispatch to handlers
CAN RX SMCAN Transportcan_rx_statemachine.cCAN frame classification and assembly (runs in ISR context)

24.2 Main Loop Calling Order

flowchart TD ML["OpenLcb_run()"] --> A["CanMainStateMachine_run()"] A --> B["OpenLcbLoginMainStatemachine_run()"] B --> C["OpenLcbMainStatemachine_run()"] C --> D["_run_periodic_services()"] subgraph CAN_SM["CAN Main SM Priority"] A1["1. Handle duplicate aliases"] A2["2. Send outgoing CAN frame"] A3["3. Send login CAN frame"] A4["4. Enumerate first/next node for login"] end subgraph OLCB_SM["OpenLCB Main SM Priority"] C1["1. Send outgoing OpenLCB message"] C2["2. Re-enumerate (multi-response)"] C3["3. Pop incoming from FIFO"] C4["4. Enumerate first node"] C5["5. Enumerate next node"] end A --> CAN_SM C --> OLCB_SM style ML fill:#f3e5f5,stroke:#7b1fa2 style A fill:#fff3e0,stroke:#e65100 style B fill:#e3f2fd,stroke:#1565c0 style C fill:#e3f2fd,stroke:#1565c0 style D fill:#fce4ec,stroke:#c62828

24.3 Trace 1: Incoming CAN Frame to Protocol Response

This trace follows a single-frame Verify Node ID (Global) message from arrival to response.

sequenceDiagram participant HW as CAN Hardware participant ISR as CAN RX ISR participant FIFO as CAN RX FIFO participant CANSM as CAN Main SM participant RXSM as CAN RX SM participant OLCB_FIFO as OpenLCB FIFO participant OLCB_SM as OpenLCB Main SM participant Handler as Protocol Handler participant TXSM as CAN TX SM participant Bus as CAN Bus HW->>ISR: CAN interrupt fires ISR->>FIFO: Push raw can_msg_t Note over ISR: Returns to main loop CANSM->>FIFO: Pop can_msg_t CANSM->>RXSM: Classify frame Note over RXSM: Single frame, OpenLCB message RXSM->>RXSM: Allocate openlcb_msg_t (BASIC) RXSM->>RXSM: Extract MTI, aliases, Node IDs RXSM->>OLCB_FIFO: Push openlcb_msg_t OLCB_SM->>OLCB_FIFO: Pop openlcb_msg_t Note over OLCB_SM: MTI = 0x0490 (Verify Node ID Global) OLCB_SM->>OLCB_SM: Enumerate first node OLCB_SM->>Handler: ProtocolMessageNetwork_handle_verify_node_id_global() Handler->>Handler: Build Verified Node ID response Handler->>OLCB_SM: Response in worker buffer OLCB_SM->>TXSM: CanTxStatemachine_send_openlcb_message() TXSM->>TXSM: Convert openlcb_msg_t to CAN frame(s) TXSM->>Bus: Transmit CAN frame Note over OLCB_SM: Enumerate next node (repeat for all nodes)

24.4 Trace 2: Multi-Frame Datagram Assembly

sequenceDiagram participant Bus as CAN Bus participant RX as CAN RX Handler participant List as Buffer List participant FIFO as OpenLCB FIFO participant SM as OpenLCB Main SM Bus->>RX: Datagram First frame (8 bytes) RX->>RX: Allocate openlcb_msg_t (DATAGRAM) RX->>RX: Set state.inprocess = true RX->>RX: Copy payload bytes, set timer.assembly_ticks RX->>List: Store in Buffer List (in-flight) Bus->>RX: Datagram Middle frame (8 bytes) RX->>List: Find matching entry (by source alias) RX->>RX: Append payload bytes Bus->>RX: Datagram Final frame (2-8 bytes) RX->>List: Find matching entry RX->>RX: Append final bytes RX->>RX: Set state.inprocess = false RX->>FIFO: Push completed openlcb_msg_t SM->>FIFO: Pop completed datagram Note over SM: Process as complete message

24.5 Trace 3: Application Sends Event

sequenceDiagram participant App as Application participant API as OpenLcbApplication API participant Store as Buffer Store participant SM as OpenLCB Main SM participant TX as CAN TX SM participant Bus as CAN Bus App->>API: Send PCER (event_id) API->>Store: Allocate openlcb_msg_t (BASIC) API->>API: Fill MTI=0x05B4, source_alias, event_id API->>SM: CanTxStatemachine_send_openlcb_message() SM->>TX: Convert to CAN frame TX->>TX: Build CAN identifier with Frame Type 1 TX->>TX: Insert event_id in 8-byte payload TX->>Bus: Transmit single CAN frame TX->>Store: Free openlcb_msg_t

24.6 Trace 4: Node Login (Power-On to RUN)

stateDiagram-v2 [*] --> INIT: Power on INIT --> GENERATE_ALIAS: seed = Node ID GENERATE_ALIAS --> CID7: alias = LFSR(seed) CID7 --> CID6: Send CID7 frame CID6 --> CID5: Send CID6 frame CID5 --> CID4: Send CID5 frame CID4 --> WAIT: Send CID4, snapshot tick WAIT --> RID: elapsed > 200ms RID --> AMD: Send RID AMD --> INIT_COMPLETE: Send AMD, permitted=true state "OpenLCB Login" as OL { INIT_COMPLETE --> CONSUMER_EVENTS: Send Init Complete CONSUMER_EVENTS --> PRODUCER_EVENTS: Enumerate all consumer events PRODUCER_EVENTS --> LOGIN_COMPLETE: Enumerate all producer events LOGIN_COMPLETE --> RUN: on_login_complete callback } note right of WAIT CAN Login SM handles INIT through AMD. end note note right of OL OpenLCB Login SM handles INIT_COMPLETE through RUN. end note

24.7 Trace 5: Datagram Round-Trip

sequenceDiagram participant A as Node A (sender) participant Bus as CAN Bus participant B as Node B (receiver) A->>Bus: Datagram Only/First/Middle/Final Bus->>B: Datagram assembled B->>B: Protocol handler processes datagram B->>Bus: Datagram Received OK (with Reply Pending flag) Bus->>A: Datagram OK received Note over A: Start reply timeout B->>B: Handler prepares reply datagram B->>Bus: Reply datagram (Only/First/Middle/Final) Bus->>A: Reply received A->>Bus: Datagram Received OK Bus->>B: OK received

24.8 CAN RX Frame Classification

flowchart TD A["Incoming CAN Frame"] --> B{Bit 27 set?} B -->|Yes| C["OpenLCB Message Frame"] B -->|No| D["Control Frame"] C --> E{Frame Type bits 26-24} E -->|001| F["Standard OpenLCB Message"] E -->|010| G["Datagram Only"] E -->|011| H["Datagram First"] E -->|100| I["Datagram Middle"] E -->|101| J["Datagram Final"] E -->|111| K["Stream Data"] D --> L{Control Frame Type} L -->|CID 7-4| M["Check ID Frame"] L -->|RID| N["Reserve ID Frame"] L -->|AMD| O["Alias Map Definition"] L -->|AME| P["Alias Map Enquiry"] L -->|AMR| Q["Alias Map Reset"] L -->|Error| R["Error Info Report"]

24.9 CAN TX Message Routing

flowchart TD A["openlcb_msg_t to send"] --> B{Message type?} B -->|"Global (no dest)"| C["Unaddressed frame handler"] B -->|"Addressed"| D["Addressed frame handler"] B -->|"Datagram"| E["Datagram frame handler"] B -->|"Stream"| F["Stream frame handler"] C --> G["Single CAN frame:
MTI in variable field"] D --> H{Payload fits in 1 frame?} H -->|Yes| I["Single frame with dest alias in bytes 0-1"] H -->|No| J["Multi-frame: First/Middle/Last
with framing bits"] E --> K{Payload <= 8 bytes?} K -->|Yes| L["Datagram Only frame"] K -->|No| M["Datagram First + Middle(s) + Final"]

24.10 Thread Safety at ISR Boundary

flowchart LR subgraph ISR["ISR Context"] I1["CAN RX interrupt"] I2["Push to CAN FIFO"] I3["Set is_duplicate flag"] I4["Set listener alias"] end subgraph MAIN["Main Loop Context"] M1["Pop from CAN FIFO"] M2["Register/unregister aliases"] M3["Allocate/free buffers"] M4["Process messages"] end LOCK["lock_shared_resources()"] UNLOCK["unlock_shared_resources()"] ISR ---|"FIFO is the boundary"| MAIN MAIN --> LOCK LOCK --> M1 M1 --> UNLOCK style ISR fill:#fce4ec,stroke:#c62828 style MAIN fill:#e3f2fd,stroke:#1565c0

24.11 Buffer Lifecycle

stateDiagram-v2 [*] --> Free: Pool initialized Free --> Allocated: allocate_buffer(type) Allocated --> InFIFO: Push to FIFO InFIFO --> Processing: Pop from FIFO Processing --> SharedRef: inc_reference_count() SharedRef --> Processing: dec via free_buffer() Processing --> Free: free_buffer() when ref_count=0 Allocated --> Free: free_buffer() (no FIFO) note right of SharedRef Multiple consumers can hold references via reference counting. end note

24.12 Debugging Checklist for Each Trace Step

StepWhat to CheckCommon Failure
CAN frame arrivesIs CAN RX interrupt firing? Check FIFO push.CAN hardware not initialized, wrong baud rate
FIFO popIs main loop calling OpenLcb_run() fast enough?FIFO overflow (too slow main loop)
Frame classificationIs the 29-bit identifier correct?Standard (11-bit) frame sent to extended-only handler
Buffer allocationIs the correct pool type being allocated?Pool exhaustion -- increase USER_DEFINED_*_BUFFER_DEPTH
Multi-frame assemblyDo all frames arrive within timeout?3-second assembly timeout expired
MTI dispatchIs the handler wired in the interface struct?NULL handler pointer -- sends OIR automatically
Response TXIs is_tx_buffer_clear() returning true?CAN TX hardware busy, frames queued but not sent
Buffer freeIs reference count reaching zero?Buffer leak -- forgot to free after processing
← Prev: Ch 23 — Wiring It Together Next: Ch 25 — Debugging Guide →