From caacfe37fc37e555e9c72a2d7bf5d47bc516b34d Mon Sep 17 00:00:00 2001 From: Jonathan Hogg Date: Wed, 5 Sep 2018 15:49:22 +0100 Subject: [PATCH] Some outstanding tweaks; fix to using arbitrary waveform; support for serial comms on Windows --- scope.py | 84 +++++++++++++++++++++++++++++------------------------- streams.py | 37 ++++++++++++++++++++++-- utils.py | 8 ++++++ vm.py | 10 ++----- 4 files changed, 91 insertions(+), 48 deletions(-) create mode 100644 utils.py diff --git a/scope.py b/scope.py index 16578d4..7f7c47b 100755 --- a/scope.py +++ b/scope.py @@ -13,6 +13,7 @@ import sys from urllib.parse import urlparse import streams +from utils import DotDict import vm @@ -30,26 +31,29 @@ class ConfigurationError(Exception): class Scope(vm.VirtualMachine): - AnalogParams = namedtuple('AnalogParams', ['la', 'lb', 'lc', 'ha', 'hb', 'hc', 'scale', 'offset', 'safe_low', 'safe_high', 'ab_offset']) + class AnalogParams(namedtuple('AnalogParams', ['la', 'lb', 'lc', 'ha', 'hb', 'hc', 'scale', 'offset', 'safe_low', 'safe_high', 'ab_offset'])): + def __repr__(self): + return (f"la={self.la:.3f} lb={self.lb:.3e} lc={self.lc:.3e} ha={self.ha:.3f} hb={self.hb:.3e} hc={self.hc:.3e} " + f"scale={self.scale:.3f}V offset={self.offset:.3f}V safe_low={self.safe_low:.2f}V safe_high={self.safe_high:.2f}V " + f"ab_offset={self.ab_offset*1000:.1f}mV") - @classmethod - async def connect(cls, url=None): + async def connect(self, url): if url is None: port = next(streams.SerialStream.ports_matching(vid=0x0403, pid=0x6001)) url = f'file:{port.device}' LOG.info(f"Connecting to scope at {url}") + self.close() parts = urlparse(url, scheme='file') if parts.scheme == 'file': - reader = writer = streams.SerialStream(device=parts.path) + self._reader = self._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)) + self._reader, self._writer = await asyncio.open_connection(host, int(port)) else: raise ValueError(f"Don't know what to do with url: {url}") - scope = cls(reader, writer) - scope.url = url - await scope.reset() - return scope + self.url = url + await self.reset() + return self async def reset(self): LOG.info("Resetting scope") @@ -65,7 +69,7 @@ class Scope(vm.VirtualMachine): self.awg_minimum_clock = 33 self.logic_low = 0 self.awg_maximum_voltage = self.clock_voltage = self.logic_high = 3.3 - self.analog_params = {'x1': self.AnalogParams(1.1, -.05, 0, 1.1, -.05, -.05, 18.3, -7.50, -5.5, 8, 0)} + self.analog_params = {'x1': self.AnalogParams(1.1, -.05, 0, 1.1, -.05, -.05, 18.333, -7.517, -5.5, 8, 0)} self.analog_lo_min = 0.07 self.analog_hi_max = 0.88 self.timeout_clock_period = (1<<8) * self.master_clock_period @@ -84,7 +88,9 @@ class Scope(vm.VirtualMachine): 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())) + params = self.AnalogParams(*map(float, config[url][probes].split())) + analog_params[probes] = params + LOG.debug(f"Loading saved parameters for {probes}: {params!r}") if analog_params: self.analog_params.update(analog_params) LOG.info(f"Loaded analog parameters for probes: {', '.join(analog_params.keys())}") @@ -112,7 +118,7 @@ class Scope(vm.VirtualMachine): def calculate_lo_hi(self, low, high, params): if not isinstance(params, self.AnalogParams): - params = self.AnalogParams(*(list(params) + [None]*(11-len(params)))) + params = self.AnalogParams(*list(params) + [None]*(11-len(params))) l = (low - params.offset) / params.scale h = (high - params.offset) / params.scale dl = params.la*l + params.lb*h + params.lc @@ -234,19 +240,22 @@ class Scope(vm.VirtualMachine): if channel.startswith('L'): channel = int(channel[1:]) else: - raise TypeError("Unrecognised trigger value") + raise ValueError("Unrecognised trigger value") if channel < 0 or channel > 7: - raise TypeError("Unrecognised trigger value") + raise ValueError("Unrecognised trigger value") mask = 1< 1 else timestamps, - 'samples': array.array('f', (value*value_multiplier+value_offset for value in data)), - 'start_time': start_time+sample_period*dump_channel, - 'sample_period': sample_period*len(analog_channels), - 'sample_rate': sample_rate/len(analog_channels), - 'cause': cause}) + traces[channel] = DotDict({'timestamps': timestamps[dump_channel::len(analog_channels)] if len(analog_channels) > 1 else timestamps, + 'samples': array.array('f', (value*value_multiplier+value_offset for value in data)), + 'sample_period': sample_period*len(analog_channels), + 'sample_rate': sample_rate/len(analog_channels), + 'cause': cause}) if logic_channels: async with self.transaction(): await self.set_registers(SampleAddress=(address - nsamples) % buffer_width, @@ -320,12 +328,11 @@ class Scope(vm.VirtualMachine): data = await self.read_logic_samples(nsamples) for i in logic_channels: mask = 1<" + """ $ ipython3 --pylab @@ -536,7 +542,7 @@ async def main(): parser.add_argument('--verbose', action='store_true', default=False, help="Verbose logging") args = parser.parse_args() 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.url) + s = await Scope().connect(args.url) def await_(g): task = asyncio.Task(g) diff --git a/streams.py b/streams.py index e06e4e2..ee61dde 100644 --- a/streams.py +++ b/streams.py @@ -8,6 +8,7 @@ Package for asynchronous serial IO. import asyncio import logging import sys +import threading import serial from serial.tools.list_ports import comports @@ -30,13 +31,16 @@ class SerialStream: return SerialStream(port.device, **kwargs) raise RuntimeError("No matching serial device") - def __init__(self, device, loop=None, **kwargs): + def __init__(self, device, use_threads=None, loop=None, **kwargs): self._device = device - self._connection = serial.Serial(self._device, timeout=0, write_timeout=0, **kwargs) + self._use_threads = sys.platform == 'win32' if use_threads is None else use_threads + self._connection = serial.Serial(self._device, **kwargs) if self._use_threads else \ + serial.Serial(self._device, timeout=0, write_timeout=0, **kwargs) LOG.debug(f"Opened SerialStream on {device}") self._loop = loop if loop is not None else asyncio.get_event_loop() self._output_buffer = bytes() self._output_buffer_empty = None + self._output_buffer_lock = threading.Lock() if self._use_threads else None def __repr__(self): return f'<{self.__class__.__name__}:{self._device}>' @@ -47,6 +51,12 @@ class SerialStream: self._connection = None def write(self, data): + if self._use_threads: + with self._output_buffer_lock: + self._output_buffer += data + if self._output_buffer_empty is None: + self._output_buffer_empty = self._loop.run_in_executor(None, self._write_blocking) + return if not self._output_buffer: try: n = self._connection.write(data) @@ -85,7 +95,22 @@ class SerialStream: self._output_buffer_empty.set_result(None) self._output_buffer_empty = None + def _write_blocking(self): + with self._output_buffer_lock: + while self._output_buffer: + data = bytes(self._output_buffer) + self._output_buffer_lock.release() + try: + n = self._connection.write(data) + finally: + self._output_buffer_lock.acquire() + LOG.debug(f"Write {self._output_buffer[:n]!r}") + self._output_buffer = self._output_buffer[n:] + self._output_buffer_empty = None + async def read(self, n=None): + if self._use_threads: + return await self._loop.run_in_executor(None, self._read_blocking, n) while True: w = self._connection.in_waiting if w: @@ -100,6 +125,14 @@ class SerialStream: finally: self._loop.remove_reader(self._connection) + def _read_blocking(self, n=None): + data = self._connection.read(1) + w = self._connection.in_waiting + if w and (n is None or n > 1): + data += self._connection.read(w if n is None else min(n-1, w)) + LOG.debug(f"Read {data!r}") + return data + async def readexactly(self, n): data = b'' while len(data) < n: diff --git a/utils.py b/utils.py new file mode 100644 index 0000000..599583a --- /dev/null +++ b/utils.py @@ -0,0 +1,8 @@ + + +class DotDict(dict): + __getattr__ = dict.__getitem__ + __setattr__ = dict.__setitem__ + __delattr__ = dict.__delitem__ + + diff --git a/vm.py b/vm.py index 7104775..d2a9db6 100644 --- a/vm.py +++ b/vm.py @@ -20,16 +20,12 @@ from enum import IntEnum import logging import struct +from utils import DotDict + LOG = logging.getLogger(__name__) -class DotDict(dict): - __getattr__ = dict.__getitem__ - __setattr__ = dict.__setitem__ - __delattr__ = dict.__delitem__ - - class Register(namedtuple('Register', ['base', 'dtype', 'description'])): def encode(self, value): sign = self.dtype[0] @@ -258,7 +254,7 @@ class VirtualMachine: await self._vm.issue(self._data) return False - def __init__(self, reader, writer): + def __init__(self, reader=None, writer=None): self._reader = reader self._writer = writer self._transactions = []