PG-4 — 4-Channel Pulse Generator
Contents
- Overview
- Connecting to a host via USB
- Basic usage
- How pulse timing works
- Indicator LEDs
- Command interface
- pgctl: a one-file Python utility
- Updating the firmware
- Comparison to similar instruments
- Specifications
- Schematics, Software, and Other Sundries
Overview
The PG-4 is a four-channel pulse generator. Each channel produces a repeating pulse train whose period, high-time (width), and starting phase (delay) you set independently, from a period of about 24 nanoseconds (≈ 41 MHz) all the way out to about 34 seconds (≈ 0.03 Hz) — a range of nine orders of magnitude on every channel.
Edges are placed on a 250-picosecond grid for periods up to about 524 µs, and an 8-nanosecond grid for longer periods out to the full 34 s. Everything else — width, delay, sync, burst — behaves identically across the whole range; the placement grid is the only thing that changes with period, and even 8 ns is about 15 parts per billion at a half-millisecond period.
The four channels are reference-locked. All timing derives from an external 10 MHz reference you supply — a rubidium standard, a GPS-disciplined oscillator, or any 10 MHz source you trust — so the absolute frequency of every output is as accurate as that reference. This also makes the PG-4 a natural companion to a reference-locked timestamper: source and measurement share one timebase.
Channels can run in one of two modes:
- ASYNC (the default) — four fully independent channels, each with its own period, width, and delay. Set four different frequencies at once.
- SYNC — all four channels share one period and start phase-aligned, and each channel’s delay places its rising edge relative to a common t = 0. This is the mode for building precise multi-channel timing patterns — a four-step staircase of edges a few hundred picoseconds apart, for example.
Any channel (in async) or the whole device (in sync) can also run in burst mode: emit exactly N pulses (1 to 65,534), rest, and repeat at a programmable interval. The pulse count and the intra-burst spacing are hardware-exact, and the repetition interval is timed by a dedicated hardware timer, so the whole pattern stays coherent with the pulse train.
Like the rest of the Lectrobox line it is easy to use: it connects over USB and enumerates as a standard virtual serial port on Linux, macOS, and Windows — no driver, no companion app, no SDK. You configure it by sending short SCPI text commands (or with the bundled one-file Python helper), and the hardware and firmware are open source.
Connecting to a host via USB
The PG-4 enumerates as a standard USB CDC ACM (Communications Device Class) virtual serial port — the same class used by countless USB-to-serial cables, dev boards, and embedded instruments. There’s no Lectrobox-specific driver to install: every modern operating system already ships the host-side CDC driver, and the device shows up as a plain serial port. Any program that can open one can send it commands.
-
Linux
The Linux kernel binds the built-in
cdc_acmdriver automatically. The device appears as/dev/ttyACM0(or/dev/ttyACM1, etc., if you have other CDC devices attached). No driver installation needed. Useful terminal programs:pyserial-miniterm /dev/ttyACM0(comes with thepyserialpackage).picocom— a small, friendly terminal:picocom /dev/ttyACM0.minicom— older but common and capable.screen /dev/ttyACM0— built into most distros.
For a stable device node regardless of which
/dev/ttyACM*the kernel assigns, drop the following into/etc/udev/rules.d/60-pg4.rulesand runsudo udevadm control --reload:SUBSYSTEM=="tty", ATTRS{idVendor}=="1209", ATTRS{idProduct}=="71c5", \ SYMLINK+="pg-4"You then always have
/dev/pg-4no matter what else is plugged in. -
macOS
macOS loads its built-in CDC ACM driver automatically. The device appears as
/dev/cu.usbmodem*(the wildcard is a long serial-number-derived suffix). No driver installation needed. Useful terminal programs: -
Windows
The PG-4 is a standard USB CDC (serial) device, and the driver Windows needs (
usbser.sys) is built into every version from XP through 11 — but only newer versions bind it automatically. Windows 10 (version 1607 / Anniversary Update, August 2016) and later does so on plug-in: the PG-4 appears in Device Manager under “Ports (COM & LPT)” as a new COM port. Older Windows (XP through 8.1) needs a one-time INF install to point it at the built-in driver (a signed INF will ship with the firmware — TBD).Once the device shows up as a COM port (check Device Manager under “Ports (COM & LPT)” for the COM number — e.g.
COM5), pick a terminal program:
Basic usage
- Connect a 10 MHz reference clock (rubidium, GPS-disciplined, or any other stable source) to the 10 MHz In input. The reference is checked at boot and monitored continuously; if it’s missing the device refuses to emit pulses, since their frequency would be untrustworthy.
- Plug the device into a USB port. The 10 MHz indicator LED begins flashing once the reference is locked. The device is bus-powered — no separate supply.
- Open the virtual serial port the device created —
/dev/ttyACM*on Linux,/dev/cu.usbmodem*on macOS, or a new COM port on Windows. - Send commands to configure the channels, and connect the outputs (CH 0 … CH 3) to your circuit.
Out of the box every channel is off; a pulse generator does nothing
until you tell it what to emit. The quickest way to get a signal is the
bundled pgctl.py helper — for example
pgctl.py freq 0 1e6 puts a 1 MHz square wave on CH 0 — but any terminal
program and the SCPI commands below work just as
well.
The output electrical characteristics (connector type, logic levels, drive strength, and source impedance) are properties of the Rev B board and are TBD until the hardware is finalized; see Specifications.
How pulse timing works
Everything the PG-4 does is built from four numbers per channel — period, width, delay, and (for burst) a cycle count — plus a mode that says whether the four channels are independent or phase-locked. This section explains how they fit together.
Period, width, and delay
- Period is the time from one rising edge to the next: the inverse of the pulse frequency. It spans roughly 24 ns (≈ 41 MHz) to 34 s (≈ 0.03 Hz) on every channel.
- Width is how long the pulse stays high each period. It must be greater than zero and less than the period.
- Delay shifts a channel’s rising edge later in time. It is only
meaningful in SYNC mode, where all channels share a common t = 0 to
measure the delay against; in ASYNC each channel free-runs with no
shared reference, so there is nothing for a delay to be relative to.
delay + widthmust not exceed the period.
Edge-placement resolution
How finely you can place an edge — and set a width or delay — depends only on the period:
- For periods up to about 524 µs, edges land on a 250 ps grid.
- For longer periods, out to the full ~34 s, edges land on an 8 ns grid.
That is the only thing that changes across the range; width, delay, sync, and burst all behave identically everywhere. And 8 ns is a tiny fraction even of the shortest period it applies to — about 15 parts per billion at 524 µs — so in practice the resolution is always far finer than your reference’s accuracy.
ASYNC vs SYNC
-
In ASYNC mode (the default) the four channels are fully independent: four separate periods, four separate widths. Setting one channel’s period affects only that channel.
-
In SYNC mode all four channels share a single period and are started phase-aligned off a common master, so their timelines coincide. Setting the period on any channel sets it for all four. Each channel’s delay then places its rising edge at a known offset from the shared t = 0 — this is how you build a multi-channel timing pattern, such as a staircase of four edges a few hundred picoseconds apart for testing a time-interval instrument.
Burst mode
With burst enabled, a domain emits exactly N pulses (1 to 65,534), goes quiet, and repeats after a programmable interval. In ASYNC a single channel bursts at a time; in SYNC all four channels burst together coherently.
The pulse count and the intra-burst spacing (the channel’s period) are hardware-exact — a burst is always exactly N whole pulses, never a fractional one — and the repetition interval is hardware-timed and locked to the same reference as the pulses, so the cadence doesn’t jitter or drift relative to them. The interval must be longer than one whole burst frame, and at most 60 s.
Disabling a running burst turns the channel’s output off rather than leaving it running continuously — because the burst’s period is the tight intra-burst spacing, letting it free-run would suddenly stream pulses at that rate. Re-enable the output explicitly when you want a continuous train back.
Indicator LEDs
10 MHz
- Dark: reference clock missing or failed. The device refuses to emit pulses until you restore the reference and reset.
- Continuous blink: reference clock locked, device is running.
USB
- Dark: nothing on the host has opened the device’s serial port.
- Solid: a host program has opened the port.
- Blinking: commands are flowing.
CH 0–CH 3 (one per channel)
- Dark: channel is off.
- Continuous blink (fixed rate): channel is on and running faster than ~2 Hz.
- One blink per pulse: channel is on at ≤ 2 Hz, so you can see each individual slow pulse go by.
Command interface
The PG-4 accepts SCPI-style commands on its USB CDC port. Send a single
command line terminated with \n; the device answers with silence for
setters, one line of data for queries, or latches an error 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). A channel suffix <n> is 0, 1, 2, or 3; if you omit it
the command operates on channel 0.
Standard IEEE 488.2 commands:
| Command | Effect |
|---|---|
*IDN? |
Identity string: Lectrobox,PG-4,<serial>,<release>-<git-hash> (e.g. Lectrobox,PG-4,PG4-0030003A3334510537303334,0.4.0-8b63f339). <serial> is the unit’s factory-unique serial — a PG4- tag followed by 24 hex digits, the same value the device reports as its USB serial number. <release> is a monotonically increasing firmware version; <git-hash> pins the exact build. |
*RST |
Reset to power-on defaults: ASYNC mode, all outputs off, all per-channel period/width/delay cleared, burst off (count 1, interval 1 s). |
*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. |
Mode and per-channel output:
| Command | Effect |
|---|---|
MODE ASYNC|SYNC |
Select four independent channels (ASYNC, default) or one shared phase-locked period (SYNC). |
MODE? |
Returns ASYNC or SYNC. |
OUTPut<n>:STATe ON|OFF (OUTP<n>:STAT 1|0) |
Enable or disable channel <n>’s output. |
Per-channel pulse timing (all values in seconds; scientific notation
accepted). In ASYNC, PERiod sets only channel <n>; in SYNC it sets all
four. WIDTh and DELay are always per channel:
| Command | Effect |
|---|---|
SOURce<n>:PULSe:PERiod <seconds> |
Set the pulse period (~24 ns … ~34 s). |
SOURce<n>:PULSe:WIDTh <seconds> |
Set the high-time. Must be > 0 and < period. |
SOURce<n>:PULSe:DELay <seconds> |
Set the rising-edge offset from the shared t = 0 (SYNC). delay + width must not exceed the period. |
Per-channel burst (each setter has a query form — append ?):
| Command | Effect |
|---|---|
SOURce<n>:BURSt:STATe ON|OFF |
Enable or disable burst on the channel’s domain. |
SOURce<n>:BURSt:NCYCles <count> |
Pulses per burst, 1–65534. |
SOURce<n>:BURSt:INTernal:PERiod <seconds> |
Burst repetition interval (default 1 s). Must exceed one whole burst frame, max 60 s. |
Out-of-range or inconsistent values (width ≥ period, delay + width >
period, period out of range, a burst interval shorter than one frame,
etc.) don’t take effect; they latch a -200-class error you can read
with SYST:ERR?.
Example session — a 1 MHz, 100 ns-wide train on CH 0, then a 50-pulse burst on CH 1 repeating every 200 ms:
*IDN?
Lectrobox,PG-4,PG4-0030003A3334510537303334,0.4.0-8b63f339
SOUR0:PULS:PER 1e-6
SOUR0:PULS:WIDT 100e-9
OUTP0:STAT ON
SOUR1:PULS:PER 1e-6
SOUR1:PULS:WIDT 100e-9
SOUR1:BURS:NCYC 50
SOUR1:BURS:INT:PER 0.2
SOUR1:BURS:STAT ON
OUTP1:STAT ON
SYST:ERR?
0,"No error"
Configuration is not persistent: the PG-4 has no save command, so
every setting returns to its default on power-up. Re-send your setup
after a power cycle (a short script or pgctl.py invocation makes this
painless).
pgctl: a one-file Python utility
Any terminal program works, but
pgctl.py
is a small, self-contained Python script (only dependency:
pyserial) that wraps the SCPI
interface behind a friendly command line. Save it, chmod +x, and run.
It auto-detects the PG-4 by USB VID/PID (no --port needed unless you
want to override), with subcommands for everything the device exposes:
pgctl.py idn— read*IDN?pgctl.py reset—*RSTpgctl.py mode [async|sync]— set or query the modepgctl.py state <ch> on|off— enable or disable a channelpgctl.py period <ch> <seconds>/width <ch> <seconds>/delay <ch> <seconds>pgctl.py pulse <ch> <period> <width>— configure and turn a channel on in one callpgctl.py freq <ch> <hz> [duty%]— drive a channel at a frequency and duty cyclepgctl.py stair <period> <width> <step>— SYNC 4-channel staircase (channel n delayed by n × step)pgctl.py burst <ch> <period> <width> <ncyc> [rep]— configure and start an N-cycle burstpgctl.py off [ch]— turn off one channel, or all of thempgctl.py error— read the latched errorpgctl.py raw '<scpi>'— send any SCPI command verbatim
pgctl.py also configures the serial port for raw / no-echo operation
itself (no stty needed), and on Linux works regardless of which
/dev/ttyACM* the kernel assigned.
Library interface
pgctl.py is also a small Python library — drop it next to your own
script (or pip install from the URL), from pgctl import Pulsegen, and
you have an autodetecting, context-managed handle with method-style
access to every feature:
from pgctl import Pulsegen
# Autodetects by USB VID/PID; pass port="/dev/ttyACM1" to override.
with Pulsegen() as pg:
print(pg.idn())
pg.reset()
# A 1 MHz, 100 ns-wide train on channel 0.
pg.pulse(0, period_s=1e-6, width_s=100e-9)
# A SYNC 4-channel staircase: every channel at 1 us / 100 ns,
# channel n's rising edge delayed by n * 250 ps.
pg.stair(period_s=1e-6, width_s=100e-9, step_s=250e-12)
# A 50-pulse burst on channel 1, repeating every 200 ms.
pg.burst(1, period_s=1e-6, width_s=100e-9, ncyc=50, rep_s=0.2)
print(pg.get_error()) # '0,"No error"'
The regression test
under src/app/pulsegen/test/ is a larger working example of the same
library driving the device against a LectroTIC-4 timestamper.
Updating the firmware
PG-4 firmware can be updated over USB with
dfu-util — no programming probe
needed. Install it (apt install dfu-util, brew install dfu-util, or
the Windows binary plus the WinUSB
driver via Zadig), then, with the device
connected and idle, run (Windows: omit sudo):
sudo dfu-util -d 1209:71c5,0483:df11 -a 0 -s 0x08000000:leave -D pg4-<version>.bin
This switches the device into its USB bootloader, writes the image, and
restarts into the new firmware. Confirm the running version with
pgctl.py idn. If the update is interrupted the device stays in its
bootloader and is still visible to dfu-util; re-run the same command.
Comparison to similar instruments
Programmable pulse and digital-delay generators come in two broad flavors. Bench function/arbitrary generators (Siglent SDG, Rigol DG, Keysight 33500) synthesize arbitrary waveforms via a DAC and are extremely flexible on shape, but typically offer one or two channels and tie all channels to a shared sample clock. Digital delay/pulse generators (Stanford Research DG645, Quantum Composers 9500-series, Berkeley Nucleonics 575/577) specialize in exactly what the PG-4 does — placing clean logic edges at precise, independently programmable times across several channels — and are the closer comparison.
The PG-4’s niche within that second group: four independent channels, 250 ps edge placement, a very wide period range (tens of nanoseconds to tens of seconds) on every channel, reference-locked timing so its absolute accuracy is your lab standard’s, and it is open-source and inexpensive. It is a pure digital edge generator — it does not do analog level programming, variable amplitude, or arbitrary waveform shape — so if you need a programmable output amplitude or a non-logic waveform, a function generator is the right tool. If you need several channels of precisely-timed, reference-locked logic edges — clocks, triggers, gate patterns, timing stimulus for a counter or timestamper — that is what the PG-4 is built for.
(A detailed feature/price comparison table against specific instruments is TBD for this draft.)
Specifications
Timing figures are set by the firmware and are final. Electrical and physical figures marked (TBD) depend on the Rev B board and are not yet finalized.
Channels and timing
| Channels | 4 |
| Period range | ~24 ns (≈ 41 MHz) to ~34 s (≈ 0.03 Hz), per channel |
| Edge-placement resolution | 250 ps for periods ≤ ~524 µs; 8 ns above |
| Width | Independent per channel, > 0 and < period |
| Delay | Per channel, relative to a shared t = 0 in SYNC mode |
| Timing accuracy | Set by your 10 MHz reference. With a rubidium standard, parts in 1011. |
| Channel modes | ASYNC (four independent periods) or SYNC (one shared, phase-locked period with per-channel delay) |
| Burst | 1–65,534 pulses per frame, hardware-exact count and spacing; hardware-timed repetition interval (one frame … 60 s) |
Reference clock and host interface
| Reference clock | 10 MHz at the 10 MHz In connector, continuously monitored; the device refuses to emit pulses if it’s missing. Input coupling / level range (TBD). |
| Interface | USB 2.0 Full-Speed (12 Mbps), USB Type-C connector |
| USB VID:PID | 1209:71C5 |
| 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. |
| Configuration persistence | None — settings return to defaults on power-up. |
Outputs, power, and physical (TBD)
| Output connectors | (TBD) |
| Output logic levels / drive | Logic-level pulses; connector, level, drive strength, and source impedance (TBD) |
| Power | USB bus-powered (current TBD) |
| Operating temperature | (TBD) |
| Dimensions | (TBD) |
Schematics, Software, and Other Sundries
The hardware design and firmware are open-source.
- Firmware: src/app/pulsegen/ in the RULOS project on GitHub (GPLv3).
- Schematic (Rev B): PDF · KiCad source.
- Board layout (Rev B): PDF
- BOM (Rev B): CSV with reference designators, values, footprints, and part links.
- Fabrication (Rev B): gerbers and pick-and-place positions.