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 -------------- .. graphviz:: 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=<
👤 User Application
SensorNode::StrainData msg;
msg.fx = 123;
can.send(msg);
can.processMessages();
Work with CAN map structs only
>, shape=box, style="filled,rounded", fillcolor="#EBF5FB:#D6EAF8", gradientangle=90, penwidth=2, color="#3498DB", width=4, height=2 ]; // CANMANAGER CanManager [ label=<
🔧 CanManager
send<Message>(msg)
• Type checking • Serialization
processMessages()
• Poll FIFOs • Deserialize • Dispatch
Constructor
• Init hardware • Configure filters
>, shape=box, style="filled,rounded", fillcolor="#FEF5E7:#FCE4B6", gradientangle=90, penwidth=2, color="#F39C12", width=4, height=2.2 ]; // GENERATED CODE Generated [ label=<
⚙️ Generated from YAML
can0.hpp - Message structs
can0_serialize.hpp - Pack/unpack
NodeTraits - Permissions
✓ Single source of truth
✓ Compile-time validation
>, shape=box, style="filled,rounded", fillcolor="#E8F8F5:#D5F5E3", gradientangle=90, penwidth=2, color="#27AE60", width=4, height=2 ]; // MODM Modm [ label=<
🔌 modm HAL
Fdcan1::sendMessage()
Fdcan1::getMessage()
Fdcan1::initialize()
STM32G4 hardware drivers
>, shape=box, style="filled,rounded", fillcolor="#F4ECF7:#EBDEF0", gradientangle=90, penwidth=2, color="#8E44AD", width=4, height=1.8 ]; // HARDWARE Hardware [ label=<
🔩 Hardware
STM32G4 FDCAN Peripheral
CAN Bus (1M/5M bit/s)
Physical layer
>, 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=<
📄 can_map.yaml
messages:
- name: StrainData
id: 0x201
fields: [...]
>, shape=note, style="filled", fillcolor="#FFFACD", penwidth=1.5, color="#F39C12", width=2.5 ]; // TX FLOW TxFlow [ label=<
📤 Send Flow
1. Create struct
2. send(msg)
3. Serialize
4. Hardware TX
5. CAN bus
>, shape=box, style="filled,rounded", fillcolor="#FADBD8", penwidth=0, width=1.8 ]; // RX FLOW RxFlow [ label=<
📥 Receive Flow
1. CAN bus
2. Hardware RX
3. processMessages()
4. Deserialize
5. onMessage(msg)
>, shape=box, style="filled,rounded", fillcolor="#D6EAF8", penwidth=0, width=1.8 ]; // KEY PRINCIPLE Principle [ label=<
✨ Key Abstraction
User Sees ✓ Hidden ✗
• CAN map structs
• Normal C++ types
• send(), processMessages()
• modm::can::Message
• Bit shifting
• Hardware registers
>, shape=plaintext ]; // ================================================================ // CONNECTIONS // ================================================================ // Main vertical flow with large arrows UserCode -> CanManager [ penwidth=3, color="#E74C3C:#3498DB", arrowsize=1.2, label=< API calls > ]; CanManager -> Generated [ penwidth=2, color="#7F8C8D", style=dashed, arrowsize=1, label=< uses > ]; CanManager -> Modm [ penwidth=3, color="#8E44AD", arrowsize=1.2, label=< calls > ]; Modm -> Hardware [ penwidth=3, color="#5D6D7E", arrowsize=1.2, label=< controls > ]; // YAML generation flow YAML -> Generated [ penwidth=2.5, color="#F39C12", style=dashed, arrowsize=1.2, label=<generates> ]; // ================================================================ // 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(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.