This is the multi-page printable view of this section. Click here to print.

Return to the regular view of this page.

ULS-DeCoNet CAN Transport

Production

1 - ULS-DeCoNet CAN Transport Specification

Protocol Specification ULSDC-PBDS021125, version 1.0.5

ULSDC-PBDS021125 · version 1.0.5 · issued November 13, 2025
The numbered PDF is the controlled copy: download it from the portal.

Introduction

Purpose

This document specifies how ULS-DeCoNet protocol messages are transmitted over CAN bus, including frame structure, multi-frame messaging, and error handling specific to the CAN transport.

CAN Bus Advantages

Why CAN for ULS-DeCoNet:

  • Multi-master: Any device can initiate communication
  • Robust: Built-in error detection and retry
  • Priority-based: Critical messages transmitted first
  • Collision-free: Hardware arbitration prevents collisions
  • Long-distance: Up to 40m @ 1Mbps, longer at lower speeds
  • Industrial proven: Widely used in automotive and industrial applications

Scope

This specification covers:

  • CAN physical layer configuration
  • Application-level frame structure
  • Multi-frame message segmentation and reassembly
  • Frame pool management
  • Priority schemes
  • Error recovery mechanisms

Physical Layer

CAN Standards Support

Classical CAN:

  • Standard identifiers (11-bit)
  • Data payload: 0-8 bytes
  • Bit rates: Up to 1 Mbps
  • Fully supported

CAN-FD (Flexible Data-rate):

  • Standard identifiers (11-bit)
  • Extended data payload: Up to 64 bytes
  • Arbitration phase: Up to 1 Mbps
  • Data phase: Up to 5 Mbps
  • Supported via FDCAN peripheral

ULS-DeCoNet Implementation:

  • Uses standard 11-bit identifiers
  • Classical CAN: 8-byte payload maximum
  • CAN-FD: Can utilize extended payload for efficiency
  • Current implementation optimized for 8-byte frames

Physical Specifications

Bus Configuration:

Device 1 -----+
              |
Device 2 -----+---- CAN_H -------- 120Ω
              |    CAN_L
Device 3 -----+
              |
Device N -----+
              |
             120Ω

Electrical Specifications:

  • Differential voltage: 2V typical
  • Termination: 120Ω at both ends
  • Max stub length: 30 cm recommended
  • Common mode range: -2V to +7V
  • Max nodes: Typically 30-64 (driver dependent)

Recommended Bit Rates:

DistanceBit RateSample Point
Up to 40m1 Mbps75%
Up to 100m500 kbps75%
Up to 200m250 kbps75%
Up to 500m125 kbps75%
Up to 1000m50 kbps75%

CAN Identifier Structure

Standard 11-bit Identifier:

packet-beta
title "CAN 11-bit Identifier"
0-10: "Message ID (11 bits)"

ULS-DeCoNet Usage:

  • Identifiers determined by implementation
  • Lower identifiers = higher priority
  • Can use identifier for addressing or priority

Example Identifier Scheme:

Bits [10:8] - Message priority (0=highest)
Bits [7:0]  - Source device address

CAN Frame Structure

Hardware CAN Frame

Standard CAN Frame:

packet-beta
title "Standard CAN Frame"
0-0: "SOF"
1-11: "ID (11b)"
12-12: "RTR"
13-13: "IDE"
14-17: "DLC (4b)"
18-81: "DATA (0-8 bytes)"
82-97: "CRC (16b)"
98-99: "ACK (2b)"
FieldSizeDescription
SOF1 bitStart of Frame
ID11 bitsIdentifier (11-bit)
RTR1 bitRemote Transmission Request
IDE1 bitIdentifier Extension
DLC4 bitsData Length Code (0-8)
DATA0-8 bytesPayload
CRC16 bitsCyclic Redundancy Check
ACK2 bitsAcknowledgment
Notes:
  • Hardware handles SOF, CRC, ACK automatically
  • Application specifies ID, DLC, DATA
  • RTR and IDE typically 0 for ULS-DeCoNet

ULS-DeCoNet Application Frame

Single-Frame Structure

For messages that fit in one CAN frame (≤10 bytes payload):

packet-beta
title "Single-Frame CAN Data Field (8 bytes)"
0-7: "CMD (Byte 0)"
8-15: "DID (Byte 1)"
16-63: "PAYLOAD (Bytes 2-7)"

Fields:

  • Byte 0 (CMD): Command code from IF or CN layer
  • Byte 1 (DID): Source Device ID
    • Bits 5-0: Device ID (0-63)
    • Bits 7-6: Connection ID (0-3)
  • Bytes 2-7: Command-specific payload (up to 6 bytes)

Characteristics:

  • Total overhead: 2 bytes
  • Usable payload: 6 bytes per frame
  • Transmitted as single atomic CAN frame

Multi-Frame Structure

For messages >10 bytes:

packet-beta
title "Multi-Frame CAN Data Field (8 bytes)"
0-7: "CMD (Byte 0)"
8-15: "DID (Byte 1)"
16-23: "FRMCTL (Byte 2)"
24-63: "PAYLOAD 5 bytes (Bytes 3-7)"
packet-beta
title "FRMCTL Byte"
0-0: "END"
1-7: "FRAME NUMBER 0-127 (bits 6-0)"
FieldDescription
ENDLast frame flag: 1 = last frame
FRAME NUMBERSequence number 0-127
Fields:
  • CMD: Command code (same across all frames)
  • DID: Source Device ID (same across all frames)
  • FRMCTL: Frame control byte
    • Bit 7: End-of-message flag
    • Bits 6-0: Frame sequence number (0-127)
  • PAYLOAD: Data segment (5 bytes per frame)

Frame Numbering:

Frame 0:  FRMCTL = 0x00, payload bytes 0-4
Frame 1:  FRMCTL = 0x01, payload bytes 5-9
Frame 2:  FRMCTL = 0x02, payload bytes 10-14
...
Frame N:  FRMCTL = 0x80 | N, last payload bytes

Single-Frame Messages

Network Management Messages

Heartbeat (HB)

packet-beta
title "Heartbeat (CMD=0x01)"
0-7: "CMD 0x01"
8-15: "DID"
16-47: "UID0 (4 bytes)"

Usage: Periodic presence announcement
Payload: Device unique ID (32 bits)
Frequency: Every 1000ms

Request ID

packet-beta
title "Request ID (CMD=0x03)"
0-7: "CMD 0x03"
8-15: "DID 0x3F"
16-47: "KEY (4 bytes)"

Usage: Request ID assignment
Payload: Random key for identification
Sent by: Uninitialized devices (DID=63)

Set ID

packet-beta
title "Set ID (CMD=0x04)"
0-7: "CMD 0x04"
8-15: "DID 0x00 (Master)"
16-23: "NEWID (1 byte)"
24-55: "KEY (4 bytes)"

Usage: Master assigns ID to device
Sent by: Master device (DID=0)
Payload: New ID + matching key

Blitz Messages

packet-beta
title "Blitz Message (CMD=0x06)"
0-7: "CMD 0x06"
8-15: "DID"
16-31: "MSG_ID (2 bytes)"
32-63: "DATA (4 bytes)"

Usage: High-frequency sensor data
Payload:

  • MSG_ID (2 bytes): Blitz message type
  • DATA (4 bytes): Message-specific data

Note: Blitz messages use DLC=7 or 8 depending on data size


Multi-Frame Protocol

Overview

When Multi-Frame is Used:

  • Messages >10 bytes total length
  • CN protocol commands (routing, object access)
  • System commands with large payloads
  • Firmware update data

Key Concept:

  • Large message split into multiple CAN frames
  • Each frame carries 5 bytes of payload (with 3-byte header)
  • Frames can arrive out of order
  • Receiver assembles from frame pool

Transmission Algorithm

_io_op_result sendPacket() {
    uint8_t framecntr = 0;
    uint8_t *pxPld = ifTxPacket->pld;
    
    // Check if single-frame message
    if (ifTxPacket->cmd < 16) {
        if (ifTxLen > 12) return IO_ERROR;
        
        // Single-frame transmission
        _can_mail mail;
        mail.packet.raw.data[0] = ifTxPacket->cmd;
        mail.packet.raw.data[1] = ifTxPacket->src_lid;
        memcpy(&mail.packet.raw.data[2], ifTxPacket->pld, ifTxLen - 2);
        mail.len = (ifTxLen < 4) ? 0 : ifTxLen - 4;
        
        send_can_frame(&mail, CAN_PRIORITY_HIGH);
        return IO_OK;
    }
    
    // Multi-frame transmission
    ifTxLen -= 2;  // Subtract CMD and DID
    
    while (ifTxLen > 0) {
        _can_mail mail;
        mail.packet.raw.data[0] = ifTxPacket->cmd;
        mail.packet.raw.data[1] = ifTxPacket->src_lid;
        mail.packet.raw.data[2] = framecntr++;
        
        // Copy up to 5 bytes payload
        uint32_t len = (ifTxLen > 5) ? 5 : ifTxLen;
        for (uint32_t i = 0; i < len; i++) {
            mail.packet.raw.data[3 + i] = *pxPld++;
            ifTxLen--;
        }
        
        // Mark last frame
        if (ifTxLen == 0) {
            mail.packet.raw.data[2] |= 0x80;
        }
        
        mail.len = len + 2;  // Data length code
        
        if (!send_can_frame(&mail, CAN_PRIORITY_NORMAL)) {
            return IO_BUFFER_FULL;
        }
    }
    
    return IO_OK;
}

Reception Algorithm

_io_op_result receivePacket() {
    _can_mail mail;
    
    while (read_can_frame(&mail)) {
        uint8_t did = mail.packet.raw.data[1] & 0x3F;
        uint8_t cmd = mail.packet.raw.data[0];
        
        // Update device timeout
        _devs[did].timeout = CN_CAN_DEV_TIMEOUT;
        
        // Single-frame message
        if (cmd < 16) {
            ifRxPacket->cmd = cmd;
            ifRxPacket->src_lid = did;
            memcpy(ifRxPacket->pld, &mail.packet.raw.data[2], mail.len + 2);
            ifRxLen = 4 + mail.len;
            return IO_OK;
        }
        
        // Multi-frame message
        uint8_t frame_num = mail.packet.raw.data[2] & 0x7F;
        bool is_last = (mail.packet.raw.data[2] & 0x80) != 0;
        
        // Store frame in pool
        store_frame_in_pool(did, frame_num, &mail);
        
        if (is_last) {
            // Attempt to assemble message
            return assemble_message(did, frame_num);
        }
    }
    
    return IO_NO_DATA;
}

Message Assembly

_io_op_result assemble_message(uint8_t did, uint8_t last_frame) {
    ifRxPacket->cmd = get_cmd_from_pool(did);
    ifRxPacket->src_lid = did;
    
    uint32_t expected_frames = last_frame + 1;
    uint32_t expected_len = last_frame * 5 + get_last_frame_len(did);
    
    ifRxLen = 2;  // CMD + DID
    
    // Verify all frames present
    for (uint32_t frame = 0; frame <= last_frame; frame++) {
        if (!frame_exists_in_pool(did, frame)) {
            // Missing frame
            log_missing_frames(did, expected_frames);
            clean_pool(did);
            return IO_ERROR;
        }
        
        // Copy frame payload
        copy_frame_payload(did, frame, &ifRxPacket->pld[frame * 5]);
        ifRxLen += get_frame_payload_len(did, frame);
    }
    
    // Verify length
    if (ifRxLen != expected_len + 2) {
        clean_pool(did);
        return IO_ERROR;
    }
    
    // Success - clean up pool
    clean_pool(did);
    return IO_OK;
}

Multi-Frame Example

Scenario: Send 22-byte message from device ID=5

Calculation:

Payload: 22 bytes
Frames needed: ceil(22 / 5) = 5 frames

Frame 0: bytes 0-4   (5 bytes)
Frame 1: bytes 5-9   (5 bytes)
Frame 2: bytes 10-14 (5 bytes)
Frame 3: bytes 15-19 (5 bytes)
Frame 4: bytes 20-21 (2 bytes, last frame)

CAN Frames Transmitted:

Frame 0:
[CMD][0x05][0x00][data[0]][data[1]][data[2]][data[3]][data[4]]

Frame 1:
[CMD][0x05][0x01][data[5]][data[6]][data[7]][data[8]][data[9]]

Frame 2:
[CMD][0x05][0x02][data[10]][data[11]][data[12]][data[13]][data[14]]

Frame 3:
[CMD][0x05][0x03][data[15]][data[16]][data[17]][data[18]][data[19]]

Frame 4:
[CMD][0x05][0x84][data[20]][data[21]][PAD][PAD][PAD]
          ^^^^
          0x80 | 0x04 = Last frame, number 4

Frame Pool Management

Pool Architecture

Purpose: Store received frames until complete message assembled

Pool Structure:

#define CN_CAN_POOL_SIZE 256  // Frame storage slots
#define CN_CAN_DEV_SIZE 64    // Device tracking entries

typedef struct {
    uint32_t timeout;
    uint32_t start;
    uint32_t size;
    uint32_t len;
} _cn_can_dev;

_can_mail _pool[CN_CAN_POOL_SIZE];
_cn_can_dev _devs[CN_CAN_DEV_SIZE];

Pool Operations

Frame Storage

bool store_frame_in_pool(uint8_t did, uint8_t frame_num, _can_mail *mail) {
    // Find free slot
    uint32_t slot = allocate_pool_slot();
    if (slot == INVALID_SLOT) {
        // Pool full - trigger cleanup
        clean_expired_frames();
        slot = allocate_pool_slot();
        if (slot == INVALID_SLOT) {
            return false;  // Still full
        }
    }
    
    // Store frame
    memcpy(&_pool[slot], mail, sizeof(_can_mail));
    
    // Mark slot as used
    _pool[slot].packet.raw.data[0] = mail->packet.raw.data[0];
    
    return true;
}

Slot Allocation

uint32_t allocate_pool_slot() {
    for (uint32_t i = 0; i < CN_CAN_POOL_SIZE; i++) {
        _iempty++;
        if (_iempty >= CN_CAN_POOL_SIZE) {
            _iempty = 0;
        }
        
        // Check if slot is empty (marked with 0xFF)
        if (_pool[_iempty].packet.raw.data[0] == 0xFF) {
            return _iempty;
        }
    }
    return INVALID_SLOT;
}

Timeout Management

Device Timeout:

#define CN_CAN_DEV_TIMEOUT (CN_CAN_POOL_SIZE / 2)  // 128 frame times

Timeout Process:

void task(uint32_t dtms) {
    // Decrement all device timeouts
    for (uint32_t i = 0; i < CN_CAN_DEV_SIZE; i++) {
        if (_devs[i].timeout > 0) {
            _devs[i].timeout--;
        }
    }
    
    // On device timeout, clean its frames
    for (uint32_t i = 0; i < CN_CAN_DEV_SIZE; i++) {
        if (_devs[i].timeout == 0 && _devs[i].size > 0) {
            clean_device_frames(i);
        }
    }
}

Pool Cleanup

Clean Expired Frames

void clean_expired_frames() {
    for (uint32_t i = 0; i < CN_CAN_POOL_SIZE; i++) {
        if (_pool[i].packet.raw.data[0] == 0xFF) {
            continue;  // Already free
        }
        
        uint8_t did = _pool[i].packet.raw.data[1] & 0x3F;
        
        if (_devs[did].timeout == 0) {
            // Device timed out - free frame
            _pool[i].packet.raw.data[0] = 0xFF;
        }
    }
}

