diff --git a/artiq/devices/pdq2/pdq2-client b/artiq/devices/pdq2/pdq2-client index 1fb85463e..74862383f 100755 --- a/artiq/devices/pdq2/pdq2-client +++ b/artiq/devices/pdq2/pdq2-client @@ -1,23 +1,27 @@ #!/usr/bin/env python3 -# Robert Jordens , 2012 +# Based on code by Robert Jordens , 2012 import argparse import time -import logging from scipy import interpolate import numpy as np -from pdq2com import Pdq2 +from artiq.management.pc_rpc import Client -def _main(): - parser = argparse.ArgumentParser(description="""PDQ2 frontend. - Evaluates times and voltages, interpolates and uploads - them.""") - parser.add_argument("-s", "--serial", default=None, - help="device (FT245R) serial string [first]") +def _get_args(): + parser = argparse.ArgumentParser(description="""PDQ2 client. + Evaluates times and voltages, interpolates and uploads + them to the controller.""") + parser.add_argument("-s", "--server", default="::1", + help="hostname or IP of the master to connect to") + parser.add_argument("--port", default=8889, type=int, + help="TCP port to use to connect to the master") + parser.add_argument("-q", "--quit-controller", default=False, + action="store_true", + help="causes the controller to quit") parser.add_argument("-c", "--channel", default=0, type=int, help="channel: 3*board_num+dac_num [%(default)s]") parser.add_argument("-f", "--frame", default=0, type=int, @@ -44,42 +48,44 @@ def _main(): help="demo mode: pulse and chirp," " 1V*ch+0.1V*frame [%(default)s]") parser.add_argument("-p", "--plot", help="plot to file [%(default)s]") - parser.add_argument("-d", "--debug", default=False, - action="store_true", help="debug communications") parser.add_argument("-r", "--reset", default=False, action="store_true", help="do reset before") parser.add_argument("-b", "--bit", default=False, action="store_true", help="do bit test") + return parser.parse_args() - args = parser.parse_args() - if args.debug: - logging.basicConfig(level=logging.DEBUG) - else: - logging.basicConfig(level=logging.WARNING) - - times = eval(args.times, globals(), {}) - voltages = eval(args.voltages, globals(), dict(t=times)) - - dev = Pdq2(serial=args.serial) +def _main(): + args = _get_args() + dev = Client(args.server, args.port) + if args.quit_controller: + dev.quit() + return + dev.init() if args.reset: - dev.write(b"\x00") # flush any escape + dev.flush_escape() dev.write_cmd("RESET_EN") time.sleep(.1) if args.dcm: dev.write_cmd("DCM_EN") - dev.freq = 100e6 + dev.set_freq(100e6) elif args.dcm == 0: dev.write_cmd("DCM_DIS") - dev.freq = 50e6 + dev.set_freq(50e6) dev.write_cmd("START_DIS") + num_channels = dev.get_num_channels() + num_frames = dev.get_num_frames() + times = eval(args.times, globals(), {}) + voltages = eval(args.voltages, globals(), dict(t=times)) + if args.demo: - channels = [args.channel] if args.channel < dev.num_channels \ - else range(dev.num_channels) - frames = [args.frame] if args.frame < dev.num_frames \ - else range(dev.num_frames) + # FIXME + channels = [args.channel] if args.channel < num_channels \ + else range(num_channels) + frames = [args.frame] if args.frame < num_frames \ + else range(num_frames) for channel in channels: f = [] for frame in frames: @@ -95,32 +101,29 @@ def _main(): board, dac = divmod(channel, dev.num_dacs) dev.write_data(dev.add_mem_header(board, dac, dev.map_frames(f))) elif args.bit: - map = [0] * dev.num_frames + map = [0] * num_frames t = np.arange(2*16) * 1. v = [-1, 0, -1] for i in range(15): vi = 1 << i v.extend([vi - 1, vi]) - v = np.array(v)*dev.max_out/(1 << 15) + v = np.array(v)*dev.get_max_out()/(1 << 15) t, v = t[:3], v[:3] # print(t, v) - for channel in range(dev.num_channels): - dev.write_data(dev.multi_frame([(t, v)], channel=channel, - order=0, map=map, shift=15, - stop=False, trigger=False)) + for channel in range(num_channels): + dev.multi_frame([(t, v)], channel=channel, order=0, map=map, + shift=15, stop=False, trigger=False) else: tv = [(times, voltages)] - map = [None] * dev.num_frames + map = [None] * num_frames map[args.frame] = 0 - dev.write_data(dev.multi_frame(tv, channel=args.channel, - order=args.order, map=map)) + dev.multi_frame(tv, channel=args.channel, order=args.order, map=map) dev.write_cmd("START_EN") if not args.disarm: dev.write_cmd("ARM_EN") if args.free: dev.write_cmd("TRIGGER_EN") - dev.close() if args.plot: from matplotlib import pyplot as plt @@ -128,7 +131,7 @@ def _main(): ax0.plot(times, voltages, "xk", label="points") if args.order: spline = interpolate.splrep(times, voltages, k=args.order) - ttimes = np.arange(0, times[-1], 1/dev.freq) + ttimes = np.arange(0, times[-1], 1/dev.get_freq()) vvoltages = interpolate.splev(ttimes, spline) ax0.plot(ttimes, vvoltages, ",b", label="interpolation") fig.savefig(args.plot) diff --git a/artiq/devices/pdq2/pdq2com.py b/artiq/devices/pdq2/pdq2-controller old mode 100644 new mode 100755 similarity index 67% rename from artiq/devices/pdq2/pdq2com.py rename to artiq/devices/pdq2/pdq2-controller index 7b02ce5eb..a89f8f14f --- a/artiq/devices/pdq2/pdq2com.py +++ b/artiq/devices/pdq2/pdq2-controller @@ -1,12 +1,17 @@ -# Robert Jordens , 2012 +#!/usr/bin/env python3 + +# Based on code by Robert Jordens , 2012 import logging import struct -import warnings +import argparse +import asyncio from scipy import interpolate import numpy as np +from artiq.management.pc_rpc import Server, WaitQuit + logger = logging.getLogger("Pdq2") @@ -74,26 +79,14 @@ if Ftdi is None: self.fil.close() del self.fil - warnings.warn("no ftdi library found. writing to files") + logger.warning("no ftdi library found. writing to files") Ftdi = FileFtdi -class Pdq2: +class Pdq2(WaitQuit): """ PDQ DAC (a.k.a. QC_Waveform) """ - max_val = 1 << 15 # signed 16 bit DAC - max_out = 10. - freq = 50e6 # samples/s - max_time = 1 << 16 # unsigned 16 bit timer - num_dacs = 3 - num_frames = 8 - num_channels = 9 - max_data = 4*(1 << 10) # 8kx16 8kx16 4kx16 - escape_char = b"\xa5" - cordic_gain = 1. - for i in range(16): - cordic_gain *= np.sqrt(1 + 2**(-2*i)) commands = { "RESET_EN": b"\x00", @@ -109,24 +102,51 @@ class Pdq2: } def __init__(self, serial=None): + WaitQuit.__init__(self) self.serial = serial self.dev = Ftdi(serial) + def init(self): + self.max_val = 1 << 15 # signed 16 bit DAC + self.max_out = 10. + self.freq = 50e6 # samples/s + self.max_time = 1 << 16 # unsigned 16 bit timer + self.num_dacs = 3 + self.num_frames = 8 + self.num_channels = 9 + self.max_data = 4*(1 << 10) # 8kx16 8kx16 4kx16 + self.escape_char = b"\xa5" + self.cordic_gain = 1. + for i in range(16): + self.cordic_gain *= np.sqrt(1 + 2**(-2*i)) + def close(self): self.dev.close() del self.dev - def cmd(self, cmd): + def set_freq(self, f): + self.freq = f + + def get_freq(self): + return self.freq + + def get_num_channels(self): + return self.num_channels + + def get_num_frames(self): + return self.num_frames + + def get_max_out(self): + return self.max_out + + def _cmd(self, cmd): return self.escape_char + self.commands[cmd] - def write_cmd(self, cmd): - return self.write(self.cmd(cmd)) - - def escape(self, data): + def _escape(self, data): return data.replace(self.escape_char, self.escape_char + self.escape_char) - def write(self, *segments): + def _write(self, *segments): """ writes data segments to device """ @@ -135,17 +155,23 @@ class Pdq2: if written != len(segment): raise IOError("wrote %i of %i" % (written, len(segment))) - def write_data(self, *segments): - return self.write(*(self.escape(seg) for seg in segments)) + def flush_escape(self): + self._write(b"\x00") - def line_times(self, t, shift=0): + def write_cmd(self, cmd): + return self._write(self._cmd(cmd)) + + def _write_data(self, *segments): + return self._write(*(self._escape(seg) for seg in segments)) + + def _line_times(self, t, shift=0): scale = self.freq/2**shift t = t*scale tr = np.rint(t) dt = np.diff(tr) return t, tr, dt - def interpolate(self, t, v, order, shift=0, tr=None): + def _interpolate(self, t, v, order, shift=0, tr=None): """ calculate spline interpolation derivatives for data according to interpolation order @@ -171,7 +197,7 @@ class Pdq2: dv[i] -= c*dv[j] return dv - def pack_frame(self, *parts_dtypes): + def _pack_frame(self, *parts_dtypes): frame = [] for part, dtype in parts_dtypes: if dtype == "i6": @@ -185,9 +211,9 @@ class Pdq2: frame, frame.dtype, frame.shape, len(bytes(frame.data))) return bytes(frame.data) - def frame(self, t, v, p=None, f=None, - order=3, aux=None, shift=0, trigger=True, end=True, - silence=False, stop=True, clear=True, wait=False): + def _frame(self, t, v, p=None, f=None, + order=3, aux=None, shift=0, trigger=True, end=True, + silence=False, stop=True, clear=True, wait=False): """ serialize frame data voltages in volts, times in seconds @@ -217,7 +243,7 @@ class Pdq2: head[-1] |= (not stop and wait) << 15 # 1 parts.append((head, "u2")) - t, tr, dt = self.line_times(t, shift) + t, tr, dt = self._line_times(t, shift) assert np.all(dt*2**shift > 1 + length), (dt, length) assert np.all(dt < self.max_time), dt @@ -226,20 +252,20 @@ class Pdq2: v = np.clip(v/self.max_out, -1, 1) if p is not None: v /= self.cordic_gain - for dv, w in zip(self.interpolate(t, v, order, shift, tr), words): + for dv, w in zip(self._interpolate(t, v, order, shift, tr), words): parts.append((np.rint(dv*(2**(16*w - 1))), "i%i" % (2*w))) if p is not None: p = p/(2*np.pi) - for dv, w in zip(self.interpolate(t, p, 0, shift, tr), [1]): + for dv, w in zip(self._interpolate(t, p, 0, shift, tr), [1]): parts.append((np.rint(dv*(2**(16*w))), "u%i" % (2*w))) if f is not None: f = f/self.freq - for dv, w in zip(self.interpolate(t, f, 1, shift, tr), [2, 2]): + for dv, w in zip(self._interpolate(t, f, 1, shift, tr), [2, 2]): parts.append((np.rint(dv*(2**(16*w))), "i%i" % (2*w))) - frame = self.pack_frame(*parts) + frame = self._pack_frame(*parts) if stop: if p is not None: @@ -255,9 +281,9 @@ class Pdq2: 1, int(v[-1]*2**15)) return frame - def line(self, dt, v=(), a=(), p=(), f=(), typ=0, - silence=False, end=False, trigger=False, aux=False, - clear=False): + def _line(self, dt, v=(), a=(), p=(), f=(), typ=0, + silence=False, end=False, trigger=False, aux=False, + clear=False): raise NotImplementedError fmt = "