Reference
Pin model
Every device card maps the host MCU's pins to a uniform model:
- D0-D13, digital pins. Modes:
off,in(high-Z input),inp(input with pull-up),out,pwm(only on PWM-capable pins, marked with a ~ in the UI),freq(only on interrupt-capable pins). Click the mode badge to cycle. - A0-A5, analog inputs. 10-bit by default;
toggle to 12-bit if the board's core supports
analogReadResolution(12). - V0-V3, virtual channels. Filled in by I2C polling or cross-pin math expressions. Treated identically to A0-A5 by every visualization, alert, and CSV export.
- F0-F13, frequency channels. Populated by pins in FREQ mode.
D0 and D1 are reserved (USB serial uses them) and grayed out.
Sample rate
The Hz selector on each device card sets how often the firmware sends state packets. Range is 1 to 50 Hz. The default is 10 Hz. Higher rates give smoother strip charts and finer FFT resolution but consume more transport bandwidth.
Inside the firmware, the state push period is
1000 / hz milliseconds. Lower the rate if the wire
log shows tx pressure or if you're on a slow transport like BLE;
raise it for fast transients.
Calibration
Click any A0-A5 (or V0-V3) label on a device card to open the calibration modal. Two modes:
Manual
Type a slope m, offset b, unit, decimals,
and a label. Linear conversion y = m·raw + b is
applied to every reading on that pin and propagates through the
strip chart, FFT, spectrogram, scatter, stats, alerts, and CSV
export.
Wizard (two-point fit)
Apply a known value at the low end (often a short to ground), click CAPTURE LOW. Apply a known value at the high end, click CAPTURE HIGH. PinScope computes:
m = (knownHi - knownLo) / (rawHi - rawLo)
b = knownLo - m * rawLo
The wizard shows a live preview using the current raw reading, then SAVE writes the calibration to the pin. If the two raw captures are identical, the wizard refuses to fit and tells you so.
Calibrations persist in the session and export with the JSON.
Virtual channels
PinScope has four virtual analog slots (V0-V3) that show up in every visualization next to A0-A5. Two sources can feed a virtual slot:
- I2C polling: configure a slot to poll an I2C register at a fixed Hz; the latest read goes into the virtual channel.
- Cross-pin math: assign an expression like
v0 = sqrt(a0*a0 + a1*a1)to a slot; the expression is recomputed every state packet.
Math overrides I2C if both target the same slot.
Cross-pin math
The math engine lets a virtual channel be a function of any other
channels. The expression syntax is a small JS subset: arithmetic,
the math globals (sin, cos,
sqrt, log, exp,
abs, pow, min,
max, floor, ceil,
round), and references to channels by their key
(a0 to a5, d0 to
d13, v0 to v3,
f0 to f13).
| Expression | What it derives |
|---|---|
(a0 + a1) / 2 | average of two analog inputs |
sqrt(a0*a0 + a1*a1) | magnitude of an XY pair |
a0 - a1 | differential measurement |
v0 * 0.0078125 | TMP117 raw to degrees Celsius |
floor(v0 / 16) * 10 + (v0 % 16) | BCD-unpacked seconds from a DS3231 |
Expressions are compiled via new Function(...) with
strict mode and no access to globals beyond the math helpers. Bad
syntax surfaces in a toast; null inputs produce null outputs.
Strip chart
The strip chart shows up to 14 channels at once. Click any chip in the ANALOG / DIGITAL / VIRTUAL rows under the chart to toggle that channel on. Each trace gets a deterministic color keyed off its name.
| Button | Action |
|---|---|
15s 1m 5m 15m | window length |
STATS | per-trace stats overlay (min, max, mean, std dev) |
PAUSE | freeze chart and recorder |
CLEAR | drop the recorder buffer |
CSV | one-shot dump to a CSV file |
STREAM | append every new sample to a file (Chromium only) |
CAPTURE | save buffer as a replayable JSON file |
BASELINE | load a capture as a faded ghost overlay |
DIFF | toggle live-minus-baseline trace |
TRIG | open the trigger modal |
PNG | snapshot current chart |
The recorder buffer caps at 9000 samples (about 15 minutes at 10 Hz, 3 minutes at 50 Hz). For longer runs use STREAM.
Baseline + diff view
The strip chart can overlay a previously captured run as a faded dashed ghost and render a live-minus-baseline diff trace on top. Useful for before-vs-after comparisons: tweak a circuit, flash new firmware, swap a sensor, see exactly how the new behavior differs.
- CAPTURE a baseline run.
- BASELINE loads a capture file as the baseline overlay. Click again to clear.
- DIFF toggles the difference trace, computed as
live - baselinefor each active channel, normalized and centered on the chart's midline.
The baseline is aligned to "now" by tail-shifting its timestamps, so a capture from yesterday lines up with the current strip-chart window without clock-sync gymnastics. Diff is nearest-neighbor by time.
Oscilloscope-style trigger
The strip chart can capture a fixed window of samples around a threshold crossing and freeze on the event for inspection. Useful for one-shot events that are hard to spot in the live stream: a glitch, a power transient, a single-shot pulse from a sensor.
Click TRIG on the chart toolbar, pick a channel (any
A, V, F, or D pin), pick a condition (rising, falling, above,
below), set a threshold in raw units, define pre and post sample
windows in seconds. ARM the trigger. The button pulses red while
armed.
When the condition fires, the chart freezes onto the captured window with a red vertical line at the fire time and a red horizontal threshold line. The recorder auto-pauses. RE-ARM resets and resumes recording; DISARM clears it entirely.
FFT and spectrogram
The FFT tab shows the magnitude spectrum of the active analog or virtual channel. Window length comes from the strip chart selector. Window functions (Hann, Hamming, Blackman, none) are selectable. Magnitude is plotted on a log y-axis by default.
The Spectrogram tab is a rolling waterfall of the same FFT over time. Useful for non-stationary signals where the frequency content shifts.
XY scatter
The Scatter tab plots two channels against each other for Lissajous-style visualization. Pick X and Y from the dropdowns. Optional connecting lines link adjacent samples in time, so you can see the trajectory and not just the cloud. Useful for phase analysis, sensor correlation, or just looking at a circuit's transfer function.
Frequency measurement
Pins in FREQ mode count rising edges via
attachInterrupt. The firmware has one ISR per pin
(small wrappers around a per-pin volatile counter). Every 250 ms
the main loop reads and resets the counters with interrupts
briefly off, and converts counts to Hz.
- Minimum measurable: roughly 4 Hz (one edge per 250 ms window). Below that the readout flickers between 0 and 4 Hz.
- Maximum measurable: depends on the host MCU. Classic Uno R3 caps around 100-200 kHz before edges are missed. Uno R4 WiFi and Nano 33 IoT go higher.
Only pins with hardware interrupt support can be put in FREQ mode. The firmware returns "no interrupt on pin" if you try to set FREQ on an unsupported pin.
Threshold alerts
Each device card has a Threshold Alerts panel for raising an alert when a channel crosses a value. Pick a pin, pick a condition (above, below, rising, falling, equal), a value, optionally a sound. Alerts fire when the condition transitions from false to true (not held continuously).
I2C / Qwiic
Every device card has an I2C / Qwiic section with three tools:
- SCAN BUS: highlights every responding I2C address on a 16×8 grid.
- READ / WRITE: one-shot reads (up to 8 bytes) and writes against any register.
- POLL: configure a virtual slot to poll a register at a fixed Hz. The latest read feeds the virtual channel.
Signed reads are supported (the firmware sign-extends per the byte count). Polls are paused while a one-shot read or write runs, then resume.
Companion sensor library
The I2C panel ships with one-click presets for common breakout sensors. Pick a sensor from the dropdown, confirm the address, APPLY drops the right I2C poll into the first free virtual slot and prefills the math input below with the unit-conversion expression.
| Preset | Reads | Notes |
|---|---|---|
| TMP102 | 12-bit temperature | 0.0625 °C/LSB |
| TMP117 | precision temperature | 0.0078125 °C/LSB |
| DS3231 | RTC seconds counter | BCD-unpacked |
| MCP9808 | 13-bit signed temperature | Microchip |
| INA219 | shunt + bus voltage | two-slot preset |
| APDS-9301 | ambient light channel 0 | requires power-on init write |
| PCF8591 | ADC channel 0 | NXP |
Preset definitions live in SENSOR_PRESETS near the
top of the I2CPanel code in pinscope.html. Adding a
sensor is a few lines.
RGB LED panel
Three dropdowns (R, G, B) select PWM pins, a color picker picks a color, APPLY puts each pin in PWM mode and writes the matching duty. OFF stops driving. Useful for common-anode/cathode RGB LEDs or any three-channel PWM device.
Scripted automation
Every device card has a SCRIPTED AUTOMATION section: a textarea where you write a short async JS sequence to exercise the board.
| Helper | What it does |
|---|---|
setMode(pin, mode) | change a pin's mode |
setDigital(pin, hi) | drive a digital output |
setPWM(pin, val) | set PWM duty (0..255) |
await wait(ms) | sleep |
read(pin) | latest raw value |
log(msg) | print to output panel |
assert(cond, msg) | throw if cond is falsy |
Loops, conditionals, declarations, await all work.
Network and storage globals (fetch,
localStorage, eval, Function,
setTimeout, window, document)
are blocked at compile time via an identifier blocklist.
setMode(9, 'pwm');
for (let duty = 0; duty <= 255; duty += 32) {
setPWM(9, duty);
await wait(200);
log('duty=' + duty + ' a0=' + read('a0'));
}
CSV export
The CSV button on the strip chart toolbar does a one-shot dump of the recorder buffer. Columns: timestamp_ms, ISO 8601 time, every digital pin, every analog raw plus its calibrated value if a calibration is set, every virtual raw plus calibrated.
For longer runs, the STREAM button opens a save-file picker (File System Access API), then appends every new sample to that file in batches. Click STREAM again to stop and close the file.
CSV streaming is Chromium-only. In Firefox and Safari the STREAM button is disabled with a tooltip; fall back to the one-shot CSV or to a capture file.
Replay
The REPLAY button on the rail loads a capture JSON file as a
synthetic [replay] device card. The card plays the
captured samples back in real time. Useful for sharing a
problematic recording with a colleague, or for reviewing a run
without having to recreate the hardware setup.
Sessions
Every device card auto-saves its configuration to localStorage 400 ms after any change. Saved fields:
- alias, ADC resolution, sample rate
- per-pin calibrations
- active strip chart traces
- threshold alerts
- I2C poll slot configurations
- math expressions
- RGB LED pin assignment and most recent color
- trigger configuration
- scripted automation source
- plugin state (everything plugins write via
api.persist)
The EXPORT button writes the same snapshot to a JSON file. IMPORT reads one back. Session file format is currently version 1.3 (additive over 1.2, 1.1, 1.0).
Session diff
The DIFF button (between EXPORT and IMPORT on each device card) compares the current session against a saved JSON file. A modal shows exactly what changed: calibrations, alerts, I2C polls, math expressions, trigger config, scripted automation source (with a line-by-line LCS diff), plugin state.
Read-only; doesn't modify the active session. Useful for catching calibration drift between test rigs, confirming that an "identical" board is actually identical, pre-flight checking a session JSON before committing it to a repo.
Wire protocol
PinScope speaks a small JSON line protocol over every transport. One JSON object per line, newline-terminated.
Host to board
| Command | Notes |
|---|---|
{"cmd":"hello"} | request a hello packet |
{"cmd":"poll"} | request a state packet on demand |
{"cmd":"mode","pin":N,"mode":M} | M is one of off, in, inp, out, pwm, freq |
{"cmd":"set","pin":N,"val":V} | V is 0 or 1; only valid in out mode |
{"cmd":"pwm","pin":N,"val":V} | V is 0-255; only valid in pwm mode |
{"cmd":"hz","val":H} | H is 1-50 |
{"cmd":"i2c","op":"scan"} | scan the bus |
{"cmd":"i2c","op":"read","addr":A,"reg":R,"count":N} | one-shot read |
{"cmd":"i2c","op":"write","addr":A,"reg":R,"data":[...]} | one-shot write |
{"cmd":"i2c","op":"poll","slot":S,"addr":A,"reg":R,"count":N,"hz":H,"signed":bool} | configure polling slot |
{"cmd":"i2c","op":"stoppoll","slot":S} | stop a polling slot |
Board to host
| Packet | Notes |
|---|---|
{"t":"hello","id":"...","name":"...","hz":H,"adcMax":N} | sent on connect and on hello cmd |
{"t":"state","d":[...],"a":[...],"m":[...],"v":[...],"f":[...]} | periodic |
{"t":"i2c","op":"scan","addrs":[...]} | scan result |
{"t":"i2c","op":"read","addr":A,"reg":R,"data":[...]} | read result |
{"t":"ack","cmd":"..."} | command accepted |
{"t":"err","msg":"..."} | command rejected |
Keyboard shortcuts
| Key | Action |
|---|---|
S W M B | open SERIAL / WIFI / MQTT / BLE dialog |
R | open a capture file (REPLAY) |
P | open the plugin manager |
G | toggle stacked vs grid layout |
? | open the help modal |
1 2 3 4 | switch tabs on the active device |
Space | pause / resume the strip chart |
C | clear the wire log |
Esc | close any open modal |