# ULS-DeCoNet UART Transport Specification

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

---

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

---

> **ULSDC-PBDS031125** · version 1.0.9 · 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 the UART framing layer of the ULS-DeCoNet protocol, providing detailed information about packet delimiting, byte stuffing, and error detection mechanisms.

### Scope

The UART protocol layer operates between the Physical Layer (UART hardware) and the Network Layer (IF protocol), providing:
- Frame delimiting
- Error detection (CRC/checksum)
- Transparent data transmission
- Multiple operating modes

---

## Physical Layer

### UART Configuration

**Standard Settings**:
```
Data bits:    8
Parity:       None
Stop bits:    1
Flow control: None (optional RTS/CTS)
```

**Common Baud Rates**:
- 115200 bps - Standard rate
- 230400 bps - High-speed
- 460800 bps - Very high-speed
- 921600 bps - Maximum speed

### Hardware Requirements

**Minimum**:
- UART peripheral with TX/RX
- FIFO buffers (recommended ≥ 64 bytes)
- Interrupt or DMA support

**Recommended**:
- FIFO buffers ≥ 256 bytes
- DMA for high-speed operation
- Hardware flow control

---

## Framing Modes

### Mode Selection

```cpp
typedef enum {
    SERIAL_MODE_RAW = 0,            // No framing
    SERIAL_MODE_ESC = 1,            // Escape framing with CRC-16
    SERIAL_MODE_ESC_SIMPLE_CRC = 2, // Escape framing with 8-bit checksum
    SERIAL_MODE_COBS = 3            // COBS framing with CRC-16
} _serial_mode;
```

**Mode Selection Criteria**:

| Mode | Best For | Overhead | Error Detection |
|------|----------|----------|-----------------|
| RAW | Custom protocols | 0% | None |
| ESC | General purpose | ~15-25% | Strong (CRC-16) |
| ESC_SIMPLE | Low-power/simple | ~12-20% | Basic (8-bit sum) |
| COBS | Binary data | ~0.4% | Strong (CRC-16) |

---

## Frame Format Details

### RAW Mode

**Format**: Direct byte stream, no framing

```
[BYTE_0][BYTE_1][BYTE_2]...[BYTE_N]
```

**Characteristics**:
- No delimiters
- No error detection at this layer
- Minimum latency
- Maximum throughput
- Higher layers must handle framing

**Usage**:
```cpp
serial.mode(SERIAL_MODE_RAW);
serial.write(data, length);
// Bytes transmitted directly
```

### ESC Mode (CRC-16)

#### Frame Structure

```mermaid
---
config:
    packet:
        showBits: false
---
packet-beta
title "ESC Mode Frame Structure (CRC-16)"
0-7: "SOF 0x55 (1 byte)"
8-15: "START_TYPE 0x01 (1 byte)"
16-79: "PAYLOAD (variable, ~8 bytes)"
80-87: "CRC_LOW (1 byte)"
88-95: "CRC_HIGH (1 byte)"
96-103: "EOF 0x55 (1 byte)"
104-111: "END_TYPE 0x03 (1 byte)"
```

**Frame Components**:

The ESC mode frame follows this sequence:

| Field | Size | Value | Description |
|-------|------|-------|-------------|
| SOF | 1 byte | 0x55 | Start of Frame delimiter |
| START_TYPE | 1 byte | 0x01 | Frame start marker |
| PAYLOAD | Variable | Any | User data (byte-stuffed: 0x55 becomes 0x55 0x02) |
| CRC_LOW | 1 byte | Calculated | Low byte of CRC-16 (byte-stuffed) |
| CRC_HIGH | 1 byte | Calculated | High byte of CRC-16 (byte-stuffed) |
| EOF | 1 byte | 0x55 | End of Frame delimiter |
| END_TYPE | 1 byte | 0x03 | Frame end marker |

#### Byte Stuffing Algorithm

**Rule**: If data byte equals 0x55, insert 0x02 immediately after it

**Encoding**:
```
Original byte:  0x55
Transmitted:    0x55 0x02
```

**Example Frame**:
```
Original payload: [0x12, 0x55, 0x34, 0x55]
CRC-16:          0x1234

Transmitted:
[0x55][0x01][0x12][0x55][0x02][0x34][0x55][0x02][0x34][0x12][0x55][0x03]
 SOF   TYPE  data  ←stuffed→    data  ←stuffed→   CRC_L CRC_H  EOF TYPE
```

