4-Channel Pulse Timestamper

A four-channel zero-dead-time pulse timestamper with 4-nanosecond resolution. Plug it into your USB port, feed it pulses, and it streams a list of the times each pulse arrived, accurate to a few nanoseconds against your lab's reference clock.

The 4-channel timestamper.

Overview

The timestamper records the precise moment a pulse arrives on any of its four inputs and sends a stream of timestamps out over USB. Resolution is 4 nanoseconds, and the absolute accuracy is whatever your reference oscillator is — a rubidium standard, a GPS-disciplined oscillator, or any other 10 MHz source you trust.

It is zero-dead-time: the timescale never stops, restarts, or loses precision while the device is running. Every pulse you capture sits on the same continuous nanosecond-resolution timeline (the timeline starts at zero at power-up and counts forward indefinitely), whether the pulse arrived a microsecond or a year after the previous one.

The four channels are also coherent: they all sample against the same clock and the same counter, so a timestamp on CH 1 and a timestamp on CH 3 can be subtracted directly to get the true time difference between the two pulses — there is no per-channel clock or counter that could drift. That makes the device suitable for long unattended runs where you need to know not just when each pulse occurred but also how each pulse relates to every other pulse seen across all four channels.

Each channel can be independently configured for rising-edge, falling-edge, or both-edge capture (use both edges to time a pulse’s width directly) and for an optional integer divider that reports only every Nth pulse — useful for downsampling busy inputs or matching a reference channel’s rate. These settings are available through a small SCPI command set on the same USB connection.

It is also designed to be easy to use: the device connects over USB and enumerates as a standard virtual serial port on Linux, macOS, and Windows. There is nothing to install on the host — no driver, no companion app, no SDK — and the device emits plain ASCII text, one timestamp per line, that you can read with any terminal program or pipe directly into a script. The defaults (rising edge, divider 1, all four channels active) are sensible enough that no configuration is required for first-time use; the SCPI commands are there if and when you want them.

Basic usage

  1. Connect a 10 MHz reference clock (rubidium, GPS-disciplined, or any other stable source) to the REF IN input.
  2. Plug the device into a USB port on your computer. The 10 MHz indicator LED begins flashing once the reference clock is locked. The reference clock is checked once at boot, so if you change or reconnect the source after the device is already powered, press the RESET button to make it re-lock — no need to unplug USB.
  3. Open the virtual serial port the device created — /dev/ttyACM* on Linux, /dev/cu.usbmodem* on macOS, or a new COM port on Windows.
  4. Connect signals to any of the four SMA inputs (CH 1CH 4) and timestamps will stream to your terminal program over USB.

Pulse requirements

By default each channel timestamps the rising edge of each pulse — the moment the input goes from LOW to HIGH. The polarity is selectable per channel via the SCPI command INPut<n>:SLOPe POSitive|NEGative|BOTH (see Command interface below). Falling-edge mode captures HIGH→LOW transitions instead; both-edge mode captures both, which lets you measure pulse widths directly by subtracting consecutive timestamps on the same channel. The minimum reliable pulse width is 4 ns in any mode; anything narrower may be missed. Slow edges are tolerated thanks to the input’s ~250 mV of Schmitt-trigger hysteresis, but extremely slow ramps (below a few mV/ns) may produce duplicate triggers as the signal crosses the threshold band.

The input registers as low for voltages below ~1.0 V and as high for voltages above ~2.3 V, so a standard 3.3 V or 5 V CMOS-level pulse train works directly. Each input is protected by a 220 Ω series resistor and a BAT54S clamp to the 3.3 V rail and ground, which absorbs continuous inputs up to 12 V and brief overshoots well beyond that without damage.

Output format

The device sends a stream of plain ASCII text terminated by a single \n (LF) per line. No carriage returns, no binary framing, no driver. Output looks like this:

# Starting timestamper, version 9afaa32f
1 5293.585203496
1 5293.587201024
3 5293.601004112
1 5293.589198608
2 5293.601100008

Each timestamp line is <channel> <seconds>.<nanoseconds>. The seconds counter starts at zero when the device powers up and counts forward indefinitely (it doesn’t wrap until ~136 years). Channel numbers are 1 through 4.

