CAN Map and CAN Manager ======================= Purpose ------- Define the message specification, generator workflow and deterministic serialization strategy for the CAN-FD backbone. The CAN map is the single source of truth. Design Goals ------------ - Declarative message specification (YAML) - No manual byte indexing - No manual bit shifting in application code - Deterministic layout independent of compiler ABI - Compile-time enforcement of allowed message types - Scaled float support - Enum support - Strict DLC validation - DBC export generation CAN Map Specification (YAML) ---------------------------- Messages are defined declaratively and code is generated. Example: .. code-block:: yaml can: can0 version: 1 namespaces: - name: SensorNode messages: - name: StrainData id: 0x201 dlc: 8 fields: - { name: fx, type: int32, bits: 16 } - { name: fy, type: int32, bits: 16 } - { name: fz, type: int32, bits: 16 } - { name: valid, type: uint8, bits: 1 } The YAML file defines: - Output header name - Version - Namespaces - Message ID - DLC (bytes) - Field names - Bit width - Type - Optional scale (float) - Optional enum values Strict DLC Rule --------------- Total bits are tightly packed. Required bytes: .. code-block:: text required_bytes = ceil(total_bits / 8) Validation rule: .. code-block:: text required_bytes == dlc Unused bits inside the final byte are allowed. Unused bytes are not allowed. Nodes and Subscribers --------------------- The CAN map defines all nodes explicitly. YAML must contain: - ``nodes`` list - ``subscribers`` list per message Example: .. code-block:: yaml nodes: - ECU - SensorNode - Diagnosis namespaces: - name: SensorNode messages: - name: StrainData id: 0x201 dlc: 7 subscribers: - ECU - Diagnosis Rules: - ``subscribers`` is mandatory. - Publisher is NOT implicitly a subscriber. - If no subscribers are defined, generation fails. - Subscriber names must exist in ``nodes`` list. - Generation validates spelling and consistency. Future generator extension will use this to produce: - Node-specific filter tables - Node-specific dispatch specialization - Compile-time elimination of unused message handlers Generated C++ Message --------------------- Generator produces ``can0.hpp``. Example: .. code-block:: cpp namespace SensorNode { struct StrainData { static constexpr uint16_t id = 0x201; static constexpr uint8_t dlc = 8; #ifdef CANMAP_INCLUDE_MESSAGE_NAMES static constexpr const char* name = "SensorNode::StrainData"; #endif int32_t fx; // packed: 16 bits int32_t fy; // packed: 16 bits int32_t fz; // packed: 16 bits uint8_t valid; // packed: 1 bit }; } The struct represents physical values only. Packing is handled separately. Scaled Float Support -------------------- YAML: .. code-block:: yaml - name: temperature type: float bits: 16 scale: 0.1 Generated struct: .. code-block:: cpp float temperature; // packed: 16 bits, scale: 0.1 static constexpr float temperature_scale = 0.1f; Serialization: .. code-block:: cpp int32_t raw = static_cast( std::round(msg.temperature / temperature_scale)); packSigned(data, raw); Float is never transmitted as IEEE-754. It is transported as scaled integer. Enum Support ------------ YAML: .. code-block:: yaml - name: state type: uint8 bits: 3 enum: 0: Idle 1: Running 2: Error Generated struct: .. code-block:: cpp enum class State : uint32_t { Idle = 0, Running = 1, Error = 2, }; State state; Serialization uses underlying integer value. Serialization Model ------------------- Generator produces ``serialize()`` and ``deserialize()``. .. code-block:: cpp template<> inline void serialize(const Message& msg, uint8_t* data); template<> inline void deserialize(Message& msg, const uint8_t* data); All packing uses ``pack_utils.hpp``. Properties: - Width ≤ 32 bits per field - Offset + Width ≤ 64 - Little-endian deterministic layout - No reinterpret_cast - No compiler bitfields DBC Generation -------------- Generator also produces: - ``can0.dbc`` Includes: - Message definitions - Signal scaling - Enum value mappings - CAN map version comment Used for SavvyCAN or other tools. CAN Manager ----------- The CAN manager is the transport layer between application logic and FDCAN. It is strictly transport-only. It does not: - Construct payloads - Interpret physical values - Perform bit shifting - Access individual fields Transmit Path ^^^^^^^^^^^^^ Public API: .. code-block:: cpp template void send(const Message& msg); Transmit sequence: 1. Compile-time check: ``Message`` satisfies ``CanMessage`` concept. 2. Allocate modm CAN frame. 3. Set frame ID to ``Message::id``. 4. Set frame length to ``Message::dlc``. 5. Call generated ``serialize(msg, frame.data)``. 6. Submit frame to FDCAN driver. Conceptual implementation: .. code-block:: cpp template void CanManager::send(const Message& msg) { modm::can::Message frame( static_cast(Message::id), Message::dlc); frame.setExtended(false); serialize(msg, frame.data); transmit(frame); } The CAN manager never touches payload fields directly. Receive Path ^^^^^^^^^^^^ Reception is type-safe and compile-time constrained. Conceptually: 1. FDCAN interrupt receives raw frame. 2. CAN manager matches frame ID against generated message list. 3. Matching message type is instantiated. 4. ``deserialize()`` reconstructs typed message. 5. Typed message is dispatched. Future implementation will use compile-time node specialization: .. code-block:: cpp template struct NodeDispatch; template<> struct NodeDispatch { static void handle(const SensorNode::StrainData& msg); static void handle(const Diagnosis::Request& msg); }; CAN manager will forward deserialized messages to: .. code-block:: cpp NodeDispatch::handle(msg); This avoids: - Runtime switch statements - Dynamic dispatch - Branch-heavy receive logic Only messages subscribed by the node will generate handlers. Dispatching is type-safe. No raw buffer handling is exposed to application layer. Reception Filtering ^^^^^^^^^^^^^^^^^^^ Only CAN IDs defined in the CAN map are accepted. Unknown IDs: - Ignored - Or counted as diagnostic error - Never forwarded as raw frame Responsibilities ^^^^^^^^^^^^^^^^ - Initialize FDCAN - Configure filters - Handle TX queue - Handle RX interrupt - Perform serialize/deserialize - Monitor bus state (error passive / bus-off) - Provide error counters for diagnosis Raw frame APIs must remain private. Determinism ^^^^^^^^^^^ - No dynamic allocation in send/receive path. - Bounded execution time. - No blocking in interrupt context. - No protocol logic inside CAN manager.