Magnetic-field calibration and open-loop field programming for a 3-axis Helmholtz coil system (Bartington BHC2000), with a built-in 3D view of the generated field vector.
The 3D view: square copper coils on a cubic aluminium frame; coils on the axes that dominate the target field are highlighted in orange. Rotatable with the mouse.
The app drives the three coil axes through
HelmMagControl (TCP) and measures the resulting
field with the magnetometer of a phone running
SensorCast (UDP). From those samples it
fits the affine model B = M·I + b and, once calibrated, computes the currents
needed to generate a requested target field.
Lab tool. Primary version in Lazarus / Free Pascal (LCL), cross-platform, with no external dependencies (UI and 3D drawn on
Canvas, networking on the RTL sockets, JSON viafpjson). A Delphi (VCL) port lives indelphi/— see below.
- Connection (live): TCP client for HelmMagControl (
READ ALLpolling) and UDP client for SensorCast (magnetometer + K-sample averaging). - Calibration wizard: automatic current sweep → settle → average K samples → least-squares fit. Manual point capture, RMS residual, and profile save/load (JSON).
- Open-loop field programming: given a target B vector (coil frame), it computes the currents with per-axis clamping, warns on saturation, and shows the actually achievable field. A nominal catalog model is available before calibrating.
- 3D view: wireframe of the support cube and the three square Helmholtz coil
pairs to scale, plus the B-vector arrow. Rotate with the mouse, zoom with the
wheel, drawn on
Canvaswith no 3D libraries. Coils on the dominant axis of the target field are highlighted.
Sensor frame: B = M·I + b, with M 3×3 (µT/A) and b 3×1 (µT, ambient field).
- Fit (least squares, normal equations):
A = [M|b] = (Σ Bₖ·xₖᵀ)·(Σ xₖ·xₖᵀ)⁻¹, withxₖ = [Iₖ; 1]. Needs ≥ 4 non-coplanar points (including I = 0). - Polar decomposition
M = R·G(Jacobi onMᵀM):Rcoil→sensor rotation,Gsymmetric gain ≈ diag(kₓ, k_y, k_z). - Open loop:
I = G⁻¹·(B_target − Rᵀ·b), then clamp to ±I_max per axis.
| Unit | Responsibility |
|---|---|
uMatrix |
3×3/4×4 linear algebra: inverses, least squares, Jacobi, polar/SVD. |
uCoils |
TCP client for the HelmMagControl text protocol (pure protocol logic + TCoilClient). |
uSensor |
UDP client for SensorCast (pure ParseSensorJSON + threaded TSensorClient). |
uCalib |
B=M·I+b model: points, fit, polar decomposition, RMS residual, JSON profile, nominal/manual model. |
uField |
Open-loop field programming: inverse + clamp + achieved field + send. |
uView3D |
TView3DPanel: wireframe 3D view drawn on Canvas. |
uMainForm |
Main form with the four tabs. |
Four tabs: Connection · Calibration · Program field · 3D view.
| Connection | Program field |
|---|---|
![]() |
![]() |
The calibration wizard drives a sequence of current combinations (0, ±I per axis, and mixes); for each point it lets the field settle and averages K magnetometer samples. The animation below shows the swept points: the energized axes light up and the resulting B vector points in the corresponding direction.
Once calibrated (or with the nominal catalog model), you request a target B vector
in the coil frame and the app computes the per-axis currents I = G⁻¹·(B − Rᵀ·b),
clamping to the current limits. The animation shows the target B sweeping through
directions and the currents computed for each one.
Requires Lazarus / FPC 3.2.2.
# GUI app -> lib/x86_64-win64/HelmCalib.exe
bash build.sh
# Console tests for the logic (105+ assertions, exit = number of failures)
bash tests/run.shbuild.sh generates the project resource (HelmCalib.res, with a manifest for
themes/DPI) via fpcres before invoking lazbuild. You can also open
HelmCalib.lpi directly in the Lazarus IDE.
delphi/ contains an equivalent Delphi (VCL, Win64) version, tested
with RAD Studio Athens (Delphi 37.0). Same architecture and the same units;
only the platform dependencies differ:
- Networking: Indy 10 (
TIdTCPClient,TIdUDPClient) instead of the RTL sockets. - JSON: System.JSON instead of
fpjson. - UI/3D: VCL (
Vcl.*) with.dfminstead of LCL/.lfm.
cd delphi
bash build.sh # GUI -> delphi/HelmCalib.exe (or open HelmCalib.dproj in the IDE)
bash tests/run.sh # 115 logic assertions, exit = number of failuresThe logic is covered by the same console tests (115 assertions); the GUI and 3D view were verified on Win64.
HelmCalib embeds a TCP text server (same style as HelmMagControl) so the whole
system can be driven remotely from Python or any language. Enable it from the
Connection tab (default port 4445) or start the app with -remote
(-remote -port=NNNN for a custom port).
One line per command, responses OK ... / ERROR ..., UTF-8:
PING · STATUS · HELP
CONNECT COILS <host> <port> · CONNECT SENSOR <ip> [tx] [rx] · DISCONNECT COILS|SENSOR
GET MAG · GET MAGAVG <k> · READALL
MODEL NOMINAL A|B · LOAD PROFILE <path> · SAVE PROFILE <path> · GET MODEL
SOLVE <bx> <by> <bz> · SETFIELD <bx> <by> <bz> · SETCURRENTS <i1> <i2> <i3> · FIELDOFF
CALIB CLEAR · CALIB ADD <ix iy iz bx by bz> · CALIB COUNT · CALIB FIT
A Python client is provided in
python-client-for-helmholtz-rig
(HelmCalibClient):
from helmcalib_control import HelmCalibClient
c = HelmCalibClient("127.0.0.1", 4445); c.connect()
c.model_nominal("A")
print(c.solve(40, 20, 60)) # -> {'I': (1.61, 0.79, 2.39), 'saturated': False, ...}
c.connect_coils("127.0.0.1", 4444)
c.set_field(40, 20, 60) # computes and sends the currents- Coils: Bartington BHC2000 (3 orthogonal pairs). Model A: ~25 µT/A, 1.0 mT/axis, 40 A. Model B: ~15 µT/A, 240 µT/axis, 16 A.
- Actuator: Wanptek power supplies via HelmMagControl, TCP text protocol (port 4444).
- Sensor: Android phone running SensorCast at the center of the coils (UDP, JSON every 200 ms).
All four tabs (Connection · Calibration · Program field · 3D view) are operational. The logic is covered by console tests with synthetic data; the network I/O compiles and the protocol/parsing logic is verified — end-to-end testing against the real hardware is left for commissioning.
See CHANGELOG.md for the per-version detail.
Released under the MIT License.




