Running MCC DAQ devices on Linux and a Raspberry Pi
MCC’s open-source uldaq library runs Measurement Computing USB and Ethernet DAQ devices on desktop Linux and Raspberry Pi OS, while the DAQ HATs on a Pi’s GPIO header need MCC’s separate daqhats library.
Measurement Computing (MCC) supports Linux through uldaq, its Universal Library for Linux and macOS. It’s open source on GitHub, reaches most current MCC USB and Ethernet devices through libusb, and has a Python wrapper on PyPI. The steps below work on Ubuntu or Fedora desktops and on Raspberry Pi OS, which MCC’s README lists among the Debian-based distributions it covers.
The latest release at the time of writing is uldaq 1.2.2, dated September 16, 2026 in the changelog. It adds a packet-size check for network devices (CVE-2026-19477), so update older installs that talk to MCC’s Ethernet hardware. The Python package on PyPI is at version 1.2.3.
Build and install uldaq
uldaq builds from source with a C/C++ compiler, make and the libusb development headers. On Debian, Ubuntu and Raspberry Pi OS:
sudo apt-get install gcc g++ make libusb-1.0-0-dev
wget -N https://github.com/mccdaq/uldaq/releases/download/v1.2.2/libuldaq-1.2.2.tar.bz2
tar -xvjf libuldaq-1.2.2.tar.bz2
cd libuldaq-1.2.2
./configure && make
sudo make install
Fedora and other Red Hat-based systems need gcc gcc-c++ make libusbx-devel instead, and Arch needs gcc make libusb. The build steps are the same. The tarball also includes C examples. After the build, cd examples && ./AIn reads a few analog inputs from the first device it finds, which confirms the hardware works before you bring in Python.
sudo make install copies libuldaq into /usr/local/lib, installs a udev rules file and reloads udev. On Red Hat and Arch systems it also adds /usr/local/lib to the linker path and runs ldconfig.
USB permissions and the udev rules file
Linux only lets root open raw USB devices unless a udev rule says otherwise. uldaq installs /lib/udev/rules.d/50-uldaq.rules, which has one line per supported product under MCC’s USB vendor ID, 09db, each setting mode 0666. The first line looks like this:
SUBSYSTEM=="usb", ATTR{idVendor}=="09db", ATTR{idProduct}=="0076", GROUP="adm", MODE="0666"
If lsusb shows the device but uldaq finds nothing, or opening it fails for a normal user, work through these in order:
- Check that 50-uldaq.rules exists in /lib/udev/rules.d/. If you installed to a custom prefix or copied the library by hand, copy
rules/50-uldaq.rulesfrom the source tree yourself. - Run
sudo udevadm control --reload-rules, then unplug and replug the device. Rules apply when the device enumerates, so a device that was already plugged in keeps its old permissions. - Because the rules list product IDs one at a time, hardware newer than your uldaq release won’t match until you update.
Running scripts with sudo gets past the error but leaves the permissions broken and your data files owned by root.
The uldaq Python package
The uldaq package is a ctypes wrapper that loads the C library from the previous step (libuldaq.so on Linux). If import uldaq fails with an OSError about that file, run sudo ldconfig and try again.
Recent Debian and Raspberry Pi OS releases mark the system Python as externally managed, so a bare pip install is refused. Use a virtual environment:
python3 -m venv ~/daq
~/daq/bin/pip install uldaq
A hardware-paced scan in Python
This script opens the first USB device, checks that its analog input has a hardware pacer, and runs a finite scan of four channels at 1 kS/s per channel. uldaq fills the buffer in the background while the loop polls the scan status, as in MCC’s a_in_scan.py example.
import time
from uldaq import (get_daq_device_inventory, DaqDevice, InterfaceType,
AiInputMode, Range, ScanOption, AInScanFlag,
ScanStatus, create_float_buffer)
descriptors = get_daq_device_inventory(InterfaceType.USB)
if not descriptors:
raise SystemExit("No MCC USB devices found")
dev = DaqDevice(descriptors[0])
ai = dev.get_ai_device()
if ai is None or not ai.get_info().has_pacer():
raise SystemExit("No hardware-paced analog input on this device")
dev.connect()
low, high, per_chan = 0, 3, 5000 # 4 channels, 5 s at 1 kS/s
buf = create_float_buffer(high - low + 1, per_chan)
rate = ai.a_in_scan(low, high, AiInputMode.SINGLE_ENDED, Range.BIP10VOLTS,
per_chan, 1000.0, ScanOption.DEFAULTIO,
AInScanFlag.DEFAULT, buf)
try:
while True:
status, xfer = ai.get_scan_status()
print(f"{xfer.current_scan_count} of {per_chan} scans")
if status != ScanStatus.RUNNING:
break
time.sleep(0.5)
finally:
if ai.get_scan_status()[0] == ScanStatus.RUNNING:
ai.scan_stop()
dev.disconnect()
dev.release()
The buffer is interleaved, so channel c of scan i is buf[i * 4 + c]. a_in_scan returns the rate the pacer achieved, which can differ slightly from the request. Store that rate with your data. Not every device offers every range in every input mode, so check ai.get_info().get_ranges(AiInputMode.SINGLE_ENDED) before hard-coding BIP10VOLTS.
For a continuous run, pass ScanOption.CONTINUOUS. The buffer becomes circular, xfer.current_index points at the newest scan, and your loop has to copy data out before uldaq wraps around and overwrites it. A device with no analog input pacer (has_pacer() returns False) can only be read one value at a time with a_in() on a timer in your own code, with the timing problems described in hardware-timed vs software-timed sampling. Disk writing, threading and plotting around this loop are in Python data acquisition across vendors.
Which MCC devices uldaq supports
Supported models are listed in the device ID table in uldaq’s source (src/DaqDeviceId.h). By family:
| Family | Models in uldaq 1.2.2 |
|---|---|
| Low-cost multifunction | USB-1208FS-Plus, USB-1408FS-Plus, USB-1608FS-Plus, USB-201, USB-202, USB-204, USB-205 |
| High-speed multifunction | USB-1208HS series, USB-1608G/GX series, USB-1608HS series, USB-1808, USB-1808X, USB-2020 |
| Temperature and precision voltage | USB-TC, USB-TC-AI, USB-TEMP, USB-TEMP-AI, USB-2001-TC, USB-2408 and USB-2416 series, TC-32 |
| Analog output | USB-3101 through USB-3114 |
| Counters and encoders | USB-CTR04, USB-CTR08, USB-QUAD08 |
| Digital I/O and relays | USB-DIO24 and DIO24H, USB-DIO32HS, USB-DIO96H, USB-1024LS/HLS, USB-ERB08/24, USB-SSR08/24, USB-PDISO8 |
| Multifunction 26xx | USB-2623, USB-2627, USB-2633, USB-2637 |
| Ethernet | E-1608, E-DIO24, E-TC, TC-32 |
The USB-231 isn’t in the table, so uldaq can’t open it, and neither are the older non-Plus USB-1208FS and USB-1608FS. A model missing from the table stays invisible to uldaq whatever the udev rules say. Ethernet devices are found with get_daq_device_inventory(InterfaceType.ETHERNET) and need no udev rule.
Raspberry Pi specifics
Because uldaq compiles from source, the steps above work on the 32-bit and 64-bit Raspberry Pi OS images. Most Pi 4 and Pi 5 setups use the 64-bit image.
Raspberry Pi’s white paper on USB power for the Pi 5 says that unless the board negotiates 5 V at 5 A over USB Power Delivery, it limits the total current for all its USB ports to 600 mA. With a 5 A supply (or usb_max_current_enable=1 in config.txt) the limit is 1.6 A. A USB-1608G draws 230 mA quiescent and a USB-1808X 305 mA by MCC’s specs, before any load on their outputs, so two devices on a 3 A supply already use most of the budget. Use the official 5 A supply or a powered USB hub.
Write long recordings to a USB SSD rather than the SD card. Four channels at 100 kS/s of 64-bit floats is 3.2 MB/s, or about 11.5 GB per hour. The DAQ file size calculator does this math for other rates and formats.
MCC DAQ HATs use a different library
MCC’s DAQ HATs plug into the Pi’s 40-pin GPIO header and use their own library, daqhats, instead of uldaq. MCC lists five boards:
| HAT | Function | Resolution | Rate |
|---|---|---|---|
| MCC 118 | 8 single-ended voltage inputs, ±10 V | 12-bit | 100 kS/s A/D |
| MCC 128 | 8 single-ended or 4 differential voltage inputs | 16-bit | 100 kS/s A/D |
| MCC 134 | 4 thermocouple inputs (J, K, R, S, T, N, E, B) | 24-bit | Low rate, for temperature |
| MCC 152 | 2 analog outputs, 8 digital I/O | 12-bit D/A | n/a |
| MCC 172 | 2 AC-coupled differential inputs, ±5 V | 24-bit | 51.2 kS/s per channel |
Up to eight HATs can stack on one Pi, each at a unique address, and one board must sit at address 0 so the OS reads its EEPROM. Install by cloning https://github.com/mccdaq/daqhats, running sudo ./install.sh in it, then pip install daqhats into your virtual environment. MCC warns that GPIO-header LCD displays usually use the same SPI bus and stop the HATs from working, even after the display is removed, if its driver is still loaded from /boot/config.txt. A finite scan on an MCC 118 looks like this:
from daqhats import mcc118, hat_list, HatIDs, OptionFlags
board = hat_list(filter_by_id=HatIDs.MCC_118)[0]
hat = mcc118(board.address)
hat.a_in_scan_start(0b0011, 1000, 1000.0, OptionFlags.DEFAULT) # ch 0-1
result = hat.a_in_scan_read(1000, 5.0) # 1000 samples per channel, 5 s timeout
hat.a_in_scan_cleanup()
print(len(result.data), "values, overrun:", result.buffer_overrun)
HATs suit a compact build that only ever runs on a Pi. A USB device is the better fit when the same hardware also goes on a Windows or Mac laptop, or when you need the higher rates of the USB-1608G and USB-1808 lines. DAQAtlas, which is in development, has a Linux build that runs uldaq-supported MCC devices without acquisition code of your own, and the beta list is open. For macOS, see running MCC hardware on macOS.