diff --git a/.gitignore b/.gitignore index ee8335b..1cfe9f2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ +.DS_Store .*.swp __pycache__/ diff --git a/VM registers.xlsx b/VM registers.xlsx index c4dbf42..0134a29 100644 Binary files a/VM registers.xlsx and b/VM registers.xlsx differ diff --git a/scope.py b/scope.py index ba354ff..ec413f1 100644 --- a/scope.py +++ b/scope.py @@ -8,6 +8,8 @@ import vm class Scope(vm.VirtualMachine): + PARAMS_MAGIC = 0xb0b2 + @classmethod async def connect(cls, stream=None): scope = cls(stream if stream is not None else streams.SerialStream()) @@ -17,6 +19,10 @@ class Scope(vm.VirtualMachine): def __init__(self, stream): super(Scope, self).__init__(stream) + @staticmethod + def _analog_map_func(ks, low, high): + return ks[0] + ks[1]*low + ks[2]*high + async def setup(self): await self.reset() await self.issue_get_revision() @@ -27,17 +33,34 @@ class Scope(vm.VirtualMachine): self.awg_sample_buffer_size = 1024 self.awg_minimum_clock = 33 self.awg_maximum_voltage = 3.3 - self.analog_low = -5.6912 - self.analog_high = 8.0048 - self.analog_range = self.analog_high - self.analog_low + self.analog_low_ks = (0.43307040504672523, 0.060970272170312846, -0.0037186072558476487) + self.analog_high_ks = (0.37575241029061407, -0.0039308497942329686, 0.060955881466731247) + self.analog_min = -5.7 + self.analog_max = 8 self.capture_clock_period = 25e-9 self.capture_buffer_size = 12*1024 self.trigger_timeout_tick = 6.4e-6 - #self.analog_range = 136.96 - #self.analog_zero = 71.923 + self.trigger_low = -7.517 + self.trigger_high = 10.816 + await self.load_params() - async def capture(self, channels=['A'], trigger_channel=None, trigger_level=0, trigger_direction=+1, - period=1e-3, nsamples=1000, timeout=None, low=None, high=None): + async def load_params(self): + params = [] + for i in range(struct.calcsize('{}h'.format(nsamples), data)] + if raw: + trace = [(value / 65536 + 0.5) for value in struct.unpack('>{}h'.format(nsamples), data)] + else: + trace = [(value / 65536 + 0.5) * (high - low) + low for value in struct.unpack('>{}h'.format(nsamples), data)] else: - trace = [value / 256 * (high - low) + low for value in data] + if raw: + trace = [value / 256 for value in data] + else: + trace = [value / 256 * (high - low) + low for value in data] traces[channel] = trace return traces @@ -181,10 +207,9 @@ class Scope(vm.VirtualMachine): if not possible_params: raise ValueError("No solution to required frequency/min_samples/max_error") size, nwaves, clock, actualf = sorted(possible_params)[-1][1] - print(len(possible_params), size, nwaves, clock, actualf) async with self.transaction(): if wavetable is None: - mode = {'sine': 0, 'sawtooth': 1, 'exponential': 2, 'square': 3}[waveform.lower()] + mode = {'sine': 0, 'triangle': 1, 'sawtooth': 1, 'exponential': 2, 'square': 3}[waveform.lower()] await self.set_registers(Cmd=0, Mode=mode, Ratio=ratio) await self.issue_synthesize_wavetable() else: @@ -230,22 +255,51 @@ class Scope(vm.VirtualMachine): await self.issue_write_eeprom() return int((await self.read_replies(2))[1], 16) + async def calibrate(self, channels='AB', n=40): + import numpy as np + import pandas as pd + from scipy.optimize import leastsq + await self.start_generator(1000, waveform='square') + items = [] + for low in np.linspace(0.063, 0.4, n): + for high in np.linspace(0.877, 0.6, n): + data = await self.capture(channels=channels, period=1e-3, trigger_level=0.5, nsamples=1000, low=low, high=high, raw=True) + values = np.hstack(list(data.values())) + values.sort() + zero = values[10:len(values)//2-10].mean() + v33 = values[-len(values)//2+10:-10].mean() + analog_range = 3.3 / (v33 - zero) + analog_low = -zero * analog_range + analog_high = analog_low + analog_range + items.append({'low': low, 'high': high, 'analog_low': analog_low, 'analog_high': analog_high}) + data = pd.DataFrame(items) + analog_low_ks, success1 = leastsq(lambda ks, low, high, y: y - self._analog_map_func(ks, low, high), self.analog_low_ks, + args=(data.analog_low, data.analog_high, data.low)) + if success1: + self.analog_low_ks = tuple(analog_low_ks) + analog_high_ks, success2 = leastsq(lambda ks, low, high, y: y - self._analog_map_func(ks, low, high), self.analog_high_ks, + args=(data.analog_low, data.analog_high, data.high)) + if success2: + self.analog_high_ks = tuple(analog_high_ks) + await self.stop_generator() + return success1 and success2 +import numpy as np +import pandas as pd + async def main(): - import numpy as np global s, x, y, data s = await Scope.connect() x = np.linspace(0, 2*np.pi, s.awg_wavetable_size, endpoint=False) y = np.round((np.sin(x)**5)*127 + 128, 0).astype('uint8') - print(await s.start_generator(5000, wavetable=y)) - #print(await s.start_generator(10000, waveform='square', vpp=3, offset=-0.15)) + await s.start_generator(1000, wavetable=y) + #if await s.calibrate(): + # await s.save_params() def capture(*args, **kwargs): - import pandas as pd return pd.DataFrame(asyncio.get_event_loop().run_until_complete(s.capture(*args, **kwargs))) - if __name__ == '__main__': asyncio.get_event_loop().run_until_complete(main()) diff --git a/vm.py b/vm.py index 72c2e36..ad459c5 100644 --- a/vm.py +++ b/vm.py @@ -1,18 +1,21 @@ import asyncio -import numpy as np +import logging import struct +Log = logging.getLogger('streams') + + Registers = { "TriggerLogic": (0x05, 'U8', "Trigger Logic, one bit per channel (0 => Low, 1 => High)"), "TriggerMask": (0x06, 'U8', "Trigger Mask, one bit per channel (0 => Don’t Care, 1 => Active)"), "SpockOption": (0x07, 'U8', "Spock Option Register (see bit definition table for details)"), "SampleAddress": (0x08, 'U24', "Sample address (write) 24 bit"), "SampleCounter": (0x0b, 'U24', "Sample address (read) 24 bit"), - "TriggerIntro": (0x32, 'U24', "Edge trigger intro filter counter (samples/2)"), + "TriggerIntro": (0x32, 'U16', "Edge trigger intro filter counter (samples/2)"), "TriggerOutro": (0x34, 'U16', "Edge trigger outro filter counter (samples/2)"), - "TriggerValue": (0x44, 'S16', "Digital (comparator) trigger (signed)"), + "TriggerValue": (0x44, 'S0.16', "Digital (comparator) trigger (signed)"), "TriggerTime": (0x40, 'U32', "Stopwatch trigger time (ticks)"), "ClockTicks": (0x2e, 'U16', "Master Sample (clock) period (ticks)"), "ClockScale": (0x14, 'U16', "Clock divide by N (low byte)"), @@ -62,7 +65,7 @@ Registers = { "EepromAddress": (0x11, 'U8', "EE Address Register"), "ConverterLo": (0x64, 'U0.16', "VRB ADC Range Bottom (D Trace Mode)"), "ConverterHi": (0x66, 'U0.16', "VRB ADC Range Top (D Trace Mode)"), - "TriggerLevel": (0x68, 'U16', "Trigger Level (comparator, unsigned)"), + "TriggerLevel": (0x68, 'U0.16', "Trigger Level (comparator, unsigned)"), "LogicControl": (0x74, 'U8', "Logic Control"), "Rest": (0x78, 'U16', "DAC (rest) level"), "KitchenSinkA": (0x7b, 'U8', "Kitchen Sink Register A"), @@ -229,7 +232,9 @@ class VirtualMachine: r0 = r1 = None for base, name in sorted((Registers[name][0], name) for name in kwargs): base, dtype, desc = Registers[name] - for i, byte in enumerate(encode(kwargs[name], dtype)): + bs = encode(kwargs[name], dtype) + Log.debug("{} = 0x{}".format(name, ''.join('{:02x}'.format(b) for b in reversed(bs)))) + for i, byte in enumerate(bs): if cmd: cmd += 'z' r1 += 1