Decision records for the EPG redesign, newest at the bottom. Each entry: the decision, why, and any constraints it imposes. Open questions tracked at the end.
Decision: Reuse the proven differential in-amp topology (2 electrometer buffers + difference amp, 50× primary gain) rather than inventing a new front end. Why: It is a textbook, well-characterized circuit; the original's choice is sound. The hard part is the electrometer op-amp + Gigaohm node, which we keep.
Decision: Default 1 GΩ (entangled R+emf, the routine aphid mode); retain a switch to 10 TΩ pure-emf mode. Why: The 10 TΩ value is the op-amp's own input resistance once the external 1 GΩ is switched out — so the feature costs only a low-leakage switch (reed relay or guarded analog switch), given we already use an electrometer op-amp. Near-zero cost, preserves plant-electrophysiology capability, loses nothing for routine work. Constraint: switch must be genuinely low-leakage (pA); immaculate board cleanliness / conformal coat around the input node.
Decision: Implement the analog front end as a small per-channel daughtercard (carrying the high-impedance node: in-amp + Ri + low-leakage switch) that connects to a shared motherboard. Only low-impedance buffered analog crosses the connector. Why: Engineering effort (electrometer layout, firmware, software, Vs servo) is nearly independent of channel count — only the analog front-end BOM scales (~$30/ch vs ~$30–40 fixed shared). Prototype/validate one channel end-to-end, then populate the rest with zero redesign. Matches the original's individually-replaceable-probe philosophy. Rough BOM: 1-channel ≈ $60, 8-channel ≈ $270 (single-unit, excl. enclosure/assembly).
Decision: Put Vs under DAC control and run a slow firmware servo that corrects only minutes-scale baseline drift toward a user-set target level, and logs every Vs change into the recording. Why: The drift is electrode-potential drift (not op-amp drift). Vs also sets the R/emf balance and waveform appearance, so correction must be slow and fully reconstructable in analysis — never a silent fast auto-center.
Decision: Cross-platform acquisition + analysis app in Python (pyqtgraph/Qt). Device enumerates as a standard USB CDC serial device (no custom drivers). Why: Strong scientific ecosystem, fast real-time plotting, runs on any OS. USB CDC removes the Windows-only driver problem of the original Stylet+.
Decision: Digitize centrally on the motherboard with one shared simultaneous-sampling Σ-Δ ADC (e.g. ADS131M08 class). Each daughtercard sends its buffered signal differentially over the cable (add one inverting op-amp on the card to make the complementary line); the ADC's native differential input does the A−B subtraction. Why: Keeps all switching/clocks far from the femtoamp 1 GΩ node (the original deliberately keeps digital out of the cage). One shared ADC gives free inter-channel sample synchronization, lower parts count, simpler firmware. Differential drive adds common-mode rejection of 50/60 Hz cable pickup and breaks ground loops — most of the noise benefit of per-probe digitizing, without the noise penalty. Constraint: differential swing must stay within ADC input range / card rails; signal pair should be twisted/paired for good CMRR. Revisit per-probe digital only if long-cable field use becomes a primary requirement. Bonus simplification: the −50 mV calibration pulse becomes a firmware-commanded step on the Vs line — no dedicated cal hardware or conductor.
Decision: Each daughtercard lives in its own probe head on a flexible ~8-conductor shielded cable to the motherboard (probes are positioned individually on stands inside the cage — no backplane). Per-channel cable carries: differential signal pair A/B (twisted), Vs (with cal pulse riding on it), +V, −V, analog ground, Ri-switch control, + overall shield ≈ 7 conductors + shield. Connectors: BNC at the probe tip for the insect electrode; Lemo/ODU push-pull 8-pin probe-to-motherboard (locking, best shielding/durability). Cost-softeners: PCB-mount sockets on the motherboard side; ODU/Fischer/compatible series can undercut genuine Lemo. Why: Matches how EPG probes are actually used (individually clamped near each insect). David chose the premium connector for shield quality, locking security, and longevity. Bonus: cal pulse rides on the Vs line (no dedicated conductor); ±V rails sent from the motherboard so nothing switches near the femtoamp node.
Decision: Input buffers = ADA4530-1 (Analog Devices femtoampere electrometer amp),
two per channel (both sit across Ri, so both must be electrometer grade). The downstream
difference amp and the differential-drive inverter are ordinary precision op-amps. Ri
(1 GΩ/10 TΩ) switch = latching reed relay (>10^14 Ω open isolation; energized only
during the rare mode change → no continuous coil current/field near the node).
Why: ADA4530-1's 20 fA max bias and integrated guard buffer make the hardest part
of the build (femtoamp board leakage / guard ring) near-turnkey, and it's ideal for the
10 TΩ mode. $10–12 ea ($170 across 8 ch) — small premium to de-risk the make-or-break
subsystem; consistent with the premium/longevity choices already made. A semiconductor
analog switch's pA leakage would compromise the 10 TΩ mode, hence the reed relay.
Constraint: PCB guard ring driven by the ADA4530-1 guard pin; conformal coat / clean
input node.
Not very cost-sensitive. Maximize sensitivity / signal quality and operational
simplicity at every step; cost is secondary. Default to the higher-sensitivity,
lower-noise, more-foolproof option; automate rather than add knobs. (Mirrors memory
epg-priorities.)
Decision: Single fixed primary gain in the probe (≥50×, exact value set by a noise budget so the front end, not the ADC, is the limiting noise source). No variable/2nd-stage gain. Rely on the 24-bit ADC's dynamic range + oversampling & decimation for effective resolution and SNR. Why: Best on both of David's axes — operational simplicity (the only per-channel adjustment left is Vs, which is auto-servoed per D4) and sensitivity (a PGA would add noise / switching artifacts; oversampling improves SNR for free and also feeds goal #1). Digital zoom in software replaces the old manual gain knob.
Decision:
- ADC: ADS131M08 — 8-ch, 24-bit, simultaneous-sampling Σ-Δ, native differential inputs (serves as the differential-cable receiver), SPI, up to 32 kSPS/ch. Baseline; AD7768-8 kept as a higher-performance upgrade path if the noise budget calls for it.
- Vs DAC: DAC8568 — 8-ch, 16-bit, internal ref, SPI (~15 µV steps over ±0.5 V).
- MCU: RP2040 (David's choice) — ample for the data rate, USB, great tooling; all digital stays on the motherboard.
- Bipolar power: LM27762 — clean ±rails from USB 5 V with integrated low-noise LDOs; digital 3.3 V from a separate LDO. Stays within USB current budget.
Decision: Firmware Vs servo with two phases.
- Acquire (recording start / on demand): quickly seek Vs to bring each channel's baseline to target — automates the start-of-recording adjustment the manual calls essential.
- Track (during recording): keep-in-range only — hardware moves Vs only when drift threatens to clip the ADC (mirrors the manual's "only re-adjust if off scale"). Slow loop (τ ~ tens of s, well below waveform content); pauses during the cal pulse; per-channel freeze + manual override always available. Logging: every Vs change recorded (timestamp, old→new) as a side channel so analysis can add the steps back and recover the true uncorrected trace. Display: centered in software (high-pass/detrend), independent of the hardware — so "looks centered" is decoupled from "Vs moved." Minimizes interference with the R/emf balance; hardware interventions are rare, discrete, logged events. Why: Best for David's sensitivity + simplicity priorities and scientifically faithful (doesn't silently eat slow biological level changes like sustained E2). The electrode-drift correction is still needed because the fixed 50× amplifies DC drift into volts that would otherwise rail the ADC.
Decision: Sample rate is a runtime recording parameter (e.g. 250 / 500 / 1000 / 2000 / 4000 Hz), default 1000 Hz (vs the original's 100 Hz). Not a fixed hardware target. Oversample + decimate for SNR; optionally store a high-rate raw stream. Rationale / correction (David, 2026-06-10): Aphid signal bandwidth is genuinely unknown — prior data sampled at 100 Hz only resolves <50 Hz, so the field has never been able to look higher; the original instrument may have been self-limiting. Worth exploring, but we don't know that we need 1 kHz. Key finding: sample rate is NOT a hardware design driver — 500 vs 1000 vs 2000 Hz changes nothing in the ADC chip, front end, MCU, USB, power, layout, firmware, or software (only the Nyquist ceiling and file size). So we make it configurable, default high, and let real recordings reveal whether faster aphid content exists. The genuinely separate lever is analog bandwidth (see below), which is independent of sample rate. Analog-bandwidth note: the ~150–300 Hz front-end ceiling (Ri·C_in, see D13) is what actually limits resolvable content; pushing past it needs capacitance neutralization, for which a populate-later footprint is reserved. Trigger to populate it = finding real energy near the roll-off during high-rate exploratory recordings. C_in ≈ 1 pF layout discipline (guard ring + ADA4530-1 guard buffer) remains the make-or-break spec regardless.
Findings:
- Noise floor is the 1 GΩ resistor's thermal noise (~30–65 µV RMS in-band) — op-amp and ADC noise are 100×+ below it. Fundamental to the 1 GΩ choice; the original lives here too.
- Fixed gain G ≈ 8 (not 50×) — maps ±150 mV input FS to the ADS131M08 ±1.2 V range; the 24-bit depth (not analog gain) preserves resolution. Confirm vs final max-feature spec.
- ADS131M08 confirmed; AD7768-8 not warranted (resistor floor masks any ADC-noise gain).
- Keep 24-bit for dynamic range / no-gain-knob operation (not for the floor).
- Realistic front-end bandwidth at 1 GΩ ≈ 150–300 Hz (probing), set by Ri·C_in. Hitting 500 Hz needs C_in < ~0.6 pF (impractical). So C_in ≈ 1 pF is the make-or-break layout spec (guard ring + short traces). 1 kHz sampling is confirmed/beneficial but understood as time-resolution/edge/anti-alias gains over a ~200–300 Hz band, not 500 Hz of bandwidth. Leave a populate-later footprint for capacitance neutralization if more analog BW is ever needed.
- Σ-Δ decimation filter handles anti-aliasing → no separate analog AA filter.
Decision (draft v0.1):
- Link: USB CDC (no drivers), little-endian, COBS framing + CRC-16/CCITT, typed messages. Device→host: INFO/SAMPLES/EVENT/ACK/NACK/STATUS. Host→device: GET_INFO/CONFIGURE/START/STOP/SET_VS/SET_RI/CAL_PULSE/SERVO/PING.
- Master timeline =
sample_index(monotonic from START); absolute time = start_time_utc + index/rate. Every state change (Vs, Ri, cal, servo) — host- or servo-initiated — is emitted as asample_index-stamped EVENT, making the event log the authoritative, reconstructable record. Device streams raw 24-bit ADC codes (lossless); calibration lives in metadata. - File: HDF5 canonical (
/samplesint32 codes chunked+compressed,/eventscompound table, root + per-channel metadata), written incrementally with periodic flush for crash-safety. Reconstruction conventions defined (code→input-referred volts; applied-Vs = Vs0 + Σ logged steps; display detrend is view-only, never written). CSV + future NWB export. Why: Cross-platform + driver-free (goal #5); full reconstructability protects the science given the Vs servo (D11); HDF5 is the scientific-time-series standard. Unblocks the Python software to proceed against this contract in parallel with hardware.
Done: Implemented the host software package epgrig and verified the entire pipeline
in software, no hardware:
protocol.py— COBS+CRC framing + message codecs (pure stdlib).mock_device.py— synthetic aphid-like waveforms + electrode drift + keep-in-range Vs servo, emitting real protocol frames.acquisition.py/recorder.py/reader.py— parse → incremental HDF5 → reconstruct (input-referred volts + applied-Vs from the event log).dsp.py— display detrend (moving-average + streaming one-pole HP), view-only.gui.py+scripts/run_gui.py— live pyqtgraph display (optional Qt).scripts/record_mock.py— headless end-to-end demo. Verified: 15 tests pass (protocol round-trips incl. COBS/CRC + parser resync; pipeline mock→HDF5→reconstruct; dsp). Demo: 30 s × 8 ch @ 1 kHz → 843 KB HDF5, 0 CRC errors, servo fired (VS_CHANGE events logged), input-referred trace bounded while uncompensated shows the removed drift — i.e. D11/D14 behavior proven. Real hardware later drops in behind the same protocol (swap mock byte-stream for a pyserial USB-CDC port). Env note: dev verified with system Python 3.9 + user-installed numpy/h5py; Qt not present here so the GUI is written + parse-checked but not launched.
- GUI gained a sample-rate selector (from
Info.supported_rates; reconfigures live, locked during recording), Record/Stop + file/metadata, markers, working detrend checkbox, and per-channel controls (Vs / servo off-acquire-track / Ri 1G-10T / cal pulse). - Mock device now acts on SET_VS/SET_RI/SERVO/CAL_PULSE and emits the matching logged
events (cal injects −50 mV + auto-offs; servo respects mode).
tests/test_commands.pyadded → 19 tests pass. - Repo: local git initialized at
EPG_redesign/(vendor PDFs/installers excluded), initial commit made. GitHub remoteDavidSternLab/ElectricalPenetrationGraphpending David's auth (no gh/token/SSH in the dev environment).
Done: First schematic-level design (hardware/single-channel-schematic.md): block diagram,
signal chain, component table, power tree, layout-critical notes. Topology decided:
- Daughtercard: A1, A2 = ADA4530-1 unity-gain electrometer followers sensing the two ends of Ri; Ri = 1 GΩ + latching reed relay (open → 10 TΩ intrinsic); Vs_in applied at the S node (cal pulse rides on it); FDA = ADA4940-1 provides the gain and the differential cable drive in one stage. Gain G ≈ 8 via Rg=1.0k / Rf=8.06k (D13).
- Motherboard: ADS131M08 diff input (Σ-Δ AA → no analog AA filter), DAC8568→scale/offset
(±0.5 V)→~10 Hz LP→Vs_in, RP2040, LM27762 ±5 V + 3.3 V LDO, on-card latching-relay driver.
Verified by SPICE (
hardware/sim/single_channel.cir, ngspice): Ohm's-law divider Vi = 0.500 mV ✓, differential gain Vout = 4.00 mV (=8×) ✓, and front-end f₋₃dB = 318.3 Hz, matching the analytic1/(2π·(Rbe‖Ri)·Cin)to 100% — empirically confirming the D13 bandwidth budget and that Cin ≤ ~1 pF is the dominant layout lever.
Done:
firmware/proto/epg_proto.{h,c}— the protocol codec in portable C (COBS, CRC-16, i24, frame builders, incremental parser, command decoders), mirroring the Python codec.firmware/test/— host-compiled C selftest (make check) and a cross-language interop test (make interop,interop_test.py) that builds frames in C and parses them in Python and vice-versa. Proven byte-for-byte compatible both directions → the firmware will talk to the existing host GUI unchanged.firmware/src/— RP2040 Arduino app skeleton:main.cpp(USB-CDC loop, command dispatch, DRDY-driven 8-ch acquisition, block streaming, keep-in-range Vs servo, sample-stamped EVENTs),ads131m08.{h,cpp},dac8568.{h,cpp},board.hpin map;platformio.ini(arduino-pico) compiling the shared proto/ alongside src/. Verified: C selftest (7 checks) + interop (both directions) pass; all 4 Python host test files still pass. Not hardware-tested: ADC/DAC register sequences + relay/servo scaling are markedTODO: verify vs datasheet; src/ not compiled here (needs RP2040 toolchain).
Done:
channel_netlist.py— the single-channel daughtercard as machine-checkable data (23 components, 71 pins, 21 nets) + an ERC-style checker: 0 errors / 0 warnings (every pin connected exactly once; power + diff pair consistent). Generatesconnection-table.md.gen_kicad_sch.py— emits a realsingle_channel.kicad_schfrom that netlist (box symbols + global-label-per-pin connectivity). Validated withkicad-cli(KiCad 10.0.3): upgrade + ERC pass with only benign warnings — 23 "symbol library 'epg' not configured" (symbols are embedded/self-contained) and 3 isolated-label (the intentional single-node nets SHLD/GUARD1/GUARD2). Zero connectivity errors. Rendered tosingle_channel.svg/.pdfand eyeballed — all parts, pins, and net labels present and correct. Note: box symbols (not op-amp glyphs) and no footprints yet — intended as a verified first capture; swap to library symbols + assign footprints during manual cleanup in KiCad (now installed). The netlist data is the source of truth and regenerates the sheet.
Context: David will recruit a KiCad layout engineer to finish; this is the verifiable groundwork + hand-off (routing of an fA front end is deliberately a human task). Done:
- Footprints assigned to all 23 parts in
channel_netlist.py(verified present in KiCad 10 libs; 3 PLACEHOLDERs flagged: Lemo/ODU J2, latching reed relay K1, ADA4940 LFCSP). Schematic now carries aFootprintproperty per symbol (so Update PCB from Schematic pulls nets+footprints). Connection table regenerated with a footprint column. - Starting board
pcb/single_channel.kicad_pcbgenerated via thepcbnewAPI (gen_pcb_placement.py): 23 footprints pre-placed with electrometer-aware intent (BNC/A1 input cluster left → FDA centre → Lemo output right), 60×40 mm outline, 4 layers. Validated withkicad-cli pcb drc: 0 unconnected/structural errors; 40 items are placement-refinement only (courtyard/silk/edge/clearance) for the layout engineer. Renderedpcb/board_top.svgand eyeballed. hardware/pcb-layout.md— the layout spec / hand-off: 4-layer stackup; the make-or-break input-node rules (Cin≤1 pF, no plane under M, no vias on M, driven guard rings from the ADA4530-1 GUARD pins, surface-leakage slot + conformal coat + cleanliness); net classes (HighZ/Guard/Power/Signal/Diff); placement intent; grounding/shielding.pcb/single_channel.kicad_dru— custom design rules (HighZ clearance/no-via/thin-track, diff-pair skew, fab floors). Confirmed it parses cleanly (kicad-cli DRC, 0 rule errors).
- Layout engineer: Update-PCB-from-Schematic, refine placement (clear the 40 items), implement the guard scheme, route, swap the 3 PLACEHOLDER footprints, DRC-clean.
- Finalize FDA supply/VOCM, relay-driver sub-circuit, ADC RC + CLKIN, Vs scale/offset, USB budget.
- Firmware hardware bring-up: ADS131M08/DAC8568 register init + CRC, Vs calibration, relay pulse driver; compile with PlatformIO + flash + talk to the host GUI over a real port.
- Mechanical: shielded probe-head enclosure, guard-ring layout, conformal coating.
- Software niceties: analysis/Y-zoom UI, waveform labeling, NWB export, real serial transport.
- Channel count confirm (assume 8).
- USB bipolar-rail generation.
- Vs servo loop details (rate, target, logging format).
- Open data file format definition + converter.