Any line that starts with # is a status message rather than a timestamp:

  • # Starting timestamper, version <git-hash> — printed once after USB enumeration and the reference clock locks. The git hash identifies which firmware build is running.
  • # FATAL: External oscillator failure. Connect a 10MHz source and press reset. — printed once if the reference clock fails to start at boot, or drops out mid-run. The device stops emitting timestamps until reset.
  • # ch<N>: <X> overcaptures, <Y> buf overflows — printed when a channel’s pulse rate exceeded what the device could handle. The two counters are distinct failure modes. Overcaptures count pulses that arrived closer together than the minimum event interval, so a new capture stomped on the previous one before it could be recorded. Buf overflows count pulses dropped because the 16,384-timestamp internal buffer filled faster than USB could drain it. To eliminate overcaptures, space your pulses further apart; to eliminate buf overflows, reduce the average rate or split your work into bursts of ≤ 16,384 pulses with quiet periods between to let the buffer drain.

If the reference clock fails

The reference clock is checked once at boot and continuously monitored afterward. If the 10 MHz signal disappears or drifts out of range:

  1. The 10 MHz LED goes dark.
  2. The device emits the # FATAL line above on USB.
  3. Timestamping halts. No further timestamps appear, even if pulses are arriving on the inputs — the device refuses to operate without a trusted reference, since any timestamps it produced would be wrong.

To recover, restore the 10 MHz signal at the REF IN input and press RESET. There’s no need to unplug USB.

Indicator LEDs

10 MHz

  • Dark: reference clock missing or failed. The device will refuse to emit timestamps until you restore the reference and reset.
  • Continuous blink: reference clock locked, device is timing.

USB

  • Dark: nothing on the host has opened the device’s serial port — this is the state both when the cable is unplugged and when it’s plugged in but no program is reading
  • Solid: a host program has opened the port
  • Blinking: data is flowing to that host program

CH 1–CH 4 (one per channel)

  • Dark: no pulses arriving
  • Periodic blink: a pulse has arrived
  • Continuous blink: pulses are arriving at >5 Hz

Command interface

The timestamper accepts SCPI-style commands on the same USB CDC port it streams timestamps over. Sending commands is strictly optional: out of the box, the device starts streaming on plug-in with all four channels in rising-edge capture mode and no divider, and you never need to send anything to use it. Commands are only there for users who want to change those defaults — invert a channel’s edge polarity, set a divider, or suppress streaming during a synchronous query session.

Send a single command line (terminated with \n); the device responds with silence for setters, one line of data for queries, or latches an error that you can read back later. Commands are case-insensitive and accept either the standard SCPI long form or the abbreviated short form (the capital letters in each keyword name).

Standard IEEE 488.2 commands:

Command Effect
*IDN? Identity string.
*RST Reset all channels to defaults: rising-edge capture, divider 1.
*CLS Clear the latched error.
SYSTem:ERRor? (SYST:ERR?) Read and clear the most recent error. Returns 0,"No error" when nothing has gone wrong.

Per-channel input configuration (channel suffix <n> is 1, 2, 3, or 4; if you omit it the command operates on channel 1):

Command Effect
INPut<n>:SLOPe POSitive|NEGative|BOTH Choose rising-edge (default), falling-edge, or both-edge capture. EITHer is accepted as a synonym for BOTH. Both-edge mode timestamps every transition, so consecutive timestamps on the same channel give pulse widths directly.
INPut<n>:SLOPe? Returns POS, NEG, or BOTH.
INPut<n>:DIVider <N> Report only every Nth pulse on this channel. N=1 reports every pulse (the default).
INPut<n>:DIVider? Returns the current divider as a decimal integer.

Streaming output:

Command Effect
OUTPut:STATe ON|OFF (OUTP:STAT 1|0) Enable (default) or disable continuous timestamp output. With output disabled, hardware capture keeps running and the internal buffer keeps filling, but nothing streams to USB and missed-pulse reports are suppressed. Use this if you need clean SCPI request-response without timestamps interleaving. Re-enable to drain the backlog.
OUTPut:STATe? Returns 1 or 0.

Example session:

*IDN?
Lectrobox,Timestamper,0,9afaa32f

INP2:SLOP NEG
INP3:DIV 100

INP2:SLOP?
NEG
INP3:DIV?
100

