Skip to the content
PCBKitNew board
4 layers127.8 × 53 mmLow Current Driver, the printed circuit board in 3D

Low Current Driver

Kayzerjali/FieldZero

System to reduce the residual magnetic field inside the QUBIC lab's mu-metal shielded enclosure, allowing the QuSpin QZFM sensors to zero and lock reliably.

Size
127.8 × 53 mm
Copper layers
4
Thickness
1.6 mm
Parts
73
Nets
53
Pads
222
Tracks, vias
356, 65
Pours
10
Routed
Yes
KiCad
KiCad 10.0 (file 20260206)
Created
23 Jun 2026
Updated
23 Jul 2026

Overview

Low Current Driver is an open-source printed circuit board by Kayzerjali, from the Kayzerjali/FieldZero repository on GitHub. It is a 4-layer board of 127.8 × 53 mm with 73 parts on 53 nets, routed with 356 tracks and 65 vias.

It is built around the OPA2210ID, LTC6655CHLS8-2.5#PBF and RC0603FR-0710KL, with the TMK107B7105KA-T, RG1608P-2490-B-T5 and RC0603FR-071KL among its other parts. Its parts include 24 capacitors, 22 resistors, 6 ICs, 3 connectors and 1 switch. It belongs with the AVR (ATmega, ATtiny) boards of this library.

Main components

7

README

README.md

FieldZero — Magnetic Field Cancellation for OPM MEG

System to reduce the residual magnetic field inside the QUBIC lab's mu-metal shielded enclosure, allowing the QuSpin QZFM sensors to zero and lock reliably.

See docs/context.md for full project context.

Status

In development — lab access pending.

Repository layout

text
docs/                 Project documentation (grows as work progresses)
data/
  raw/                Unmodified instrument output (write-once)
  processed/          Script outputs only — never hand-edit
Hardware/             KiCad — coil-driver PCB (see Hardware/Low_Current_Driver/)
  Common/             Shared symbol/footprint libraries
Software/             Python acquisition/analysis tooling
  src/fieldzero/      Python package
  scripts/
    characterisation/ Field measurement and analysis
    coil_cal/         Coil sensitivity calibration
    nulling/          Nulling procedure support
    figures/          All figure generation
  tests/

The Python setup and commands below are run from inside Software/.

Setup

Python 3.11+. A venv is created per machine — do not copy .venv between machines, it hardcodes absolute paths and platform-specific wheels. Recreate it instead; it takes about a minute.

powershell
py -m venv .venv
.venv\Scripts\activate
pip install -e ".[daq,dev]"

[daq] pulls in nidaqmx. Drop it on a machine with no card — --mock needs nothing else. Drop dev if you are not running the tests. The NI-DAQmx driver itself is a separate install from National Instruments and is not a pip package.

Installing on a machine with no internet

Instrument PCs are often locked down. Build a wheelhouse before you go, on a machine that does have network:

powershell
.venv\Scripts\python.exe -m pip download -d wheels numpy scipy fastapi "uvicorn[standard]" nidaqmx

Copy wheels/ across with the repo, then on the lab machine:

powershell
py -m venv .venv
.venv\Scripts\activate
pip install --no-index --find-links wheels -e ".[daq]"

The front end needs no network either — uPlot is vendored into src/fieldzero/viewer/static/vendor/ rather than loaded from a CDN.

Live viewer

bash
python -m fieldzero.viewer            # against the NI-DAQ
python -m fieldzero.viewer --mock     # no hardware required

Opens a browser at http://127.0.0.1:8000. Split any pane left/right or top/bottom and choose what goes in it: time domain, frequency domain (amplitude or ASD), or phase (any channel against any other, with Pearson r and fitted slope). The stats table below the panes gives per-channel mean and standard deviation, and flags any channel sitting at the input rail.

Layouts persist across reloads and can be saved to / loaded from JSON.

Run scripts/characterisation/verify_acquisition.py [--mock] on arrival at the lab to confirm the DAQ is reading every channel before starting a measurement.

The viewer binds to loopback, so a Windows firewall prompt on first run can be declined. Use --host 0.0.0.0 to watch from a laptop while the DAQ machine sits in the shielded room — that one does need the firewall exception.

Fallback scope

A plain matplotlib window. No server, no browser, no socket — a timer reads the ring buffer in-process, so there is nothing between acquisition and the screen that can stall, congest or time out. Use it if the browser viewer feels sluggish on a given machine, or when you just want a scope up in one command.

It runs the same reducers as the browser viewer, so the DSP is identical: the same windowing, the same ASD scaling, the same volts-based rail check, the same correlation maths. Only the drawing differs.

Running it

bash
python -m fieldzero.scope                # against the NI-DAQ
python -m fieldzero.scope --mock         # no hardware required

With no --pane, you get one time-domain plot of all six channels. Close the window to stop it.

