Last active
June 17, 2020 20:31
-
-
Save atx/5193168cfd5f2fe07f95175f91ac03f6 to your computer and use it in GitHub Desktop.
Hacky script which combines `sigrok-cli` and `dpsctl.py` to calibrate OpenDPS. Will likely need modifications before use. Note: The current calibration is currently untested, it looks like the DPS needs nonzero load resistor in order to measure current and I do not have any at hand.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#! /usr/bin/env python3 | |
import argparse | |
import subprocess | |
import pathlib | |
import time | |
import scipy.stats | |
import numpy as np | |
DAC_MAX = 2**12 - 1 | |
def to_base_units(val, unit_scaled): | |
if len(unit_scaled) == 1: | |
return val, unit_scaled | |
assert len(unit_scaled) == 2 | |
FACTORS = { | |
"m": 1e-3 | |
} | |
factor = unit_scaled[0] | |
unit = unit_scaled[1] | |
return FACTORS[factor] * val, unit | |
def sigrok_measure(driver): | |
# My sigrok-cli returns weird exit codes, hence check_output does not work | |
p = subprocess.Popen( | |
["sigrok-cli", "--driver", driver, "-O", "analog", "--samples", "1"], | |
stderr=subprocess.PIPE, stdout=subprocess.PIPE | |
) | |
output, error = p.communicate() | |
_, val_raw, unit_raw, *_ = output.split() | |
return to_base_units(float(val_raw), unit_raw.decode()) | |
class DPSCTL: | |
def __init__(self, path, dev): | |
self.path = path | |
self.dev = dev | |
def call(self, *args): | |
return subprocess.check_output( | |
[self.path, "-d", self.dev] + list(args) | |
) | |
def set_param(self, name, value): | |
output = self.call("-p", f"{name}={value}") | |
assert output.endswith(b"ok\n") | |
if __name__ == "__main__": | |
parser = argparse.ArgumentParser() | |
parser.add_argument("-c", "--dpsctl", type=pathlib.Path) | |
parser.add_argument("-d", "--dpsctl-dev", required=True) | |
parser.add_argument("-s", "--sigrok-driver", required=True) | |
parser.add_argument("-t", "--type", choices=["voltage", "current"], required=True) | |
parser.add_argument("-n", "--sample-count", type=int, default=50) | |
parser.add_argument("--dry-run", action="store_true") | |
parser.add_argument("--dump", type=pathlib.Path) | |
args = parser.parse_args() | |
ctl = DPSCTL(args.dpsctl, args.dpsctl_dev) | |
# This disables various limits | |
ctl.call("--screen", "settings") | |
ctl.call("-o", "on") | |
if args.type == "voltage": | |
par_dac = "V_DAC" | |
par_dac_other = "A_DAC" | |
par_adc = "V_ADC" | |
par_out_adc = "VOUT_ADC" | |
units = "V" | |
elif args.type == "current": | |
par_dac = "A_DAC" | |
par_dac_other = "V_DAC" | |
par_adc = "A_ADC" | |
par_out_adc = "IOUT_ADC" | |
units = "A" | |
ctl.set_param(par_dac, 0) | |
ctl.set_param(par_dac_other, DAC_MAX) | |
time.sleep(3) # This can take a long time if we are at the other side | |
dacs_full = np.arange(0, DAC_MAX, DAC_MAX / args.sample_count, dtype=np.int32) | |
dacs = [] | |
out_real = [] | |
out_measured = [] | |
for i, v_dac in enumerate(dacs_full): | |
ctl.set_param(par_dac, v_dac) | |
time.sleep(1.0) | |
v, u = sigrok_measure(args.sigrok_driver) | |
assert u == units, "Wrong units!" | |
print(v_dac, v) | |
dacs.append(v_dac) | |
out_real.append(v) | |
cal_report = map(str.strip, ctl.call("-cr").decode().split("\n")) | |
for line in cal_report: | |
if not line.startswith(par_out_adc): | |
continue | |
out_measured.append(int(line.split()[-1])) | |
# The current range does not work from 0, so we have to cut the beginning | |
# too | |
slopes = np.diff(out_real) / np.diff(dacs) | |
i_first, i_last = np.where(abs(slopes) > 0.005)[0][[0, -1]] | |
i_last += 1 | |
out_measured = out_measured[i_first:i_last] | |
out_real = out_real[i_first:i_last] | |
dacs = dacs[i_first:i_last] | |
if args.dump: | |
with args.dump.open("w") as f: | |
for v_dac, v_out, v_adc in zip(dacs, out_real, out_measured): | |
f.write(f"{v_dac} {v_out} {v_adc}\n") | |
out_real = np.array(out_real)*1e3 # Should be in mV | |
dac_k, dac_c, *_ = scipy.stats.linregress(out_real, dacs) | |
print(f"DAC K = {dac_k} C = {dac_c}") | |
adc_k, adc_c, *_ = scipy.stats.linregress(out_measured, out_real) | |
print(f"ADC K = {adc_k} C = {adc_c}") | |
if not args.dry_run: | |
ctl.call( | |
"-c", | |
f"{par_dac}_K={dac_k}", | |
f"{par_dac}_C={dac_c}" | |
) | |
ctl.call( | |
"-c", | |
f"{par_adc}_K={adc_k}", | |
f"{par_adc}_C={adc_c}" | |
) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment