1
0
mirror of https://github.com/jonathanhogg/scopething synced 2025-07-14 11:12:09 +01:00

Load/save analog parameters to a config file; more debug logging; quicker clock divider determination in capturing; quicker selection of clock for waveform generation; documented calibration; removed dodgy/unused scope methods; fixed sample scaling; fixes for using scope over network connection; new URL-based connection method

This commit is contained in:
2018-07-03 18:54:36 +01:00
parent bbc7596292
commit 352e3e65f5
3 changed files with 150 additions and 100 deletions

194
scope.py
View File

@ -4,16 +4,21 @@ import argparse
import array import array
import asyncio import asyncio
from collections import namedtuple from collections import namedtuple
from configparser import ConfigParser
import logging import logging
import math import math
import os import os
from pathlib import Path
import sys import sys
from urllib.parse import urlparse
import streams import streams
import vm import vm
LOG = logging.getLogger('scope') LOG = logging.getLogger(__name__)
ANALOG_PARAMETERS_PATH = Path('~/.config/scopething/analog.conf').expanduser()
class UsageError(Exception): class UsageError(Exception):
@ -28,36 +33,39 @@ class Scope(vm.VirtualMachine):
AnalogParams = namedtuple('AnalogParams', ['la', 'lb', 'lc', 'ha', 'hb', 'hc', 'scale', 'offset', 'safe_low', 'safe_high', 'ab_offset']) AnalogParams = namedtuple('AnalogParams', ['la', 'lb', 'lc', 'ha', 'hb', 'hc', 'scale', 'offset', 'safe_low', 'safe_high', 'ab_offset'])
@classmethod @classmethod
async def connect(cls, device=None): async def connect(cls, url=None):
if device is None: if url is None:
reader = writer = streams.SerialStream.stream_matching(0x0403, 0x6001) port = next(streams.SerialStream.ports_matching(vid=0x0403, pid=0x6001))
elif os.path.exists(device): url = f'file:{port.device}'
reader = writer = streams.SerialStream(device=device) LOG.info(f"Connecting to scope at {url}")
elif ':' in device: parts = urlparse(url, scheme='file')
host, port = device.split(':', 1) if parts.scheme == 'file':
LOG.info(f"Connecting to remote scope at {host}:{port}") reader = writer = streams.SerialStream(device=parts.path)
elif parts.scheme == 'socket':
host, port = parts.netloc.split(':', 1)
reader, writer = await asyncio.open_connection(host, int(port)) reader, writer = await asyncio.open_connection(host, int(port))
else: else:
raise ValueError(f"Don't know what to do with {device!r}") raise ValueError(f"Don't know what to do with url: {url}")
scope = cls(reader, writer) scope = cls(reader, writer)
await scope.setup() scope.url = url
await scope.reset()
return scope return scope
async def setup(self): async def reset(self):
LOG.info("Resetting scope") LOG.info("Resetting scope")
await self.reset() await self.issue_reset()
await self.issue_get_revision() await self.issue_get_revision()
revision = ((await self.read_replies(2))[1]).decode('ascii') revision = ((await self.read_replies(2))[1]).decode('ascii')
if revision == 'BS000501': if revision == 'BS000501':
self.master_clock_period = 25e-9 self.master_clock_rate = 40000000
self.master_clock_period = 1/self.master_clock_rate
self.capture_buffer_size = 12<<10 self.capture_buffer_size = 12<<10
self.awg_wavetable_size = 1024 self.awg_wavetable_size = 1024
self.awg_sample_buffer_size = 1024 self.awg_sample_buffer_size = 1024
self.awg_minimum_clock = 33 self.awg_minimum_clock = 33
self.logic_low = 0 self.logic_low = 0
self.awg_maximum_voltage = self.clock_voltage = self.logic_high = 3.3 self.awg_maximum_voltage = self.clock_voltage = self.logic_high = 3.3
self.analog_params = {'x1': self.AnalogParams(1.11, -6.57e-2, 8.46e-3, 1.11, -7.32e-2, -5.19e-2, 18.28, -7.45, -5.5, 8, 5.3e-3), self.analog_params = {'x1': self.AnalogParams(1.1, -.05, 0, 1.1, -.05, -.05, 18.3, -7.50, -5.5, 8, 0)}
'x10': self.AnalogParams(1.10, -6.11e-2, 8.61e-3, 1.10, -6.68e-2, -4.32e-2, 184.3, -90.4, -71.3, 65.7, 175e-3)}
self.analog_lo_min = 0.07 self.analog_lo_min = 0.07
self.analog_hi_max = 0.88 self.analog_hi_max = 0.88
self.timeout_clock_period = (1<<8) * self.master_clock_period self.timeout_clock_period = (1<<8) * self.master_clock_period
@ -66,8 +74,32 @@ class Scope(vm.VirtualMachine):
raise RuntimeError(f"Unsupported scope, revision: {revision}") raise RuntimeError(f"Unsupported scope, revision: {revision}")
self._awg_running = False self._awg_running = False
self._clock_running = False self._clock_running = False
self.load_analog_params()
LOG.info(f"Initialised scope, revision: {revision}") LOG.info(f"Initialised scope, revision: {revision}")
def load_analog_params(self):
config = ConfigParser()
config.read(ANALOG_PARAMETERS_PATH)
analog_params = {}
for url in config.sections():
if url == self.url:
for probes in config[url]:
analog_params[probes] = self.AnalogParams(*map(float, config[url][probes].split()))
if analog_params:
self.analog_params.update(analog_params)
LOG.info(f"Loaded analog parameters for probes: {', '.join(analog_params.keys())}")
def save_analog_params(self):
LOG.info("Saving analog parameters")
config = ConfigParser()
config.read(ANALOG_PARAMETERS_PATH)
config[self.url] = {probes: ' '.join(map(str, self.analog_params[probes])) for probes in self.analog_params}
parent = ANALOG_PARAMETERS_PATH.parent
if not parent.is_dir():
parent.mkdir(parents=True)
with open(ANALOG_PARAMETERS_PATH, 'w') as parameters_file:
config.write(parameters_file)
def __enter__(self): def __enter__(self):
return self return self
@ -119,40 +151,49 @@ class Scope(vm.VirtualMachine):
analog_enable = sum(1<<(ord(channel)-ord('A')) for channel in analog_channels) analog_enable = sum(1<<(ord(channel)-ord('A')) for channel in analog_channels)
logic_enable = sum(1<<channel for channel in logic_channels) logic_enable = sum(1<<channel for channel in logic_channels)
ticks = int(round(period / nsamples / self.master_clock_period))
for capture_mode in vm.CaptureModes: for capture_mode in vm.CaptureModes:
ticks = int(round(period / self.master_clock_period / nsamples))
clock_scale = 1
if capture_mode.analog_channels == len(analog_channels) and capture_mode.logic_channels == bool(logic_channels): if capture_mode.analog_channels == len(analog_channels) and capture_mode.logic_channels == bool(logic_channels):
if ticks > capture_mode.clock_high and capture_mode.clock_divide: LOG.debug(f"Considering trace mode {capture_mode.trace_mode.name}...")
for clock_scale in range(2, vm.Registers.ClockScale.maximum_value+1): if ticks > capture_mode.clock_high and capture_mode.clock_divide > 1:
test_ticks = int(round(period / nsamples / self.master_clock_period / clock_scale)) clock_scale = int(math.ceil(period / self.master_clock_period / nsamples / capture_mode.clock_high))
if test_ticks in range(capture_mode.clock_low, capture_mode.clock_high + 1): ticks = int(round(period / self.master_clock_period / nsamples / clock_scale))
ticks = test_ticks if ticks in range(capture_mode.clock_low, capture_mode.clock_high+1):
break LOG.debug(f"- try with tick count {ticks} x {clock_scale}")
else: else:
continue continue
break
elif ticks >= capture_mode.clock_low: elif ticks >= capture_mode.clock_low:
clock_scale = 1
if ticks > capture_mode.clock_high: if ticks > capture_mode.clock_high:
ticks = capture_mode.clock_high ticks = capture_mode.clock_high
LOG.debug(f"- try with tick count {ticks}")
else: else:
LOG.debug(f"- mode too slow")
continue continue
n = int(round(period / ticks / self.master_clock_period / clock_scale)) n = int(round(period / self.master_clock_period / ticks / clock_scale))
if len(analog_channels) == 2: if len(analog_channels) == 2:
n -= n % 2 n -= n % 2
buffer_width = self.capture_buffer_size // capture_mode.sample_width buffer_width = self.capture_buffer_size // capture_mode.sample_width
if logic_channels and analog_channels: if logic_channels and analog_channels:
buffer_width //= 2 buffer_width //= 2
if n <= buffer_width: if n <= buffer_width:
LOG.debug(f"- OK; period is {n} samples")
nsamples = n nsamples = n
break break
LOG.debug(f"- insufficient buffer space for necessary {n} samples")
else: else:
raise ConfigurationError("Unable to find appropriate capture mode") raise ConfigurationError("Unable to find appropriate capture mode")
sample_period = ticks*clock_scale*self.master_clock_period
sample_rate = 1/sample_period
if trigger_position and sample_rate > 5e6:
LOG.warn(f"Pre-trigger capture not supported above 5M samples/s; forcing trigger_position=0")
trigger_position = 0
analog_params = self.analog_params[probes]
if raw: if raw:
analog_params = None
lo, hi = low, high lo, hi = low, high
else: else:
analog_params = self.analog_params[probes]
if low is None: if low is None:
low = analog_params.safe_low if analog_channels else self.logic_low low = analog_params.safe_low if analog_channels else self.logic_low
elif low < analog_params.safe_low: elif low < analog_params.safe_low:
@ -175,7 +216,8 @@ class Scope(vm.VirtualMachine):
kitchen_sink_b |= vm.KitchenSinkB.AnalogFilterEnable kitchen_sink_b |= vm.KitchenSinkB.AnalogFilterEnable
if trigger_level is None: if trigger_level is None:
trigger_level = (high + low) / 2 trigger_level = (high + low) / 2
trigger_level = (trigger_level - analog_params.offset) / analog_params.scale if not raw:
trigger_level = (trigger_level - analog_params.offset) / analog_params.scale
if trigger == 'A' or trigger == 'B': if trigger == 'A' or trigger == 'B':
if trigger == 'A': if trigger == 'A':
spock_option |= vm.SpockOption.TriggerSourceA spock_option |= vm.SpockOption.TriggerSourceA
@ -219,8 +261,6 @@ class Scope(vm.VirtualMachine):
else: else:
raise ConfigurationError("Required trigger timeout too long, use a later trigger position") raise ConfigurationError("Required trigger timeout too long, use a later trigger position")
sample_period = ticks*clock_scale*self.master_clock_period
sample_rate = 1/sample_period
LOG.info(f"Begin {('mixed' if logic_channels else 'analogue') if analog_channels else 'logic'} signal capture " LOG.info(f"Begin {('mixed' if logic_channels else 'analogue') if analog_channels else 'logic'} signal capture "
f"at {sample_rate:,.0f} samples per second (trace mode {capture_mode.trace_mode.name})") f"at {sample_rate:,.0f} samples per second (trace mode {capture_mode.trace_mode.name})")
async with self.transaction(): async with self.transaction():
@ -235,6 +275,7 @@ class Scope(vm.VirtualMachine):
await self.issue_program_spock_registers() await self.issue_program_spock_registers()
await self.issue_configure_device_hardware() await self.issue_configure_device_hardware()
await self.issue_triggered_trace() await self.issue_triggered_trace()
begin_timestamp = None
while True: while True:
try: try:
code, timestamp = (int(x, 16) for x in await self.read_replies(2)) code, timestamp = (int(x, 16) for x in await self.read_replies(2))
@ -297,32 +338,36 @@ class Scope(vm.VirtualMachine):
raise ValueError(f"high out of range (0-{self.awg_maximum_voltage})") raise ValueError(f"high out of range (0-{self.awg_maximum_voltage})")
if low < 0 or low > high: if low < 0 or low > high:
raise ValueError("low out of range (0-high)") raise ValueError("low out of range (0-high)")
possible_params = [] max_clock = min(vm.Registers.Clock.maximum_value, int(math.floor(self.master_clock_rate / frequency / min_samples)))
max_clock = int(math.floor(1 / frequency / min_samples / self.master_clock_period)) min_clock = max(self.awg_minimum_clock, int(math.ceil(self.master_clock_rate / frequency / self.awg_sample_buffer_size)))
for clock in range(self.awg_minimum_clock, max_clock+1): best_solution = None
width = 1 / frequency / (clock * self.master_clock_period) for clock in range(min_clock, max_clock+1):
if width <= self.awg_sample_buffer_size: width = self.master_clock_rate / frequency / clock
nwaves = int(self.awg_sample_buffer_size / width) nwaves = int(self.awg_sample_buffer_size / width)
size = int(round(nwaves * width)) size = int(round(nwaves * width))
width = size / nwaves actualf = self.master_clock_rate * nwaves / size / clock
actualf = 1 / (width * clock * self.master_clock_period) if actualf == frequency:
error = abs(frequency - actualf) / frequency LOG.debug(f"Exact solution: size={size} nwaves={nwaves} clock={clock}")
if error < max_error: break
possible_params.append((width if error == 0 else -error, (size, nwaves, clock, actualf))) error = abs(frequency - actualf) / frequency
if not possible_params: if error < max_error and (best_solution is None or error < best_solution[0]):
raise ConfigurationError("No solution to required frequency/min_samples/max_error") best_solution = error, size, nwaves, clock, actualf
size, nwaves, clock, actualf = sorted(possible_params)[-1][1] else:
if best_solution is None:
raise ConfigurationError("No solution to required frequency/min_samples/max_error")
error, size, nwaves, clock, actualf = best_solution
LOG.debug(f"Best solution: size={size} nwaves={nwaves} clock={clock} actualf={actualf}")
async with self.transaction(): async with self.transaction():
if isinstance(waveform, str): if isinstance(waveform, str):
mode = {'sine': 0, 'triangle': 1, 'exponential': 2, 'square': 3}[waveform.lower()] mode = {'sine': 0, 'triangle': 1, 'exponential': 2, 'square': 3}[waveform.lower()]
await self.set_registers(Cmd=0, Mode=mode, Ratio=ratio) await self.set_registers(Cmd=0, Mode=mode, Ratio=ratio)
await self.issue_synthesize_wavetable() await self.issue_synthesize_wavetable()
elif len(wavetable) == self.awg_wavetable_size: elif len(wavetable) == self.awg_wavetable_size:
wavetable = bytes(min(max(0, int(round(y*255))),255) for y in wavetable) wavetable = bytes(min(max(0, int(round(y*256))), 255) for y in wavetable)
await self.set_registers(Cmd=0, Mode=1, Address=0, Size=1) await self.set_registers(Cmd=0, Mode=1, Address=0, Size=1)
await self.wavetable_write_bytes(wavetable) await self.wavetable_write_bytes(wavetable)
else: else:
raise ValueError(f"waveform must be a valid name or {self.awg_wavetable_size} samples") raise ValueError(f"waveform must be a valid name or a sequence of {self.awg_wavetable_size} samples [0,1)")
async with self.transaction(): async with self.transaction():
offset = (high+low)/2 - self.awg_maximum_voltage/2 offset = (high+low)/2 - self.awg_maximum_voltage/2
await self.set_registers(Cmd=0, Mode=0, Level=(high-low)/self.awg_maximum_voltage, await self.set_registers(Cmd=0, Mode=0, Level=(high-low)/self.awg_maximum_voltage,
@ -355,9 +400,9 @@ class Scope(vm.VirtualMachine):
async def start_clock(self, frequency, ratio=0.5, max_error=1e-4): async def start_clock(self, frequency, ratio=0.5, max_error=1e-4):
if self._awg_running: if self._awg_running:
raise UsageError("Cannot start clock while waveform generator in use") raise UsageError("Cannot start clock while waveform generator in use")
ticks = min(max(2, int(round(1 / frequency / self.master_clock_period))), vm.Registers.Clock.maximum_value) ticks = min(max(2, int(round(self.master_clock_rate / frequency))), vm.Registers.Clock.maximum_value)
fall = min(max(1, int(round(ticks * ratio))), ticks-1) fall = min(max(1, int(round(ticks * ratio))), ticks-1)
actualf, actualr = 1 / ticks / self.master_clock_period, fall / ticks actualf, actualr = self.master_clock_rate / ticks, fall / ticks
if abs(actualf - frequency) / frequency > max_error: if abs(actualf - frequency) / frequency > max_error:
raise ConfigurationError("No solution to required frequency and max_error") raise ConfigurationError("No solution to required frequency and max_error")
async with self.transaction(): async with self.transaction():
@ -376,26 +421,30 @@ class Scope(vm.VirtualMachine):
LOG.info("Clock generator stopped") LOG.info("Clock generator stopped")
self._clock_running = False self._clock_running = False
async def read_wavetable(self): async def calibrate(self, probes='x1', n=32, save=True):
with self.transaction(): """
self.set_registers(Address=0, Size=self.awg_wavetable_size) Derive values for the analogue parameters based on generating a 3.3V 10kHz clock
self.issue_wavetable_read() signal and then sampling the analogue channels to measure this. The first step is
return list(self.wavetable_read_bytes(self.awg_wavetable_size)) to set the low and high range DACs to 1/3 and 2/3, respectively. This results in
*neutral* voltages matching the three series 300Ω resistances created by the ADC
ladder resistance and the upper and lower bias resistors. Thus no current should
be flowing in or out of the DACs and their effect on the ADC range voltages can
be ignored. This allows an initial measurement to determine the full analogue
range and zero offset.
async def read_eeprom(self, address): After this initial measurement, an `n`x`n` matrix of measurements are taken with
async with self.transaction(): different `lo` and `hi` DAC input values and these are used, with the known clock
await self.set_registers(EepromAddress=address) voltage, to reverse out the actual `low` and `high` measurement voltage range.
await self.issue_read_eeprom() The full set of measurements are then fed into the SciPy SLSQP minimiser to find
return int((await self.read_replies(2))[1], 16) parameters for two plane functions mapping the `low` and `high` voltages to the
necessary `lo` and `hi` DAC values to achieve these. (Note that these functions
are constrained to ensure that they pass through the *neutral* points.
async def write_eeprom(self, address, byte): A further minimisation step is done to determine the safe analogue range based
async with self.transaction(): on the observed linear range of the DACs (`self.analog_lo_min` to
await self.set_registers(EepromAddress=address, EepromData=byte) `self.analog_hi_max`). The mean of the measured offsets between the A and B
await self.issue_write_eeprom() channel readings are used to determine an AB offset.
if int((await self.read_replies(2))[1], 16) != byte: """
raise RuntimeError("Error writing EEPROM byte")
async def calibrate(self, probes='x1', n=32):
import numpy as np import numpy as np
from scipy.optimize import minimize from scipy.optimize import minimize
items = [] items = []
@ -418,7 +467,7 @@ class Scope(vm.VirtualMachine):
full = (full + 1) / 3 full = (full + 1) / 3
analog_scale = self.clock_voltage / (full - zero) analog_scale = self.clock_voltage / (full - zero)
analog_offset = -zero * analog_scale analog_offset = -zero * analog_scale
LOG.info(f"Analog full range = {analog_scale:.1f}V, zero offset = {analog_offset:.1f}V") LOG.info(f"Analog full range = {analog_scale:.2f}V, zero offset = {analog_offset:.2f}V")
for lo in np.linspace(self.analog_lo_min, 0.5, n, endpoint=False): for lo in np.linspace(self.analog_lo_min, 0.5, n, endpoint=False):
for hi in np.linspace(self.analog_hi_max, 0.5, n): for hi in np.linspace(self.analog_hi_max, 0.5, n):
period = 2e-3 if len(items) % 4 < 2 else 1e-3 period = 2e-3 if len(items) % 4 < 2 else 1e-3
@ -427,8 +476,7 @@ class Scope(vm.VirtualMachine):
analog_range = self.clock_voltage / (full - zero) analog_range = self.clock_voltage / (full - zero)
items.append((lo, hi, -zero*analog_range, (1-zero)*analog_range, offset*analog_range)) items.append((lo, hi, -zero*analog_range, (1-zero)*analog_range, offset*analog_range))
await self.stop_clock() await self.stop_clock()
items = np.array(items).T lo, hi, low, high, offset = np.array(items).T
lo, hi, low, high, offset = items
def f(params): def f(params):
dl, dh = self.calculate_lo_hi(low, high, self.AnalogParams(*params, analog_scale, analog_offset, None, None, None)) dl, dh = self.calculate_lo_hi(low, high, self.AnalogParams(*params, analog_scale, analog_offset, None, None, None))
return np.sqrt((lo-dl)**2 + (hi-dh)**2).mean() return np.sqrt((lo-dl)**2 + (hi-dh)**2).mean()
@ -455,6 +503,8 @@ class Scope(vm.VirtualMachine):
lo_error = np.sqrt((((clo-lo)/(hi-lo))**2).mean()) lo_error = np.sqrt((((clo-lo)/(hi-lo))**2).mean())
hi_error = np.sqrt((((chi-hi)/(hi-lo))**2).mean()) hi_error = np.sqrt((((chi-hi)/(hi-lo))**2).mean())
LOG.info(f"Mean error: lo={lo_error*10000:.1f}bps hi={hi_error*10000:.1f}bps") LOG.info(f"Mean error: lo={lo_error*10000:.1f}bps hi={hi_error*10000:.1f}bps")
if save:
self.save_analog_params()
else: else:
LOG.warning(f"Calibration failed: {result.message}") LOG.warning(f"Calibration failed: {result.message}")
return result.success return result.success
@ -485,12 +535,12 @@ In [6]:
async def main(): async def main():
global s global s
parser = argparse.ArgumentParser(description="scopething") parser = argparse.ArgumentParser(description="scopething")
parser.add_argument('device', nargs='?', default=None, type=str, help="Device to connect to") parser.add_argument('url', nargs='?', default=None, type=str, help="Device to connect to")
parser.add_argument('--debug', action='store_true', default=False, help="Debug logging") parser.add_argument('--debug', action='store_true', default=False, help="Debug logging")
parser.add_argument('--verbose', action='store_true', default=False, help="Verbose logging") parser.add_argument('--verbose', action='store_true', default=False, help="Verbose logging")
args = parser.parse_args() args = parser.parse_args()
logging.basicConfig(level=logging.DEBUG if args.debug else (logging.INFO if args.verbose else logging.WARNING), stream=sys.stdout) logging.basicConfig(level=logging.DEBUG if args.debug else (logging.INFO if args.verbose else logging.WARNING), stream=sys.stdout)
s = await Scope.connect(args.device) s = await Scope.connect(args.url)
def await_(g): def await_(g):
task = asyncio.Task(g) task = asyncio.Task(g)

View File

@ -13,21 +13,21 @@ import serial
from serial.tools.list_ports import comports from serial.tools.list_ports import comports
LOG = logging.getLogger('streams') LOG = logging.getLogger(__name__)
class SerialStream: class SerialStream:
@classmethod @classmethod
def devices_matching(cls, vid=None, pid=None, serial=None): def ports_matching(cls, vid=None, pid=None, serial=None):
for port in comports(): for port in comports():
if (vid is None or vid == port.vid) and (pid is None or pid == port.pid) and (serial is None or serial == port.serial_number): if (vid is None or vid == port.vid) and (pid is None or pid == port.pid) and (serial is None or serial == port.serial_number):
yield port.device yield port
@classmethod @classmethod
def stream_matching(cls, vid=None, pid=None, serial=None, **kwargs): def stream_matching(cls, vid=None, pid=None, serial=None, **kwargs):
for device in cls.devices_matching(vid, pid, serial): for port in cls.devices_matching(vid, pid, serial):
return SerialStream(device, **kwargs) return SerialStream(port.device, **kwargs)
raise RuntimeError("No matching serial device") raise RuntimeError("No matching serial device")
def __init__(self, device, loop=None, **kwargs): def __init__(self, device, loop=None, **kwargs):

46
vm.py
View File

@ -21,7 +21,7 @@ import logging
import struct import struct
LOG = logging.getLogger('vm') LOG = logging.getLogger(__name__)
class DotDict(dict): class DotDict(dict):
@ -220,23 +220,23 @@ CaptureMode = namedtuple('CaptureMode', ('trace_mode', 'clock_low', 'clock_high'
'analog_channels', 'sample_width', 'logic_channels', 'buffer_mode')) 'analog_channels', 'sample_width', 'logic_channels', 'buffer_mode'))
CaptureModes = [ CaptureModes = [
CaptureMode(TraceMode.Macro, 40, 16384, False, 1, 2, False, BufferMode.Macro), CaptureMode(TraceMode.Macro, 40, 16384, 1, 1, 2, False, BufferMode.Macro),
CaptureMode(TraceMode.MacroChop, 40, 16384, False, 2, 2, False, BufferMode.MacroChop), CaptureMode(TraceMode.MacroChop, 40, 16384, 1, 2, 2, False, BufferMode.MacroChop),
CaptureMode(TraceMode.Analog, 15, 40, True, 1, 1, False, BufferMode.Single), CaptureMode(TraceMode.Analog, 15, 40, 16383, 1, 1, False, BufferMode.Single),
CaptureMode(TraceMode.AnalogChop, 13, 40, True, 2, 1, False, BufferMode.Chop), CaptureMode(TraceMode.AnalogChop, 13, 40, 16383, 2, 1, False, BufferMode.Chop),
CaptureMode(TraceMode.AnalogFast, 8, 14, False, 1, 1, False, BufferMode.Single), CaptureMode(TraceMode.AnalogFast, 8, 14, 1, 1, 1, False, BufferMode.Single),
CaptureMode(TraceMode.AnalogFastChop, 8, 40, False, 2, 1, False, BufferMode.Chop), CaptureMode(TraceMode.AnalogFastChop, 8, 40, 1, 2, 1, False, BufferMode.Chop),
CaptureMode(TraceMode.AnalogShot, 2, 5, False, 1, 1, False, BufferMode.Single), CaptureMode(TraceMode.AnalogShot, 2, 5, 1, 1, 1, False, BufferMode.Single),
CaptureMode(TraceMode.AnalogShotChop, 4, 5, False, 2, 1, False, BufferMode.Chop), CaptureMode(TraceMode.AnalogShotChop, 4, 5, 1, 2, 1, False, BufferMode.Chop),
CaptureMode(TraceMode.Logic, 5, 16384, False, 0, 1, True, BufferMode.Single), CaptureMode(TraceMode.Logic, 5, 16384, 1, 0, 1, True, BufferMode.Single),
CaptureMode(TraceMode.LogicFast, 4, 4, False, 0, 1, True, BufferMode.Single), CaptureMode(TraceMode.LogicFast, 4, 4, 1, 0, 1, True, BufferMode.Single),
CaptureMode(TraceMode.LogicShot, 1, 3, False, 0, 1, True, BufferMode.Single), CaptureMode(TraceMode.LogicShot, 1, 3, 1, 0, 1, True, BufferMode.Single),
CaptureMode(TraceMode.Mixed, 15, 40, True, 1, 1, True, BufferMode.Dual), CaptureMode(TraceMode.Mixed, 15, 40, 16383, 1, 1, True, BufferMode.Dual),
CaptureMode(TraceMode.MixedChop, 13, 40, True, 2, 1, True, BufferMode.ChopDual), CaptureMode(TraceMode.MixedChop, 13, 40, 16383, 2, 1, True, BufferMode.ChopDual),
CaptureMode(TraceMode.MixedFast, 8, 14, False, 1, 1, True, BufferMode.Dual), CaptureMode(TraceMode.MixedFast, 8, 14, 1, 1, 1, True, BufferMode.Dual),
CaptureMode(TraceMode.MixedFastChop, 8, 40, False, 2, 1, True, BufferMode.ChopDual), CaptureMode(TraceMode.MixedFastChop, 8, 40, 1, 2, 1, True, BufferMode.ChopDual),
CaptureMode(TraceMode.MixedShot, 2, 5, False, 1, 1, True, BufferMode.Dual), CaptureMode(TraceMode.MixedShot, 2, 5, 1, 1, 1, True, BufferMode.Dual),
CaptureMode(TraceMode.MixedShotChop, 4, 5, False, 2, 1, True, BufferMode.ChopDual), CaptureMode(TraceMode.MixedShotChop, 4, 5, 1, 2, 1, True, BufferMode.ChopDual),
] ]
@ -301,18 +301,18 @@ class VirtualMachine:
replies.append(reply) replies.append(reply)
data = data[index+1:] data = data[index+1:]
else: else:
data += await self._reader.read() data += await self._reader.read(100)
if data: if data:
self._reply_buffer = data self._reply_buffer = data
return replies return replies
async def reset(self): async def issue_reset(self):
if self._transactions: if self._transactions:
raise TypeError("Command transaction in progress") raise TypeError("Command transaction in progress")
LOG.debug("Issue reset") LOG.debug("Issue reset")
self._writer.write(b'!') self._writer.write(b'!')
await self._writer.drain() await self._writer.drain()
while not (await self._reader.read()).endswith(b'!'): while not (await self._reader.read(1000)).endswith(b'!'):
pass pass
self._reply_buffer = b'' self._reply_buffer = b''
LOG.debug("Reset complete") LOG.debug("Reset complete")
@ -375,10 +375,10 @@ class VirtualMachine:
raise TypeError("Command transaction in progress") raise TypeError("Command transaction in progress")
if sample_width == 2: if sample_width == 2:
data = await self._reader.readexactly(2*n) data = await self._reader.readexactly(2*n)
return array.array('f', ((value+32768)/65535 for (value,) in struct.iter_unpack('>h', data))) return array.array('f', ((value+32768)/65536 for (value,) in struct.iter_unpack('>h', data)))
elif sample_width == 1: elif sample_width == 1:
data = await self._reader.readexactly(n) data = await self._reader.readexactly(n)
return array.array('f', (value/255 for value in data)) return array.array('f', (value/256 for value in data))
else: else:
raise ValueError(f"Bad sample width: {sample_width}") raise ValueError(f"Bad sample width: {sample_width}")