#### CRC-16 Calculation

**Algorithm**: CRC-16-IBM/ANSI  
**Polynomial**: 0x8005  
**Initial Value**: 0xFFFF  
**Method**: Table lookup

**Calculation**:
```c
uint16_t crc = 0xFFFF;
for (int i = 0; i < payload_length; i++) {
    crc = (crc >> 8) ^ crc16_table[(crc ^ payload[i]) & 0xFF];
}
```

**Table** (partial, see full table in crc16.h):
```c
uint16_t const crc16_table[256] = {
    0x0000, 0xC0C1, 0xC181, 0x0140, 0xC301, 0x03C0, 0x0280, 0xC241,
    0xC601, 0x06C0, 0x0780, 0xC741, 0x0500, 0xC5C1, 0xC481, 0x0440,
    // ... 248 more entries
};
```

#### Transmission Pseudocode

```python
def transmit_esc_frame(payload):
    # Start frame
    send_byte(0x55)
    send_byte(0x01)
    
    # Calculate CRC
    crc = calculate_crc16(payload)
    
    # Send payload with byte stuffing
    for byte in payload:
        send_byte(byte)
        if byte == 0x55:
            send_byte(0x02)  # Stuff byte
    
    # Send CRC with byte stuffing
    crc_low = crc & 0xFF
    send_byte(crc_low)
    if crc_low == 0x55:
        send_byte(0x02)
    
    crc_high = (crc >> 8) & 0xFF
    send_byte(crc_high)
    if crc_high == 0x55:
        send_byte(0x02)
    
    # End frame
    send_byte(0x55)
    send_byte(0x03)
```

#### Reception State Machine

```mermaid
stateDiagram-v2
    [*] --> IDLE
    IDLE --> CHECK_START: 0x55 received
    CHECK_START --> RECEIVING: 0x01 start marker
    CHECK_START --> IDLE: 0x03 or other byte

    RECEIVING --> CHECK_END: 0x55 received
    RECEIVING --> RECEIVING: data byte; update CRC

    CHECK_END --> RECEIVING: 0x02 escaped 0x55
    CHECK_END --> VALIDATE: 0x03 end marker
    CHECK_END --> IDLE: 0x01 or other byte error

    VALIDATE --> IDLE: CRC OK process frame
    VALIDATE --> IDLE: CRC error discard frame and increment error counter
```

#### Reception Pseudocode

```python
def receive_esc_frame():
    stage = IDLE
    buffer = []
    crc = 0xFFFF
    last_byte = 0
    
    while True:
        byte = read_byte()
        
        if stage == IDLE:
            if byte == 0x55:
                stage = CHECK_START
        
        elif stage == CHECK_START:
            if byte == 0x01:
                stage = RECEIVING
                buffer = []
                crc = 0xFFFF
            else:
                stage = IDLE
        
        elif stage == RECEIVING:
            if byte == 0x55:
                stage = CHECK_END
                last_byte = 0x55
            else:
                buffer.append(byte)
                crc = update_crc(crc, byte)
        
        elif stage == CHECK_END:
            if byte == 0x02:
                # Escaped 0x55
                buffer.append(0x55)
                crc = update_crc(crc, 0x55)
                stage = RECEIVING
            elif byte == 0x03:
                # End of frame
                if validate_crc(buffer, crc):
                    return buffer[:-2]  # Remove CRC bytes
                else:
                    increment_crc_error()
                    stage = IDLE
            else:
                # Error
                stage = IDLE
```

### ESC Simple CRC Mode

#### Frame Structure

```mermaid
packet-beta
title "ESC Simple Mode Frame Structure (8-bit Checksum)"
0-7: "SOF 0x55 (1 byte)"
8-15: "START_TYPE 0x01 (1 byte)"
16-79: "PAYLOAD (variable, ~8 bytes)"
80-87: "CHECKSUM (1 byte)"
88-95: "EOF 0x55 (1 byte)"
96-103: "END_TYPE 0x03 (1 byte)"
```

**Differences from ESC Mode**:
- Only 1 byte checksum (instead of 2 bytes CRC)
- Simpler calculation

#### Checksum Algorithm

**Simple 8-bit summation**:
```c
uint8_t checksum = 0;
for (int i = 0; i < length; i++) {
    checksum += payload[i];
}
checksum = checksum & 0xFF;
```

