calibrator.py 10.1 KB
Newer Older
1 2 3 4 5 6 7 8
#!/usr/bin/python3

import sys
import time
import numpy
import traceback
from serialio import SerialIO
import math
9
import curses
10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30

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):
31
        if self.k==1:                # Loop until external ref disabled
32 33 34 35 36 37 38 39 40 41 42 43 44
            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

45 46
    def __init__(self, stdscr, port):
        self.stdscr = stdscr
47 48 49
        self.io = SerialIO(port)
        self.action_msg = ""
        self.status_msg = ""
50 51 52 53 54 55
        begin_x = 20; begin_y = 7
        height = 5; width = 40
        if self.stdscr:
            self.iowin = curses.newwin(2 + len(self.io.analogIn()), 80, 0,0)
            self.gotoxy(0,0)
            pass
56 57 58 59 60
        pass
    
    def close(self):
        self.io.setchannel(self.ANALOG_OUT_0, 0x8000)
        self.io.setchannel(self.ANALOG_OUT_1, 0x8000)
61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81
        pass

    def gotoxy(self, x, y):
        if self.stdscr == None:
            return
        self.iowin.move(y,x)

    def print(self, *args, **kwargs):
        if self.stdscr == None:
            print(*args, **kwargs)
            return

        for s in args:
            try:
                self.iowin.addstr(s)
            except:
                pass
            pass
        if not 'end' in kwargs or kwargs['end'] != '':
            self.iowin.refresh()
            pass
82 83 84 85 86

    def measure(self, analogIn, N=100, message=''):
        time.sleep(0.1)
        stat = {}
        for b in range(N):
87 88 89 90 91
            self.gotoxy(0,0)
            self.print(message, end='')
            channels = analogIn.items()
            for i,c in channels:
                self.gotoxy(0,i+2)
92 93 94 95 96 97 98
                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
99
                self.print('%s ' % stat[i], end='')
100
                pass
101
            self.print(end='\r')
102
            pass
103
        self.print()
104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124
        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
125 126 127 128
        self.gotoxy(0,1)
        self.print("Ref=", str(ref), " Expecting=", str(expect))
        self.sample = self.measure(analogIn, N=N, message=message)
        for e,v in zip(expect, self.sample):
129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147
            if e != None and (v.mean() < e[0] or e[1] < v.mean()):
                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:
148 149
            raise Exception('Did not find 4 analogIn channels (%d)' %
                            (len(analogIn)))
150
        if len(analogOut) != 2:
151 152
            raise Exception('Did not find 2 analogOut channels (%d)'
                            (len(analogOut)))
153 154 155 156
        for i,c in analogIn.items():
            if c.bits != 18:
                raise Exception('analogIn[%d] is not 18 bit (%d)' % (i, c.bits))
            pass
157 158 159 160
        for i,c in analogOut.items():
            if c.bits != 16:
                raise Exception('analogOut[%d] is not 16 bit (%d)' % (i, c.bits))
            pass
161 162 163 164 165 166 167

        # 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)

168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202
        # Loop while connected to test harness 5V ref
        badSamples = 0
        inHarness = 0
        while True:
            badSamples += 1
            if self.expect(analogIn,
                               [ 0, 0 ], 
                               [ (4.5, 5.5), (-0.1, 0.1),
                                 (4.5, 5.5), (-0.1, 0.1) ],
                               N=50,
                               message='Checking if in test harness 1'):
                badSamples = 0
                pass
            if self.expect(analogIn,
                           [ 0, 0 ], 
                           [ (-0.1, 0.1), (4.5, 5.5), 
                             (-0.1, 0.1), (4.5, 5.5) ],
                           N=50,
                           message='Checking if in test harness 2'):
                badSamples = 0
                pass
            inHarness += 1
            if badSamples > 4:
                break
            pass

        if inHarness < 10:
            # 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
203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260
            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())) ]
261
        self.print("Done %x %x : %x %x" % (v0, v1, best[0][0][0], best[1][0][0]))
262 263 264 265 266 267 268 269 270 271 272 273
        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)')
274
        self.print("0: Min: %f, Max: %f" % (min_sample[0].mean() / 9 * 10,
275
                                       max_sample[0].mean() / 9 * 10))
276
        self.print("1: Min: %f, Max: %f" % (min_sample[1].mean() / 9 * 10,
277 278 279 280 281 282 283 284 285 286 287 288 289 290 291
                                       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
292
        self.print("%d %8x" % (index, tmp))
293 294
        self.io.setchannel(31, tmp, 0xffffffff)
        
295 296 297

def main(stdscr):
    io = ChinaIO(stdscr, sys.argv[1])
298 299 300
    try:
        io.calibrate()
        pass
301 302 303 304
    finally:
        io.close()
        pass
    pass
305

306 307
if __name__ == "__main__":
    curses.wrapper(main)