Settings save/restore helpers UNTESTED
authorDan White <dan@whiteaudio.com>
Mon, 15 Oct 2012 19:49:11 +0000 (14:49 -0500)
committerDan White <dan@whiteaudio.com>
Mon, 15 Oct 2012 19:49:11 +0000 (14:49 -0500)
python-lib/usbio.py

index d9c0f530355ac638dc8a49a868318476087025f6..5b807a582945f7bd05060ec148df42508171f343 100644 (file)
@@ -653,6 +653,14 @@ class NCO(object):
         v = intbv(value, max=2**self.FCW_WIDTH)
         self._word[self.FCW_WIDTH:] = v
 
+    def dump_settings(self):
+        """Return a dict appropriate for passing to constructor to restore the
+        amplifier state."""
+        s = {}
+        s['rst'] = int(self.rst)
+        s['fcw'] = int(self.fcw)
+        return s
+
 
 class OTA(object):
     CINT_POS = 15
@@ -774,9 +782,24 @@ class OTA(object):
         v = (v ^ (1 << (self.OFFSET_WIDTH-1)))[self.OFFSET_WIDTH:]
         self._word[a:b] = v
 
+    def dump_settings(self):
+        """Return a dict appropriate for passing to constructor to restore the
+        amplifier state."""
+        s = {}
+        s['cint'] = int(self.cint)
+        s['zero'] = int(self.zero)
+        s['se'] = int(self.se)
+        s['fast'] = int(self.fast)
+        s['gain'] = int(self.gain)
+        s['offset'] = int(self.offset)
+        return s
+
+
 
 class MuxOTA(OTA):
-    """Cast bits cint and zero as the mode.  Bit se is unused."""
+    """Cast bits cint and zero as the mode.  Bit se is unused.
+    TODO: verify bit positions and shifts -- symbols are 3-bits wide.
+    """
     MODE_SHIFT = 13
     MODE_WIDTH = 3
 
@@ -786,6 +809,15 @@ class MuxOTA(OTA):
     CAL_CMP = 4
     CAL_BUF = 6
 
+    def __init__(self, mode=0, fast=0, gain=0, offset=0):
+        # setup main functionality
+        super(MuxOTA, self).__init__(fast=fast, gain=gain, offset=offset)
+        # remove references to re-defined bits, they are named ".mode" now
+        del self.__dict__['cint']
+        del self.__dict__['zero']
+        del self.__dict__['se']
+        self.mode = mode
+
     @property
     def mode(self):
         b = self.MODE_SHIFT
@@ -799,6 +831,15 @@ class MuxOTA(OTA):
         a = b + self.MODE_WIDTH
         self._word[a:b] = v
 
+    def dump_settings(self):
+        """Return a dict appropriate for passing to constructor to restore the
+        amplifier state."""
+        s = {}
+        s['mode'] = int(self.mode)
+        s['fast'] = int(self.fast)
+        s['gain'] = int(self.gain)
+        s['offset'] = int(self.offset)
+        return s
 
 class Harmonic(object):
     HARMONIC_WIDTH = 48
@@ -807,12 +848,28 @@ class Harmonic(object):
     OTA_A_SHIFT = 16
     OTA_B_SHIFT = 0
 
-    def __init__(self, nco=None, ota=None):
+    def __init__(self, cal=0, nco=None, otaA=None, otaB=None):
+        """Restore state via kwargs, nco, otaA, and otaB are dicts to send to
+        the respective constructors if specified."""
         self._word = intbv(0)[self.HARMONIC_WIDTH:]
-        self._cal = intbv(0)[0]
-        self.nco = NCO()
-        self.otaA = OTA()
-        self.otaB = OTA()
+
+        self.cal = intbv(cal)[0]
+
+        if nco:
+            self.nco = NCO(**nco)
+        else:
+            self.nco = NCO()
+
+        if otaA:
+            self.otaA = OTA(**otaA)
+        else:
+            self.otaA = OTA()
+
+        if otaB:
+            self.otaB = OTA(**otaB)
+        else:
+            self.otaB = OTA()
+
         self.ota = (self.otaA, self.otaB)
 
     @property