Clean Device Frames

uint32_t clean_device_frames(uint8_t did) {
    uint32_t count = 0;
    
    for (uint32_t i = 0; i < CN_CAN_POOL_SIZE; i++) {
        if (_pool[i].packet.raw.data[0] == 0xFF) {
            continue;
        }
        
        uint8_t frame_did = _pool[i].packet.raw.data[1] & 0x3F;
        
        if (frame_did == did) {
            _pool[i].packet.raw.data[0] = 0xFF;
            count++;
        }
    }
    
    return count;
}

Pool Monitoring

uint32_t get_pool_free_count() {
    uint32_t count = 0;
    for (uint32_t i = 0; i < CN_CAN_POOL_SIZE; i++) {
        if (_pool[i].packet.raw.data[0] == 0xFF) {
            count++;
        }
    }
    return count;
}

void check_pool_health() {
    uint32_t free = get_pool_free_count();
    
    if (free < CN_CAN_POOL_SIZE / 4) {
        // Less than 25% free - warning
        log_warning("CAN pool low: %d free", free);
    }
    
    if (free == 0) {
        // Pool exhausted - critical
        log_error("CAN pool exhausted!");
        emergency_cleanup();
    }
}

Priority and Arbitration

CAN Arbitration

Hardware Arbitration:

  • Lower CAN ID wins arbitration
  • Non-destructive (losing node automatically retries)
  • Deterministic priority

Message Priority Levels:

typedef enum {
    _CAN_MAIL_PRIORITY_HI = 0,      // Critical messages
    _CAN_MAIL_PRIORITY_NORMAL = 1,   // Regular data
    _CAN_MAIL_PRIORITY_LOW = 2       // Background tasks
} _can_mail_priority;

Priority Assignment

High Priority:

  • Network management messages (HB, REQUEST_ID, SET_ID)
  • Safety-critical commands
  • Single-frame short messages

Normal Priority:

  • Multi-frame data transfers
  • Object access commands
  • System commands

Low Priority:

  • Diagnostic data
  • Non-critical telemetry
  • Bulk data transfers

Identifier Allocation Strategy

Option 1: Source Address Based:

CAN ID = Source Device ID
  - Simple
  - Natural priority (lower DID = higher priority)
  - Master (DID=0) always highest priority

Option 2: Priority + Source:

CAN ID[10:8] = Priority (0=high, 7=low)
CAN ID[7:0]  = Source Device ID

Example:
  0x000-0x0FF: Highest priority messages
  0x100-0x1FF: Normal priority
  0x200-0x7FF: Lower priorities

Performance Characteristics

Throughput Analysis

Single-Frame Messages:

CAN @ 1 Mbps:
  Frame overhead: ~44 bits
  Data: 8 bytes = 64 bits
  Total: 108 bits
  Time per frame: 108 µs
  Max throughput: ~9260 frames/sec

Multi-Frame Messages:

Payload per frame: 5 bytes
Effective data rate: 5/8 = 62.5% efficiency
Net throughput @ 1 Mbps: ~625 kbps for data

Example Transfer Rates:

Message SizeFramesTime @ 1MbpsThroughput
10 bytes1108 µs92.6 KB/s
50 bytes101.08 ms46.3 KB/s
256 bytes525.62 ms45.6 KB/s
1024 bytes20522.1 ms46.3 KB/s

Latency Analysis

Best Case (single frame, no contention):

Latency = CAN_frame_time
        = 108 µs @ 1 Mbps

Typical Case (multi-frame, some contention):

Latency = (N_frames × CAN_frame_time) + queuing_delay
        ≈ (N_frames × 108 µs) + (0-500 µs)

Worst Case (high bus load):