**Properties**:
- Fast computation
- Low error detection capability
- Catches single bit errors
- May miss multi-bit errors or byte swaps

**Use Case**: Low-power devices or low-error-rate channels

### COBS Mode

#### Introduction to COBS

**COBS** = Consistent Overhead Byte Stuffing

**Advantages**:
- Predictable overhead
- No data expansion for most patterns
- Very efficient for binary data
- Zero byte (0x00) always available as delimiter

#### Frame Structure

```mermaid
packet-beta
title "COBS Mode Frame Structure (with CRC-16)"
0-7: "SOF 0x00 (1 byte)"
8-15: "COBS CODE (1 byte)"
16-79: "ENCODED DATA (variable, ~8 bytes)"
80-87: "CRC_LOW (1 byte)"
88-95: "CRC_HIGH (1 byte)"
96-103: "EOF 0x00 (1 byte)"
```

#### COBS Encoding Algorithm

**Principle**: Replace zeros with code bytes indicating distance to next zero

**Code Byte Values**:
- 0x01 to 0xFE: Number of non-zero bytes following + 1
- 0xFF: Special case - 254 non-zero bytes follow

**Encoding Process**:

1. Scan data for zero bytes
2. For each segment between zeros:
   - Count non-zero bytes (max 254)
   - Insert code byte before segment
   - Code byte = count + 1
3. Replace zero positions with code bytes

**Example 1 - Simple Data**:
```
Original: [0x45, 0x00, 0x00, 0x2C, 0x4C, 0x79]

Segments:
  [0x45]          → 1 byte
  []              → 0 bytes
  [0x2C, 0x4C, 0x79] → 3 bytes

Encoded:
  [0x02, 0x45]       // code=2, 1 byte follows
  [0x01]             // code=1, 0 bytes follow (zero position)
  [0x04, 0x2C, 0x4C, 0x79] // code=4, 3 bytes follow

Result: [0x02, 0x45, 0x01, 0x04, 0x2C, 0x4C, 0x79]
```

**Example 2 - No Zeros**:
```
Original: [0x11, 0x22, 0x33, 0x44, 0x55]

Segments:
  [0x11, 0x22, 0x33, 0x44, 0x55] → 5 bytes

Encoded:
  [0x06, 0x11, 0x22, 0x33, 0x44, 0x55]
  // code=6 means 5 bytes follow, no zero at end

Result: [0x06, 0x11, 0x22, 0x33, 0x44, 0x55]
Overhead: 1 byte for 5 bytes of data = 20%
```

**Example 3 - Long Segment (>254 bytes)**:
```
Original: [0x01, 0x02, ..., 0xFF, 0xFE] (255 bytes, no zeros)

Segments:
  First 254 bytes
  Last 1 byte

Encoded:
  [0xFF, 0x01, 0x02, ..., (254 bytes)]
  [0x02, 0xFE]

Overhead: 2 bytes for 255 bytes = 0.8%
```

#### COBS Decoding Algorithm

**Process**:

1. Read code byte
2. If code == 0x00: Frame delimiter (start/end)
3. If code == 0xFF: Copy next 254 bytes as-is
4. If code < 0xFF:
   - Copy next (code-1) bytes
   - Insert 0x00 after them (unless end of data)
5. Repeat until frame delimiter

**Pseudocode**:
```python
def decode_cobs(encoded_data):
    decoded = []
    pos = 0
    
    while pos < len(encoded_data):
        code = encoded_data[pos]
        pos += 1
        
        if code == 0x00:
            break  # End of frame
        
        # Copy (code-1) bytes
        for i in range(code - 1):
            if pos < len(encoded_data):
                decoded.append(encoded_data[pos])
                pos += 1
        
        # Insert zero (if code != 0xFF and not end of data)
        if code < 0xFF and pos < len(encoded_data):
            decoded.append(0x00)
    
    return decoded
```

#### COBS Frame Transmission

```python
def transmit_cobs_frame(payload):
    # Calculate CRC on original data
    crc = calculate_crc16(payload)
    
    # Append CRC to payload
    full_data = payload + [crc & 0xFF, (crc >> 8) & 0xFF]
    
    # COBS encode
    encoded = cobs_encode(full_data)
    
    # Transmit frame
    send_byte(0x00)  # Start delimiter
    for byte in encoded:
        send_byte(byte)
    send_byte(0x00)  # End delimiter
```

#### COBS Frame Reception