Choosing what to plot

--pane takes KIND or KIND:CH,CH,... and is repeatable — each one adds a plot, stacked top to bottom in the order you give them.

kindchannelsshows
timeany number (default: all six)rolling time trace, newest sample at t=0
spectrumany number (default: all six)amplitude spectral density, log axis
phaseexactly twofirst against second, with a least-squares fit

Channel names are S1-X, S1-Y, S1-Z, S2-X, S2-Y, S2-Z — the same names the stats table uses. Get one wrong and it tells you the valid ones and exits.

bash
# one time plot of two channels, and their spectrum underneath
python -m fieldzero.scope --pane time:S1-X,S2-X --pane spectrum:S1-X,S2-X

# is sensor 2 seeing the same field as sensor 1?
python -m fieldzero.scope --pane phase:S1-X,S2-X

# what is the residual on the Z axis, in raw volts, over 10 seconds?
python -m fieldzero.scope --pane time:S1-Z,S2-Z --units V --window 10

The other flags

flagdefaultwhat it does
--mockoffsimulated source; no DAQ needed
--window N5seconds of history each pane shows
--units pT|VpTdisplay units. Volts are ground truth — the V/nT figure is unverified
--fps N10redraw rate. Drop it to 5 on a slow machine
--sample-rate N1000samples/second per channel. Startup only

Reading the window

The red or grey strip along the top is the readout, updated twice a second:

  • Normally it lists every channel's mean ± standard deviation in the chosen units.
  • If any channel is at the input rail it turns red and says so, and it says nothing else. That warning takes priority because a clipped channel still reports a mean that looks like a perfectly reasonable field value. When you see it, the true field on that channel is larger than ±5 V can represent, and every number from it is fiction.

A phase pane also prints its Pearson r and fitted slope into that strip: r near 1 means both channels are seeing the same field, and the slope is the gain ratio between them.

What you give up

Panes are fixed at launch. There is no clicking, no splitting, no changing channels while it runs — to change the layout, close it and relaunch with different --pane flags. That is the trade for having nothing that can go wrong.

If you edit scope.py

Rendering uses blitting, and has to. A full matplotlib redraw of three panes costs ~120 ms (~8 fps), and the cost is the redraw itself — thinning the data does not help. Blitting the traces over a cached background costs ~20 ms instead. Two consequences are load-bearing:

  • A pane's axes contain traces and nothing else. Rasterising text costs more per frame than every trace in every pane put together (~27 ms vs ~22 ms), which is why all text lives in the status strip — it has its own bbox, so it can be redrawn at 2 Hz independently — and why legends and titles sit above the axes, outside the blitted region, where they survive in the cached background for free.
  • Axis limits use hysteresis. They only move when the data leaves them, because a rescale forces a full redraw. Rescaling every frame would throw away everything blitting buys. test_scope.py asserts steady state needs none.

Acquisition notes

Sample rate is a startup parameter (--sample-rate, default 1000 Hz), not a runtime one: changing it means rebuilding the DAQ task, and doing that mid-run is what made the previous project's axis switching unreliable. The task is opened once over all six channels and never reconfigured; selecting an axis is purely a display concern.

Acquisition yields volts. The V/nT conversion is applied at display time (see below) — so saturation is detected against the known input range rather than an unverified sensitivity figure.

Hardware

ItemStatus
QuSpin QZFM sensors × 2In lab — gen TBD
Cancellation coilsNot yet installed
Current sourceTBD
NI-DAQ (Dev1, ai0–ai5)In lab — model TBD, confirm in NI MAX

Three acquisition settings in src/fieldzero/config.py need confirming against the actual card. MEG_DSP never set them, so it silently ran on the nidaqmx defaults:

SettingValueWhy it matters
voltage_range±5 VWhere a channel clips. This is the nidaqmx default, never a decision. At the inherited 2.7 V/nT that is a clip at ~1.85 nT — and the residual field is believed to be nT-scale, so channels may already be railing.
terminal_configRSERSE vs differential materially changes the noise floor.
sensitivity_v_per_nT2.7Unverified — inherited from MEG_DSP, and the QZFM generation is still unconfirmed. Volts are ground truth until this is checked against the sensor label.

Parts

