#!/usr/bin/python3 import sys import time import numpy import traceback from serialio import SerialIO import math class Welford: def __init__(self, value): self.k = 0 self.M = 0 self.S = 0 self.update(value) pass def update(self, value): self.k += 1 newM = self.M + (value - self.M) / self.k newS = self.S + (value - self.M) * (value - newM) self.M = newM self.S = newS def mean(self): return self.M def stddev(self): if self.k==1: return 0 return math.sqrt(self.S/(self.k-1)) def __repr__(self): return "%f +- %f" % (self.mean(), self.stddev()) class ChinaIO: ANALOG_OUT_0 = 0 ANALOG_OUT_1 = 1 AIN_STEP = 20/(1<<18) # 20V, 18 bit resolution AOUT_STEP = 20/(1<<16) # 20V, 18 bit resolution def __init__(self, port): self.io = SerialIO(port) self.action_msg = "" self.status_msg = "" pass def close(self): self.io.setchannel(self.ANALOG_OUT_0, 0x8000) self.io.setchannel(self.ANALOG_OUT_1, 0x8000) def measure(self, analogIn, N=100, message=''): time.sleep(0.1) stat = {} for b in range(N): print(message, '[', end='') for i,c in analogIn.items(): value = (self.io.getchannel(i) - (1<<17)) * self.AIN_STEP if not i in stat: stat[i] = Welford(value) pass else: stat[i].update(value) pass print('%s ' % stat[i], end='') pass print(']', end='\r') pass print() return [ stat[i] for i in sorted(stat.keys()) ] def setCalibration(self, index, value): if value > 0: self.io.setchannel(31, (value << 8) | index & 0x3f) pass else: self.io.setchannel(31, (-value << 8) | 0x80 | index & 0x7f) pass pass def setVoltage(self, channel, value): self.io.setchannel(channel, int(value/ self.AOUT_STEP + (1<<15))) def expect(self, analogIn, ref, expect, N, message): for i,r in zip(range(100), ref): if r != None: self.setVoltage(i, r) pass pass time.sleep(0.1) # Let values settle sample = self.measure(analogIn, N=N, message=message) for e,v in zip(expect, sample): if e != None and (v.mean() < e[0] or e[1] < v.mean()): print(" Expected:", expect) return False pass return True def calibrate(self): # Calibration steps: # 1. Sanity check returned channels # 2. Set offset calibration to default value # 3. Read values from grounded inputs # 4. Connect AOut0 -> AIn0,AIn2 (+9V) # 5. Connect AOut1 -> AIn1,AIn3 (-9V) # 6. Zero adjust # 7. Determine min and max values # Sanity check analogIn = self.io.analogIn() analogOut = self.io.analogOut() if len(analogIn) != 4: raise Exception('Did not find 4 analogIn channels (%d)' % (len(analogIn))) if len(analogOut) != 2: raise Exception('Did not find 2 analogOut channels (%d)' (len(analogOut))) for i,c in analogIn.items(): if c.bits != 18: raise Exception('analogIn[%d] is not 18 bit (%d)' % (i, c.bits)) pass for i,c in analogOut.items(): if c.bits != 16: raise Exception('analogOut[%d] is not 16 bit (%d)' % (i, c.bits)) pass # Set offset calibration to default value # self.io.setchannel(31, (0x8000 << 8) | 0x00) # self.io.setchannel(31, (0x8000 << 8) | 0x00) self.setCalibration(0, 0x8000) self.setCalibration(1, 0x8000) # Read values from grounded inputs while not self.expect(analogIn, [ -9, 9 ], [ (-0.1, 0.1), (-0.1, 0.1), (-0.1, 0.1), (-0.1, 0.1) ], N=500, message='Ground all input pins'): pass # Connect AOut0 -> AIn0,AIn2 (+9V) while not self.expect(analogIn, [ 9, None ], [ (8.7,9.3), None, (8.7,9.3), None ], N=500, message='Connect: AOut0 -> AIn0, AIn2'): pass # Connect AOut1 -> AIn1,AIn3 (-9V) while not self.expect(analogIn, [ None, -9 ], [ None, (-9.3,-8.7), None, (-9.3,-8.7) ], N=500, message='Connect: AOut1 -> AIn1, AIn3'): pass # Zero adjust while not self.expect(analogIn, [ 0, 0 ], [ (-0.1, 0.1), (-0.1, 0.1), (-0.1, 0.1),(-0.1, 0.1) ], N=500, message='Calibrating zero'): pass # 5. Zero adjust v0 = 0x8000 v1 = 0x8000 delta = 0x4000 self.io.setchannel(0, 0x8000) self.io.setchannel(1, 0x8000) self.setCalibration(0, v0) self.setCalibration(1, v1) sample = self.measure(analogIn, N=100, message='Sampling baseline') def nextv(oldv, delta, sample): if sample.mean() > 0: return oldv + delta else: return oldv - delta pass candidate = [[], []] while delta >= 1: v0 = nextv(v0, delta, sample[0]) v1 = nextv(v1, delta, sample[1]) self.setCalibration(0, v0) self.setCalibration(1, v1) sample = self.measure(analogIn, N=200, message='Zero (v0=%x, v1=%x)' % (v0, v1)) candidate[0].append([v0, sample[0]]) candidate[1].append([v1, sample[1]]) delta = delta // 2 pass best = [ sorted(candidate[0], key=lambda x: abs(x[1].mean())), sorted(candidate[1], key=lambda x: abs(x[1].mean())) ] print("Done %x %x : %x %x" % (v0, v1, best[0][0][0], best[1][0][0])) v0 = best[0][0][0] v1 = best[0][0][0] self.setCalibration(0, v0) self.setCalibration(1, v1) # Determine min and max values self.setVoltage(0, -9) self.setVoltage(1, -9) min_sample = self.measure(analogIn, N=200, message='Min (-9V)') self.setVoltage(0, 9) self.setVoltage(1, 9) max_sample = self.measure(analogIn, N=200, message='Max (+9V)') print("0: Min: %f, Max: %f" % (min_sample[0].mean() / 9 * 10, max_sample[0].mean() / 9 * 10)) print("1: Min: %f, Max: %f" % (min_sample[1].mean() / 9 * 10, max_sample[1].mean() / 9 * 10)) def millivolt(v): return int(v * 1000) self.setCalibration(2, millivolt(min_sample[0].mean() / 9 * 10)) self.setCalibration(3, millivolt(max_sample[0].mean() / 9 * 10)) self.setCalibration(4, millivolt(min_sample[1].mean() / 9 * 10)) self.setCalibration(5, millivolt(max_sample[1].mean() / 9 * 10)) self.setCalibration(6, 0); pass def write_calibration(self, index, value): if value < 0: tmp = (int(-value * 1000) << 8) | 0x80 | index else: tmp = (int(value * 1000) << 8) | index print("%d %8x" % (index, tmp)) self.io.setchannel(31, tmp, 0xffffffff) if __name__ == "__main__": io = ChinaIO(sys.argv[1]) try: io.calibrate() except: traceback.print_exc() pass io.close()