**State Machine**:
```mermaid
stateDiagram-v2
    [*] --> IDLE
    IDLE --> RECEIVING: 0x00 start delimiter
    RECEIVING --> RECEIVING: code byte process block
    RECEIVING --> VALIDATE: 0x00 end delimiter
    VALIDATE --> IDLE: CRC OK return decoded data
    VALIDATE --> IDLE: CRC error discard and increment counter
```

**Reception with Validation**:
```cpp
bool readCobsCheck(uint32_t sizelimit) {
    uint8_t v = 0;
    while (fifo->seek(&v)) {
        switch(stage) {
            case 0:  // Start of packet
                cobs_code = cobs_counter = v;
                stage = 1;
                len = 0;
                crc = 0xFFFF;
                break;
            
            case 1:  // Analyzing packet
                cobs_counter--;
                if (cobs_counter != 0) {
                    len++;
                    crc = GetCrcByte(crc, v);
                } else {
                    if (v == 0) {  // Packet complete
                        stage = 0;
                        if (crc != 0 || len < 2 || len > sizelimit + 2) {
                            fifo->flush_to_seeker();
                            return false;  // Error
                        }
                        len -= 2;  // Remove CRC
                        return true;  // Valid packet
                    }
                    // Next block
                    if (cobs_code != 0xFF) {
                        len++;
                        crc = GetCrcByte(crc, 0);  // Implicit zero
                    }
                    cobs_counter = cobs_code = v;
                }
                break;
        }
    }
    return false;
}
```

#### COBS Overhead Analysis

**Best Case** (no zeros in data):
```
Data length: 254 bytes
Overhead: 1 code byte
Percentage: 0.39%
```

**Worst Case** (alternating data and zeros):
```
Data: [0x01, 0x00, 0x02, 0x00, 0x03, 0x00]
Encoded: [0x02, 0x01, 0x02, 0x02, 0x02, 0x03, 0x01]
Overhead: 3 code bytes for 6 bytes = 50%
```

**Typical Case** (random binary data):
```
Average overhead: ~1-2%
```

**Fixed Overhead**:
- Start delimiter: 1 byte
- End delimiter: 1 byte
- CRC: 2 bytes
- Total fixed: 4 bytes

---

## Implementation Details

### ULSSerial Class

```cpp
class ULSSerial {
public:
    ULSSerial(_io_fifo_u8 *rxBuf, _io_fifo_u8 *txBuf);
    
    // Mode selection
    void mode(_serial_mode mode);
    _serial_mode mode();
    
    // Transmission
    uint32_t write(uint8_t *buf, uint32_t size);
    uint32_t writeEsc(uint8_t *buf, uint32_t size);
    uint32_t writeCobs(uint8_t *buf, uint32_t size);
    
    // Reception
    uint32_t read(uint8_t *buf, uint32_t sizelimit);
    uint32_t readEsc(uint8_t *buf, uint32_t sizelimit);
    uint32_t readCobs(uint8_t *buf, uint32_t sizelimit);
    
    // Statistics
    uint32_t _crcerrors;
    uint32_t _packeterrors;
    
private:
    _io_fifo_u8 *_rxFifo;
    _io_fifo_u8 *_txFifo;
    _serial_mode _mode;
};
```

### FIFO Buffers

**Required Buffer Sizes**:

| Application | RX Buffer | TX Buffer |
|-------------|-----------|-----------|
| Low-speed | 256 bytes | 256 bytes |
| Standard | 512 bytes | 512 bytes |
| High-speed | 1024 bytes | 1024 bytes |

**Buffer Management**:
- Circular FIFO implementation
- Interrupt-safe operations
- DMA support for high throughput

### Error Counters

```cpp
uint32_t _crcerrors;      // CRC validation failures
uint32_t _packeterrors;   // Framing errors
```

**Usage**:
- Monitor link quality
- Detect noisy channels
- Trigger error recovery

---

## Performance Analysis

### Throughput Comparison

**Test Conditions**: 1000 bytes payload, 115200 bps

| Mode | Frame Size | Transmission Time | Throughput |
|------|-----------|-------------------|------------|
| RAW | 1000 | 86.8 ms | 11.52 KB/s |
| ESC (worst case) | 2006 | 174.2 ms | 5.74 KB/s |
| ESC (typical) | 1200 | 104.2 ms | 9.59 KB/s |
| ESC_SIMPLE (typical) | 1150 | 99.8 ms | 10.02 KB/s |
| COBS (best) | 1007 | 87.4 ms | 11.44 KB/s |
| COBS (typical) | 1020 | 88.5 ms | 11.30 KB/s |