73
QtyValueFootprintReferences
12100nCapacitor_SMD:C_0603_1608Metric_Pad1.08x0.95mm_HandSolderC14, C19, C20, C21, C30, C31, C36, C37, C38, C39, C40, C41
12TestPoint_ProbeTestPoint:TestPoint_Pad_D1.0mm2.5V, Coil, Coil, DAC1, DAC2, GND, GND, GND, HCP, HCP, MSB, RS
1010kResistor_SMD:R_0603_1608Metric_Pad0.98x0.95mm_HandSolderR18, R19, R26, R27, R28, R29, R30, R31, R34, R37
81uCapacitor_SMD:C_0603_1608Metric_Pad1.08x0.95mm_HandSolderC27, C32, C33, C34, C49, C50, C52, C53
8249Resistor_SMD:R_0603_1608Metric_Pad0.98x0.95mm_HandSolderR38, R39, R40, R41, R44, R45, R46, R47
41kResistor_SMD:R_0603_1608Metric_Pad0.98x0.95mm_HandSolderR32, R33, R35, R36
4MountingHole_PadMountingHole:MountingHole_2.2mm_M2_DIN965_PadH1, H2, H3, H4
4OPA2210Package_SO:SOIC-8_3.9x4.9mm_P1.27mmU11, U12, U13, U14
24.7uCapacitor_SMD:C_0603_1608Metric_Pad1.08x0.95mm_HandSolderC54, C55
2Conn_01x02_SocketConnector_Molex:Molex_Micro-Fit_3.0_43650-0215_1x02_P3.00mm_VerticalJ1, J2
110nCapacitor_SMD:C_0603_1608Metric_Pad1.08x0.95mm_HandSolderC29
110uCapacitor_SMD:C_1206_3216Metric_Pad1.33x1.80mm_HandSolderC28
1Arduino_Nano_R4Module:Arduino_NanoA1
1DAC8814Package_SO:SSOP-28_5.3x10.2mm_P0.65mmU8
1LTC6655-2.5Package_LCC:Analog_LCC-8_5x5mm_P1.27mmU7
1SW_DPSTfieldZero:DPST_RA83231100SW1
1Screw_Terminal_01x04TerminalBlock_Phoenix:TerminalBlock_Phoenix_MKDS-1,5-4-5.08_1x04_P5.08mm_HorizontalJ3

Questions

How big is the Low Current Driver PCB?

It measures 127.8 × 53 mm and has 4 copper layers, 1.6 mm thick.

What parts does the Low Current Driver use?

73 parts, built around the OPA2210ID, LTC6655CHLS8-2.5#PBF, RC0603FR-0710KL and TMK107B7105KA-T. The full list of its parts is on this page, with links to each part's symbol and footprint where PCBKit's library has them.

Where can I download the Low Current Driver KiCad files?

From the Kayzerjali/FieldZero repository on GitHub, at the commit this page was made from: the board is Hardware/Low_Current_Driver/Low_Current_Driver.kicad_pcb and its schematic Hardware/Low_Current_Driver/Channel_Outputs.kicad_sch. PCBKit opens the board in the browser, with nothing to install.

Can I use the Low Current Driver design in my own project?

Its repository states no licence, so its author keeps all rights: ask Kayzerjali before reusing the design.

Similar boards

  • 4 layers70 × 54.5 mmInt debugger, the printed circuit board in 3D

    Int debugger

    TUDSSL/DIPS

    This is the official public repository for DIPS: Debugger for Intermittently-Powered Systems. The repository consists of all components that make DIPS: - Hardware - Debugger Firmware - Emulator Firmware - Emulator Host…

    224 parts
  • 8 layers110 × 181.8 mmZest, the printed circuit board in 3D

    Zest

    BerkeleyLab/Zest

    Zest is a FMC mezzanine board with 8 ADC channels and 2 DACs

    441 parts12
  • 4 layers160 × 99.5 mmHigh Current Driver, the printed circuit board in 3D

    High Current Driver

    Strathclyde-EQOP/Vernier-Current-Driver

    The Vernier Current Driver is an ultra low noise, programmable, bi-polar current source. It comes in two flavours:

    248 parts9CC-BY-SA-4.0
  • 4 layers194 × 94 mmBottom board, the printed circuit board in 3D

    Bottom board

    mod-audio/hw-mod-dwarf

    Hardware project of MOD Dwarf

    319 parts6
  • 4 layers172 × 140.5 mmBottom board, the printed circuit board in 3D

    Bottom board

    mod-audio/mod-hw-duoX

    Hardware project of MOD Duo X

    403 parts1
  • 6 layers229.2 × 100.8 mmGadas MSE Slave Module, the printed circuit board in 3D

    Gadas MSE Slave Module

    Gady159/Gadas_MSE_ELoad

    Master's thesis project by Jan Gadas: STM32 firmware, Nextion HMI and KiCad hardware design files for a modular high-power electronic load.

    576 parts
  • 4 layers270 × 100 mmInput board, the printed circuit board in 3D

    Input board

    robmacl/ilemt_hw

    Hardware design files for ILEMT position tracker

    416 parts
  • 2 layers60 × 111.3 mmSilkscreen pcb, the printed circuit board in 3D

    Silkscreen pcb

    iandchasse/silkscreen-pcb

    Silkscreen: open-hardware ESP32-S3 e-paper reader mainboard (KiCad 9). Formerly the de-link hub.

    189 parts589CERN-OHL-S-2.0