Radar Data Protocol Packet Structure and Frame Format in AERIS‑10 LFM Radar
The AERIS‑10 radar system uses a compact binary protocol with a fixed 17‑byte header, variable‑length IQ payload, and trailing CRC‑16, enabling high‑throughput streaming from FPGA to host PC via USB.
The radar data protocol defines how raw detection data travels from the FPGA‑based front‑end to the Python GUI host application. This binary format prioritizes speed and simplicity, supporting data rates up to several hundred MB/s while remaining parseable with minimal struct.unpack operations. The authoritative implementation resides in the UART capture utility and system architecture documentation within the PLFM_RADAR repository.
Protocol Overview and Design Goals
The AERIS‑10 radar, developed in the NawfalMotii79/PLFM_RADAR open‑source project, streams chirp IQ samples through a lightweight frame structure. Each frame contains one or more packets, where each packet carries:
- A fixed preamble for resynchronization
- Explicit length for boundary detection
- Timing metadata for range‑Doppler processing
- Integrity checking via fast CRC‑16
This design eliminates protocol overhead while supporting variable payload sizes—critical when different pulse repetition frequencies (PRFs) require different sample counts.
Packet Structure Byte‑by‑Byte
The binary layout uses little‑endian encoding throughout. The following table specifies the complete radar data protocol packet structure:
| Offset (bytes) | Length (bytes) | Field | Description |
|---|---|---|---|
| 0 | 2 | SOF (Start‑of‑Frame) | Fixed value 0xAA55 identifying packet start |
| 2 | 2 | Packet Length | Total bytes including header and CRC |
| 4 | 1 | Packet Type | 0x01 = IQ data, 0x02 = status, 0x03 = metadata |
| 5 | 8 | Timestamp | 64‑bit unsigned integer (FPGA time‑base, nanoseconds) |
| 13 | 2 | Chirp ID | Sequence number within current burst |
| 15 | 2 | Num Samples | Count of IQ sample pairs (each sample = 4 bytes) |
| 17 | Num Samples × 4 |
IQ Payload | Interleaved I and Q values (int16 each) |
| 17 + payload | 2 | CRC‑16 | CCITT‑XModem checksum over SOF through last payload byte |
The IQ payload uses signed 16‑bit two's‑complement values, matching the ADC output format of the FMC‑ADC on the FPGA. This direct mapping eliminates conversion overhead in the data path.
Frame Format and Multi‑Packet Sequences
A frame in the radar frame format may concatenate multiple packets—for example, a burst of 64 chirps transmitted as sequential packets. The host parsing algorithm:
- Read the
Packet Lengthfield (bytes 2–3) - Extract exactly that many bytes from the stream
- Validate the trailing CRC‑16
- Proceed to the next packet
This length‑prefixed approach ensures robust recovery even when payload sizes vary between chirps. The implementation in 9_Firmware/tools/uart_capture.py demonstrates this streaming parser pattern.
Reference Implementation: Parsing Code
The Python GUI and capture utilities use identical parsing logic. Below is the authoritative implementation from the repository, packaged for standalone use:
import struct
SOF = 0xAA55
HEADER_FMT = "<HHBQIHH" # little-endian: SOF, length, type, timestamp, chirp_id, num_samples
HEADER_SIZE = struct.calcsize(HEADER_FMT)
def crc16(data: bytes) -> int:
"""CCITT-XModem polynomial 0x1021."""
crc = 0x0000
for b in data:
crc ^= b << 8
for _ in range(8):
crc = (crc << 1) ^ 0x1021 if (crc & 0x8000) else crc << 1
crc &= 0xFFFF
return crc
def parse_packet(buf: bytes):
"""
Parse one radar packet from the front of *buf*.
Returns (packet_dict, remaining_buffer).
"""
if len(buf) < HEADER_SIZE:
raise ValueError("Buffer too short for header")
sof, length, ptype, ts, chirp_id, n_samples = struct.unpack_from(HEADER_FMT, buf)
if sof != SOF:
raise ValueError(f"Bad SOF: {hex(sof)}")
if len(buf) < length:
raise ValueError("Incomplete packet")
payload = buf[HEADER_SIZE:length - 2] # exclude CRC
crc_received = struct.unpack_from("<H", buf, length - 2)[0]
if crc16(buf[:length - 2]) != crc_received:
raise ValueError("CRC mismatch")
iq = struct.unpack("<" + "hh" * n_samples, payload) # interleaved I,Q
packet = {
"type": ptype,
"timestamp": ts,
"chirp_id": chirp_id,
"samples": iq,
}
return packet, buf[length:]
For generating test data, the matching packet builder follows the same field order and CRC calculation:
def make_packet(ptype: int, ts: int, chirp_id: int, iq_samples):
"""Generate a complete radar packet with proper CRC."""
n = len(iq_samples) // 2
payload = struct.pack("<" + "hh" * n, *iq_samples)
length = HEADER_SIZE + len(payload) + 2 # +2 for CRC
header = struct.pack(HEADER_FMT, SOF, length, ptype, ts, chirp_id, n)
pkt_without_crc = header + payload
crc = crc16(pkt_without_crc)
return pkt_without_crc + struct.pack("<H", crc)
# Example: create dummy packet with 128 IQ pairs
dummy = make_packet(1, 12345678, 0, [0, 0] * 128)
Key Design Rationale
Each field in the radar data protocol serves a specific purpose in the high‑speed streaming architecture:
- SOF (
0xAA55) — Enables byte‑level resynchronization after USB buffer overruns or cable disconnects - Length field — Supports variable chirp lengths without delimiter scanning
- Timestamp — Preserves absolute nanosecond timing for coherent processing algorithms
- Chirp ID — Detects dropped chirps or FPGA/host clock drift
- CRC‑16 — Provides error detection at ~99.998% coverage without cryptographic overhead
The protocol intentionally avoids complex headers (no version fields, no flags) to maximize throughput. Extensibility is handled through the Packet Type byte, with three defined types and room for custom extensions.
Source Files and Documentation
The following files in NawfalMotii79/PLFM_RADAR contain authoritative definitions:
| File | Purpose |
|---|---|
9_Firmware/tools/uart_capture.py |
Reference parser implementation with CRC routine and streaming logic |
docs/architecture.html |
System data flow diagram expanding the USB→Host PC packet format |
docs/implementation-log.html |
Firmware revision history including timestamp field addition (v0.3) |
README.md |
High‑level stack overview linking to packet format documentation |
Summary
- The radar data protocol uses a 17‑byte fixed header with little‑endian fields and variable IQ payload
- SOF marker
0xAA55and explicit Packet Length enable robust stream synchronization - 64‑bit timestamps in nanoseconds preserve absolute timing for signal processing
- CRC‑16 (CCITT‑XModem) provides lightweight integrity checking without protocol overhead
- Packet Type byte supports IQ data, status, metadata, and future extensions
- Reference parser in
uart_capture.pydemonstrates production‑ready implementation
Frequently Asked Questions
What is the purpose of the SOF marker in the radar packet format?
The Start‑of‑Frame marker (0xAA55) enables the receiver to resynchronize after byte‑level stream corruption. Since USB bulk transfers may experience buffer overruns or cable events, the fixed two‑byte pattern allows the parser to scan forward and re‑establish packet boundaries without protocol reset.
How does the protocol handle variable chirp lengths?
The Num Samples field (bytes 15–16) declares the payload size, and Packet Length (bytes 2–3) declares the total packet size. This dual specification lets the host read exactly the required bytes without scanning for delimiters, supporting different PRF configurations in the same data stream.
What CRC algorithm does the AERIS‑10 radar protocol use?
The protocol implements CRC‑16 with CCITT‑XModem polynomial 0x1021. This checksum covers all bytes from SOF through the final payload byte. The reference implementation in uart_capture.py computes this incrementally for streaming validation.
Can the packet format be extended for custom sensor data?
Yes. The Packet Type byte (byte 4) defines 0x01 for IQ data, 0x02 for status, and 0x03 for metadata, with values 0x04–0xFF reserved for custom extensions. New types must maintain the same header structure through the Num Samples field, but may reinterpret the payload portion.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →