A programmable laboratory bench — signal generator, oscilloscope, DAQ card, and test rig collapsed into one scriptable system.
Highlights · Dashboard · Bill of Materials · Quick start · Architecture · Roadmap
Designed & built by Angelo Leto · 2014 → 2026
Where a benchtop scope and signal generator are manually operated, PhyCMD is programmable. Where an Arduino sketch is non-deterministic, PhyCMD is real-time. Where a National Instruments DAQ is proprietary, PhyCMD is end-to-end open — from the firmware on the SAM3X8E up through the Rust streaming server and the browser dashboard.
You get an Intel mini-PC, an Arduino Due, a handful of protection components, and suddenly your lab has a sample-coherent 8 kHz DAC↔ADC↔DIN↔DOUT link with a PREEMPT_RT Linux brain that can run PID loops, lock-in detectors, Kalman filters, frequency sweeps, or whatever else you care to script.
PhyCMD started in 2014. The reference Intel DN2800MT mini-PC, the aluminium chassis, the steel front panel (hand-drawn in AutoCAD — see docs/hardware/FrontPanel_2014.dwg), the DB-25 I/O harness, and the SAM3X8E firmware's I/O core all date back to that build. The original host software was a small custom C/C++ bulk-USB broker. In 2026 the project was extended on both sides without changing the hardware: the firmware kept its proven I/O layer and grew an on-chip function generator + a switch from bulk to isochronous USB at 8 kHz; the host-side broker was replaced by the new Rust physerver and the browser dashboard you see in these screenshots.
- 8 kHz sample-coherent iso-USB link — 64-byte PhyCMD frames every USB microframe (125 µs), HS isochronous EP on a dual-interface Vendor/CDC device.
- On-chip function generator in firmware — sine / square / triangle / sawtooth / arbitrary / LUT / threshold / pulse-trig / PID on 2 DACs, 4 PWM channels, 16 DOUTs. Up to 1 MSPS per DAC; DOUT reactive modes evaluated at 1 kHz.
- Full dashboard in the browser — live scope with analytic synth at canvas-pixel resolution, collapsible drag-and-drop panels, On-Chip FnGen controls per channel, PREEMPT_RT scheduler telemetry with URB-level jitter histogram.
- PREEMPT_RT host — source-based policy routing, CPU isolation (2,3), IRQ pinning — documented turn-key setup in
docs/deployment/. - Clean layered wire protocol — the 64-byte frame is a single source of truth:
_Static_assertin C,const _: () = assert!(...)in Rust. Any drift between firmware and host fails the build loudly. - Python, Rust, REST, WebSocket, and SHM IPC — pick the surface that fits your workflow.
Open http://<host>:8080/ from any browser on your LAN.
Because the WebSocket stream is throttled at ~250 Hz, anything above ~50 Hz would alias on a naive sampled trace. The scope therefore evaluates the on-chip generator's waveform analytically at canvas-pixel resolution for sine / square / triangle / sawtooth DACs and for PWM squares — so you see the real shape even at several kHz. Digital inputs and outputs and raw ADC traces are drawn from the captured sample buffer.
Every DAC, PWM, and DOUT channel has its own row with mode dropdown, parameter form, Apply, and Stop. userDirty flag protects in-progress edits from being stomped by the 1 s state poll. The DOUT strip is a single 16-pin panel — click a row to expand the config, click × on the row head to stop a channel without expanding.
Every panel collapses to its header with a ▸ chevron; state is persisted in localStorage. Drag-handles stay visible even when collapsed so you can reorder the layout in either state.
Total cost of a fresh build is ≈ 180–250 € depending on the mini-PC. Sensors, actuators, and the DUT are obviously project-specific.
| Qty | Item | Notes | Typical price |
|---|---|---|---|
| 1 | Intel mini-PC with x86_64 CPU, ≥ 2 GB RAM, USB 2.0 HS port | Reference box: Intel DN2800MT (Atom N2800, Cedar Trail) running Ubuntu 24.04 PREEMPT_RT. Any similar fanless Atom/Celeron box works. A host with xHCI (USB 3.0+) instead of the reference EHCI controller handles iso-USB microframes more aggressively and can push the effective refresh rate well above 8 kHz — EHCI caps cleanly at HS-iso's 8 kHz, xHCI's per-bus scheduler should give lower jitter. | 40–120 € used |
| 1 | Arduino Due (SAM3X8E, 84 MHz Cortex-M3) | Native USB 2.0 HS. Stock board, no hardware mods. | 35 € |
| 1 | USB A → micro-B cable | Connect Due native port to the host. Programming port only needed for first flash. | 3 € |
| Qty | Item | Notes |
|---|---|---|
| 1 | Ethernet cable + switch / LAN router | Dashboard is served at http://host:8080. |
| 1 | Screen + keyboard (optional, first boot only) | After network setup all interaction is over SSH + browser. |
The reference enclosure, front / rear steel panels, and PSU-support bar are from the 2014 build. The Amazon Italy listings below are the ones actually used — swap for local equivalents as you prefer. The front panel and the PSU-support bar were hand-drawn in AutoCAD (see docs/hardware/FrontPanel_2014.dwg).
| Qty | Item | Amazon IT ASIN |
|---|---|---|
| 1 | Compact aluminium rack/bench case with integrated vent grille | B00GUFL76U |
| 1 | Power / chassis accessory | B00E7QGHE6 |
| 1 | Power / chassis accessory | B00COFMPAM |
| 1 | Internal hardware kit (brackets / fasteners / cabling) | B007BVDVAM |
| 1 | Custom-cut steel front panel | Machined from the DWG above. 9 × DB-25F cut-outs + 3 push-button holes. |
| 1 | Custom-cut rear panel | Shaped around the mini-PC's native I/O (VGA/HDMI/USB/audio). |
The Due's DACs output 0.55–2.75 V and its ADCs expect 0–3.3 V — you usually want some protection + buffering around them. The project's author prefers through-hole / DIP parts and prebuilt buck/boost modules over SMT. docs/technical/PCB_BACKPLANE_PINOUT.md sketches a passive Arduino Due shield that fans out all I/O to nine DB-25 connectors following this rule — the design is a paper sketch only, never fabricated. See Contributions wanted below.
| Qty | Item | Role |
|---|---|---|
| 2 | Op-amp ×2 (MCP6002 / TL072 DIP-8) | DAC buffer & ADC input follower |
| 2 | Rail-to-rail op-amp or instrumentation amplifier | Scale DAC output to ±10 V (optional) |
| 16 | TVS diode (SMAJ3.3CA-like) | Clamp DIN pins to 0–3.3 V |
| 16 | 1 kΩ series resistor | DIN current-limit |
| 16 | 10 kΩ pull-down resistor | DIN default level |
| 16 | N-MOS logic-level (e.g. 2N7000) + 1 kΩ gate resistor | Level-shift DOUTs to whatever your load needs |
| 4 | 330 Ω + LED | Optional status LEDs on the PWM pins |
| 1 | Buck module 12 V → 5 V (prebuilt) | Power Arduino Due from the lab supply |
| 1 | Pluggable screw-terminal blocks | All external I/O lands here |
| 1 | Drilled aluminium or 3 mm ABS enclosure | See companion_board/panel_sketch/ for the front/rear panel SVGs (front/rear are realised on the reference build; pcb_backplane_*.svg is the not-yet-fabricated companion shield — see Contributions wanted) |
- Real-time clock / battery. Host NTP is enough; Due has its own 1 ms SysTick for
uptime_ms. - Dedicated crystal oscillator. Due's on-board 84 MHz is stable to ±50 ppm — plenty for audio-band work.
- Fan PWM control. The reference Intel DN2800MT has no hwmon path for fans; use the motherboard BIOS. If you must, the SAM3X PWM pins can drive a fan through a transistor.
# --- On the mini-PC (Ubuntu 24.04 LTS + PREEMPT_RT kernel) -----------
git clone https://github.com/<you>/phycommander.git
cd phycommander
# Build + flash firmware (ARM GNU Toolchain required).
# scripts/flash_firmware.sh handles the whole flow: it asks the
# running physerver to drop the SAM3X into SAM-BA via
# POST /api/firmware/enter-bootloader (JTAG-free, no 1200-baud
# trick), runs bossac, then issues a clean RSTC_CR soft-reset.
sudo ./scripts/flash_firmware.sh
# See docs/firmware/FIRMWARE_UPLOAD.md for the entry-path fallback
# and why we never use bossac -R.
# Build + install physerver
cd ../../physerver && cargo build --release
sudo cp target/release/physerver /usr/local/bin/
sudo cp ../deploy/systemd/physerver.service /etc/systemd/system/
sudo systemctl enable --now physerver
# Open the dashboard
xdg-open http://localhost:8080For the full PREEMPT_RT bring-up, dual-NIC policy routing, and systemd plumbing, see docs/deployment/DEPLOYMENT.md.
┌───────────────────────────────────────────────────────────────┐
│ Application Layer │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Browser │ │ Python │ │ Rust │ │ Test │ │
│ │ (Dash) │ │ (pyo3) │ │ Custom │ │ Scripts │ │
│ └─────┬────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
│ WebSocket IPC (SHM) Rust crate REST / curl │
└────────┼────────────┼─────────────┼─────────────┼─────────────┘
│ │ │ │
┌────────┴────────────┴─────────────┴─────────────┴─────────────┐
│ physerver (Rust, PREEMPT_RT) │
│ • IsoTransport — 8 kHz HS isochronous USB on EP1 IN/OUT │
│ • WaveformBank, CommandStaging, StatusBus │
│ • Web (axum), WS broadcast, sysinfo, RT stats │
│ • Vendor SETUP client for on-chip generator control │
└──────────────────────────────┬────────────────────────────────┘
│ USB 2.0 HS (Vendor iso + Vendor EP0 control)
┌──────────────────────────────┴────────────────────────────────┐
│ SAM3X8E firmware (ATSAM3X8E_FW/) │
│ • PhyCMD-64 wire protocol (CRC-16-CCITT) │
│ • 2 DAC + 8 PWM + 16 DOUT + 16 DIN + 12 ADC │
│ • On-chip generator: BUILTIN / ARBITRARY / LUT / THRESHOLD / │
│ PULSE_TRIG / PID — per-channel, per-microframe │
│ • DACC PDC ping-pong, TC-triggered ADC, PWM peripheral B │
└────────────────────────────────────────────────────────────────┘
| Path | Contents |
|---|---|
ATSAM3X8E_FW/ |
SAM3X8E firmware (ARM GCC + ASF, GNU Make) |
physerver/ |
Rust streaming server + dashboard (Cargo workspace) |
physerver/crates/phycmd-core/ |
wire protocol, transports, scheduler, stats |
physerver/crates/phycmd-rust/ |
public Rust client crate |
physerver/crates/phycmd-py/ |
pyo3 Python bindings |
physerver/static/ |
dashboard (single-page, no build step) |
docs/ |
protocol, firmware, deployment, applications |
deploy/ |
systemd units, udev rules, policy-routing scripts |
companion_board/panel_sketch/ |
front / rear / backplane SVGs for the enclosure |
scripts/ |
CLI tools (phycmd_waveform.py, deploy.sh, …) |
- v2.0 — live watchdog + crash-state dump from firmware; host-side auto-recovery over USB reset instead of SAM-BA reflash.
- v3.0 — rule-chain mode (
MODE_RULE_CHAIN) for composing reactive primitives on-chip without round-tripping to the host. - v4.x — second supported MCU (SAMD51 / RP2350) with the same wire protocol.
- Long term — pluggable front-end PCB with galvanic isolation and ±10 V amplifiers, designed hand-solder-first.
PhyCMD is maintained solo and a few useful pieces are sketched but not built. Concrete help with any of these lands faster than broad feature requests:
- Fabricate the companion shield PCB.
docs/technical/PCB_BACKPLANE_PINOUT.mddescribes a passive Arduino Due shield that fans out all I/O to nine DB-25 connectors on the front panel, replacing the hand-crimped wire harness shown in the photos. The pinout is decided, the 1:1 layer SVGs are incompanion_board/panel_sketch/, and a KiCad-8 skeleton sits undercompanion_board/pcb/— but nothing has been fabricated or validated. Pull-request-ready help would be: clean KiCad project, generated gerbers, a built prototype with continuity + signal-integrity notes. - Front-end analog conditioning. The BOM above is a recommended parts list, not a validated schematic — an actual op-amp buffer + ADC protection daughterboard (DIP, hand-solderable, documented) would save everyone time. Bonus points for optional ±10 V rail-to-rail scaling.
- xHCI benchmarking. The reference host has EHCI only; somebody with an xHCI machine can characterise jitter / max effective refresh rate and contribute a short report +
docs/technical/XHCI_NOTES.md. - Watchdog-driven crash recovery. Firmware currently has to be SAM-BA reflashed after a rare wedge (see session history). A firmware watchdog + host-side auto-reset over USB would eliminate this.
- Non-reference deployments. If you get it running on a different mini-PC / different Linux distro, open a PR against
docs/deployment/DEPLOYMENT.mdwith the delta.
Issues tagged help wanted on GitHub track these. Pull requests for smaller polish work (docs, typos, small refactors) also very welcome.
PhyCMD is © 2014-2026 Angelo Leto (@angleto).
The licensing is deliberately copyleft across the whole stack, with per-component boundaries chosen to protect each layer with the appropriate tool:
- Network-facing software — Rust server,
phycmd-core,phycmd-rust,phycmd-py, dashboard HTML/JS — is released under AGPL-3.0-or-later (seeLICENSE-AGPL-3.0). A third party that takes this code, modifies it, and runs it as a network service (SaaS) has to publish their corresponding source. This is the clause that prevents silent appropriation by cloud operators. - Firmware (SAM3X8E C code), deployment plumbing, and CLI scripts are released under GPL-3.0-or-later (see
LICENSE-GPL-3.0). Binary derivatives distributed to third parties must come with source. - Hardware designs — PCB (KiCad), front / rear / backplane panels (SVG), mechanical CAD — are released under CERN-OHL-S v2 (Strongly Reciprocal; see
LICENSE-CERN-OHL-S-2.0). The copyleft analogue of AGPL for physical artefacts. - Prose documentation, tutorials, README narrative, and photographs are released under CC-BY-SA 4.0 (see
LICENSE-CC-BY-SA-4.0). - The project name "PhyCommander" / "PhyCMD" and the logo are unregistered trademarks of the author, not covered by any of the above licences. See
TRADEMARKS.mdfor what you can and cannot do with the name; short version: forks that diverge from upstream must rename.
The top-level LICENSE contains the authoritative boundary definitions and every source file carries an SPDX-License-Identifier. NOTICE carries the project attribution that redistributions must preserve.
Commercial use. The licences above let you ship PhyCommander commercially (sell copies, sell support, run it as a paid service) as long as you respect the copyleft obligations: keep the attribution, and share the source where each licence asks for it. If those terms don't fit your specific case (closed-source product integration, hosted service without source disclosure, binary appliance without a source offer), a separate commercial licence can be arranged. Open an issue or contact @angleto on GitHub.
If you build on PhyCMD — academic paper, product, student project, blog post — credit would be appreciated in whichever form fits (README line, paper citation, slide footer).
A complete, navigable documentation index lives in docs/INDEX.md. Highlights:
- Protocol reference — PhyCMD-64 wire format + vendor SETUP requests
- Function Generator guide — all on-chip modes with examples
- Deployment playbook — PREEMPT_RT host bring-up
- User manual — dashboard walkthrough
- Example application — lock-in optical demo, end to end