Latency = (N_frames × CAN_frame_time) + max_retry_delay
        Can be significantly higher during bus saturation

Bus Utilization

Recommended Maximum Load: 70-80%
Critical Threshold: 90%

Load Calculation:

Load = (Messages_per_second × Bits_per_message) / Bus_bitrate

Example:
  100 msgs/sec × 108 bits/msg = 10,800 bps
  Load @ 1 Mbps = 1.08%

Error Handling

CAN Hardware Errors

CAN Controller Error States:

stateDiagram-v2
  [*] --> ErrorActive
  ErrorActive: Error Active\n(Normal operation)
  ErrorPassive: Error Passive\n(Reduced priority)
  BusOff: Bus Off\n(Disconnected)

  ErrorActive --> ErrorPassive: TEC/REC > 127
  ErrorPassive --> BusOff: TEC > 255
  BusOff --> ErrorActive: Recovery (128 × 11 recessive bits)
CounterMeaning
TECTransmit Error Counter
RECReceive Error Counter
Hardware Error Handling:
  • Automatic retransmission
  • Error frames generation
  • Automatic recovery from Bus Off (implementation-dependent)

Application-Level Errors

Missing Frames

Detection:

// Check for missing frames in sequence
for (uint32_t i = 0; i <= last_frame; i++) {
    if (!frame_exists(did, i)) {
        log_error("Missing frame %d from device %d", i, did);
        _missedFrames[i] = 1;
    }
}

Recovery:

  • Discard incomplete message
  • Clean pool entries
  • Higher layer handles timeout/retransmission

Pool Overflow

Detection:

if (allocate_pool_slot() == INVALID_SLOT) {
    log_error("CAN pool exhausted!");
    return IO_BUFFER_FULL;
}

Recovery:

1. Trigger emergency cleanup
2. Remove oldest/timed-out frames
3. If still full, reject new frames
4. Report error to higher layer

Timeout Errors

Device Timeout:

void handle_device_timeout(uint8_t did) {
    log_warning("Device %d timeout", did);
    
    // Clean all frames from device
    uint32_t cleaned = clean_device_frames(did);
    log_info("Cleaned %d frames from device %d", cleaned, did);
    
    // Notify higher layer
    notify_device_disconnected(did);
}

Error Statistics

typedef struct {
    uint32_t tx_ok;
    uint32_t tx_errors;
    uint32_t rx_ok;
    uint32_t rx_errors;
    uint32_t missing_frames;
    uint32_t pool_overflows;
    uint32_t device_timeouts;
} can_stats_t;

Implementation Details

ULSBusConnectionCAN Class

class ULSBusConnectionCAN : public ULSBusConnection, DrvCAN {
public:
    ULSBusConnectionCAN(_drvcan_handle_td* handler, 
                       ULSDBase* dev,
                       ULSBusConnectionsList* connections, 
                       const char* name, 
                       uint8_t id, 
                       uint8_t cid);
    
    // Configuration
    bool open() override;
    void setSpeed(uint32_t speed);
    
    // Task processing
    void task(uint32_t dtms) override;
    
    // Frame I/O
    _io_op_result sendPacket() override;
    _io_op_result receivePacket() override;
    
    // Statistics
    uint32_t poolSize();
    
private:
    _can_mail _pool[CN_CAN_POOL_SIZE];
    _cn_can_dev _devs[CN_CAN_DEV_SIZE];
    uint32_t _iempty;
    uint32_t _missedFrames[128];
    
    bool allocate();
    void clean();
    uint32_t clean(uint8_t id);
    void setTimeouts();
};

CAN Mail Structure

typedef struct {
    union {
        struct {
            uint32_t id;           // CAN identifier
            uint8_t data[8];       // Payload
            uint8_t len;           // DLC (0-8)
        } raw;
        // Alternative access methods
    } packet;
} _can_mail;

Driver Integration