*RST
INP2:SLOP?
POS
INP3:DIV?
1

Commands and timestamp data share the stream, so if you send a query while pulses are flowing, the response line will appear inline with the timestamps. To get a clean back-and-forth, send OUTPut:STATe OFF first to pause the timestamp stream, run your queries, then OUTPut:STATe ON to resume; the device buffers up to 16,384 timestamps internally during the pause and drains them when streaming resumes.

Terminal programs

Any program that can read a serial port works. All of the suggestions below are free and open-source.

  • Linux
    • cat /dev/ttyACM0 — quickest way to see if it’s working.
    • grabserial (pip install grabserial) — Python command-line tool that logs serial output with a host-side timestamp on each line; great for capture-to-file. Run as grabserial -d /dev/ttyACM0.
    • pyserial-miniterm /dev/ttyACM0 (comes with the pyserial package).
    • picocom — a small, friendly terminal: picocom /dev/ttyACM0.
    • minicom — older but common and capable.
    • screen /dev/ttyACM0 — built into most distros.
  • macOS
    • cat /dev/cu.usbmodem* — quick check.
    • screen /dev/cu.usbmodem* — built in.
    • grabserial and pyserial-miniterm install the same way as on Linux (via pip).
    • picocom and minicom are available via Homebrew: brew install picocom minicom.
  • Windows
    • PuTTY — pick “Serial,” type the COM port name (e.g. COM5), click Open.
    • Tera Term — open-source terminal with a clean serial-port dialog.
    • pyserial-miniterm COM5 and grabserial -d COM5 work the same as on Linux/macOS once you have Python installed.

Schematics, Software, and Other Sundries

The hardware design and firmware are open-source.

Specifications

Channels and timing

   
Channels 4 (SMA connectors)
Time resolution 4 ns
Minimum pulse width 4 ns
Time accuracy Limited by your 10 MHz reference. With a rubidium standard, parts in 1011.
Dead time Zero — every captured pulse sits on the same continuous timeline, indefinitely.
Minimum interval between pulses (per channel) 4 ns for the first 4,096 pulses in a burst; 150 ns up to 16,384 pulses; ~10,000 pulses/sec sustained (approximate, pending further USB-throughput testing).
Internal buffer 16,384 timestamps. If a burst exceeds this, the device drops the surplus and reports the loss as a # ch<N>: ... buf overflows line in the output stream — you always know if you missed pulses.

Inputs

   
Trigger slope Rising edge, falling edge, or both edges — selectable per channel via SCPI. Default rising.
Per-channel divider 1 to 4,294,967,295 (32-bit). With divider N, the channel reports only every Nth pulse. Default 1 (every pulse).
Input coupling DC
Input impedance High — chip-side is a CMOS input (essentially open-circuit DC, ~9 pF AC). A 220 Ω series resistor sits between each SMA and the input pin to limit current into the overvoltage clamp; under normal signal levels it does not affect impedance.
Maximum continuous input voltage 12 V. Each input has a 220 Ω series resistor and a BAT54S Schottky clamp to the 3.3 V rail and ground, so brief overshoots and reverse-polarity transients are absorbed without damage.
Input threshold CMOS Schmitt-trigger. Reads as low for inputs below ~1.0 V, high for inputs above ~2.3 V, with ~250 mV of hysteresis around the switching point.

Reference clock and host interface

   
Reference clock 10 MHz at the REF IN input. AC-coupled, so DC offset of the source doesn’t matter. Sine-wave sources (rubidium standards, GPS-disciplined oscillators) work from 0.2 V to 2.2 V peak-to-peak; square-wave / CMOS-output sources work up to 3.3 V peak-to-peak (rail-to-rail).
Interface USB 2.0 Full-Speed (12 Mbps), USB Type-C connector
Host OS support Enumerates as a CDC virtual serial port on Linux (/dev/ttyACM*), macOS (/dev/cu.usbmodem*), and Windows (a new COM port). No driver install required.

Physical and environmental

   
Power USB bus-powered (~50 mA)
Operating temperature -40°C to +85°C (industrial range)
Dimensions 100 mm tall × 46 mm wide. Four M3 mounting holes on a 94 mm × 40 mm pattern.

Join the Conversation