← Closed-Loop Current Calibration on a Raspberry PLC
Fuse test benchRaspberry PLC 19RSCPII2CControl
Closed-Loop Current Calibration on a Raspberry PLC — full example
Closed-loop current calibration in Python: read a shunt every 50 ms via ADS1015 and trim a SCPI power supply setpoint in 0.1 A steps until it converges.
Complete, runnable program for the Raspberry PLC 19R (closed-loop-current-calibration.py): wiring header, requirements and integration notes included.
Download the full project pack — freeThis example + the related ones + bill of materials
Read-only preview.
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
FULL EXAMPLE — Closed-loop current calibration
Hardware: Raspberry PLC 19R (Industrial Shields)
Based on: a fuse test bench project
Wiring:
- Sorensen XG power supply over SCPI/serial on /dev/ttyUSB0 (9600 baud)
- ADS1015 over I2C (0x48): 60 mOhm shunt on A0-A1
Requirements:
pip3 install pyserial adafruit-circuitpython-ads1x15
Idea:
The supply delivers "its" current, but the test is certified against the
REAL current measured at the shunt. This loop reads the shunt every 50 ms
and trims the SCPI setpoint in ±0.1 A steps until the measurement
converges with the target (5 s timeout).
Integration with the catalog:
- Supply driver: sorensen-scpi-power-supply-control.py
- Current measurement: ads1015-differential-adc-autogain.py
- This loop runs at the start of every test sequence
(fuse-test-sequences.py).
"""
import time
import serial
# --- Loop parameters --------------------------------------------------------
PERIOD_S = 0.05 # read the shunt every 50 ms
STEP_A = 0.1 # trim the setpoint in ±0.1 A steps
TOLERANCE_A = 0.15 # dead band: inside it the loop is considered converged
TIMEOUT_S = 5.0 # maximum calibration time
# --- Power supply (reduced version of the SCPI driver in the catalog) -------
class PowerSupply:
def __init__(self, port="/dev/ttyUSB0"):
self.ser = serial.Serial(port=port, baudrate=9600,
bytesize=serial.EIGHTBITS,
parity=serial.PARITY_NONE,
stopbits=serial.STOPBITS_ONE, timeout=1)
self._tx("*ADR 1")
def _tx(self, cmd):
self.ser.write((cmd + "\r").encode())
time.sleep(0.05)
def set_current(self, amps):
self._tx("SOUR:CURR %.2f" % amps)
def output(self, on):
self._tx("OUTP %s" % ("ON" if on else "OFF"))
def read_shunt_current():
"""Real current through the 60 mOhm shunt via ADS1015 (gain x16).
The full auto-ranging version lives in ads1015-differential-adc-autogain.py;
this is the bench formula in short:
current = (raw/65535) * 2.0 * 0.256 * 200.0 / 0.06 * 1.02
"""
raw = _read_raw_ads1015() # differential reading A0-A1
return (raw / 65535.0) * 2.0 * 0.256 * 200.0 / 0.06 * 1.02
def _read_raw_ads1015():
import adafruit_ads1x15.ads1015 as ADS
from adafruit_ads1x15.analog_in import AnalogIn
import board, busio
ads = ADS.ADS1015(busio.I2C(board.SCL, board.SDA), address=0x48)
ads.gain = 16
return AnalogIn(ads, ADS.P0, ADS.P1).value
def calibrate(supply, target_a):
"""Trim the setpoint until the shunt current matches the target.
Returns (final_setpoint, measured_current, converged).
"""
setpoint = target_a # first approximation: setpoint = target
supply.set_current(setpoint)
t0 = time.time()
while time.time() - t0 < TIMEOUT_S:
measured = read_shunt_current()
error = target_a - measured
if abs(error) <= TOLERANCE_A:
return setpoint, measured, True # converged
# Fixed 0.1 A correction steps in the direction of the error
setpoint += STEP_A if error > 0 else -STEP_A
setpoint = max(0.0, min(setpoint, 220.0)) # XG limits
supply.set_current(setpoint)
time.sleep(PERIOD_S)
return setpoint, read_shunt_current(), False # timed out without converging
def main():
supply = PowerSupply()
target = 32.0 # A — e.g. non-fusing test of a 25 A fuse
try:
supply.output(True)
print("Calibrating to %.1f A..." % target)
setpoint, measured, ok = calibrate(supply, target)
print("Final setpoint : %.2f A" % setpoint)
print("Real current : %.2f A" % measured)
print("Result : %s" % ("CONVERGED" if ok else "TIMEOUT 5 s"))
# The test itself would start here, using the calibrated setpoint
finally:
supply.output(False)
if __name__ == "__main__":
main()
Download the full project pack — freeThis example + the related ones + bill of materials