Projects / 32-Channel Test Stand Telemetry DAQ

32-Channel Test Stand Telemetry DAQ

A ruggedized data acquisition board for propulsion and structural test benches — 32 analog channels, hardware timestamping, and a Python host stack.

Embedded FirmwarePCB DesignTest & VerificationMechanical & Enclosure CAD
Render of the telemetry data acquisition board
Channels32 analog
Sample rate50 kS/s per ch.
Resolution24-bit ΔΣ
Timestamp jitter< 40 ns

Overview

Test-stand data is only as good as its time base. This board acquires 32 differential channels — bridge, thermocouple, and voltage — with a hardware time base disciplined to an external trigger, so pressure, thrust, and strain from separate boxes can actually be overlaid on the same axis afterwards.

Year2024
RoleHardware and firmware design, host software
DisciplinesEmbedded Firmware, PCB Design, Test & Verification, Mechanical & Enclosure CAD

Signal chain

Each channel is a differential input with programmable gain, an anti-alias filter set for the channel's configured rate, and a 24-bit delta-sigma converter. Excitation for bridge channels is generated on-board and sensed ratiometrically so that excitation drift cancels out of the measurement instead of showing up as a slow strain signal.

Specifications

Channels32 differential, individually configurable
Input types±10 V, bridge (350 Ω / 1 kΩ), type K/T/J thermocouple
Resolution24-bit ΔΣ, 50 kS/s per channel
Isolation1 kV channel-to-chassis
Time baseTCXO disciplined to external 1 PPS or trigger
InterfaceGigabit Ethernet, UDP streaming + TCP control
StorageLocal ring buffer to microSD on link loss
Power18 – 36 V DC, 11 W typical
DAQ board layout with annotated regions
Scroll to zoom · drag to pan · click a marker 100%

Select a numbered marker for the design rationale behind that region.

Analog front end on the left, digital and network on the right, with a single-point ground tie at the isolation barrier.
stream_client.py
"""Host-side reader for the DAQ UDP stream.

Frames are fixed-size so that a dropped packet costs exactly one frame and
never desynchronises the decoder. Sequence numbers are used to detect gaps
and to reconstruct the time base without trusting host arrival time.
"""

import socket
import struct
import numpy as np

FRAME = struct.Struct("<IQH H 32i")   # seq, t_ns, flags, crc, 32 samples
CHANNELS = 32


def stream(host: str = "0.0.0.0", port: int = 5005, seconds: float = 10.0):
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    sock.bind((host, port))
    sock.settimeout(1.0)

    samples, stamps, expected, dropped = [], [], None, 0

    while len(stamps) < int(seconds * 50_000):
        seq, t_ns, flags, _crc, *chans = FRAME.unpack(sock.recv(FRAME.size))

        if expected is not None and seq != expected:
            dropped += (seq - expected) & 0xFFFFFFFF
        expected = (seq + 1) & 0xFFFFFFFF

        stamps.append(t_ns)
        samples.append(chans)

    data = np.asarray(samples, dtype=np.int32).reshape(-1, CHANNELS)
    t = (np.asarray(stamps, dtype=np.uint64) - stamps[0]) * 1e-9
    return t, data * (10.0 / (1 << 23)), dropped   # volts, dropped frames
Host-side stream reader — decodes the UDP frame format and reassembles per-channel arrays with corrected timestamps.

Verified performance

Noise floor1.4 µV RMSShorted input, 1 kS/s, gain 128
Channel crosstalk< −118 dBAdjacent channel, 1 kHz
Timestamp jitter38 nsRMS, against external 1 PPS
Sustained throughput38 Mb/s32 channels at 50 kS/s, zero drops over 6 h

Questions about this design? I am happy to walk through the trade studies, the measurements, or anything I glossed over here — drakeajoseph@gmail.com.