From c8211dceb033282cddcfe7c9187415b149beef4a Mon Sep 17 00:00:00 2001 From: Jonathan Hogg Date: Mon, 17 Oct 2016 19:14:19 +0100 Subject: [PATCH] Large part of capture working --- scope.py | 243 ++++++++++++++++++++++++++++++++++----------- streams.py | 13 ++- vm.py | 283 ++++++++++++++++++++++++++++------------------------- 3 files changed, 343 insertions(+), 196 deletions(-) diff --git a/scope.py b/scope.py index 8452e09..ba354ff 100644 --- a/scope.py +++ b/scope.py @@ -1,7 +1,8 @@ import asyncio -from streams import SerialStream +import struct +import streams import vm @@ -9,7 +10,7 @@ class Scope(vm.VirtualMachine): @classmethod async def connect(cls, stream=None): - scope = cls(stream if stream is not None else SerialStream()) + scope = cls(stream if stream is not None else streams.SerialStream()) await scope.setup() return scope @@ -19,98 +20,230 @@ class Scope(vm.VirtualMachine): async def setup(self): await self.reset() await self.issue_get_revision() - revision = (await self.read_reply()).decode('ascii') + revision = ((await self.read_replies(2))[1]).decode('ascii') if revision.startswith('BS0005'): self.awg_clock_period = 25e-9 self.awg_wavetable_size = 1024 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.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 - async def generate_waveform(self, frequency, waveform='sine', ratio=0.5, vpp=None, offset=0, min_samples=40, max_error=0.0001): + 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): + if 'A' in channels and 'B' in channels: + nsamples_multiplier = 2 + else: + nsamples_multiplier = 1 + ticks = int(period / nsamples / nsamples_multiplier / self.capture_clock_period) + if ticks >= 40 and ticks < 65536: + sample_width = 2 + buffer_width = 6*1024 + dump_mode = vm.DumpMode.Native + if 'A' in channels and 'B' in channels: + trace_mode = vm.TraceMode.MacroChop + buffer_mode = vm.BufferMode.MacroChop + else: + trace_mode = vm.TraceMode.Macro + buffer_mode = vm.BufferMode.Macro + elif ticks >= 15 and ticks < 40: + sample_width = 1 + buffer_width = 12*1024 + dump_mode = vm.DumpMode.Raw + if 'A' in channels and 'B' in channels: + trace_mode = vm.TraceMode.AnalogChop + buffer_mode = vm.BufferMode.Chop + else: + trace_mode = vm.TraceMode.Analog + buffer_mode = vm.BufferMode.Single + elif ticks >= 8 and ticks < 15: + sample_width = 1 + buffer_width = 12*1024 + dump_mode = vm.DumpMode.Raw + if 'A' in channels and 'B' in channels: + trace_mode = vm.TraceMode.AnalogFastChop + buffer_mode = vm.BufferMode.Chop + else: + trace_mode = vm.TraceMode.AnalogFast + buffer_mode = vm.BufferMode.Single + elif ticks >= 2 and ticks < 8: + if ticks > 5: + ticks = 5 + sample_width = 1 + buffer_width = 12*1024 + dump_mode = vm.DumpMode.Raw + if 'A' in channels and 'B' in channels: + trace_mode = vm.TraceMode.AnalogShotChop + buffer_mode = vm.BufferMode.Chop + else: + trace_mode = vm.TraceMode.AnalogShot + buffer_mode = vm.BufferMode.Single + else: + raise RuntimeError("Unsupported clock period: {}".format(ticks)) + nsamples = int(round(period / ticks / nsamples_multiplier / self.capture_clock_period)) + total_samples = nsamples * nsamples_multiplier + assert total_samples <= buffer_width + print(ticks, nsamples, nsamples_multiplier, sample_width) + + if low is None: + low = self.analog_low + if high is None: + high = self.analog_high + print((low - self.analog_low) / self.analog_range, (high - self.analog_low) / self.analog_range) + + if trigger_channel is None: + trigger_channel = channels[0] + else: + assert trigger_channel in channels + if trigger_channel == 'A': + kitchen_sink_a = vm.KitchenSinkA.ChannelAComparatorEnable + spock_option = vm.SpockOption.TriggerSourceA | vm.SpockOption.TriggerTypeHardwareComparator + elif trigger_channel == 'B': + kitchen_sink_a = vm.KitchenSinkA.ChannelBComparatorEnable + spock_option = vm.SpockOption.TriggerSourceB | vm.SpockOption.TriggerTypeHardwareComparator + trigger_level = int(round(trigger_level - low) / (high - low) * 65536) + analog_enable = 0 + if 'A' in channels: + analog_enable |= 1 + if 'B' in channels: + analog_enable |= 2 + + if timeout is None: + timeout = period * 5 + + async with self.transaction(): + await self.set_registers(TraceMode=trace_mode, ClockTicks=ticks, ClockScale=1, + TraceIntro=total_samples//4, TraceOutro=total_samples//4, TraceDelay=0, + Timeout=int(round(timeout / self.trigger_timeout_tick)), + TriggerMask=0x7f, TriggerLogic=0x80, TriggerValue=0, + TriggerLevel=trigger_level, TriggerIntro=4, TriggerOutro=4, + SpockOption=spock_option, Prelude=0, + ConverterLo=(low - self.analog_low) / self.analog_range, + ConverterHi=(high - self.analog_low) / self.analog_range, + KitchenSinkA=kitchen_sink_a, + KitchenSinkB=vm.KitchenSinkB.AnalogFilterEnable | vm.KitchenSinkB.WaveformGeneratorEnable, + AnalogEnable=analog_enable, BufferMode=buffer_mode, SampleAddress=0) + await self.issue_program_spock_registers() + await self.issue_configure_device_hardware() + await self.issue_triggered_trace() + while True: + code, timestamp = await self.read_replies(2) + code = int(code.decode('ascii'), 16) + timestamp = int(timestamp.decode('ascii'), 16) + if code == 2: + start_timestamp = timestamp + else: + end_timestamp = timestamp + break + address = int((await self.read_replies(1))[0].decode('ascii'), 16) // nsamples_multiplier + print(code, (end_timestamp - start_timestamp) * 25e-9, address) + traces = {} + for channel in channels: + dump_channel = {'A': 0, 'B': 1}[channel] + async with self.transaction(): + await self.set_registers(SampleAddress=(address - nsamples) * nsamples_multiplier % buffer_width, + DumpMode=dump_mode, DumpChan=dump_channel, + DumpCount=nsamples, DumpRepeat=1, DumpSend=1, DumpSkip=0) + await self.issue_program_spock_registers() + await self.issue_analog_dump_binary() + data = await self._stream.readexactly(nsamples * sample_width) + if sample_width == 2: + 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] + traces[channel] = trace + return traces + + async def start_generator(self, frequency, waveform='sine', wavetable=None, ratio=0.5, vpp=None, offset=0, + min_samples=50, max_error=1e-4): if vpp is None: vpp = self.awg_maximum_voltage - best_width, best_params = None, None - clock = self.awg_minimum_clock - while True: + possible_params = [] + max_clock = int(round(1 / frequency / min_samples / self.awg_clock_period, 0)) + for clock in range(self.awg_minimum_clock, max_clock+1): width = 1 / frequency / (clock * self.awg_clock_period) if width <= self.awg_sample_buffer_size: nwaves = int(self.awg_sample_buffer_size / width) size = int(round(nwaves * width)) width = size / nwaves - if width < min_samples: - break - actualf = 1 / (size / nwaves * clock * self.awg_clock_period) - if abs(frequency - actualf) / frequency < max_error and (best_width is None or width > best_width): - best_width, best_params = width, (size, nwaves, clock, actualf) + actualf = 1 / (width * clock * self.awg_clock_period) + error = abs(frequency - actualf) / frequency + if error < max_error: + possible_params.append(((error == 0, width), (size, nwaves, clock, actualf))) clock += 1 - if best_params is None: - raise ValueError("Unable to find appropriate solution to required frequency") - size, nwaves, clock, actualf = best_params + 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(): - await self.set_registers(vrKitchenSinkB=vm.KitchenSinkB.WaveformGeneratorEnable) + if wavetable is None: + mode = {'sine': 0, 'sawtooth': 1, 'exponential': 2, 'square': 3}[waveform.lower()] + await self.set_registers(Cmd=0, Mode=mode, Ratio=ratio) + await self.issue_synthesize_wavetable() + else: + if len(wavetable) != self.awg_wavetable_size: + raise ValueError("Wavetable data must be {} samples".format(self.awg_wavetable_size)) + await self.set_registers(Cmd=0, Mode=1, Address=0, Size=1) + await self.wavetable_write_bytes(wavetable) + await self.set_registers(Cmd=0, Mode=0, Level=vpp/self.awg_maximum_voltage, + Offset=2*offset/self.awg_maximum_voltage, + Ratio=nwaves * self.awg_wavetable_size / size, + Index=0, Address=0, Size=size) + await self.issue_translate_wavetable() + await self.set_registers(Cmd=2, Mode=0, Clock=clock, Modulo=size, + Mark=10, Space=1, Rest=0x7f00, Option=0x8004) + await self.issue_control_waveform_generator() + await self.set_registers(KitchenSinkB=vm.KitchenSinkB.WaveformGeneratorEnable) await self.issue_configure_device_hardware() - await self.synthesize_wavetable(waveform, ratio) - await self.translate_wavetable(nwaves=nwaves, size=size, level=vpp/self.awg_maximum_voltage, offset=offset/self.awg_maximum_voltage) - await self.start_waveform_generator(clock=clock, modulo=size, mark=10, space=2, rest=0x7f00, option=0x8004) + await self.issue('.') return actualf async def stop_generator(self): - await self.stop_waveform_generator() async with self.transaction(): - await self.set_registers(vrKitchenSinkB=0) + await self.set_registers(Cmd=1, Mode=0) + await self.issue_control_waveform_generator() + await self.set_registers(KitchenSinkB=0) await self.issue_configure_device_hardware() async def read_wavetable(self): with self.transaction(): - self.set_registers(vpAddress=0, vpSize=self.awg_wavetable_size) + self.set_registers(Address=0, Size=self.awg_wavetable_size) self.issue_wavetable_read() return list(self.read_exactly(self.awg_wavetable_size)) - async def write_wavetable(self, data): - if len(data) != self.awg_wavetable_size: - raise ValueError("Wavetable data must be {} samples".format(self.awg_wavetable_size)) - with self.transaction(): - self.set_registers(vpAddress=0, vpSize=1) - for byte in data: - self.wavetable_write(byte) - - async def synthesize_wavetable(self, waveform='sine', ratio=0.5): - mode = {'sine': 0, 'sawtooth': 1, 'exponential': 2, 'square': 3}[waveform.lower()] - async with self.transaction(): - await self.set_registers(vpCmd=0, vpMode=mode, vpRatio=ratio) - await self.issue_synthesize_wavetable() - - async def translate_wavetable(self, nwaves, size, level=1, offset=0, index=0, address=0): - async with self.transaction(): - await self.set_registers(vpCmd=0, vpMode=0, vpLevel=level, vpOffset=offset, - vpRatio=nwaves * self.awg_wavetable_size / size, - vpIndex=index, vpAddress=address, vpSize=size) - await self.issue_translate_wavetable() - - async def start_waveform_generator(self, clock, modulo, mark, space, rest, option): - async with self.transaction(): - await self.set_registers(vpCmd=2, vpMode=0, vpClock=clock, vpModulo=modulo, - vpMark=mark, vpSpace=space, vrRest=rest, vpOption=option) - await self.issue_control_waveform_generator() - async def read_eeprom(self, address): async with self.transaction(): - await self.set_registers(vrEepromAddress=address) + await self.set_registers(EepromAddress=address) await self.issue_read_eeprom() - return int(await self.read_reply(), 16) + return int((await self.read_replies(2))[1], 16) async def write_eeprom(self, address, byte): async with self.transaction(): - await self.set_registers(vrEepromAddress=address, vrEepromData=byte) + await self.set_registers(EepromAddress=address, EepromData=byte) await self.issue_write_eeprom() - return int(await self.read_reply(), 16) + return int((await self.read_replies(2))[1], 16) + async def main(): - global s + import numpy as np + global s, x, y, data s = await Scope.connect() - print(await s.generate_waveform(440*16, 'sawtooth')) + 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)) + +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__': diff --git a/streams.py b/streams.py index 76f84c3..b2d330a 100644 --- a/streams.py +++ b/streams.py @@ -1,9 +1,12 @@ import asyncio +import logging import os import serial import serial.tools.list_ports -import time + + +Log = logging.getLogger('streams') class SerialStream: @@ -16,7 +19,7 @@ class SerialStream: self._device = self.available_ports()[port] self._connection = serial.Serial(self._device, timeout=0, write_timeout=0, **kwargs) self._loop = loop if loop is not None else asyncio.get_event_loop() - self._input_buffer = b'' + self._input_buffer = bytes() def __repr__(self): return '<{}:{}>'.format(self.__class__.__name__, self._device) @@ -37,7 +40,7 @@ class SerialStream: def _feed_data(self, data, future): n = self._connection.write(data) - print('{:.3f} -> {}'.format(time.time(), repr(data[:n]))) + Log.debug('Write {}'.format(repr(data[:n]))) future.set_result(n) self._loop.remove_writer(self._connection) @@ -45,7 +48,7 @@ class SerialStream: while True: if self._input_buffer: if n is None: - data, self._input_buffer = self._input__buffer, b'' + data, self._input_buffer = self._input_buffer, bytes() else: data, self._input_buffer = self._input_buffer[:n], self._input_buffer[n:] return data @@ -77,7 +80,7 @@ class SerialStream: def _handle_data(self, n, future): data = self._connection.read(n if n is not None else self._connection.in_waiting) - print('{:.3f} <- {}'.format(time.time(), repr(data))) + Log.debug('Read {}'.format(repr(data))) future.set_result(data) self._loop.remove_reader(self._connection) diff --git a/vm.py b/vm.py index 48bef09..72c2e36 100644 --- a/vm.py +++ b/vm.py @@ -3,83 +3,80 @@ import asyncio import numpy as np import struct + Registers = { - "vrTriggerLogic": (0x05, 'U8', "Trigger Logic, one bit per channel (0 => Low, 1 => High)"), - "vrTriggerMask": (0x06, 'U8', "Trigger Mask, one bit per channel (0 => Don’t Care, 1 => Active)"), - "vrSpockOption": (0x07, 'U8', "Spock Option Register (see bit definition table for details)"), - "vrSampleAddress": (0x08, 'U24', "Sample address (write) 24 bit"), - "vrSampleCounter": (0x0b, 'U24', "Sample address (read) 24 bit"), - "vrTriggerIntro": (0x32, 'U24', "Edge trigger intro filter counter (samples/2)"), - "vrTriggerOutro": (0x34, 'U16', "Edge trigger outro filter counter (samples/2)"), - "vrTriggerValue": (0x44, 'S16', "Digital (comparator) trigger (signed)"), - "vrTriggerTime": (0x40, 'U32', "Stopwatch trigger time (ticks)"), - "vrClockTicks": (0x2e, 'U16', "Master Sample (clock) period (ticks)"), - "vrClockScale": (0x14, 'U16', "Clock divide by N (low byte)"), - "vrTraceOption": (0x20, 'U8', "Trace Mode Option bits"), - "vrTraceMode": (0x21, 'U8', "Trace Mode (see Trace Mode Table)"), - "vrTraceIntro": (0x26, 'U16', "Pre-trigger capture count (samples)"), - "vrTraceDelay": (0x22, 'U32', "Delay period (uS)"), - "vrTraceOutro": (0x2a, 'U16', "Post-trigger capture count (samples)"), - "vrTimeout": (0x2c, 'U16', "Auto trace timeout (auto-ticks)"), - "vrPrelude": (0x3a, 'U16', "Buffer prefill value"), - "vrBufferMode": (0x31, 'U8', "Buffer mode"), - "vrDumpMode": (0x1e, 'U8', "Dump mode"), - "vrDumpChan": (0x30, 'U8', "Dump (buffer) Channel (0..127,128..254,255)"), - "vrDumpSend": (0x18, 'U16', "Dump send (samples)"), - "vrDumpSkip": (0x1a, 'U16', "Dump skip (samples)"), - "vrDumpCount": (0x1c, 'U16', "Dump size (samples)"), - "vrDumpRepeat": (0x16, 'U16', "Dump repeat (iterations)"), - "vrStreamIdent": (0x36, 'U8', "Stream data token"), - "vrStampIdent": (0x3c, 'U8', "Timestamp token"), - "vrAnalogEnable": (0x37, 'U8', "Analog channel enable (bitmap)"), - "vrDigitalEnable": (0x38, 'U8', "Digital channel enable (bitmap)"), - "vrSnoopEnable": (0x39, 'U8', "Frequency (snoop) channel enable (bitmap)"), - "vpCmd": (0x46, 'U8', "Command Vector"), - "vpMode": (0x47, 'U8', "Operation Mode (per command)"), - "vpOption": (0x48, 'U16', "Command Option (bits fields per command)"), - "vpSize": (0x4a, 'U16', "Operation (unit/block) size"), - "vpIndex": (0x4c, 'U16', "Operation index (eg, P Memory Page)"), - "vpAddress": (0x4e, 'U16', "General purpose address"), - "vpClock": (0x50, 'U16', "Sample (clock) period (ticks)"), - "vpModulo": (0x52, 'U16', "Modulo Size (generic)"), - "vpLevel": (0x54, 'U0.16', "Output (analog) attenuation (unsigned)"), - "vpOffset": (0x56, 'S1.15', "Output (analog) offset (signed)"), - "vpMask": (0x58, 'U16', "Translate source modulo mask"), - "vpRatio": (0x5a, 'U16.16', "Translate command ratio (phase step)"), - "vpMark": (0x5e, 'U16', "Mark count/phase (ticks/step)"), - "vpSpace": (0x60, 'U16', "Space count/phase (ticks/step)"), - "vpRise": (0x82, 'U16', "Rising edge clock (channel 1) phase (ticks)"), - "vpFall": (0x84, 'U16', "Falling edge clock (channel 1) phase (ticks)"), - "vpControl": (0x86, 'U8', "Clock Control Register (channel 1)"), - "vpRise2": (0x88, 'U16', "Rising edge clock (channel 2) phase (ticks)"), - "vpFall2": (0x8a, 'U16', "Falling edge clock (channel 2) phase (ticks)"), - "vpControl2": (0x8c, 'U8', "Clock Control Register (channel 2)"), - "vpRise3": (0x8e, 'U16', "Rising edge clock (channel 3) phase (ticks)"), - "vpFall3": (0x90, 'U16', "Falling edge clock (channel 3) phase (ticks)"), - "vpControl3": (0x92, 'U8', "Clock Control Register (channel 3)"), - "vrEepromData": (0x10, 'U8', "EE Data Register"), - "vrEepromAddress": (0x11, 'U8', "EE Address Register"), - "vrConverterLo": (0x64, 'U16', "VRB ADC Range Bottom (D Trace Mode)"), - "vrConverterHi": (0x66, 'U16', "VRB ADC Range Top (D Trace Mode)"), - "vrTriggerLevel": (0x68, 'U16', "Trigger Level (comparator, unsigned)"), - "vrLogicControl": (0x74, 'U8', "Logic Control"), - "vrRest": (0x78, 'U16', "DAC (rest) level"), - "vrKitchenSinkA": (0x7b, 'U8', "Kitchen Sink Register A"), - "vrKitchenSinkB": (0x7c, 'U8', "Kitchen Sink Register B"), - "vpMap0": (0x94, 'U8', "Peripheral Pin Select Channel 0"), - "vpMap1": (0x95, 'U8', "Peripheral Pin Select Channel 1"), - "vpMap2": (0x96, 'U8', "Peripheral Pin Select Channel 2"), - "vpMap3": (0x97, 'U8', "Peripheral Pin Select Channel 3"), - "vpMap4": (0x98, 'U8', "Peripheral Pin Select Channel 4"), - "vpMap5": (0x99, 'U8', "Peripheral Pin Select Channel 5"), - "vpMap6": (0x9a, 'U8', "Peripheral Pin Select Channel 6"), - "vpMap7": (0x9b, 'U8', "Peripheral Pin Select Channel 7"), - "vrMasterClockN": (0xf7, 'U8', "PLL prescale (DIV N)"), - "vrMasterClockM": (0xf8, 'U16', "PLL multiplier (MUL M)"), - "vrLedLevelRED": (0xfa, 'U8', "Red LED Intensity (VM10 only)"), - "vrLedLevelGRN": (0xfb, 'U8', "Green LED Intensity (VM10 only)"), - "vrLedLevelYEL": (0xfc, 'U8', "Yellow LED Intensity (VM10 only)"), - "vcBaudHost": (0xfe, 'U16', "baud rate (host side)"), + "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)"), + "TriggerOutro": (0x34, 'U16', "Edge trigger outro filter counter (samples/2)"), + "TriggerValue": (0x44, 'S16', "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)"), + "TraceOption": (0x20, 'U8', "Trace Mode Option bits"), + "TraceMode": (0x21, 'U8', "Trace Mode (see Trace Mode Table)"), + "TraceIntro": (0x26, 'U16', "Pre-trigger capture count (samples)"), + "TraceDelay": (0x22, 'U32', "Delay period (uS)"), + "TraceOutro": (0x2a, 'U16', "Post-trigger capture count (samples)"), + "Timeout": (0x2c, 'U16', "Auto trace timeout (auto-ticks)"), + "Prelude": (0x3a, 'U16', "Buffer prefill value"), + "BufferMode": (0x31, 'U8', "Buffer mode"), + "DumpMode": (0x1e, 'U8', "Dump mode"), + "DumpChan": (0x30, 'U8', "Dump (buffer) Channel (0..127,128..254,255)"), + "DumpSend": (0x18, 'U16', "Dump send (samples)"), + "DumpSkip": (0x1a, 'U16', "Dump skip (samples)"), + "DumpCount": (0x1c, 'U16', "Dump size (samples)"), + "DumpRepeat": (0x16, 'U16', "Dump repeat (iterations)"), + "StreamIdent": (0x36, 'U8', "Stream data token"), + "StampIdent": (0x3c, 'U8', "Timestamp token"), + "AnalogEnable": (0x37, 'U8', "Analog channel enable (bitmap)"), + "DigitalEnable": (0x38, 'U8', "Digital channel enable (bitmap)"), + "SnoopEnable": (0x39, 'U8', "Frequency (snoop) channel enable (bitmap)"), + "Cmd": (0x46, 'U8', "Command Vector"), + "Mode": (0x47, 'U8', "Operation Mode (per command)"), + "Option": (0x48, 'U16', "Command Option (bits fields per command)"), + "Size": (0x4a, 'U16', "Operation (unit/block) size"), + "Index": (0x4c, 'U16', "Operation index (eg, P Memory Page)"), + "Address": (0x4e, 'U16', "General purpose address"), + "Clock": (0x50, 'U16', "Sample (clock) period (ticks)"), + "Modulo": (0x52, 'U16', "Modulo Size (generic)"), + "Level": (0x54, 'U0.16', "Output (analog) attenuation (unsigned)"), + "Offset": (0x56, 'S1.15', "Output (analog) offset (signed)"), + "Mask": (0x58, 'U16', "Translate source modulo mask"), + "Ratio": (0x5a, 'U16.16', "Translate command ratio (phase step)"), + "Mark": (0x5e, 'U16', "Mark count/phase (ticks/step)"), + "Space": (0x60, 'U16', "Space count/phase (ticks/step)"), + "Rise": (0x82, 'U16', "Rising edge clock (channel 1) phase (ticks)"), + "Fall": (0x84, 'U16', "Falling edge clock (channel 1) phase (ticks)"), + "Control": (0x86, 'U8', "Clock Control Register (channel 1)"), + "Rise2": (0x88, 'U16', "Rising edge clock (channel 2) phase (ticks)"), + "Fall2": (0x8a, 'U16', "Falling edge clock (channel 2) phase (ticks)"), + "Control2": (0x8c, 'U8', "Clock Control Register (channel 2)"), + "Rise3": (0x8e, 'U16', "Rising edge clock (channel 3) phase (ticks)"), + "Fall3": (0x90, 'U16', "Falling edge clock (channel 3) phase (ticks)"), + "Control3": (0x92, 'U8', "Clock Control Register (channel 3)"), + "EepromData": (0x10, 'U8', "EE Data Register"), + "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)"), + "LogicControl": (0x74, 'U8', "Logic Control"), + "Rest": (0x78, 'U16', "DAC (rest) level"), + "KitchenSinkA": (0x7b, 'U8', "Kitchen Sink Register A"), + "KitchenSinkB": (0x7c, 'U8', "Kitchen Sink Register B"), + "Map0": (0x94, 'U8', "Peripheral Pin Select Channel 0"), + "Map1": (0x95, 'U8', "Peripheral Pin Select Channel 1"), + "Map2": (0x96, 'U8', "Peripheral Pin Select Channel 2"), + "Map3": (0x97, 'U8', "Peripheral Pin Select Channel 3"), + "Map4": (0x98, 'U8', "Peripheral Pin Select Channel 4"), + "Map5": (0x99, 'U8', "Peripheral Pin Select Channel 5"), + "Map6": (0x9a, 'U8', "Peripheral Pin Select Channel 6"), + "Map7": (0x9b, 'U8', "Peripheral Pin Select Channel 7"), + "MasterClockN": (0xf7, 'U8', "PLL prescale (DIV N)"), + "MasterClockM": (0xf8, 'U16', "PLL multiplier (MUL M)"), } @@ -103,7 +100,7 @@ class TraceMode: MacroChop = 19 -class BufferModes: +class BufferMode: Single = 0 Chop = 1 Dual = 2 @@ -111,15 +108,23 @@ class BufferModes: Macro = 4 MacroChop = 5 +class DumpMode: + Raw = 0 + Burst = 1 + Summed = 2 + MinMax = 3 + AndOr = 4 + Native = 5 + Filter = 6 + Span = 7 -class SpockOptions: - TriggerInvert = 0x40 - TriggerSourceA = 0x00 - TriggerSourceB = 0x04 - TriggerSwap = 0x02 - TriggerSampledAnalog = 0x00 - TriggerHardware = 0x01 - +class SpockOption: + TriggerInvert = 0x40 + TriggerSourceA = 0x04 * 0 + TriggerSourceB = 0x04 * 1 + TriggerSwap = 0x02 + TriggerTypeSampledAnalog = 0x01 * 0 + TriggerTypeHardwareComparator = 0x01 * 1 class KitchenSinkA: ChannelAComparatorEnable = 0x80 @@ -130,6 +135,46 @@ class KitchenSinkB: WaveformGeneratorEnable = 0x40 +def encode(value, dtype): + sign = dtype[0] + if '.' in dtype: + whole, fraction = map(int, dtype[1:].split('.', 1)) + width = whole + fraction + value = int(round(value * (1 << fraction))) + else: + width = int(dtype[1:]) + if sign == 'U': + n = 1 << width + value = max(0, min(value, n-1)) + bs = struct.pack('