ULS-DeCoNet Protocol Documentation
The ULS Devices Connection Network protocol for UART and CAN.
Application
ULS-DeCoNet (ULS Devices Connection Network) is the protocol UAVLAS devices
use to talk to each other and to host systems over UART and CAN bus. These
documents are for developers integrating UAVLAS devices into their own
hardware or software: device discovery, object-based configuration,
firmware update, multi-hop routing and the Blitz sensor data messages.
Documents
Each page is generated from the source of the controlled PDF; the numbered PDF can be downloaded from the document portal.
1 - ULS Blitz Types and Description
Reference for developers and integrators of ULS Blitz Protocol — the ULS Devices Connection Network protocol for UART and CAN.
ULSDC-PBDS031025 · version 1.0.1 · issued November 3, 2025
The numbered PDF is the controlled copy: download it from the portal.
Overview
The protocol used by devices for configuration is proprietary and is described in other documents. However, the protocol implements the possibility of transmitting messages (Blitz messages) that can be used by the user to control the device and obtain information. These messages are universal, and the format describes the data that is transmitted by various devices in our system connected to a single network.
Use CMD=6 to send Blitz messages.
In this document, the CAN protocol is specified. RS485 is used to implement custom protocols that can be defined in the future.
flowchart LR
A[Sender Device] --> B[GLOBAL]
B --> C[CMD = 6]
C --> D[ID]
D --> E[dsn_lid]
E --> F[msg_id]
F --> G[Blitz Payload data <= 8 bytes]
G --> H[Receiver Device]
The CAN protocol uses the following frame structure:
| Field | Description |
|---|
| GLOBAL | Flag used to send packets over several local networks |
| CMD | Command field (Use CMD=6 to send Blitz messages) |
| ID | Packet sender ID (local id src_lid for local packets and device id for global packets src_did) |
| dsn_lid | Destination local ID address |
| msg_id | Message ID used to identify Blitz packet types |
Table: CAN Protocol Frame Fields
Status Bit Definitions
Most messages include a status byte with the following bit definitions:
| Bit | Flag | Description |
|---|
| 0 | ULS_STATUS_RX_CARRIER_OK | IR signal detected |
| 1 | ULS_STATUS_RX_SQ_OK | Signal quality OK |
| 2 | ULS_STATUS_RX_POS_OK | Position estimated |
| 3 | ULS_STATUS_RX_VEL_OK | Velocity estimated |
| 4 | ULS_STATUS_RX_GU_IMU_OK | Ground Unit IMU OK |
| 5 | ULS_STATUS_RX_MRX_OK | MRX message received (secondary receiver) |
| 6 | ULS_STATUS_RX_MRXDATA_OK | MRX Yaw and Distance calculated OK |
Table: Status Bit Definitions
Message list by device
Message Types
XCG1 Raw Measurement (0x0010)
Message ID: ULS_BLITZ_MSG_ID_XCG1_RX_RAW_MEASURMENT (0x0010)
Structure: __uls_blitz_xcg1_rx_raw_measurmet
| Field | Type | Description |
|---|
| status | uint8_t | Status bits (see Status Bit Definitions above) |
| d | uint8_t | Distance in 10 cm increments |
| aX | int16_t | Angular information X-axis, proportional [-30000 = -30°, +30000 = +30°] |
| aY | int16_t | Angular information Y-axis, proportional [-30000 = -30°, +30000 = +30°] |
| oF | int8_t | Sensor offset Forward in cm |
| oR | int8_t | Sensor offset Right in cm |
Table: XCG1 Raw Measurement Message Structure
Usage: Legacy raw measurement data. Message generated by receiver each time it decode signal from transmitter. Client can capture this frame to get receivers raw data. Do not send this type of message to Bus.
Senders list (this message are generated by):
- ULSDV-XC-RX-G1 - ULS-XCopter receiver (generation 1).
- ULSDV-XC-RX-G2 - ULS-XCopter-G2 receiver (generation 2).
- ULSDV-XS-RXM - ULS-XSpot master receiver.
- ULSDV-XS-RXS - ULS-XSpot slave receiver.
- ULSDV-HL-RXM - ULS-Heli master receiver.
- ULSDV-HL-RXS - ULS-Heli slave receiver.
- ULSDV-PC-RX - ULS-ProCopter receiver.
Receivers list (this message is consumed by):
- Ground control stations (for monitoring)
- Debug/diagnostic tools
- Data logging systems
Raw Measurement (0x0011)
Message ID: ULS_BLITZ_MSG_ID_RX_RAW_MEASURMENT (0x0011)
Structure: __uls_blitz_msg_rx_raw_measurmet
| Field | Type | Description |
|---|
| status | uint8_t | Status bits (see Status Bit Definitions above) |
| counter | uint8_t | Message counter for tracking |
| aX | int16_t | Angular information X-axis [-180° to +180°] |
| aY | int16_t | Angular information Y-axis [-180° to +180°] |
| distance | uint16_t | Distance measurement |
Table: Raw Measurement Message Structure
Angular Conversion Formula:
degrees = (value_int16 * 180.0f) / 32767.0f
Usage: Measurement data generated by receiver each time when signal from transmitter decoded.
Senders list (this message are generated by):
- ULSDV-XC-RX-G1 - ULS-XCopter receiver (generation 1).
- ULSDV-XC-RX-G2 - ULS-XCopter-G2 receiver (generation 2).
- ULSDV-XS-RXM - ULS-XSpot master receiver.
- ULSDV-XS-RXS - ULS-XSpot slave receiver.
- ULSDV-HL-RXM - ULS-Heli master receiver.
- ULSDV-HL-RXS - ULS-Heli slave receiver.
- ULSDV-PC-RX - ULS-ProCopter receiver.
Receivers list (this message is consumed by):
- Autopilot systems (Ardupilot, PX4)
- Ground control stations
- External navigation computers
- Data logging systems
Position in TX Frame (0x0012)
Message ID: ULS_BLITZ_MSG_ID_RX_POS_IN_TX_FRAME (0x0012)
Structure: __uls_blitz_msg_rx_pos_in_tx_frame
| Field | Type | Description |
|---|
| status | uint8_t | Status bits (see Status Bit Definitions above) |
| counter | uint8_t | Message counter for tracking |
| posX | int16_t | Position X-coordinate in transmitter frame (cm) |
| posY | int16_t | Position Y-coordinate in transmitter frame (cm) |
| posZ | int16_t | Position Z-coordinate in transmitter frame (cm) |
Table: Position in TX Frame Message Structure
Usage: 3D position data relative to the transmitter coordinate system. Generated by receiver after calculating position from raw measurements.
Senders list (this message is generated by):
- ULSDV-XC-RX-G1 - ULS-XCopter receiver (generation 1).
- ULSDV-XC-RX-G2 - ULS-XCopter-G2 receiver (generation 2).
- ULSDV-XS-RXM - ULS-XSpot master receiver.
- ULSDV-XS-RXS - ULS-XSpot slave receiver.
- ULSDV-HL-RXM - ULS-Heli master receiver.
- ULSDV-HL-RXS - ULS-Heli slave receiver.
- ULSDV-PC-RX - ULS-ProCopter receiver.
Receivers list (this message is consumed by):
- Autopilot systems (Ardupilot, PX4)
- Ground control stations
- External navigation computers
Message ID: ULS_BLITZ_MSG_ID_RX_MRX (0x0013)
Structure: __uls_blitz_msg_rx_mrx
| Field | Type | Description |
|---|
| status | uint8_t | Status bits (see Status Bit Definitions above) |
| counter | uint8_t | Message counter for tracking |
| yaw | int16_t | Yaw angle [-180° to +180°] |
| distance | uint16_t | Distance measurement in cm |
Table: MRX Information Message Structure
Usage: Multi-receiver (MRX) information for secondary receiver data. Generated when multiple receivers calculate relative orientation.
Senders list (this message is generated by):
- ULSDV-XS-RXM - ULS-XSpot master receiver (when paired with slaves).
- ULSDV-HL-RXM - ULS-Heli master receiver (when paired with slaves).
Receivers list (this message is consumed by):
- Autopilot systems (for yaw correction)
- External navigation computers
- Ground control stations
Message ID: ULS_BLITZ_MSG_ID_RX_DACS (0x0014)
Structure: __uls_blitz_msg_rx_dacs
| Field | Type | Description |
|---|
| status | uint8_t | Status bits (see Status Bit Definitions above) |
| counter | uint8_t | Message counter for tracking |
| yaw | int16_t | Yaw angle [-180° to +180°] |
| distance | uint16_t | Distance measurement |
| yawContinuous | int16_t | Continuous yaw angle ±32768° |
Table: DACS Information Message Structure
Usage: DACS (Directional Angle Calculation System) data with continuous yaw tracking. Advanced multi-receiver mode with unwrapped yaw angle.
Senders list (this message is generated by):
- ULSDV-XS-RXM - ULS-XSpot master receiver (DACS mode).
- ULSDV-HL-RXM - ULS-Heli master receiver (DACS mode).
Receivers list (this message is consumed by):
- Autopilot systems (for continuous yaw tracking)
- External navigation computers
- Ground control stations
Ground IMU Measurement (0x0020)
Message ID: ULS_BLITZ_MSG_ID_GIMU_MEASUREMENT (0x0020)
Structure: __uls_blitz_msg_gimu_data
| Field | Type | Description |
|---|
| status | uint8_t | Status bits (see Status Bit Definitions above) |
| counter | uint8_t | Message counter for tracking |
| roll | int16_t | Roll angle [-180° to +180°] |
| pitch | int16_t | Pitch angle [-180° to +180°] |
| yaw | int16_t | Yaw angle [-180° to +180°] |
Table: Ground IMU Measurement Message Structure
Angular Conversion Formula:
degrees = (value_int16 * 180.0f) / 32767.0f
Usage: Ground unit IMU (Inertial Measurement Unit) orientation data. Provides transmitter orientation for coordinate system transformation.
Senders list (this message is generated by):
- ULSDV-XC-TX-G1 - ULS-XCopter transmitter (generation 1).
- ULSDV-XC-TX-G2 - ULS-XCopter-G2 transmitter (generation 2).
- ULSDV-XS-TX - ULS-XSpot transmitter.
- ULSDV-HL-TX - ULS-Heli transmitter.
- ULSDV-PC-TX - ULS-ProCopter transmitter.
Receivers list (this message is consumed by):
- Ground control stations (for transmitter monitoring)
IMU Override (0x0030)
Message ID: ULS_BLITZ_MSG_ID_XX_TXC_IMU_OVERRIDE (0x0030)
Structure: __uls_blitz_msg_xx_txc_imu_override
| Field | Type | Description |
|---|
| control | uint8_t | Control bits (see Control Bits below) |
| counter | uint8_t | Message counter for tracking |
| roll | int16_t | Override roll angle [-180° to +180°] |
| pitch | int16_t | Override pitch angle [-180° to +180°] |
| yaw | int16_t | Override yaw angle [-180° to +180°] |
Table: IMU Override Message Structure
Control Bits:
| Bit | Description |
|---|
| 0 | SET Roll - Enable roll angle override |
| 1 | SET Pitch - Enable pitch angle override |
| 2 | SET Yaw - Enable yaw angle override |
| 3-7 | Reserved |
Table: IMU Override Control Bits
Angular Conversion Formula:
degrees = (value_int16 * 180.0f) / 32767.0f
Usage: Override IMU data on ground unit for external orientation control. Allows external IMU/compass to replace internal sensors.
Senders list (this message is sent by):
- External control PC/computer
- Autopilot systems (for transmitter orientation override)
- Ground control stations
Receivers list (this message is consumed by):
- ULSDV-XC-TX-G2 - ULS-XCopter-G2 transmitter (generation 2).
- ULSDV-XS-TX - ULS-XSpot transmitter.
- ULSDV-HL-TX - ULS-Heli transmitter.
- ULSDV-PC-TX - ULS-ProCopter transmitter.
Magnetometer Override (0x0031)
Message ID: ULS_BLITZ_MSG_ID_XX_TXC_MAG_OVERRIDE (0x0031)
Structure: __uls_blitz_msg_xx_txc_mag_override
| Field | Type | Description |
|---|
| control | uint8_t | Control bits (see Control Bits below) |
| counter | uint8_t | Message counter for tracking |
| mx | int16_t | Magnetic field X-component |
| my | int16_t | Magnetic field Y-component |
| mz | int16_t | Magnetic field Z-component |
Table: Magnetometer Override Message Structure
Control Bits:
| Bit | Description |
|---|
| 0 | SET override - Enable magnetometer override |
| 1-7 | Reserved |
Table: Magnetometer Override Control Bits
Magnetic Field Conversion Formula:
mag_value = (value_int16 * 1.0f) / 32767.0f
Usage: Override magnetometer data on ground unit for external compass control. Allows external magnetometer to replace internal compass.
Senders list (this message is sent by):
- External control PC/computer
- Autopilot systems (for compass override)
- Ground control stations
- External magnetometer systems
Receivers list (this message is consumed by):
- ULSDV-XC-TX-G2 - ULS-XCopter-G2 transmitter (generation 2).
- ULSDV-XS-TX - ULS-XSpot transmitter.
- ULSDV-HL-TX - ULS-Heli transmitter.
- ULSDV-PC-TX - ULS-ProCopter transmitter.
Implementation Notes
Packed Structures: All structures use __attribute__((packed)) to ensure no padding bytes are added by the compiler.
Endianness: The protocol assumes little-endian byte order for multi-byte fields.
Message Counters: Most messages include a counter field for packet ordering and loss detection.
Angular Precision: Angular measurements use 16-bit signed integers with specific scaling factors for different ranges.
Distance Units: Distance measurements may use different units (cm, 10cm increments) depending on the message type.
2 - ULS-DeCoNet Protocol Interface Control Document
Electrical, mechanical and communication interfaces of ULS-DeCoNet — the ULS Devices Connection Network protocol for UART and CAN.
ULSDC-PBICD011125 · version 1.0.5 · issued November 13, 2025
The numbered PDF is the controlled copy: download it from the portal.
Introduction
Purpose
The ULS-DeCoNet (ULS Devices Connection Network) protocol is a comprehensive communication protocol designed for Ultra-precise Location Systems (ULS). It provides a robust, hierarchical network architecture for connecting ULS devices across multiple transport mediums including UART and CAN bus.
Scope
This protocol defines:
- Device discovery and network management
- Object-oriented data exchange
- System control and configuration
- Firmware update mechanisms
- Multi-hop routing capabilities
- Blitz sensor data transmission
Key Features
- Multi-transport Support: UART (with various framing modes) and CAN bus
- Automatic Device Discovery: Dynamic network topology with automatic ID allocation
- Hierarchical Addressing: Support for up to 63 local devices per interface
- Multi-hop Routing: Packet forwarding through intermediate nodes
- Object-based Data Model: Structured access to device parameters and data
- Permission System: Read/write/protected access control for objects
- Firmware Update: Secure bootloader mode with signature verification
- Low Overhead: Optimized for embedded systems with limited resources
Terminology
| Term | Description |
|---|
| DID | Device ID - Logical identifier for devices on a network segment (0-63) |
| LID | Local ID - Synonym for DID |
| CID | Connection ID - Identifies which connection interface (0-3) |
| HB | Heartbeat - Periodic keep-alive message |
| NM | Network Management - Protocol layer for device discovery |
| CN | Connection - Higher-level protocol for data exchange |
| Blitz | Fast sensor data messaging subsystem |
| MRX | Multi-Receiver eXtension - Secondary receiver support |
| GIMU | Ground IMU - Inertial Measurement Unit on transmitter |
| TXC | Transmitter Control - Ground unit control interface |
Protocol Architecture
Protocol Stack
block-beta
columns 1
A["Application Layer\n(Device Objects, Blitz Messages, Config)"]
B["Connection Layer (CN)\n(Object Access, System Commands, Routing)"]
C["Interface Layer (IF)\n(Network Management, Device Discovery)"]
D["Framing Layer\n(UART: ESC/COBS, CAN: Multi-frame)"]
E["Physical Layer\n(UART, CAN/FDCAN)"]
Design Principles
- Layered Architecture: Clear separation of concerns between protocol layers
- Transport Agnostic: Core protocol independent of physical medium
- Efficient Framing: Multiple framing modes optimized for different scenarios
- Scalable Addressing: Support for hierarchical and flat addressing schemes
- Robust Error Handling: CRC protection and timeout-based error recovery
- Plug-and-Play: Automatic device discovery and ID allocation
OSI Layer Model Mapping
The ULS-DeCoNet protocol maps to the OSI reference model as follows:
| OSI Layer | ULS-DeCoNet Component | Responsibility |
|---|
| 7. Application | ULSObject, ULSDBase, Blitz Messages | Device-specific data, sensor measurements, configuration |
| 6. Presentation | Data Structures (packed structs) | Data formatting, byte ordering (little-endian) |
| 5. Session | ULSBusConnection | Connection establishment, session management |
| 4. Transport | CN Protocol Layer | Reliable message delivery, routing, flow control |
| 3. Network | IF Protocol Layer | Addressing, device discovery, network management |
| 2. Data Link | Framing (ESC/COBS/CAN) | Frame delimiting, error detection (CRC) |
| 1. Physical | UART/CAN Hardware | Physical signaling, bit transmission |
Layer Interactions
Application Data (Objects/Blitz)
↓
[CN Layer]
- Add routing information
- Add command type
↓
[IF Layer]
- Add source LID
- Add command flags
- Network management
↓
[Framing Layer]
- Add frame delimiters
- Add CRC
- Escape special characters (UART)
- Multi-frame splitting (CAN)
↓
[Physical Layer]
- Transmit bytes/frames
Physical Layer
UART Physical Layer
Specifications:
- Data Format: 8-N-1 (8 data bits, no parity, 1 stop bit)
- Baud Rates: Configurable (typically 115200, 230400, 460800, 921600 bps)
- Voltage Levels: Device-dependent (typically 3.3V or 5V TTL/RS232)
- Flow Control: Optional hardware (RTS/CTS) or software (XON/XOFF)
Advantages:
- Simple two-wire interface
- Widely supported
- Point-to-point or multi-drop with appropriate hardware
Limitations:
- Distance limitations without level shifters
- No built-in arbitration for multi-device scenarios
CAN Physical Layer
Specifications:
- CAN Version: Classical CAN and CAN-FD (FDCAN) supported
- Bit Rates:
- Classical CAN: Up to 1 Mbps
- CAN-FD: Up to 5 Mbps (data phase)
- Bus Topology: Multi-drop differential bus
- Termination: 120Ω termination resistors required at both ends
- Max Devices: Theoretically unlimited (practical limit ~32-64)
- Max Distance: Up to 40m @ 1Mbps, longer at lower speeds
Advantages:
- Multi-master capability with arbitration
- Built-in error detection and signaling
- Robust in electrically noisy environments
- Supports broadcast and multicast
CAN Identifiers:
- Standard 11-bit identifiers used
- Identifier structure defined by implementation
Data Link Layer
The Data Link Layer provides reliable frame transmission with error detection. Different framing modes are supported for UART, while CAN has its own native framing.
UART Framing Modes
The protocol supports four UART framing modes:
RAW Mode (SERIAL_MODE_RAW)
Description: No framing - direct byte transmission
Use Case: Binary data transfer when frame boundaries are managed at higher layers
Structure:
[DATA BYTES...]
Characteristics:
- No overhead
- No frame delimiting
- No error detection at this layer
- Highest throughput
- Requires higher layer to manage message boundaries
ESC Mode (SERIAL_MODE_ESC)
Description: Frame delimiting with byte stuffing and CRC-16
Frame Structure:
packet-beta
title "ESC Mode Frame Structure"
0-7: "SOF 0x55 (1 byte)"
8-15: "START_TYPE 0x01 (1 byte)"
16-23: "PAYLOAD (variable, ~1 byte)"
24-31: "CRC_LOW (1 byte)"
32-39: "CRC_HIGH (1 byte)"
40-47: "EOF 0x55 (1 byte)"
48-55: "END_TYPE 0x03 (1 byte)"
Byte Stuffing:
- If data byte ==
0x55, insert 0x02 after it - Delimiter
0x55 is escaped when it appears in payload or CRC
CRC Calculation:
- Algorithm: CRC-16 (polynomial 0x8005, init 0xFFFF)
- Applied to payload only
- 2 bytes transmitted (low byte, high byte)
Example:
Original payload: [0x12, 0x55, 0x34]
CRC: 0xABCD
Transmitted frame:
0x55 0x01 0x12 0x55 0x02 0x34 0xCD 0xAB 0x55 0x03
Characteristics:
- Frame boundaries clearly defined
- Error detection with CRC-16
- Variable overhead (depends on data content)
- Average overhead: ~15-25%
ESC Simple CRC Mode (SERIAL_MODE_ESC_SIMPLE_CRC)
Description: Similar to ESC mode but with simpler 8-bit checksum
Frame Structure:
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-23: "PAYLOAD (variable, ~1 byte)"
24-31: "CHECKSUM (1 byte)"
32-39: "EOF 0x55 (1 byte)"
40-47: "END_TYPE 0x03 (1 byte)"
Checksum:
- Simple byte summation:
checksum = Σ(data_bytes) & 0xFF - 1 byte transmitted
Characteristics:
- Faster than CRC-16
- Lower error detection capability
- Less overhead than full ESC mode
- Suitable for low-error-rate environments
COBS Mode (SERIAL_MODE_COBS)
Description: Consistent Overhead Byte Stuffing with CRC-16
Advantages:
- Predictable framing overhead
- Zero byte (
0x00) used as frame delimiter - No data expansion for most byte values
- Very efficient for binary data
Frame Structure:
packet-beta
title "COBS Mode Frame Structure"
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:
- Packet is divided into blocks separated by zero bytes
- Each block is preceded by a code byte:
- Code byte = number of non-zero bytes following + 1
- Maximum block size = 254 bytes
- Code byte
0xFF means 254 non-zero bytes follow
Example:
Original data: [0x45, 0x00, 0x00, 0x2C, 0x4C]
COBS encoded: [0x02, 0x45, 0x01, 0x03, 0x2C, 0x4C]
^^^^^ ^^^^ ^^^^ ^^^^^ ^^^^^^^^^^
code data code code data data
With frame delimiters:
0x00 0x02 0x45 0x01 0x03 0x2C 0x4C [CRC_L] [CRC_H] 0x00
COBS Characteristics:
- Overhead: +1 byte per 254 bytes + frame delimiters
- Maximum overhead: ~0.4% + fixed overhead
- Excellent for binary protocols
- No byte escaping needed
- Efficient zero-crossing detection
CRC Calculation in COBS:
- CRC-16 calculated on original unencoded data
- CRC bytes are COBS encoded with the data
CAN Framing
CAN bus has built-in framing at the hardware level, but ULS-DeCoNet adds an additional application-level framing layer for large messages.
Single-Frame CAN Messages
Structure:
packet-beta
title "Single-Frame CAN Message (8-byte data field)"
0-7: "CMD (1 byte)"
8-15: "SRC (1 byte)"
16-23: "(varies) (1 byte)"
24-87: "PAYLOAD (variable, ~8 bytes)"
Field Description:
- Byte 0 (CMD): Command type (IF layer command)
- Byte 1 (SRC): Source device ID (bits 0-5), connection ID (bits 6-7)
- Bytes 2+: Command-specific payload
Used for:
- Network management messages (HB, REQUEST_ID, SET_ID)
- Blitz messages (up to 10 bytes payload)
- Short system commands
Multi-Frame CAN Messages
For messages larger than can fit in a single CAN frame, multi-frame protocol is used.
Frame Structure:
packet-beta
title "Multi-Frame CAN Message"
0-7: "CMD (1 byte)"
8-15: "DID (1 byte)"
16-23: "FRMCTL (1 byte)"
24-95: "PAYLOAD (variable, ~9 bytes)"
FRMCTL byte: Bit 7 = End-of-message flag (1 = last frame); Bits 6-0 = Frame sequence number (0–127)
Multi-Frame Protocol:
- Frame Sequence: Frames numbered 0, 1, 2, … N
- Last Frame Marker: Frame N has bit 7 set (value = N | 0x80)
- Payload per Frame: 9 bytes (DLC = 11, minus 3 header bytes)
- Frame Pool: Receiver maintains a pool of 256 frame buffers
- Timeout: Device timeout of 128 frames worth of time
Transmission Algorithm:
total_frames = ceil(message_len / 9)
for frame_num in 0..total_frames-1:
can_frame.data[0] = CMD
can_frame.data[1] = SRC_DID
can_frame.data[2] = frame_num
if frame_num == total_frames-1:
can_frame.data[2] |= 0x80 // Mark last frame
copy up to 9 bytes to can_frame.data[3..11]
send_can_frame()
Reception Algorithm:
On receiving CAN frame:
1. Store frame in pool indexed by (DID, frame_num)
2. Set device timeout
3. If last_frame_flag:
a. Verify all frames 0..N received
b. Reconstruct message from frames
c. If complete: process message
d. If incomplete: log missing frames, discard
e. Free pool entries for this DID
Frame Pool Management:
- Pool Size: 256 frame slots
- Device Table: 64 device entries with timeouts
- Timeout Value:
CN_CAN_DEV_TIMEOUT = 128 frames - Cleanup: On timeout, all frames from device are freed
Example Multi-Frame Message:
Message length: 25 bytes
Frames needed: 3 (9+9+7)
Frame 0: [CMD][DID][0x00][data 0-8]
Frame 1: [CMD][DID][0x01][data 9-17]
Frame 2: [CMD][DID][0x82][data 18-24][pad]
^^^^
0x80 | 0x02 = last frame, number 2
Error Handling:
- Missing frame detection by sequence number gaps
- Device timeout triggers cleanup
- Duplicate frames overwrite previous (allows retransmission)
- Pool full triggers emergency cleanup of timed-out devices
CRC-16 Algorithm
Polynomial: 0x8005 (CRC-16-IBM/ANSI)
Initial Value: 0xFFFF
Table-based implementation for efficiency
uint16_t crc = 0xFFFF;
for each byte in data:
crc = (crc >> 8) ^ crc16_table[(crc ^ byte) & 0xFF]
Validation:
- Receiver calculates CRC over received data
- Valid frame: calculated CRC == 0x0000 (after including received CRC bytes)
Network Layer
The Network Layer (IF Layer) manages device addressing, discovery, and basic packet routing.
Addressing Scheme
Device ID (DID/LID)
Range: 0-63 (6 bits)
| ID Range | Usage |
|---|
| 0 | Master device (network controller) |
| 1-62 | Regular network nodes |
| 63 | Broadcast address / Uninitialized device |
Special IDs:
- ID 0: Reserved for the master device (typically the main controller or coordinator)
- ID 63: Used by devices before ID assignment and for broadcast messages
Connection ID (CID)
Range: 0-3 (2 bits)
Identifies which physical connection a device uses when a node has multiple network interfaces.
Combined Routing ID (CNRID):
CNRID = (CID << 6) | DID
= [CID(2)][DID(6)]
Bits: 7 6 5 4 3 2 1 0
└─┘ └─────────┘
CID DID
Network Management (NM) Protocol
The NM protocol handles device discovery and ID allocation.
Device States
stateDiagram-v2
[*] --> UNINITIALIZED: Power On (DID=63)
UNINITIALIZED --> WaitingSetID: Send REQUEST_ID
WaitingSetID --> OK: Receive SET_ID (DID assigned)
OK --> OK: Send HB / Timeout
NM Messages
IF_CMD_NM_HB (0x01) - Heartbeat
Purpose: Keep-alive message to announce presence
Structure:
packet-beta
title "IF_CMD_NM_HB (0x01) Heartbeat"
0-7: "CMD 0x01 (1 byte)"
8-15: "SRC (DID) (1 byte)"
16-47: "UID0 (4 bytes)"
Fields:
- CMD: 0x01
- SRC: Source device ID
- UID0: Device unique ID (first 32 bits)
Timing:
- Send Interval: Every 1000ms (
IF_NM_PING_HB_TIMEOUT) - Receive Timeout: 3000ms (
IF_NM_DVICE_HB_TIMEOUT) - Device considered disconnected if no HB received within timeout
Behavior:
- All devices with assigned ID send periodic HB
- Master and slaves both participate
- Timeout detection triggers disconnection callback
IF_CMD_NM_REQUEST_ID (0x03) - Request ID Allocation
Purpose: Device requests ID assignment from master
Structure:
packet-beta
title "IF_CMD_NM_REQUEST_ID (0x03)"
0-7: "CMD 0x03 (1 byte)"
8-15: "SRC 63 (1 byte)"
16-47: "KEY (4 bytes)"
Fields:
- CMD: 0x03
- SRC: 63 (uninitialized)
- KEY: Random 32-bit key derived from device UID
Key Generation:
key = DEVICE_KEY ^ key_counter
where DEVICE_KEY = UID0 ^ UID1 ^ UID2 ^ UID3
Timing:
- Sent every 100ms + random offset
- Up to 5 attempts
- If no master responds, device self-assigns random free ID
IF_CMD_NM_SET_ID (0x04) - ID Assignment
Purpose: Master assigns ID to requesting device
Structure:
packet-beta
title "IF_CMD_NM_SET_ID (0x04)"
0-7: "CMD 0x04 (1 byte)"
8-15: "SRC 0 (1 byte)"
16-23: "NEW_ID (1 byte)"
24-55: "KEY (4 bytes)"
Fields:
- CMD: 0x04
- SRC: 0 (master device)
- NEW_ID: Assigned device ID (1-62)
- KEY: The key from REQUEST_ID message
ID Allocation Algorithm (Master):
1. Maintain table of active devices (timeout-based)
2. On REQUEST_ID received:
a. Scan table for first free ID (timeout == 0)
b. Assign that ID
c. Send SET_ID with matching KEY
d. Set timeout for new device
Reception (Requesting Device):
1. Receive SET_ID
2. Verify KEY matches own key
3. If match:
a. Set DID = NEW_ID
b. Change state to OK
c. Send immediate HB
4. If no match:
Ignore (not for us)
IF_CMD_NM_RESET_ID (0x05) - Reset Device ID
Purpose: Force device to return to uninitialized state
Structure:
packet-beta
title "IF_CMD_NM_RESET_ID (0x05)"
0-7: "CMD 0x05 (1 byte)"
8-15: "SRC 0 (1 byte)"
16-23: "DST_DID (1 byte)"
Note: Defined but not fully implemented in current version
Static ID Assignment
Method: ifSetStaticDID(uint8_t static_did)
Devices can be configured with static IDs (1-62) that bypass the dynamic allocation process.
Usage:
- Configuration storage in device
- Fixed topology networks
- Deterministic addressing requirements
Behavior:
- Device uses static ID immediately
- Still sends HB for presence indication
- Bypasses REQUEST_ID mechanism
Network Topology
The IF layer supports various network topologies:
Star Topology
flowchart TB
Master["Master (ID=0)"] --> N1["Node (1)"]
Master --> N2["Node (2)"]
Master --> N3["Node (3)"]
Characteristics:
- Single master (ID=0)
- All slaves connect directly to master
- Master performs ID allocation
- Common for UART bus configurations
Multi-Master (CAN)
flowchart LR
N0["(0)"] --- N1["(1)"] --- N2["(2)"] --- N3["(3)"]
N0 --- N4["(4)"]
N3 --- N5["(5)"]
N4~~~N5
CAN BUS (shared medium)
Characteristics:
- Any device can be master (first ID=0 or all peer)
- Peer-to-peer communication possible
- ID allocation can be distributed
- Typical for CAN bus configurations
Packet Structure (IF Layer)
packet-beta
title "IF Layer Packet Structure"
0-7: "CMD (1 byte)"
8-15: "SRC_LID (1 byte)"
16-79: "PAYLOAD (variable, ~8 bytes)"
CMD Field (1 byte):
- Bit 4: Frame type flag
0x00-0x0F: Network management / System commands0x10-0x1F: Higher layer protocols (CN layer)
- Bits 3-0: Command code
IF Layer Commands:
| Code | Name | Description |
|---|
| 0x00 | IF_CMD_SYS | System command |
| 0x01 | IF_CMD_NM_HB | Network heartbeat |
| 0x02 | IF_CMD_NM_GET_STATUS | Get device status |
| 0x03 | IF_CMD_NM_REQUEST_ID | Request ID allocation |
| 0x04 | IF_CMD_NM_SET_ID | Assign device ID |
| 0x05 | IF_CMD_NM_RESET_ID | Reset device ID |
| 0x06 | IF_CMD_BLITZ | Blitz sensor data |
Transport Layer
The Transport Layer (CN - Connection Layer) provides routing, object access, and system management.
CN Protocol Overview
The Connection layer adds:
- Multi-hop routing
- Object-oriented data access
- System configuration and firmware update
- Reliable request-response patterns
Routing Mechanism
Route Table
Routes are stored as arrays of routing IDs (CNRID):
Route = [hop0, hop1, hop2, ..., hopN]
Each hop contains:
- Bits 7-6: CID (Connection ID)
- Bits 5-0: DID (Device ID)
Route Direction:
- Forward Route (command): hop0 = source, hopN = destination
- Reverse Route (response): route is reversed
CN Packet Structure
packet-beta
title "CN Packet Structure"
0-7: "CMD (1 byte)"
8-15: "SRC_DID (1 byte)"
16-23: "HOP (1 byte)"
24-79: "ROUTE (variable, ~8 bytes)"
80-143: "PAYLOAD (variable, ~8 bytes)"
HOP byte: Bits 7-4 = current hop index; Bits 3-0 = route size
Packet Fields:
- CMD: CN command type
- SRC_DID: Original source device ID
- HOP: Hop counter (high nibble) and route size (low nibble)
- ROUTE: Array of CNRID values defining path
- PAYLOAD: Command-specific data
Routing Example
Scenario: Device A (DID=1, CID=0) sends message to Device C (DID=3, CID=2) through Device B (DID=2, CID=1)
Forward Packet (A→B→C):
At A:
CMD = CN_CMD_GETOBJ
SRC_DID = 1
HOP = 0x03 (hop=0, route_size=3)
ROUTE = [0x01, 0x42, 0x83]
^^^^ ^^^^ ^^^^
A B C
CID=0 CID=1 CID=2
DID=1 DID=2 DID=3
At B (forwarding):
- Increment hop: HOP = 0x13 (hop=1, route_size=3)
- Next destination = ROUTE[1] = 0x42 (DID=2)
- Forward to CID=1
At C (final destination):
- hop == route_size-1, packet is for us
- Process CN_CMD_GETOBJ
Response Packet (C→B→A):
At C:
CMD = CN_ACK_GETOBJ
SRC_DID = 3
HOP = 0x03 (hop=0, route_size=3)
ROUTE = [0x83, 0x42, 0x01] // Reversed!
^^^^ ^^^^ ^^^^
C B A
Routing process same as forward
Route Discovery
Routes are built dynamically through Explorer messages:
- Master sends
CN_CMD_EXPLORER with empty route - Each device:
- Adds its CNRID to route
- Forwards to all connections
- Responds with
CN_ACK_EXPLORER containing reverse route
- Master builds topology map from responses
CN Commands
Command Types
| Code | Name | Description |
|---|
| 0x10 | CN_CMD_EXPLORER | Network exploration |
| 0x11 | CN_ACK_EXPLORER | Explorer response with status |
| 0x12 | CN_CMD_SYS | System command |
| 0x13 | CN_ACK_SYS | System command acknowledgment |
| 0x14 | CN_CMD_GETOBJ | Get object data |
| 0x15 | CN_ACK_GETOBJ | Object data response |
| 0x16 | CN_CMD_SETOBJ | Set object data |
| 0x17 | CN_ACK_SETOBJ | Set object acknowledgment |
Explorer Protocol
CN_CMD_EXPLORER (0x10)
Purpose: Discover network topology
Structure:
packet-beta
title "CN_CMD_EXPLORER (0x10)"
0-7: "0x10 (1 byte)"
8-15: "SRC_DID (1 byte)"
16-23: "HOP (1 byte)"
24-87: "ROUTE (variable, ~8 bytes)"
Transmission:
1. Master sends with HOP=0x00, empty route
2. Each device receives:
a. Append own CNRID to route
b. If HOP < 15:
- Increment HOP
- Forward to all other connections
c. Send CN_ACK_EXPLORER with reverse route
CN_ACK_EXPLORER (0x11)
Purpose: Respond to explorer with device info
Structure:
packet-beta
title "CN_ACK_EXPLORER (0x11)"
0-7: "0x11 (1 byte)"
8-15: "SRC_DID (1 byte)"
16-23: "HOP (1 byte)"
24-79: "ROUTE (variable, ~8 bytes)"
80-111: "TYPE (4 bytes)"
112-239: "NAME (16 bytes)"
Fields:
- TYPE: Device type code (uint32_t)
- NAME: Device name (null-terminated string, max 16 chars)
Object Access
CN_CMD_GETOBJ (0x14) - Read Object
Purpose: Read data from device object
Request Structure:
packet-beta
title "CN_CMD_GETOBJ (0x14) Request"
0-7: "0x14 (1 byte)"
8-15: "SRC_DID (1 byte)"
16-23: "HOP (1 byte)"
24-79: "ROUTE (variable, ~8 bytes)"
80-95: "OBJ_ADDR (2 bytes)"
Fields:
- OBJ_ADDR: Object address/ID (uint16_t)
Response (CN_ACK_GETOBJ):
packet-beta
title "CN_ACK_GETOBJ (0x15) Response"
0-7: "0x15 (1 byte)"
8-15: "SRC_DID (1 byte)"
16-23: "HOP (1 byte)"
24-79: "ROUTE (variable, ~8 bytes)"
80-95: "OBJ_ADDR (2 bytes)"
96-159: "OBJ_DATA (variable, ~8 bytes)"
Process:
- Request routed to target device
- Device looks up object by OBJ_ADDR
- Check read permission
- If OK: respond with object data
- If error: no response or error code
CN_CMD_SETOBJ (0x16) - Write Object
Purpose: Write data to device object
Request Structure:
packet-beta
title "CN_CMD_SETOBJ (0x16) Request"
0-7: "0x16 (1 byte)"
8-15: "SRC_DID (1 byte)"
16-23: "HOP (1 byte)"
24-79: "ROUTE (variable, ~8 bytes)"
80-95: "OBJ_ADDR (2 bytes)"
96-159: "OBJ_DATA (variable, ~8 bytes)"
Response (CN_ACK_SETOBJ):
packet-beta
title "CN_ACK_SETOBJ (0x17) Response"
0-7: "0x17 (1 byte)"
8-15: "SRC_DID (1 byte)"
16-23: "HOP (1 byte)"
24-79: "ROUTE (variable, ~8 bytes)"
80-95: "OBJ_ADDR (2 bytes)"
Process:
- Request routed to target device
- Device looks up object by OBJ_ADDR
- Check write permission
- If OK: write data, respond with ACK
- If error: no response or error code
System Commands
CN_CMD_SYS (0x12) - System Operations
Purpose: Device configuration and firmware update
Structure:
packet-beta
title "CN_CMD_SYS (0x12)"
0-7: "0x12 (1 byte)"
8-15: "SRC_DID (1 byte)"
16-23: "HOP (1 byte)"
24-79: "ROUTE (variable, ~8 bytes)"
80-87: "SYSCMD (1 byte)"
88-151: "SYS_PAYLOAD (variable, ~8 bytes)"
System Sub-Commands:
| Code | Name | Description |
|---|
| 0 | CN_SYS_CMD_PING | Ping device |
| 1 | CN_SYS_CMD_SETMODE | Set operational mode |
| 2 | CN_SYS_CMD_ERASE | Erase flash memory |
| 3 | CN_SYS_CMD_WRITE | Write flash memory |
| 4 | CN_SYS_CMD_SETSIGNATURE | Set firmware signature |
| 5 | CN_SYS_CMD_SAVECFG | Save configuration |
PING (0x00)
Request:
packet-beta
title "SYS PING (0x00) Request"
0-7: "SYSCMD 0x00 (1 byte)"
8-23: "DEVTYPE (2 bytes)"
Response: Operation result (CN_ACK_SYS)
SETMODE (0x01)
Purpose: Switch between application and bootloader mode
Request:
┌─────────┬──────┐
│ SYSCMD │ MODE │
│ 0x01 │(1 b) │
└─────────┴──────┘
MODE values:
0 = CN_SYS_MODE_APP (application)
1 = CN_SYS_MODE_LOADER (bootloader)
Usage:
- Switch to loader mode for firmware update
- Requires device reset after mode change
ERASE (0x02)
Purpose: Erase flash memory region
Request:
packet-beta
title "SYS ERASE (0x02)"
0-7: "SYSCMD 0x02 (1 byte)"
8-39: "KEY (4 bytes)"
40-71: "START (4 bytes)"
72-103: "LEN (4 bytes)"
Fields:
- KEY: Operation authorization key
- START: Start address in flash
- LEN: Length to erase (bytes)
Response: Operation result with status code
WRITE (0x03)
Purpose: Write data to flash memory
Request:
packet-beta
title "SYS WRITE (0x03)"
0-7: "SYSCMD 0x03 (1 byte)"
8-39: "KEY (4 bytes)"
40-71: "START (4 bytes)"
72-103: "LEN (4 bytes)"
104-135: "DATA (up to 512 bytes)"
Fields:
- KEY: Operation authorization key
- START: Start address in flash
- LEN: Length to write (bytes)
- DATA: Data to write
Response: Operation result with status code
SETSIGNATURE (0x04)
Purpose: Set firmware signature information
Request:
packet-beta
title "SYS SETSIGNATURE (0x04)"
0-7: "SYSCMD 0x04 (1 byte)"
8-39: "KEY (4 bytes)"
40-295: "FW version (32 bytes)"
296-551: "LDR version (32 bytes)"
552-583: "FWTIME (4 bytes)"
584-615: "PROGSIZE (4 bytes)"
616-647: "PROGCRC (4 bytes)"
Fields:
- KEY: Operation authorization key
- FW: Firmware version string (32 chars)
- LDR: Loader version string (32 chars)
- FWTIME: Firmware flashing timestamp
- PROGSIZE: Program size in bytes
- PROGCRC: Program CRC32 checksum
Purpose: Store metadata after firmware update completion
SAVECFG (0x05)
Purpose: Save current configuration to non-volatile memory
Request:
packet-beta
title "SYS SAVECFG (0x05)"
0-7: "SYSCMD 0x05 (1 byte)"
8-39: "KEY (4 bytes)"
CN_ACK_SYS (0x13) - System Command Response
Structure:
packet-beta
title "CN_ACK_SYS (0x13)"
0-7: "0x13 (1 byte)"
8-15: "SRC_DID (1 byte)"
16-23: "HOP (1 byte)"
24-79: "ROUTE (variable, ~8 bytes)"
80-87: "RESULT (1 byte)"
Result Codes:
| Code | Name | Description |
|---|
| 0 | CN_SYS_OPERATION_UNDEFINED | Operation not defined |
| 1 | CN_SYS_OPERATION_OK | Operation successful |
| 2 | CN_SYS_OPERATION_ERROR | Operation failed |
| 3 | CN_SYS_OPERATION_WRONGKEY | Invalid authorization key |
Operation Keys
Purpose: Prevent unauthorized firmware modifications
Key Generation:
#define _ULS_OPERATION_KEY 0x24041982
#define DEVICE_KEY (UID0 ^ UID1 ^ UID2 ^ UID3)
uint32_t operation_key = _ULS_OPERATION_KEY;
// Or device-specific key for some operations
Usage:
- ERASE, WRITE, SETSIGNATURE, SAVECFG require valid key
- Key validation prevents accidental or malicious operations
- Each device can have unique key derived from UID
Application Layer
The Application Layer defines device-specific data structures and high-level protocols.
Object Model
Object Architecture
Objects provide structured access to device parameters, configuration, and data.
Object Base Class:
class ULSObjectBase {
uint16_t id; // Object address
uint16_t size; // Size of single element
uint16_t len; // Array length (1 for scalar)
uint8_t *data; // Pointer to data
const char *name; // Object name
const char *description; // Object description
_ulsbus_obj_permissions permission; // Access permission
}
Object Permissions
| Permission | Value | Read | Write | Description |
|---|
| READONLY | 0 | ✓ | ✗ | Read-only data |
| WRITEONLY | 1 | ✗ | ✓ | Write-only (e.g., commands) |
| READWRITE | 2 | ✓ | ✓ | Full access |
| PROTECTED | 3 | ✓ | ✓* | Requires authorization |
| CONFIG | 4 | ✓ | ✓* | Configuration parameters |
| SYSCONFIG | 5 | ✓ | ✓* | System configuration |
| ADMIN | 6 | ✓ | ✓* | Administrator only |
*Note: Protected/Config/Admin levels require additional authorization mechanisms (implementation-specific)
Standard Objects
System Signature Object (0x0001)
Purpose: Device identification and firmware information
Structure:
struct ULSObjectSignature {
char fw[32]; // Firmware version
char ldr[32]; // Loader/bootloader version
uint32_t serial[4]; // Device serial number (UID)
uint32_t progflashingtime; // Firmware flash timestamp
uint32_t progsize; // Program size
uint32_t progcrc; // Program CRC32
uint32_t type; // Device type code
}; // Total: 128 bytes
Permission: READONLY
Usage:
- Device identification
- Firmware version tracking
- Update verification
Device Base Class
class ULSDBase {
const char *devname; // Device name
const char *typeName; // Type name string
uint16_t typeCode; // Numeric type code
ULSList<ULSObjectBase> objects; // List of objects
ULSObjectBase* getObject(uint16_t obj_id);
void setData(uint16_t obj_id, uint8_t *buf);
};
Purpose: Base class for all ULS devices providing:
- Object management
- Device identification
- Configuration storage
Blitz Protocol
The Blitz protocol provides high-frequency sensor data transmission with minimal overhead.
Blitz Packet Structure (IF Layer)
packet-beta
title "Blitz Packet (IF Layer)"
0-7: "CMD 0x06 (1 byte)"
8-15: "SRC_LID (1 byte)"
16-31: "MSG_ID (2 bytes)"
32-95: "DATA (8 bytes)"
Fields:
- CMD: 0x06 (IF_CMD_BLITZ)
- SRC_LID: Source device ID
- MSG_ID: Blitz message type identifier
- DATA: Message-specific payload (max 8 bytes)
Blitz Message Types
See separate document: ULS-DeCoNet_Blitz_Messages.md for complete message definitions.
Summary of Message Types:
| Message ID | Name | Description |
|---|
| 0x0010 | XCG1_RX_RAW_MEASURMENT | Legacy raw measurement (limited angular range) |
| 0x0011 | RX_RAW_MEASURMENT | Raw measurement with full angular range |
| 0x0012 | RX_POS_IN_TX_FRAME | 3D position in transmitter frame |
| 0x0013 | RX_MRX | Multi-receiver information (yaw, distance) |
| 0x0014 | RX_DACS | DACS information with continuous yaw |
| 0x0020 | GIMU_MEASUREMENT | Ground IMU measurement (roll, pitch, yaw) |
| 0x0030 | XX_TXC_IMU_OVERRIDE | IMU override for ground unit |
| 0x0031 | XX_TXC_MAG_OVERRIDE | Magnetometer override for ground unit |
Blitz Data Flow
Sensor → Measurement → Blitz Packet → IF Layer → Framing → Physical Layer
↓
[Status Bits]
[Counter]
[Sensor Data]
Status Bits (common to most Blitz messages):
| Bit | Flag | Description |
|---|
| 0 | CARRIER_OK | IR signal detected |
| 1 | SQ_OK | Signal quality OK |
| 2 | POS_OK | Position estimated |
| 3 | VEL_OK | Velocity estimated |
| 4 | GU_IMU_OK | Ground Unit IMU OK |
| 5 | MRX_OK | MRX message received |
| 6 | MRXDATA_OK | MRX calculations OK |
Blitz Usage Pattern
Transmitter:
// Construct Blitz message
__uls_blitz_msg_rx_raw_measurmet msg;
msg.status = calculate_status();
msg.counter = counter++;
msg.aX = angle_x_scaled;
msg.aY = angle_y_scaled;
msg.distance = distance_cm;
// Send via IF layer
interface->ifSendBLITZ(
ULS_BLITZ_MSG_ID_RX_RAW_MEASURMENT,
(uint8_t*)&msg,
sizeof(msg)
);
Receiver:
// Set callback
interface->ifclbkBlitzReceived = &onBlitzReceived;
void onBlitzReceived(ULSBusInterface *iface) {
uint16_t msg_id = iface->ifRxPacket->blitz.msg_id;
uint8_t *data = iface->ifRxPacket->blitz.data;
switch(msg_id) {
case ULS_BLITZ_MSG_ID_RX_RAW_MEASURMENT:
process_raw_measurement(data);
break;
// ... handle other message types
}
}
Application Examples
Read Configuration Object
Scenario: Read object 0x0100 from device at DID=5
// Prepare route (direct connection)
uint8_t route[] = {0x05}; // DID=5, CID=0
// Send GET request
connection->cnSendGetObject(
route, // Route array
1, // Route size (hops)
0x0100 // Object address
);
// Response handled in callback
connection->cnclbkObjReceived = &onObjectReceived;
void onObjectReceived(ULSBusConnection *cn) {
uint16_t obj_addr = *((uint16_t*)&cn->cnRxPacket->pld[cn->cnRxPacket->hop & 0xF]);
uint8_t *obj_data = &cn->cnRxPacket->pld[(cn->cnRxPacket->hop & 0xF) + 2];
// Process object data
process_config(obj_addr, obj_data);
}
Firmware Update Procedure
1. Switch to Loader Mode:
cnSendSysSetMode(route, hops, CN_SYS_MODE_LOADER)
→ Device resets into bootloader
2. Erase Flash:
cnSendSysErase(route, hops, key, start_addr, length)
→ Wait for CN_ACK_SYS with OK result
3. Write Firmware (in blocks):
for each 512-byte block:
cnSendSysWrite(route, hops, key, addr, 512, data)
→ Wait for CN_ACK_SYS with OK result
addr += 512
4. Set Signature:
cnSendSysSetSignature(route, hops, key, fw_ver, ldr_ver,
timestamp, size, crc)
→ Device validates and stores metadata
5. Save Configuration:
cnSendSysSaveConfig(route, hops, key)
→ Device commits changes
6. Switch to Application Mode:
cnSendSysSetMode(route, hops, CN_SYS_MODE_APP)
→ Device resets into new firmware
Security Considerations
Authentication
Operation Keys:
- System operations require 32-bit keys
- Key validation prevents unauthorized firmware modification
- Keys can be device-specific (derived from UID)
Recommendations:
- Change default
_ULS_OPERATION_KEY for production - Implement challenge-response for critical operations
- Consider encryption for sensitive data
Access Control
Object Permissions:
- Enforce read/write restrictions
- Protected objects require authorization
- Validate permissions before object access
Firmware Integrity
CRC Validation:
- Firmware CRC32 stored in signature
- Bootloader verifies before execution
- Prevents corrupted firmware execution
Signature Verification:
- Firmware version and timestamp tracking
- Size validation
- Metadata consistency checks
Network Security
ID Allocation:
- Master controls ID assignment
- Duplicate ID detection
- Timeout-based cleanup
Routing:
- Route validation
- Hop count limits prevent loops
- Source verification
Best Practices
- Use static IDs for critical devices
- Implement authentication for system commands
- Validate all inputs at object level
- Use CRC checking on all transports
- Implement timeouts for all operations
- Log security events for audit trails
- Encrypt sensitive data if required by application
- Protect operation keys in device storage
References
- ULS-DeCoNet Source Code:
ULSOpenLib/ - ULSDC-PBDS021125 - ULS-DeCoNet CAN Transport Specification
- ULSDC-PBDS031125 - ULS-DeCoNet UART Transport Specification
- ULSDC-PBDS041125 - ULS-DeCoNet Quick Reference Guide
- ULSDC-PBDS051125 - ULS-DeCoNet Blitz Messages Specification
Copyright © 2020-2026 UAVLAS <www.uavlas.com>
License: GNU Lesser General Public License v3.0+
Project: ULS Devices Connection Network (ULS-DeCoNet)
3 - ULS-DeCoNet Quick Reference Guide
Reference for developers and integrators of ULS-DeCoNet — the ULS Devices Connection Network protocol for UART and CAN.
ULSDC-PBDS041125 · version 1.0.0 · issued November 13, 2025
The numbered PDF is the controlled copy: download it from the portal.
Protocol Stack
Application Layer │ Objects, Blitz Messages, Device Data
Connection Layer │ Routing, Object Access, System Commands
Interface Layer │ Network Management, Device Discovery
Framing Layer │ UART: ESC/COBS | CAN: Multi-frame
Physical Layer │ UART 115200+ | CAN 1Mbps
Addressing
| Component | Size | Range | Description |
|---|
| Device ID (DID) | 6 bits | 0-63 | Local device address |
| Connection ID (CID) | 2 bits | 0-3 | Interface identifier |
| CNRID | 8 bits | 0-255 | Combined routing ID: (CID<<6)|DID |
Special Addresses:
- 0: Master device
- 63: Broadcast / Uninitialized
- 1-62: Regular devices
UART Framing Modes
| Mode | Delimiter | CRC | Overhead | Best For |
|---|
| RAW | None | None | 0% | Custom protocols |
| ESC | 0x55 0x01/0x03 | CRC-16 | ~15-25% | General purpose |
| ESC_SIMPLE | 0x55 0x01/0x03 | 8-bit sum | ~12-20% | Low-power |
| COBS | 0x00 | CRC-16 | ~0.4-2% | Binary data |
Frame Examples
ESC Mode:
[0x55][0x01][DATA...][CRC_L][CRC_H][0x55][0x03]
COBS Mode:
[0x00][CODE][DATA...][CRC_L][CRC_H][0x00]
CAN Frame Structure
Single-Frame (≤10 bytes payload)
[CMD][DID][PAYLOAD(6 bytes)]
Multi-Frame (>10 bytes)
Frame N: [CMD][DID][FRMCTL][PAYLOAD(5 bytes)]
Last Frame: FRMCTL = 0x80 | frame_number
Frame Control (FRMCTL):
- Bit 7: End flag (1 = last frame)
- Bits 6-0: Frame number (0-127)
IF Layer Commands
| Code | Name | Description |
|---|
| 0x00 | SYS | System command |
| 0x01 | NM_HB | Heartbeat (1000ms interval) |
| 0x02 | NM_GET_STATUS | Get device status |
| 0x03 | NM_REQUEST_ID | Request ID allocation |
| 0x04 | NM_SET_ID | Assign device ID |
| 0x05 | NM_RESET_ID | Reset device ID |
| 0x06 | BLITZ | Sensor data message |
Packet Structure:
[CMD][SRC_LID][PAYLOAD]
CN Layer Commands
| Code | Name | Description |
|---|
| 0x10 | EXPLORER | Network discovery |
| 0x11 | ACK_EXPLORER | Discovery response |
| 0x12 | SYS | System operation |
| 0x13 | ACK_SYS | System response |
| 0x14 | GETOBJ | Read object |
| 0x15 | ACK_GETOBJ | Object data response |
| 0x16 | SETOBJ | Write object |
| 0x17 | ACK_SETOBJ | Write acknowledgment |
Packet Structure:
[CMD][SRC_DID][HOP][ROUTE...][PAYLOAD]
HOP byte:
Bits 7-4: Current hop index
Bits 3-0: Route size
System Sub-Commands
| Code | Name | Description |
|---|
| 0 | PING | Device ping |
| 1 | SETMODE | Set APP/LOADER mode |
| 2 | ERASE | Erase flash memory |
| 3 | WRITE | Write flash memory |
| 4 | SETSIGNATURE | Set firmware signature |
| 5 | SAVECFG | Save configuration |
Result Codes:
- 0: UNDEFINED
- 1: OK
- 2: ERROR
- 3: WRONGKEY
Blitz Messages
Message Types
| ID | Name | Size | Description |
|---|
| 0x0010 | XCG1_RAW | 8B | Legacy ±30° measurement |
| 0x0011 | RAW_MEASURMENT | 8B | ±180° measurement |
| 0x0012 | POS_TX_FRAME | 8B | 3D position (cm) |
| 0x0013 | MRX | 6B | Multi-receiver data |
| 0x0014 | DACS | 8B | Continuous yaw |
| 0x0020 | GIMU | 8B | Ground IMU data |
| 0x0030 | IMU_OVERRIDE | 8B | Override IMU |
| 0x0031 | MAG_OVERRIDE | 8B | Override magnetometer |
Status Bits
| Bit | Flag | Description |
|---|
| 0 | CARRIER_OK | IR signal present |
| 1 | SQ_OK | Signal quality OK |
| 2 | POS_OK | Position valid |
| 3 | VEL_OK | Velocity valid |
| 4 | GU_IMU_OK | Ground IMU OK |
| 5 | MRX_OK | MRX received |
| 6 | MRXDATA_OK | MRX data valid |
Data Encoding
Angles (±180°):
int16_t = (degrees / 180.0) * 32767.0
degrees = (int16_t / 32767.0) * 180.0
Position/Distance (cm):
value_cm = int16_t or uint16_t
value_m = value_cm * 0.01
Object Permissions
| Value | Name | Read | Write |
|---|
| 0 | READONLY | ✓ | ✗ |
| 1 | WRITEONLY | ✗ | ✓ |
| 2 | READWRITE | ✓ | ✓ |
| 3 | PROTECTED | ✓ | ✓* |
| 4 | CONFIG | ✓ | ✓* |
| 5 | SYSCONFIG | ✓ | ✓* |
| 6 | ADMIN | ✓ | ✓* |
*Requires authorization
Network States
Device States
UNINITIALIZED (DID=63)
↓ Send REQUEST_ID
↓ Receive SET_ID
OK (DID assigned)
↓ Send HB every 1000ms
↓ Timeout 3000ms
ERROR
Common Operations
Device Discovery
1. Master sends CN_CMD_EXPLORER
2. Devices forward and respond with ACK_EXPLORER
3. Master builds topology map
Read Object
1. Send CN_CMD_GETOBJ with object ID
2. Receive CN_ACK_GETOBJ with data
Write Object
1. Send CN_CMD_SETOBJ with object ID and data
2. Receive CN_ACK_SETOBJ confirmation
Firmware Update
1. SETMODE → LOADER
2. ERASE flash region
3. WRITE firmware blocks (512 bytes each)
4. SETSIGNATURE with metadata
5. SAVECFG
6. SETMODE → APP
Timing Parameters
| Parameter | Value | Description |
|---|
| HB_INTERVAL | 1000 ms | Heartbeat send interval |
| DEVICE_TIMEOUT | 3000 ms | Device disconnection timeout |
| REQUEST_ID_INTERVAL | 100 ms | ID request interval |
| REQUEST_ID_RETRIES | 5 | Max ID request attempts |
| CAN_DEV_TIMEOUT | 128 frames | CAN multi-frame timeout |
| CAN_POOL_SIZE | 256 slots | CAN frame buffer pool |
UART @ 115200 bps
| Mode | Throughput | Latency |
|---|
| RAW | 11.5 KB/s | ~0 ms |
| ESC | 9.6 KB/s | ~0.17 ms |
| COBS | 11.3 KB/s | ~0.09 ms |
CAN @ 1 Mbps
| Message | Frames | Time | Throughput |
|---|
| 10 bytes | 1 | 108 µs | 92.6 KB/s |
| 256 bytes | 52 | 5.6 ms | 45.6 KB/s |
Error Handling
UART
- CRC errors → discard frame, increment counter
- Framing errors → resync to next delimiter
- Buffer overflow → flush and restart
CAN
- Missing frames → timeout and cleanup
- Pool overflow → emergency cleanup
- Bus-Off → automatic recovery
Code Examples
Initialize Connection (UART)
ULSSerial serial(&rxFifo, &txFifo);
serial.mode(SERIAL_MODE_COBS);
ULSBusInterface interface("UART1", static_did);
interface.open();
Initialize Connection (CAN)
ULSBusConnectionCAN can(&hfdcan1, &device,
&connections, "CAN1",
static_did, cid);
can.setSpeed(1000000);
can.open();
Send Blitz Message
__uls_blitz_msg_rx_raw_measurmet msg;
msg.status = calc_status();
msg.counter = counter++;
msg.aX = (angle_x / 180.0) * 32767;
msg.aY = (angle_y / 180.0) * 32767;
msg.distance = distance_m * 100;
interface->ifSendBLITZ(
ULS_BLITZ_MSG_ID_RX_RAW_MEASURMENT,
(uint8_t*)&msg, sizeof(msg)
);
Receive Blitz Message
interface->ifclbkBlitzReceived = &onBlitz;
void onBlitz(ULSBusInterface *iface) {
uint16_t msg_id = iface->ifRxPacket->blitz.msg_id;
uint8_t *data = iface->ifRxPacket->blitz.data;
switch(msg_id) {
case ULS_BLITZ_MSG_ID_RX_RAW_MEASURMENT:
process_measurement(data);
break;
}
}
Read Object
uint8_t route[] = {0x05}; // DID=5
connection->cnSendGetObject(route, 1, 0x0100);
// In callback
connection->cnclbkObjReceived = &onObjReceived;
System Command
uint8_t route[] = {0x05};
uint32_t key = _ULS_OPERATION_KEY;
connection->cnSendSysSetMode(route, 1,
CN_SYS_MODE_LOADER);
Troubleshooting
| Issue | Check | Solution |
|---|
| No communication | Baud rate, wiring | Match settings, check connections |
| CRC errors | Noise, cable quality | Add shielding, lower baud rate |
| Device timeout | HB interval | Check network connectivity |
| Pool overflow | Traffic load | Increase pool size, reduce rate |
| Missing frames | Bus load | Reduce message rate |
Key Definitions
// Operation Key
#define _ULS_OPERATION_KEY 0x24041982
// Buffer Sizes
#define IF_PACKET_SIZE 1324
#define CN_CAN_POOL_SIZE 256
#define CN_CAN_DEV_SIZE 64
// Timeouts
#define IF_NM_DVICE_HB_TIMEOUT 3000
#define IF_NM_PING_HB_TIMEOUT 1000
#define IF_NM_REQUESTID_TIMEOUT 100
Document References
- Overview:
ULS-DeCoNet_Protocol_Overview.md - UART:
ULS-DeCoNet_UART_Protocol.md - CAN:
ULS-DeCoNet_CAN_Protocol.md - Blitz:
ULS-DeCoNet_Blitz_Messages.md
Copyright © 2020-2026 UAVLAS
License: LGPL-3.0+
Version: 1.0
Copyright © 2020-2026 UAVLAS <www.uavlas.com>
License: GNU Lesser General Public License v3.0+
4 - ULS-DeCoNet Blitz Messages Specification
Reference for developers and integrators of ULS-DeCoNet Blitz Protocol — the ULS Devices Connection Network protocol for UART and CAN.
ULSDC-PBDS051125 · version 1.0.4 · issued November 13, 2025
The numbered PDF is the controlled copy: download it from the portal.
Introduction
Purpose
The Blitz protocol is a specialized messaging subsystem within ULS-DeCoNet designed for high-frequency transmission of sensor measurement data from ULS positioning systems.
Key Characteristics
- Low Overhead: Fixed 11-byte messages (3-byte header + 8-byte payload)
- High Frequency: Optimized for 100+ Hz update rates
- Real-time: Minimal latency from sensor to network
- Type-safe: Strongly typed message definitions
- Compact: Efficient packing of measurement data
Blitz Protocol Overview
Protocol Stack Position
block-beta
columns 1
A["Application Layer\n(Blitz Message Handling)"]
B["IF Protocol\n(IF_CMD_BLITZ = 0x06)"]
C["Framing Layer"]
Message Flow
flowchart LR
Sensor --> Measurement --> BlitzEncode["Blitz Encode"] --> IFLayer["IF Layer"] --> Transport --> Receiver
BlitzEncode --> MsgId["MSG_ID + Data"] --> BlitzCmd["IF_CMD_BLITZ"] --> Frame["UART/CAN Frame"]
IF Layer Packet Structure
packet-beta
title "Blitz IF Layer Packet"
0-7: "CMD 0x06 (1 byte)"
8-15: "SRC_LID (1 byte)"
16-31: "MSG_ID (2 bytes)"
32-95: "DATA (8 bytes)"
Total Size: 11 bytes
Fields:
- CMD: Always 0x06 (IF_CMD_BLITZ)
- SRC_LID: Source device ID
- MSG_ID: Blitz message type identifier (uint16_t, little-endian)
- DATA: Message-specific payload (8 bytes maximum)
Common Data Structure
typedef struct {
uint8_t src_lid; // Source logical ID
uint16_t msg_id; // Message type ID
union {
uint8_t data[8]; // Raw data access
// Specific message structures
__uls_blitz_xcg1_rx_raw_measurmet xcg1_rx_raw_measurmet;
__uls_blitz_msg_rx_raw_measurmet rx_raw_measurmet;
__uls_blitz_msg_rx_pos_in_tx_frame rx_pos_in_tx_frame;
__uls_blitz_msg_rx_mrx rx_mrx;
__uls_blitz_msg_rx_dacs rx_dacs;
};
} __blitz_common_data_struct;
Message Types
Message Type Summary
| Message ID | Name | Size | Description |
|---|
| 0x0010 | XCG1_RX_RAW_MEASURMENT | 8 bytes | Legacy raw measurement (±30°) |
| 0x0011 | RX_RAW_MEASURMENT | 8 bytes | Raw measurement (±180°) |
| 0x0012 | RX_POS_IN_TX_FRAME | 8 bytes | 3D position in TX frame |
| 0x0013 | RX_MRX | 6 bytes | Multi-receiver info |
| 0x0014 | RX_DACS | 8 bytes | DACS with continuous yaw |
| 0x0020 | GIMU_MEASUREMENT | 8 bytes | Ground IMU data |
| 0x0030 | XX_TXC_IMU_OVERRIDE | 8 bytes | IMU override command |
| 0x0031 | XX_TXC_MAG_OVERRIDE | 8 bytes | Magnetometer override |
XCG1 Raw Measurement (0x0010)
Legacy message for XCopter Generation 1 systems
Structure
typedef struct __attribute__((packed)) {
uint8_t status; // Status bits
uint8_t d; // Distance in 10 cm increments
int16_t aX; // Angle X: [-30000 = -30°, +30000 = +30°]
int16_t aY; // Angle Y: [-30000 = -30°, +30000 = +30°]
int8_t oF; // Sensor offset Forward (cm)
int8_t oR; // Sensor offset Right (cm)
} __uls_blitz_xcg1_rx_raw_measurmet;
Total Size: 8 bytes
Field Descriptions
| Field | Type | Range | Description |
|---|
| status | uint8_t | 0-255 | Status flags (see section 5) |
| d | uint8_t | 0-255 | Distance in 10 cm steps (0-25.5m) |
| aX | int16_t | -30000 to +30000 | Angle X proportional to ±30° |
| aY | int16_t | -30000 to +30000 | Angle Y proportional to ±30° |
| oF | int8_t | -128 to +127 | Forward offset in cm |
| oR | int8_t | -128 to +127 | Right offset in cm |
Angular Conversion
float angle_x_deg = (aX * 30.0f) / 30000.0f;
float angle_y_deg = (aY * 30.0f) / 30000.0f;
Distance Conversion
float distance_m = d * 0.1f; // d in 10 cm steps
Raw Measurement (0x0011)
Standard raw measurement with full angular range
Structure
typedef struct __attribute__((packed)) {
uint8_t status; // Status bits
uint8_t counter; // Message counter
int16_t aX; // Angle X: [-180° to +180°]
int16_t aY; // Angle Y: [-180° to +180°]
uint16_t distance; // Distance measurement
} __uls_blitz_msg_rx_raw_measurmet;
Total Size: 8 bytes
Field Descriptions
| Field | Type | Range | Description |
|---|
| status | uint8_t | 0-255 | Status flags |
| counter | uint8_t | 0-255 | Packet counter (wraps at 256) |
| aX | int16_t | -32768 to +32767 | Angle X scaled to ±180° |
| aY | int16_t | -32768 to +32767 | Angle Y scaled to ±180° |
| distance | uint16_t | 0-65535 | Distance in cm (0-655.35m) |
Angular Conversion
float angle_x_deg = (aX * 180.0f) / 32767.0f;
float angle_y_deg = (aY * 180.0f) / 32767.0f;
Precision: ~0.0055° per LSB
Distance Conversion
float distance_m = distance * 0.01f; // distance in cm
Position in TX Frame (0x0012)
3D position vector in transmitter coordinate frame
Structure
typedef struct __attribute__((packed)) {
uint8_t status; // Status bits
uint8_t counter; // Message counter
int16_t posX; // X position (cm)
int16_t posY; // Y position (cm)
int16_t posZ; // Z position (cm)
} __uls_blitz_msg_rx_pos_in_tx_frame;
Total Size: 8 bytes
Field Descriptions
| Field | Type | Range | Description |
|---|
| status | uint8_t | 0-255 | Status flags |
| counter | uint8_t | 0-255 | Packet counter |
| posX | int16_t | -32768 to +32767 | X position in cm (±327.68m) |
| posY | int16_t | -32768 to +32767 | Y position in cm (±327.68m) |
| posZ | int16_t | -32768 to +32767 | Z position in cm (±327.68m) |
Coordinate System
Transmitter Frame:
+Z (up)
|
|
+------- +X (forward)
/
/
+Y (right)
Position Conversion
float pos_x_m = posX * 0.01f; // cm to m
float pos_y_m = posY * 0.01f;
float pos_z_m = posZ * 0.01f;
Range: ±327.68 meters
Resolution: 1 cm
Multi-Receiver Extension data
Structure
typedef struct __attribute__((packed)) {
uint8_t status; // Status bits
uint8_t counter; // Message counter
int16_t yaw; // Yaw angle [-180° to +180°]
uint16_t distance; // Distance in cm
} __uls_blitz_msg_rx_mrx;
Total Size: 6 bytes (2 bytes unused in 8-byte payload)
Field Descriptions
| Field | Type | Range | Description |
|---|
| status | uint8_t | 0-255 | Status flags (bit 5: MRX_OK) |
| counter | uint8_t | 0-255 | Packet counter |
| yaw | int16_t | -32768 to +32767 | Yaw angle scaled to ±180° |
| distance | uint16_t | 0-65535 | Distance in cm |
Angular Conversion
float yaw_deg = (yaw * 180.0f) / 32767.0f;
Usage
MRX data is calculated from measurements of a secondary receiver, providing:
- Relative yaw orientation
- Distance between receivers
- Enhanced positioning accuracy
Directional Angle Calculation System with continuous yaw tracking
Structure
typedef struct __attribute__((packed)) {
uint8_t status; // Status bits
uint8_t counter; // Message counter
int16_t yaw; // Yaw angle [-180° to +180°]
uint16_t distance; // Distance in cm
int16_t yawContinuous; // Continuous yaw (±32768°)
} __uls_blitz_msg_rx_dacs;
Total Size: 8 bytes
Field Descriptions
| Field | Type | Range | Description |
|---|
| status | uint8_t | 0-255 | Status flags |
| counter | uint8_t | 0-255 | Packet counter |
| yaw | int16_t | -32768 to +32767 | Wrapped yaw ±180° |
| distance | uint16_t | 0-65535 | Distance in cm |
| yawContinuous | int16_t | -32768 to +32767 | Unwrapped yaw ±32768° |
Angular Conversion
Wrapped Yaw:
float yaw_deg = (yaw * 180.0f) / 32767.0f; // -180 to +180°
Continuous Yaw:
float yaw_continuous_deg = yawContinuous; // Direct degree value
Usage
DACS provides:
- Standard wrapped yaw for heading
- Continuous yaw for counting full rotations
- Useful for applications requiring rotation tracking beyond ±180°
Example:
Rotation: 450° clockwise
yaw = 90° (wrapped to ±180° range)
yawContinuous = 450°
Ground IMU Measurement (0x0020)
Inertial Measurement Unit data from ground transmitter
Structure
typedef struct __attribute__((packed)) {
uint8_t status; // Status bits
uint8_t counter; // Message counter
int16_t roll; // Roll angle [-180° to +180°]
int16_t pitch; // Pitch angle [-180° to +180°]
int16_t yaw; // Yaw angle [-180° to +180°]
} __uls_blitz_msg_gimu_data;
Total Size: 8 bytes
Field Descriptions
| Field | Type | Range | Description |
|---|
| status | uint8_t | 0-255 | Status flags (bit 4: GU_IMU_OK) |
| counter | uint8_t | 0-255 | Packet counter |
| roll | int16_t | -32768 to +32767 | Roll scaled to ±180° |
| pitch | int16_t | -32768 to +32767 | Pitch scaled to ±180° |
| yaw | int16_t | -32768 to +32767 | Yaw scaled to ±180° |
Angular Conversion
float roll_deg = (roll * 180.0f) / 32767.0f;
float pitch_deg = (pitch * 180.0f) / 32767.0f;
float yaw_deg = (yaw * 180.0f) / 32767.0f;
Precision: ~0.0055° per LSB
Orientation Convention
Euler Angles (Tait-Bryan convention):
Roll: Rotation about X-axis (forward)
Pitch: Rotation about Y-axis (right)
Yaw: Rotation about Z-axis (up)
IMU Override (0x0030)
Override IMU data on ground transmitter
Structure
typedef struct __attribute__((packed)) {
uint8_t control; // Control bits
uint8_t counter; // Message counter
int16_t roll; // Override roll [-180° to +180°]
int16_t pitch; // Override pitch [-180° to +180°]
int16_t yaw; // Override yaw [-180° to +180°]
} __uls_blitz_msg_xx_txc_imu_override;
Total Size: 8 bytes
Field Descriptions
| Field | Type | Range | Description |
|---|
| control | uint8_t | 0-255 | Control flags (see below) |
| counter | uint8_t | 0-255 | Packet counter |
| roll | int16_t | -32768 to +32767 | Override roll value |
| pitch | int16_t | -32768 to +32767 | Override pitch value |
| yaw | int16_t | -32768 to +32767 | Override yaw value |
Control Bits
| Bit | Name | Description |
|---|
| 0 | SET_ROLL | Enable roll override |
| 1 | SET_PITCH | Enable pitch override |
| 2 | SET_YAW | Enable yaw override |
| 3-7 | Reserved | Must be 0 |
Usage
// Override only yaw
msg.control = (1 << 2); // SET_YAW
msg.yaw = (45.0f * 32767.0f) / 180.0f; // 45 degrees
msg.roll = 0; // Ignored (SET_ROLL not set)
msg.pitch = 0; // Ignored (SET_PITCH not set)
Purpose:
- External orientation source (e.g., motion capture, GNSS heading)
- Calibration and testing
- Compass-less operation
Magnetometer Override (0x0031)
Override magnetometer data on ground transmitter
Structure
typedef struct __attribute__((packed)) {
uint8_t control; // Control bits
uint8_t counter; // Message counter
int16_t mx; // Magnetic field X component
int16_t my; // Magnetic field Y component
int16_t mz; // Magnetic field Z component
} __uls_blitz_msg_xx_txc_mag_override;
Total Size: 8 bytes
Field Descriptions
| Field | Type | Range | Description |
|---|
| control | uint8_t | 0-255 | Control flags (see below) |
| counter | uint8_t | 0-255 | Packet counter |
| mx | int16_t | -32768 to +32767 | X magnetic field component |
| my | int16_t | -32768 to +32767 | Y magnetic field component |
| mz | int16_t | -32768 to +32767 | Z magnetic field component |
Control Bits
| Bit | Name | Description |
|---|
| 0 | SET_OVERRIDE | Enable magnetometer override |
| 1-7 | Reserved | Must be 0 |
Magnetic Field Conversion
float mx_norm = (mx * 1.0f) / 32767.0f; // Normalized [-1.0, +1.0]
float my_norm = (my * 1.0f) / 32767.0f;
float mz_norm = (mz * 1.0f) / 32767.0f;
Purpose:
- External magnetometer source
- Magnetic interference compensation
- Indoor operation with external compass
Status Bits
Common Status Flags
Most Blitz messages include a status byte with these flags:
| Bit | Flag Name | Description |
|---|
| 0 | CARRIER_OK | IR carrier signal detected |
| 1 | SQ_OK | Signal quality acceptable |
| 2 | POS_OK | Position successfully estimated |
| 3 | VEL_OK | Velocity successfully estimated |
| 4 | GU_IMU_OK | Ground Unit IMU operational |
| 5 | MRX_OK | MRX message received from secondary receiver |
| 6 | MRXDATA_OK | MRX yaw and distance calculated successfully |
| 7 | Reserved | Reserved for future use |
Status Checking
bool check_measurement_valid(uint8_t status) {
// Check minimum required flags
if (!(status & (1 << 0))) return false; // No carrier
if (!(status & (1 << 1))) return false; // Poor signal quality
if (!(status & (1 << 2))) return false; // No position
return true;
}
bool has_velocity(uint8_t status) {
return (status & (1 << 3)) != 0;
}
bool has_mrx_data(uint8_t status) {
return ((status & (1 << 5)) != 0) && ((status & (1 << 6)) != 0);
}
Data Encoding
Integer Scaling
Angular Data (±180° range):
int16_t_value = (angle_degrees / 180.0) * 32767.0
angle_degrees = (int16_t_value / 32767.0) * 180.0
Position Data (cm):
int16_t_value = position_cm
position_m = int16_t_value * 0.01
Distance Data (cm):
uint16_t_value = distance_cm
distance_m = uint16_t_value * 0.01
Byte Order
All multi-byte fields use little-endian byte order
Example (int16_t = 0x1234):
Memory layout: [0x34, 0x12]
LSB MSB
Packing
All structures use __attribute__((packed)) to ensure:
- No padding bytes inserted by compiler
- Exact memory layout as defined
- Network transmission compatibility
Usage Examples
Sending Blitz Message
// Prepare raw measurement
__uls_blitz_msg_rx_raw_measurmet msg;
msg.status = calculate_status_bits();
msg.counter = packet_counter++;
msg.aX = (int16_t)((angle_x_deg / 180.0f) * 32767.0f);
msg.aY = (int16_t)((angle_y_deg / 180.0f) * 32767.0f);
msg.distance = (uint16_t)(distance_m * 100.0f); // m to cm
// Send via interface
interface->ifSendBLITZ(
ULS_BLITZ_MSG_ID_RX_RAW_MEASURMENT,
(uint8_t*)&msg,
sizeof(msg)
);
Receiving Blitz Message
// Set callback
interface->ifclbkBlitzReceived = &onBlitzReceived;
void onBlitzReceived(ULSBusInterface *iface) {
uint16_t msg_id = iface->ifRxPacket->blitz.msg_id;
uint8_t *data = iface->ifRxPacket->blitz.data;
switch(msg_id) {
case ULS_BLITZ_MSG_ID_RX_RAW_MEASURMENT: {
__uls_blitz_msg_rx_raw_measurmet *msg =
(__uls_blitz_msg_rx_raw_measurmet*)data;
// Check status
if (!(msg->status & 0x07)) break; // Check flags 0,1,2
// Extract data
float ax = (msg->aX / 32767.0f) * 180.0f;
float ay = (msg->aY / 32767.0f) * 180.0f;
float dist = msg->distance * 0.01f;
// Process measurement
process_measurement(ax, ay, dist);
break;
}
case ULS_BLITZ_MSG_ID_GIMU_MEASUREMENT: {
__uls_blitz_msg_gimu_data *msg =
(__uls_blitz_msg_gimu_data*)data;
float roll = (msg->roll / 32767.0f) * 180.0f;
float pitch = (msg->pitch / 32767.0f) * 180.0f;
float yaw = (msg->yaw / 32767.0f) * 180.0f;
update_orientation(roll, pitch, yaw);
break;
}
// Handle other message types...
}
}
IMU Override Example
// Override yaw angle from external source
void send_yaw_override(float yaw_deg) {
__uls_blitz_msg_xx_txc_imu_override msg;
msg.control = (1 << 2); // Enable yaw override only
msg.counter = counter++;
msg.roll = 0;
msg.pitch = 0;
msg.yaw = (int16_t)((yaw_deg / 180.0f) * 32767.0f);
interface->ifSendBLITZ(
ULS_BLITZ_MSG_ID_XX_TXC_IMU_OVERRIDE,
(uint8_t*)&msg,
sizeof(msg)
);
}
Update Rates
Typical Rates:
- Raw measurements: 100-200 Hz
- Position data: 100 Hz
- IMU data: 100-200 Hz
- MRX data: 50-100 Hz
Bandwidth Usage
Per Message:
- UART (COBS mode): ~13 bytes (11 data + 2 overhead)
- CAN: 1 frame (11 bytes in 8-byte frame + header)
Example Load (100 Hz, UART @ 115200 bps):
Bandwidth = 100 Hz × 13 bytes × 8 bits = 10.4 kbps
Utilization = 10.4 / 115.2 = 9%
Latency
End-to-end latency:
- Sensor → Encoding: <1 ms
- Transmission: 0.1-1 ms (depends on transport)
- Reception → Callback: <1 ms
- Total: 1-3 ms typical
Copyright © 2020-2026 UAVLAS <www.uavlas.com>
License: GNU Lesser General Public License v3.0+
5 - 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.
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:
| 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:
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)"
| 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):
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)"
| 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)
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
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:
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:
// 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
- Keep Messages Small: Use single-frame when possible
- Set Appropriate Timeouts: Balance responsiveness vs. robustness
- Monitor Pool Usage: Implement pool health checks
- Use Priority Wisely: Don’t overuse high priority
- Handle Bus-Off: Implement recovery mechanism
- Test Under Load: Verify behavior at high utilization
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)
See section 8 for detailed performance analysis.
Copyright © 2020-2026 UAVLAS <www.uavlas.com>
License: GNU Lesser General Public License v3.0+
6 - 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.
ULSDC-PBDS031125 · version 1.0.9 · issued November 13, 2025
The numbered PDF is the controlled copy: download it from the portal.
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
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) |
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:
serial.mode(SERIAL_MODE_RAW);
serial.write(data, length);
// Bytes transmitted directly
ESC Mode (CRC-16)
Frame Structure
---
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:
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):
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
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
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 counterReception Pseudocode
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
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:
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
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:
- Scan data for zero bytes
- For each segment between zeros:
- Count non-zero bytes (max 254)
- Insert code byte before segment
- Code byte = count + 1
- 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:
- Read code byte
- If code == 0x00: Frame delimiter (start/end)
- If code == 0xFF: Copy next 254 bytes as-is
- If code < 0xFF:
- Copy next (code-1) bytes
- Insert 0x00 after them (unless end of data)
- Repeat until frame delimiter
Pseudocode:
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
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:
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 counterReception with Validation:
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
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
uint32_t _crcerrors; // CRC validation failures
uint32_t _packeterrors; // Framing errors
Usage:
- Monitor link quality
- Detect noisy channels
- Trigger error recovery
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:
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
// 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
// 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+