### Latency Analysis

**Frame Detection Latency**:

| Mode | Start Detection | End Detection | Total |
|------|----------------|---------------|-------|
| RAW | N/A | N/A | 0 |
| ESC | 2 bytes | 2 bytes | ~0.17 ms @ 115200 |
| COBS | 1 byte | 1 byte | ~0.09 ms @ 115200 |

### CPU Usage

**Relative Processing Cost**:

| Mode | Encoding | Decoding | CRC |
|------|----------|----------|-----|
| RAW | None | None | None |
| ESC | Low | Medium | High |
| ESC_SIMPLE | Low | Medium | Low |
| COBS | Medium | Medium | High |

### Mode Selection Guide

**Choose RAW when**:
- Custom framing at higher layer
- Maximum throughput required
- No error detection needed at this layer

**Choose ESC when**:
- General-purpose applications
- Strong error detection required
- Variable data content

**Choose ESC_SIMPLE when**:
- Low-power requirements
- Simple error detection sufficient
- Low CPU overhead critical

**Choose COBS when**:
- Binary data transmission
- Predictable overhead required
- Maximum efficiency needed
- Zero byte available for framing

---

## Error Handling

### Error Types

**CRC Errors**:
- Bit flips during transmission
- Electrical noise
- Buffer overruns

**Framing Errors**:
- Lost delimiters
- Synchronization loss
- Incomplete packets

### Error Recovery

**Receiver Behavior**:
```
1. Detect error (CRC mismatch or framing error)
2. Increment error counter
3. Discard corrupted frame
4. Flush FIFO to next frame delimiter
5. Resume normal operation
```

**Higher Layer Responsibility**:
- Implement timeouts
- Request retransmission
- Handle missing data

### Error Statistics

**Monitoring**:
```cpp
void check_link_quality() {
    float error_rate = (float)serial._crcerrors / total_packets;
    
    if (error_rate > 0.01) {
        // More than 1% error rate - poor link
        reduce_baud_rate();
    }
}
```

### Troubleshooting

| Symptom | Possible Cause | Solution |
|---------|---------------|----------|
| High CRC errors | Noisy channel | Lower baud rate, add shielding |
| Framing errors | Timing issues | Check baud rate configuration |
| No reception | Wrong mode | Verify sender/receiver mode match |
| Intermittent loss | Buffer overflow | Increase buffer size, use DMA |

---

## Code Examples

### Basic Usage

```cpp
// Initialize
_io_fifo_u8 rxFifo, txFifo;
ULSSerial serial(&rxFifo, &txFifo);

// Configure mode
serial.mode(SERIAL_MODE_COBS);

// Transmit
uint8_t txData[] = {0x01, 0x02, 0x03, 0x04};
serial.write(txData, sizeof(txData));

// Receive
uint8_t rxData[256];
uint32_t len = serial.read(rxData, sizeof(rxData));
if (len > 0) {
    process_data(rxData, len);
}

// Check errors
if (serial._crcerrors > 0) {
    printf("CRC errors: %d\n", serial._crcerrors);
}
```

### Mode Switching

```cpp
// Start with simple mode
serial.mode(SERIAL_MODE_ESC_SIMPLE);

// Negotiate with remote device
send_mode_negotiation();

// Switch to COBS for binary data transfer
serial.mode(SERIAL_MODE_COBS);
transfer_binary_data();

// Switch back
serial.mode(SERIAL_MODE_ESC_SIMPLE);
```

---

## Appendices

### Appendix A: CRC-16 Table

See `crc16.h` for complete 256-entry table.

### Appendix B: Test Vectors

**ESC Mode Test Vector**:
```
Payload: [0x01, 0x02, 0x03]
CRC-16: 0x3D8C

Transmitted frame:
0x55 0x01 0x01 0x02 0x03 0x8C 0x3D 0x55 0x03
```

**COBS Mode Test Vector**:
```
Payload: [0x00, 0x01, 0x00]
CRC-16: 0x628F

COBS encoded (with CRC):
0x01 0x02 0x01 0x01 0x02 0x8F 0x02 0x62

Full frame:
0x00 0x01 0x02 0x01 0x01 0x02 0x8F 0x02 0x62 0x00
```

---

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

---
