A Python desktop application for controlling up to three FNIRSI DPS-150 programmable power supplies simultaneously over USB serial, with integrated closed-loop magnetic field compensation via a WitMotion HWT3100-485 fluxgate magnetometer.
Python/PySerial port of the fnirsi-dps-150 WebSerial interface.
- Three simultaneous devices — connect and control three DPS-150 units independently from one window
- Live meters — real-time voltage, current, and power readings at 500 ms refresh
- CV / CC mode indicator and protection fault badges (OVP / OCP / OPP / OTP / LVP)
- Setpoint controls — entry fields with ▲ / ▼ steppers and Enter-to-apply for V and I
- Always-visible history plots — voltage and current traces for all three devices side by side
- Magnetometer PID compensation — closed-loop Helmholtz coil driver using the HWT3100-485 and the three DPS-150 units as X / Y / Z axis coil drivers
- Per-device tabs for Metering (Ah / Wh), Protections (OVP/OCP/OPP/OTP/LVP), Program (DSL scripting), and Settings (brightness / volume)
- DSL scripting — automate sequences with
V(),I(),ON(),OFF(),SLEEP(),times() - CSV export of measurement history
| Device | Interface | Notes |
|---|---|---|
| FNIRSI DPS-150 (×3) | USB serial, 115200 baud | One per Helmholtz coil axis |
| WitMotion HWT3100-485 | Modbus RTU / RS-485 | Via USB-to-RS485 adapter (CH340 or CP2102) |
Wire each Helmholtz coil pair so that positive current on its DPS-150 output opposes the ambient field on that axis (Dev 1 → X, Dev 2 → Y, Dev 3 → Z).
pip install pyserial customtkinter matplotlibRun:
python main.py- Click ⟳ Scan ports to populate the port dropdowns
- Select the correct COM port for each device and click Connect
- Type a voltage or current setpoint and press Set V / Set I (or Enter)
- Toggle OUTPUT ON / OFF per device
- Open the Magnetometer tab
- Select the magnetometer's COM port, set baud rate (default 9600) and Modbus address (default
0x00), then click Connect - Configure per-axis calibration:
- A / μT — coil calibration constant. For a Helmholtz pair with radius R and N turns per coil:
I/B = R / ((4/5)^(3/2) × μ₀ × N). Example: N = 100, R = 0.15 m → ~0.00084 A/μT - Polarity — set to
-1.0if your coil winding opposes the convention - Max V / Max I — compliance voltage and current clamp per axis
- A / μT — coil calibration constant. For a Helmholtz pair with radius R and N turns per coil:
- Set PID gains (Kp, Ki, Kd), integral limit, and loop interval
- Set field target (default 0, 0, 0 μT)
- Click ▶ Start PID — the loop runs, adjusting DPS-150 current setpoints to zero the field
- Click ■ Stop to ramp currents to zero and disable outputs
Write programs in the Program tab using the built-in DSL:
# Ramp voltage from 1 V to 5 V in 0.2 V steps
V(1)
I(0.5)
ON()
SLEEP(500)
while V() + 0.2 <= 5:
V(V() + 0.2)
SLEEP(200)
OFF()Available functions:
| Function | Description |
|---|---|
V(v) |
Set voltage to v V (or read current output voltage if no arg) |
I(a) |
Set current limit to a A (or read current output current if no arg) |
ON() |
Enable output |
OFF() |
Disable output |
SLEEP(ms) |
Wait ms milliseconds |
times(n, fn) |
Repeat fn n times |
The Tab target selector above the tabs determines which device the Program, Metering, Protections, and Settings tabs operate on.
The DPS-150 uses a binary framing protocol at 115200 baud 8N1:
TX (host → device): F1 <CMD> <PARAM_ID> <LEN> <PAYLOAD> <CHECKSUM>
RX (device → host): F0 <CMD> <PARAM_ID> <LEN> <PAYLOAD> <CHECKSUM>
Checksum = (PARAM_ID + LEN + Σ PAYLOAD) mod 256. Payload floats are little-endian IEEE-754 32-bit. Register 0xFF returns all device state in one bulk frame.
| File | Purpose |
|---|---|
main.py |
Full application — DPS-150 driver, PID controller, UI |
magnetometer.py |
WitMotion HWT3100-485 Modbus RTU driver |
requirements.txt |
Python dependencies |
- Serial protocol reverse-engineered from cho45/fnirsi-dps-150
- Magnetometer PID compensation architecture adapted from compensation-magnetometer-controls