CanManager Implementation

Define the implementation architecture for the type-safe CAN transport layer.

This section specifies the CanManager design, template architecture, and integration with the modm hardware abstraction layer.

Design Goals

The CanManager implementation achieves:

  1. Complete abstraction of FDCAN hardware from application code.

  2. Zero-cost compile-time type checking of message permissions.

  3. Automatic hardware initialization via template parameters.

  4. Automatic filter configuration from CAN map metadata.

  5. No dynamic allocation in transmission or reception paths.

  6. Deterministic bounded execution time.

  7. Separation of transport logic from application logic.

Software Stack

digraph CanManagerStack { // Graph settings rankdir=TB; splines=polyline; nodesep=1.2; ranksep=1.0; bgcolor="#FAFAFA"; node [fontname="Helvetica", fontsize=13]; edge [fontname="Helvetica", fontsize=11]; // ================================================================ // MAIN STACK // ================================================================ // USER CODE UserCode [ label=<<table border="0" cellborder="0" cellspacing="8"> <tr><td><font point-size="16"><b>πŸ‘€ User Application</b></font></td></tr> <tr><td height="10"></td></tr> <tr><td align="left"><font face="monospace" point-size="11">SensorNode::StrainData msg;</font></td></tr> <tr><td align="left"><font face="monospace" point-size="11">msg.fx = 123;</font></td></tr> <tr><td align="left"><font face="monospace" color="#E74C3C"><b>can.send(msg);</b></font></td></tr> <tr><td height="5"></td></tr> <tr><td align="left"><font face="monospace" color="#3498DB"><b>can.processMessages();</b></font></td></tr> <tr><td height="10"></td></tr> <tr><td><font point-size="10" color="#7F8C8D">Work with CAN map structs only</font></td></tr> </table>>, shape=box, style="filled,rounded", fillcolor="#EBF5FB:#D6EAF8", gradientangle=90, penwidth=2, color="#3498DB", width=4, height=2 ]; // CANMANAGER CanManager [ label=<<table border="0" cellborder="0" cellspacing="8"> <tr><td><font point-size="16"><b>πŸ”§ CanManager</b></font></td></tr> <tr><td height="10"></td></tr> <tr><td align="left"><font point-size="12"><b>send&lt;Message&gt;(msg)</b></font></td></tr> <tr><td align="left"><font point-size="10">β€’ Type checking β€’ Serialization</font></td></tr> <tr><td height="8"></td></tr> <tr><td align="left"><font point-size="12"><b>processMessages()</b></font></td></tr> <tr><td align="left"><font point-size="10">β€’ Poll FIFOs β€’ Deserialize β€’ Dispatch</font></td></tr> <tr><td height="8"></td></tr> <tr><td align="left"><font point-size="12"><b>Constructor</b></font></td></tr> <tr><td align="left"><font point-size="10">β€’ Init hardware β€’ Configure filters</font></td></tr> </table>>, shape=box, style="filled,rounded", fillcolor="#FEF5E7:#FCE4B6", gradientangle=90, penwidth=2, color="#F39C12", width=4, height=2.2 ]; // GENERATED CODE Generated [ label=<<table border="0" cellborder="0" cellspacing="8"> <tr><td><font point-size="16"><b>βš™οΈ Generated from YAML</b></font></td></tr> <tr><td height="10"></td></tr> <tr><td align="left"><font point-size="11"><b>can0.hpp</b> - Message structs</font></td></tr> <tr><td align="left"><font point-size="11"><b>can0_serialize.hpp</b> - Pack/unpack</font></td></tr> <tr><td align="left"><font point-size="11"><b>NodeTraits</b> - Permissions</font></td></tr> <tr><td height="10"></td></tr> <tr><td><font point-size="10" color="#27AE60">βœ“ Single source of truth</font></td></tr> <tr><td><font point-size="10" color="#27AE60">βœ“ Compile-time validation</font></td></tr> </table>>, shape=box, style="filled,rounded", fillcolor="#E8F8F5:#D5F5E3", gradientangle=90, penwidth=2, color="#27AE60", width=4, height=2 ]; // MODM Modm [ label=<<table border="0" cellborder="0" cellspacing="8"> <tr><td><font point-size="16"><b>πŸ”Œ modm HAL</b></font></td></tr> <tr><td height="10"></td></tr> <tr><td align="left"><font point-size="11">Fdcan1::sendMessage()</font></td></tr> <tr><td align="left"><font point-size="11">Fdcan1::getMessage()</font></td></tr> <tr><td align="left"><font point-size="11">Fdcan1::initialize()</font></td></tr> <tr><td height="10"></td></tr> <tr><td><font point-size="10" color="#7F8C8D">STM32G4 hardware drivers</font></td></tr> </table>>, shape=box, style="filled,rounded", fillcolor="#F4ECF7:#EBDEF0", gradientangle=90, penwidth=2, color="#8E44AD", width=4, height=1.8 ]; // HARDWARE Hardware [ label=<<table border="0" cellborder="0" cellspacing="8"> <tr><td><font point-size="16"><b>πŸ”© Hardware</b></font></td></tr> <tr><td height="10"></td></tr> <tr><td><font point-size="12">STM32G4 FDCAN Peripheral</font></td></tr> <tr><td><font point-size="12">CAN Bus (1M/5M bit/s)</font></td></tr> <tr><td height="5"></td></tr> <tr><td><font point-size="10" color="#95A5A6">Physical layer</font></td></tr> </table>>, shape=box, style="filled,rounded", fillcolor="#EAECEE:#D5D8DC", gradientangle=90, penwidth=2, color="#5D6D7E", width=4, height=1.5 ]; // ================================================================ // SIDE ELEMENTS // ================================================================ // YAML SOURCE YAML [ label=<<table border="0" cellborder="0" cellspacing="6"> <tr><td><font point-size="14"><b>πŸ“„ can_map.yaml</b></font></td></tr> <tr><td height="8"></td></tr> <tr><td align="left"><font point-size="10" face="monospace">messages:</font></td></tr> <tr><td align="left"><font point-size="10" face="monospace"> - name: StrainData</font></td></tr> <tr><td align="left"><font point-size="10" face="monospace"> id: 0x201</font></td></tr> <tr><td align="left"><font point-size="10" face="monospace"> fields: [...]</font></td></tr> </table>>, shape=note, style="filled", fillcolor="#FFFACD", penwidth=1.5, color="#F39C12", width=2.5 ]; // TX FLOW TxFlow [ label=<<table border="0" cellborder="0" cellspacing="4"> <tr><td><font point-size="13" color="#E74C3C"><b>πŸ“€ Send Flow</b></font></td></tr> <tr><td height="5"></td></tr> <tr><td align="left"><font point-size="10">1. Create struct</font></td></tr> <tr><td align="left"><font point-size="10">2. send(msg)</font></td></tr> <tr><td align="left"><font point-size="10">3. Serialize</font></td></tr> <tr><td align="left"><font point-size="10">4. Hardware TX</font></td></tr> <tr><td align="left"><font point-size="10">5. CAN bus</font></td></tr> </table>>, shape=box, style="filled,rounded", fillcolor="#FADBD8", penwidth=0, width=1.8 ]; // RX FLOW RxFlow [ label=<<table border="0" cellborder="0" cellspacing="4"> <tr><td><font point-size="13" color="#3498DB"><b>πŸ“₯ Receive Flow</b></font></td></tr> <tr><td height="5"></td></tr> <tr><td align="left"><font point-size="10">1. CAN bus</font></td></tr> <tr><td align="left"><font point-size="10">2. Hardware RX</font></td></tr> <tr><td align="left"><font point-size="10">3. processMessages()</font></td></tr> <tr><td align="left"><font point-size="10">4. Deserialize</font></td></tr> <tr><td align="left"><font point-size="10">5. onMessage(msg)</font></td></tr> </table>>, shape=box, style="filled,rounded", fillcolor="#D6EAF8", penwidth=0, width=1.8 ]; // KEY PRINCIPLE Principle [ label=<<table border="1" cellborder="1" cellspacing="0" cellpadding="8" bgcolor="#FFFEF0"> <tr><td bgcolor="#27AE60" colspan="2"><font color="white" point-size="13"><b>✨ Key Abstraction</b></font></td></tr> <tr> <td bgcolor="#D5F5E3"><font point-size="11"><b>User Sees βœ“</b></font></td> <td bgcolor="#FADBD8"><font point-size="11"><b>Hidden βœ—</b></font></td> </tr> <tr> <td align="left"> <font point-size="10">β€’ CAN map structs<br/>β€’ Normal C++ types<br/>β€’ send(), processMessages()</font> </td> <td align="left"> <font point-size="10">β€’ modm::can::Message<br/>β€’ Bit shifting<br/>β€’ Hardware registers</font> </td> </tr> </table>>, shape=plaintext ]; // ================================================================ // CONNECTIONS // ================================================================ // Main vertical flow with large arrows UserCode -> CanManager [ penwidth=3, color="#E74C3C:#3498DB", arrowsize=1.2, label=<<font point-size="11"><b> API calls </b></font>> ]; CanManager -> Generated [ penwidth=2, color="#7F8C8D", style=dashed, arrowsize=1, label=<<font point-size="10"> uses </font>> ]; CanManager -> Modm [ penwidth=3, color="#8E44AD", arrowsize=1.2, label=<<font point-size="11"><b> calls </b></font>> ]; Modm -> Hardware [ penwidth=3, color="#5D6D7E", arrowsize=1.2, label=<<font point-size="11"><b> controls </b></font>> ]; // YAML generation flow YAML -> Generated [ penwidth=2.5, color="#F39C12", style=dashed, arrowsize=1.2, label=<<font point-size="11" color="#F39C12"><b>generates</b></font>> ]; // ================================================================ // LAYOUT // ================================================================ // Position side elements {rank=same; UserCode; TxFlow;} {rank=same; CanManager; YAML;} {rank=same; Hardware; RxFlow;} {rank=min; Principle;} // Invisible edges for layout TxFlow -> RxFlow [style=invis]; YAML -> Principle [style=invis]; }

Layer Descriptions

User Application Layer

The user works exclusively with CAN map structs and normal C++ types.

Key characteristics:

  • Direct field access: msg.fx = 123

  • Type-safe API: can.send(msg)

  • Automatic message processing: can.processMessages()

  • No bit manipulation required

  • No hardware knowledge needed

CanManager (Transport Layer)

Template-based transport layer providing:

  • send<Message>(msg) - Type-checked message transmission with automatic serialization

  • processMessages() - Polls hardware FIFOs, deserializes, and dispatches to handlers

  • Constructor - Automatic hardware initialization and filter configuration

All hardware complexity is hidden from the user.

Generated Code Layer

Auto-generated from YAML source:

  • can0.hpp - Message struct definitions with metadata

  • can0_serialize.hpp - Deterministic pack/unpack functions

  • NodeTraits - Compile-time permissions (TxMessages, RxMessages)

Provides:

  • Single source of truth

  • Compile-time validation

  • Type safety guarantees

modm HAL Layer

Hardware abstraction layer providing:

  • FDCAN peripheral drivers

  • GPIO configuration

  • Interrupt handling

  • Platform-specific implementations

The CanManager calls modm APIs but hides them from the user.

Hardware Layer

Physical layer:

  • STM32G4 FDCAN peripheral

  • CAN bus interface

  • 1 Mbit/s nominal bitrate

  • 5 Mbit/s data phase (CAN-FD)

Data Flows

Send Flow (TX)

  1. User creates message struct

  2. Calls can.send(msg)

  3. CanManager checks compile-time permissions

  4. Automatic serialization via generated code

  5. modm transmits frame

  6. Hardware sends to CAN bus

Receive Flow (RX)

  1. CAN bus receives frame

  2. Hardware stores in RX FIFO

  3. User calls can.processMessages()

  4. CanManager polls FIFO

  5. Matches ID to message type

  6. Automatic deserialization

  7. Calls user’s onMessage(msg) handler

Initialization Flow

Single constructor call handles:

  1. GPIO pin connection

  2. FDCAN peripheral initialization

  3. CAN-FD mode configuration

  4. Reads RxMessages from NodeTraits

  5. Configures hardware filters automatically

  6. Ready to send/receive!

Key Abstraction Principle

What the User Sees

  • CAN map structs (SensorNode::StrainData)

  • Normal C++ types (int32_t, float, uint8_t)

  • Simple API (send(), processMessages())

What is Hidden

  • modm::can::Message

  • Bit shifting and masking

  • Hardware register access

  • Filter configuration

  • Serialization logic

The user works at a high level of abstraction, never touching low-level details.