Skip to content

Packet Protocol

The firmware communicates with the daemon over a fixed-length binary protocol. There are two packet types: the high-frequency ADC Data packet and the one-shot Settings Handshake.


Emitted at: configured sample rate (default 100 Hz)
Length: 18 bytes

OffsetBytesTypeFieldValue
01uint8header_10xAA
11uint8header_20xBB
24int32_lech0Vertical (Z) — signed 24-bit, sign-extended
64int32_lech1North (N)
104int32_lech2East (E)
144uint32_lecrcCRC-32 of bytes 0–13
import struct
from binascii import crc32
PACKET_FORMAT = "<BBiiiI" # 18 bytes
PACKET_SIZE = struct.calcsize(PACKET_FORMAT) # → 18
header_1, header_2, ch0, ch1, ch2, rx_crc = struct.unpack(PACKET_FORMAT, data)
payload = data[:-4]
calc_crc = crc32(payload) & 0xFFFFFFFF
valid = (calc_crc == rx_crc)

The ADS1256 outputs a signed 24-bit integer, sign-extended to 32 bits before packing:

CodeValue
0x007FFFFF+8,388,607 (positive full-scale)
0x00000000Zero (ground)
0xFF800000−8,388,608 (negative full-scale)

Emitted: once by the daemon on startup; echoed back by the MCU.
Length: 6 bytes

OffsetBytesTypeFieldDescription
01uint8header_10xCC
11uint8header_20xDD
22uint16_lesampling_speedTarget output rate in Hz
41uint8adc_gainPGA enum (0–6)
51uint8adc_data_rateADS1256 DRATE enum (0–15)
  1. Daemon opens the serial port (this may reset AVR/STM32 boards via DTR).
  2. Daemon waits 2 s for the MCU to finish booting and initialise the ADS1256 registers.
  3. Daemon transmits the 6-byte MCUSettingsFrame.
  4. MCU applies the settings and echoes the identical 6 bytes.
  5. Daemon waits up to 10 s, scanning for the 0xCC 0xDD header to align the response.
  6. If the echo matches → streaming starts.
    If no echo within 10 s → MCUNoResponse exception, daemon exits.
PACKET_FORMAT = "<BBHBB" # 6 bytes
struct.pack(PACKET_FORMAT, 0xCC, 0xDD, sampling_rate, adc_gain, adc_data_rate)

The daemon sends a single byte 0x01 every 500 ms to prove the link is alive. The MCU pauses streaming if no heartbeat arrives within ~1 s, preventing the UART TX buffer from overflowing into a dead host.


If the RS-422 link is noisy or the daemon restarts mid-packet, the receive buffer will be misaligned. The daemon uses a sliding-window strategy to re-sync:

while len(buffer) >= PACKET_SIZE:
if buffer[0] == 0xAA and buffer[1] == 0xBB:
process_packet(buffer[:PACKET_SIZE])
del buffer[:PACKET_SIZE]
else:
del buffer[0] # shift one byte and try again

A CRC failure also causes the daemon to discard only that packet and keep looking — it does not flush the whole buffer.


At 250,000 bps (8-N-1) transmitting 18 bytes:

StageTime
Packet TX (144 bits)0.576 ms
USB-Serial adapter latency~0.2 ms
Daemon parse + queue~0.1 ms
Total latency~0.88 ms
Budget at 100 Hz10 ms
Headroom~9.1 ms