@@ -845,13 +902,29 @@ class Harmonic(object):
         b = [w[i:i-8] for i in range(self.HARMONIC_WIDTH, 0, -8)]
         return tuple(b)
 
+    def dump_settings(self):
+        """Return a dict appropriate for passing to constructor to restore the
+        harmonic's state."""
+        s = {}
+        s['cal'] = int(self.cal)
+        s['nco'] = self.nco.dump_settings()
+        s['otaA'] = self.otaA.dump_settings()
+        s['otaB'] = self.otaB.dump_settings()
+        return s
+
 
 class Chain(object):
-    def __init__(self, spibus, csname, length=48):
+    def __init__(self, spibus, csname, length=48, harmonics=None):
         self.bus = spibus
         self.csname = csname
         self.length = length
-        h = [Harmonic() for i in range(self.length)]
+
+        # restore settings from list of dicts if given
+        if harmonics:
+            h = [Harmonic(**hi) for hi in harmonics]
+        else:
+            h = [Harmonic() for i in range(self.length)]
+
         self.h = tuple(h)
 
     def __str__(self):
@@ -874,6 +947,14 @@ class Chain(object):
         #self.bus.Stop()
         return out
 
+    def dump_settings(self):
+        """Return a dict appropriate for passing to constructor to restore the
+        chain's state."""
+        s = {}
+        s['length'] = int(self.length)
+        s['harmonics'] = [hi.dump_settings() for hi in self.h]
+        return s
+
 
 class Mux(object):
     MUX_WIDTH = 48
@@ -884,14 +965,23 @@ class Mux(object):
     OTA_B_SHIFT = 0
 
 
-    def __init__(self, spibus, csname):
+    def __init__(self, spibus, csname, selA=48, selB=48, otaA=None, otaB=None):
         self.bus = spibus
         self.csname = csname
         self._word = intbv(0)[self.MUX_WIDTH:]
-        self._selA = intbv(48, max=2**self.SEL_WIDTH) #select CMI
-        self._selB = intbv(48, max=2**self.SEL_WIDTH) #select CMI
-        self.otaA = MuxOTA()
-        self.otaB = MuxOTA()
+        self._selA = intbv(selA, max=2**self.SEL_WIDTH) #select CMI
+        self._selB = intbv(selB, max=2**self.SEL_WIDTH) #select CMI
+
+        if otaA:
+            self.otaA = MuxOTA(**otaA)
+        else:
+            self.otaA = MuxOTA()
+
+        if otaB:
+            self.otaB = MuxOTA(**otaB)
+        else:
+            self.otaB = MuxOTA()
+
         self.otaA.mode = self.otaA.MUX_BUF
         self.otaB.mode = self.otaB.MUX_BUF
         self.ota = (self.otaA, self.otaB)
@@ -946,6 +1036,16 @@ class Mux(object):
         #self.bus.Stop()
         return out
 
+    def dump_settings(self):
+        """Return a dict appropriate for passing to constructor to restore the
+        mux's state."""
+        s = {}
+        s['selA'] = int(self.selA)
+        s['selB'] = int(self.selB)
+        s['otaA'] = self.otaA.dump_settings()
+        s['otaB'] = self.otaB.dump_settings()
+        return s
+
 
 class AD524x(object):
     ADDR_BASE = intbv(0b0101100, max=2**7)
@@ -959,27 +1059,37 @@ class AD524x(object):
     READ = 1
     WRITE = 0
 
-    # cache for lazy updating
-    lastA = None
-    lastB = None
-    lastSel = None
-    lastO1 = None
-    lastO2 = None
+    # cache for lazy updating, init to POR values
+    lastA = 128
+    lastB = 128
+    lastSel = 0
+    lastO1 = truth(0)
+    lastO2 = truth(0)
 
-    def __init__(self, i2cbus, addr, immediate=True):
+    def __init__(self, i2cbus, addr, immediate=True,
+            posA=128, posB=128, gpo1=0, gpo2=0):
         self.bus = i2cbus
         self.addr = intbv(self.ADDR_BASE + intbv(addr, max=2**2), max=2**7)
         self.immediate = immediate
         self._sel = False
         self._rs = False
         self._sd = False
-        self._posA = intbv(0x80, max=2**8)
-        self._posB = intbv(0x80, max=2**8)
-        self.pos = (self._posA, self._posB)
+
+        # POR values
+        self._posA = intbv(128, max=2**8)
+        self._posB = intbv(128, max=2**8)
         self._gpo1 = False
         self._gpo2 = False
+
+        self.pos = (self._posA, self._posB)
         self.gpo = (self._gpo1, self.gpo2)
 
+        # restore state if necessary
+        self.posA = posA
+        self.posB = posB
+        self.gpo1 = gpo1
+        self.gpo2 = gpo2
+
     @property
     def sel(self): return self._sel
 
@@ -1030,7 +1140,7 @@ class AD524x(object):
         v = truth(value)
         self._gpo1 = v
         if immediate or (immediate is None and self.immediate):
-            self.sendGPO()
+            self.updateGPO()
 
     @property
     def gpo2(self): return self._gpo2
@@ -1040,7 +1150,7 @@ class AD524x(object):
         v = truth(value)
         self._gpo2 = v
         if immediate or (immediate is None and self.immediate):
-            self.sendGPO()
+            self.updateGPO()
 
     @property
     def instruction(self):
@@ -1110,6 +1220,15 @@ class AD524x(object):
         val = self.bus.read(1)
         return val[0]
 
+    def dump_settings(self):
+        """Return a dict of settings which can be passed to constructor to
+        restore the state."""
+        s = {}
+        s['posA'] = self.posA
+        s['posB'] = self.posB
+        s['gpo1'] = self.gpo1
+        s['gpo2'] = self.gpo2
+        return s
 
 class DAC8568(object):
     CTL_WIDTH = 32
@@ -1565,6 +1684,13 @@ class M25PExx(object):
             self.programPage(a, data[s:e])
 
 
+class NullSPI(SPI):
+    def write(*args, **kwargs):
+        pass
+
+    def exchange(*args, **kwargs):
+        pass
+
 
 
 class AtoiSPI0(SPI):
@@ -1645,17 +1771,24 @@ class DigiReg(AD524x):
         Vout = Vref * (1 + (Ra + Rpot)/Rb)
     It is sufficient then to specify the end voltages at pos=0 and pos=255 as
     the output is linear with pot position.
+
+    POR position for fitted part is posA/B = 128.
     """
-    def __init__(self, i2cbus, addr, va=[0.0,1.0], vb=[0.0, 1.0]):
+    def __init__(self, i2cbus, addr,
+            va_range=[0.0,1.0], vb_range=[0.0, 1.0],
+            aliases={}, posA=128, posB=128,
+            gpo1=0, gpo2=0):
         super(DigiReg, self).__init__(i2cbus, addr)
         # calibration voltages, measure actual hardware
-        self.va_min = min(va)
-        self.va_max = max(va)
-        self.vb_min = min(vb)
-        self.vb_max = max(vb)
-        # POR pot position
-        self._posA = 128
-        self._posB = 128
+        self.va_min = min(va_range)
+        self.va_max = max(va_range)
+        self.vb_min = min(vb_range)
+        self.vb_max = max(vb_range)
+        self.aliases = {}
+        for s,d in aliases.iteritems():
+            self.alias(s, d)
+        self.posA = posA
+        self.posB = posB
 
     def _va_getter(self):
         return ((float(self.posA) / 255) * (self.va_max - self.va_min)
@@ -1690,6 +1823,7 @@ class DigiReg(AD524x):
         > self.vfoo = 1.2
         > self.va = 1.2 #same effect as above
         """
