Hardware-timed vs software-timed sampling

With 100 µs RMS of timing jitter, software-timed sampling limits a 1 kHz signal to about 4 dB SNR, while a hardware pacer keeps sample spacing within its timebase’s ppm rating.

Britt Espinosa··6 min read

In hardware-timed acquisition (MCC says hardware paced, NI says sample clock), a clock on the device triggers every conversion. In software-timed acquisition (also called on-demand, static or command-response) your program asks for one reading at a time, and the sample is taken whenever that request arrives. Only hardware timing guarantees evenly spaced samples.

What happens in hardware-timed acquisition

The device divides a fixed timebase down to your sample rate with a divider called the pacer. Each pacer tick starts a conversion, and the result goes into a FIFO on the device. The driver empties the FIFO over USB in blocks, and your program reads blocks from a buffer in host memory. The computer can be tens of milliseconds late without affecting sample timing, as long as the FIFO doesn’t overflow.

The FIFOs are small. The USB-1608G has a 4 kS analog input FIFO, the NI USB-6001 2,047 samples, the USB-6008 512 bytes. At the USB-1608G’s 250 kS/s maximum, 4,096 samples is 16 ms of data, so the driver drains it continuously in the background and reports an overrun error if it falls behind.

The timebase then sets timing accuracy. NI specifies the USB-6001 timebase at 80 MHz with ±100 ppm accuracy and 12.5 ns resolution. A 100 ppm error moves a 1 kS/s rate to 999.9 or 1000.1 S/s. The error is a constant scale factor, and the spacing from one sample to the next stays even.

What happens in software-timed acquisition

A loop on the host sleeps, wakes, sends a request over USB, waits for the reply and stores the value. Every step adds variable delay: the OS timer’s wake-up precision, the scheduler, USB polling intervals (1 ms frames at full speed, 125 µs microframes at high speed) and whatever else the computer is doing. MCC’s datasheets show the gap. The USB-1608G lists software-paced analog input at “33 to 4000 S/s typ, system dependent” next to 250 kS/s hardware paced. The USB-1208FS-Plus lists 250 S/s software paced against 50 kS/s hardware paced.

OS timers on Windows, macOS and Linux

No desktop operating system promises when a sleeping thread wakes up.

  • Windows. Microsoft’s documentation for Sleep says the system clock ticks at a constant rate and that a wait longer than one tick but shorter than two can last anywhere between one and two ticks. timeBeginPeriod raises the timer resolution, but since Windows 10 version 2004 it only affects the process that calls it, and since Windows 11 Windows no longer guarantees the higher resolution once a window-owning process is minimized or fully hidden. Python 3.11 and later use a high-resolution waitable timer for time.sleep on Windows 10 and newer, which the Python docs give as 100 ns resolution. The scheduler can still wake the thread late.
  • macOS. App Nap applies timer throttling, which Apple describes as reducing how often an app’s timers fire, when an app isn’t in the foreground, hasn’t updated visible content recently, isn’t playing audio and holds no power assertion. A logging script in a background Terminal window qualifies.
  • Linux. Python’s time.sleep uses clock_nanosleep, with nanosecond resolution in the API and wake-up latency set by the kernel and load. The PREEMPT_RT real-time patches were merged into mainline in Linux 6.12, which tightens worst-case latency on kernels built with that option.

Python’s docs say the suspension “may be longer than requested by an arbitrary amount.” Measure your own machine:

import time, statistics

period, stamps = 0.010, []
t_next = time.perf_counter()
for _ in range(2000):
    t_next += period
    time.sleep(max(0.0, t_next - time.perf_counter()))
    stamps.append(time.perf_counter())       # read one sample here
d = [(b - a) * 1e3 for a, b in zip(stamps, stamps[1:])]
print(f"mean {statistics.mean(d):.3f} ms, sd {statistics.stdev(d):.3f} ms, "
      f"min {min(d):.3f} ms, max {max(d):.3f} ms")

Run it with the machine idle, then with a browser and a plot running, and compare the maximums. This only measures when the host asked. The conversion happens later, after a USB round trip.

Which devices are software-timed, and for which functions

Inexpensive devices often hardware-time their analog inputs but leave outputs or digital lines software-timed, so check the timing of the function you need, not the headline rate.

DeviceAnalog inAnalog outDigital I/O
NI USB-6001Hardware-timed, 20 kS/s aggregateHardware-timed, 5 kS/s per channelStatic (software)
NI USB-6008Hardware-timed, 10 kS/s aggregateSoftware-timed, 150 HzNo hardware timing listed
MCC USB-1208FS-Plus50 kS/s hardware paced; 250 S/s typ software paced50 kS/s per channel hardware paced; 250 S/s software pacedNo paced rate listed
MCC USB-1608G250 kS/s hardware paced; 33 to 4000 S/s software pacedNone (GX-2AO model adds outputs)33 to 8000 reads/writes per second, system paced
LabJack T7Stream mode, up to 100 kS/s; command-response otherwiseCommand-response, or hardware-timed stream-out to DAC0, DAC1 and digital output states

LabJack’s datasheet says command-response transfer “is paced by the client software” and is generally used at 1,000 scans per second or slower, while stream mode “is paced by hardware on the LabJack device.” For a T7 over USB, LabJack lists about 0.6 ms of overhead per command-response call before conversion time is added.

What timing errors do to FFTs and derivatives

An FFT assumes samples are exactly evenly spaced. Random timing error with an RMS value of σt seconds on a sine of frequency f limits the signal-to-noise ratio to about

SNR = -20 × log10(2π × f × σt)  dB

With 100 µs RMS of timing jitter, a 50 Hz signal is limited to about 30 dB, and a 1 kHz signal to about 4 dB, at which point the spectrum is mostly noise. That noise spreads across the whole spectrum and buries small harmonics. A constant rate error shifts peaks instead. If the real average rate is 2% lower than the one you assumed, every peak lands 2% off in frequency. Resample software-timed data onto an even grid using its real timestamps before an FFT, or use a method built for uneven spacing such as scipy.signal.lombscargle.

A rate of change computed as Δv/Δt with an assumed 10 ms interval is 20% wrong whenever the real interval was 12 ms, on top of the noise that differentiation amplifies. When you differentiate software-timed data, divide by the measured time differences, never the nominal period. Check any sample rate against the signal bandwidth with the sample rate calculator. Aliasing is a separate problem, covered in sample rate and aliasing.

How to tell which mode you are in

  • The API call. Scan or stream functions (a_in_scan in uldaq and mcculw, eStreamStart in LJM, a task with cfg_samp_clk_timing in nidaqmx) are hardware-timed. Single-value calls in a loop (a_in, eReadName, a task with no timing configured) are software-timed.
  • Ask the driver. In uldaq, ai_device.get_info().has_pacer() says whether the device can pace analog input at all. In nidaqmx, task.timing.samp_timing_type returns SampleTimingType.ON_DEMAND for software-timed tasks.
  • Read the datasheet wording. “Software paced,” “system dependent,” “static” and “on demand” all mean the host sets the timing.
  • Look at the file. Hardware-timed recordings usually store a start time and a rate. Logs with a separate timestamp for every row, whose differences wander, were almost certainly software-timed.
  • Test it. Feed a clean sine from a function generator and look at the FFT. A narrow peak on a low noise floor means hardware timing. A raised, uneven floor with smeared peaks means software timing.

DAQAtlas, which is in development, uses hardware-paced scans wherever a device has a pacer and software-timed polling only where it doesn’t. The beta list is open.