# ULS-DeCoNet CAN Transport Specification

> Reference for developers and integrators of ULS-DeCoNet CAN Transport — the ULS Devices Connection Network protocol for UART and CAN.

---

Site [llms.txt](/llms.txt)

---

> **ULSDC-PBDS021125** · version 1.0.5 · issued November 13, 2025  
> The numbered PDF is the controlled copy: [download it from the portal](https://downloads.uavlas.com).

## 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**:

| Distance | Bit Rate | Sample Point |
|----------|----------|--------------|
| Up to 40m | 1 Mbps | 75% |
| Up to 100m | 500 kbps | 75% |
| Up to 200m | 250 kbps | 75% |
| Up to 500m | 125 kbps | 75% |
| Up to 1000m | 50 kbps | 75% |

### CAN Identifier Structure

**Standard 11-bit Identifier**:

```mermaid
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**:
```mermaid
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)"
```

| Field | Size | Description |
|-------|------|-------------|
| SOF | 1 bit | Start of Frame |
| ID | 11 bits | Identifier (11-bit) |
| RTR | 1 bit | Remote Transmission Request |
| IDE | 1 bit | Identifier Extension |
| DLC | 4 bits | Data Length Code (0-8) |
| DATA | 0-8 bytes | Payload |
| CRC | 16 bits | Cyclic Redundancy Check |
| ACK | 2 bits | Acknowledgment |
**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):

```mermaid
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:

```mermaid
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)"
```

```mermaid
packet-beta
title "FRMCTL Byte"
0-0: "END"
1-7: "FRAME NUMBER 0-127 (bits 6-0)"
```

| Field | Description |
|-------|-------------|
| END | Last frame flag: 1 = last frame |
| FRAME NUMBER | Sequence 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)

```mermaid
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

```mermaid
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

```mermaid
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

```mermaid
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

```cpp
_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

```cpp
_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

```cpp
_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**:
```cpp
#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

```cpp
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

```cpp
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**:
```cpp
#define CN_CAN_DEV_TIMEOUT (CN_CAN_POOL_SIZE / 2)  // 128 frame times
```
**Timeout Process**:
```cpp
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

```cpp
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

```cpp
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

```cpp
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**:
```cpp
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 Size | Frames | Time @ 1Mbps | Throughput |
|--------------|--------|--------------|------------|
| 10 bytes | 1 | 108 µs | 92.6 KB/s |
| 50 bytes | 10 | 1.08 ms | 46.3 KB/s |
| 256 bytes | 52 | 5.62 ms | 45.6 KB/s |
| 1024 bytes | 205 | 22.1 ms | 46.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**:
```mermaid
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)
```

| Counter | Meaning |
|---------|---------|
| TEC | Transmit Error Counter |
| REC | Receive Error Counter |
**Hardware Error Handling**:
- Automatic retransmission
- Error frames generation
- Automatic recovery from Bus Off (implementation-dependent)

### Application-Level Errors

#### Missing Frames

**Detection**:
```cpp
// 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**:
```cpp
if (allocate_pool_slot() == INVALID_SLOT) {
    log_error("CAN pool exhausted!");
    return IO_BUFFER_FULL;
}
```
**Recovery**:
```cpp
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**:
```cpp
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

```cpp
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

```cpp
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

```cpp
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

```cpp
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

```cpp
// 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

```cpp
// 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

| Symptom | Possible Cause | Solution |
|---------|---------------|----------|
| No communication | Wrong bit rate | Verify bit rate settings match |
| Intermittent errors | No termination | Check 120Ω resistors at bus ends |
| Bus-Off state | Wiring issue | Check CAN_H/CAN_L connections |
| Missing frames | Pool overflow | Increase pool size or cleanup rate |
| High latency | Bus saturation | Reduce message rate or increase bitrate |
| Device timeouts | Lost frames | Check 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+

---