class DrvCAN {
protected:
    _drvcan_handle_td* _handler;
    
public:
    bool open();
    void setSpeed(uint32_t speed);
    bool send(_can_mail* mail, _can_mail_priority priority);
    bool read(_can_mail* mail);
    void txTask();  // Process TX queue
};

Configuration Example

// Initialize CAN peripheral
FDCAN_HandleTypeDef hfdcan1;
hfdcan1.Instance = FDCAN1;
hfdcan1.Init.FrameFormat = FDCAN_FRAME_CLASSIC;
hfdcan1.Init.Mode = FDCAN_MODE_NORMAL;
hfdcan1.Init.AutoRetransmission = ENABLE;
hfdcan1.Init.TransmitPause = DISABLE;
hfdcan1.Init.ProtocolException = ENABLE;

// Bit timing (1 Mbps @ 80 MHz APB clock)
hfdcan1.Init.NominalPrescaler = 4;
hfdcan1.Init.NominalSyncJumpWidth = 1;
hfdcan1.Init.NominalTimeSeg1 = 15;
hfdcan1.Init.NominalTimeSeg2 = 4;

// Create ULS connection
ULSBusConnectionCAN canConnection(
    &hfdcan1,           // CAN handle
    &device,            // Device object
    &connections,       // Connection list
    "CAN1",            // Name
    static_did,        // Device ID
    0                  // Connection ID
);

// Configure and open
canConnection.setSpeed(1000000);  // 1 Mbps
canConnection.open();

Usage Pattern

// Main loop
void main_loop() {
    while (1) {
        // Process CAN I/O
        canConnection.task(dt_ms);
        
        // Send data
        if (data_ready) {
            canConnection.ifSendBLITZ(msg_id, data, size);
        }
        
        // Check pool health
        if (canConnection.poolSize() < 64) {
            log_warning("CAN pool low");
        }
        
        delay(1);  // 1ms tick
    }
}

Best Practices

Design Guidelines

  1. Keep Messages Small: Use single-frame when possible
  2. Set Appropriate Timeouts: Balance responsiveness vs. robustness
  3. Monitor Pool Usage: Implement pool health checks
  4. Use Priority Wisely: Don’t overuse high priority
  5. Handle Bus-Off: Implement recovery mechanism
  6. Test Under Load: Verify behavior at high utilization

Performance Optimization

For High Throughput:

  • Use CAN-FD if available
  • Batch small messages
  • Optimize frame packing
  • Use DMA for CAN peripheral

For Low Latency:

  • Minimize multi-frame messages
  • Use high priority for time-critical data
  • Reduce bus load
  • Implement fast-path processing

For Reliability:

  • Implement message acknowledgment at application level
  • Use sequence numbers
  • Add timeout and retry logic
  • Monitor error statistics

Troubleshooting

SymptomPossible CauseSolution
No communicationWrong bit rateVerify bit rate settings match
Intermittent errorsNo terminationCheck 120Ω resistors at bus ends
Bus-Off stateWiring issueCheck CAN_H/CAN_L connections
Missing framesPool overflowIncrease pool size or cleanup rate
High latencyBus saturationReduce message rate or increase bitrate
Device timeoutsLost framesCheck for collisions or interference

Appendices

Appendix A: Frame Examples

Heartbeat Frame:

CAN ID: 0x001
Data: [0x01][0x05][0x12][0x34][0x56][0x78][00][00]
       CMD   DID   UID0 (little-endian)

Multi-Frame Example (15-byte message):

Frame 0: [0x14][0x05][0x00][d0][d1][d2][d3][d4]
Frame 1: [0x14][0x05][0x01][d5][d6][d7][d8][d9]
Frame 2: [0x14][0x05][0x82][d10][d11][d12][d13][d14]
                       ^^^^
                       Last frame (0x80 | 0x02)

Appendix B: Performance Calculations

See section 8 for detailed performance analysis.


Copyright © 2020-2026 UAVLAS <www.uavlas.com>
License: GNU Lesser General Public License v3.0+