+        self.aliases[source] = dest
         if source == 'va':
             setattr(DigiReg, dest,
                     property(lambda s: s.va, lambda s,v: s._va_setter(v)))
@@ -1701,6 +1835,41 @@ class DigiReg(AD524x):
             setattr(DigiReg, dest+'_min', self.vb_min)
             setattr(DigiReg, dest+'_max', self.vb_max)
 
+    def dump_settings(self):
+        """Return a dict of settings which can be passed to constructor to
+        restore the state."""
+        s = super(DigiReg, self).dump_settings()
+        s['va_range'] = (self.va_min, self.va_max)
+        s['vb_range'] = (self.vb_min, self.vb_max)
+        s['aliases'] = self.aliases
+        # saved but not used to restore via __init__()
+        s['va'] = self.va
+        s['vb'] = self.vb
+        return s
+
+
+class BiasGen(AD524x):
+    """
+    Wrapper for the two bias generators the i2c-controlled pots and
+    dac-controled resistor ends.
+
+    TODO: equation ibias_x = f(rpot, vdac)
+
+    POR value of fitted rpot is posA/B = 128
+    """
+    def __init__(self, i2cbus, addr,
+            posA=128, posB=128):
+        super(DigiReg, self).__init__(i2cbus, addr,
+                posA=posA, posB=posB)
+
+    def dump_settings(self):
+        """Return a dict of settings appropriate to send to __init__() to
+        restore the state."""
+        # raw settings of Rpot
+        s = super(BiasGen, self).dump_settings()
+        return s
+
+
 
 class DAC_atoi(DAC8568):
     """Specific configuration and calibration for devboard DAC."""
@@ -1721,10 +1890,38 @@ class DAC_atoi(DAC8568):
             0.0)
 
 
-    def __init__(self, spibus, cs='dac'):
+    def __init__(self, spibus, cs='dac',
+            vina=None, vinb=None, vcmi=None,
+            vbias_core=None, vbias_buf=None):
         super(DAC_atoi, self).__init__(spibus, cs)
         self._chpos = [self.POR_VALUE for i in range(8)]
 
+        # put into official operating mode
+        #
+        dac.swreset()
+        # reference is tied to ADC, do not power down
+        dac.reference(dac.REF_FLEXIBLE_ALWAYS_ON)
+        # CLR is tied high, explicitly ignore anyway
+        dac.CCR(dac.IGNORE_CLR)
+        # power up all channels
+        bitfield = 0xff
+        dac.power(dac.POWER_ON, bitfield)
+        # set outputs if we are restoring state
+        for name, value in (
+                ('vina', vina),
+                ('vinb', vinb),
+                ('vcmi', vcmi),
+                ('vbias_core', vbias_core),
+                ('vbias_buf', vbias_buf),
+                ):
+            if value:
+                getattr(self, name)(value)
+        # zero unused outputs
+        # part A already POR's to 0 but...
+        for i in range(5, 8):
+            dac.setv(i, 0.0)
+
+
     def _pos2v(self, pos):
         return (pos / float(2**self.DAC_WIDTH)) * self.VREF * self.GAIN
 
@@ -1765,5 +1962,15 @@ class DAC_atoi(DAC8568):
             self.setv(3, v, mode)
         return self._pos2v(self._chpos[3])
 
+    def dump_settings(self):
+        """Return a dict of settings appropriate to send to __init__() to
+        restore the DAC's state."""
+        s = {}
+        s['vina'] = self.vina()
+        s['vinb'] = self.vinb()
+        s['vcmi'] = self.vcmi()
+        s['vbias_core'] = self.vbias_core()
+        s['vbias_buf'] = self.vbias_buf()
+        return s