devices: basic pdq2 controller/client

This commit is contained in:
Sebastien Bourdeauducq 2014-10-25 11:38:42 +08:00
parent 16170c9013
commit 3f1391f7f2
2 changed files with 156 additions and 83 deletions

View File

@ -1,23 +1,27 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# Robert Jordens <jordens@gmail.com>, 2012 # Based on code by Robert Jordens <jordens@gmail.com>, 2012
import argparse import argparse
import time import time
import logging
from scipy import interpolate from scipy import interpolate
import numpy as np import numpy as np
from pdq2com import Pdq2 from artiq.management.pc_rpc import Client
def _main(): def _get_args():
parser = argparse.ArgumentParser(description="""PDQ2 frontend. parser = argparse.ArgumentParser(description="""PDQ2 client.
Evaluates times and voltages, interpolates and uploads Evaluates times and voltages, interpolates and uploads
them.""") them to the controller.""")
parser.add_argument("-s", "--serial", default=None, parser.add_argument("-s", "--server", default="::1",
help="device (FT245R) serial string [first]") 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, parser.add_argument("-c", "--channel", default=0, type=int,
help="channel: 3*board_num+dac_num [%(default)s]") help="channel: 3*board_num+dac_num [%(default)s]")
parser.add_argument("-f", "--frame", default=0, type=int, parser.add_argument("-f", "--frame", default=0, type=int,
@ -44,42 +48,44 @@ def _main():
help="demo mode: pulse and chirp," help="demo mode: pulse and chirp,"
" 1V*ch+0.1V*frame [%(default)s]") " 1V*ch+0.1V*frame [%(default)s]")
parser.add_argument("-p", "--plot", help="plot to file [%(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, parser.add_argument("-r", "--reset", default=False,
action="store_true", help="do reset before") action="store_true", help="do reset before")
parser.add_argument("-b", "--bit", default=False, parser.add_argument("-b", "--bit", default=False,
action="store_true", help="do bit test") action="store_true", help="do bit test")
return parser.parse_args()
args = parser.parse_args()
if args.debug: def _main():
logging.basicConfig(level=logging.DEBUG) args = _get_args()
else: dev = Client(args.server, args.port)
logging.basicConfig(level=logging.WARNING) if args.quit_controller:
dev.quit()
times = eval(args.times, globals(), {}) return
voltages = eval(args.voltages, globals(), dict(t=times)) dev.init()
dev = Pdq2(serial=args.serial)
if args.reset: if args.reset:
dev.write(b"\x00") # flush any escape dev.flush_escape()
dev.write_cmd("RESET_EN") dev.write_cmd("RESET_EN")
time.sleep(.1) time.sleep(.1)
if args.dcm: if args.dcm:
dev.write_cmd("DCM_EN") dev.write_cmd("DCM_EN")
dev.freq = 100e6 dev.set_freq(100e6)
elif args.dcm == 0: elif args.dcm == 0:
dev.write_cmd("DCM_DIS") dev.write_cmd("DCM_DIS")
dev.freq = 50e6 dev.set_freq(50e6)
dev.write_cmd("START_DIS") 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: if args.demo:
channels = [args.channel] if args.channel < dev.num_channels \ # FIXME
else range(dev.num_channels) channels = [args.channel] if args.channel < num_channels \
frames = [args.frame] if args.frame < dev.num_frames \ else range(num_channels)
else range(dev.num_frames) frames = [args.frame] if args.frame < num_frames \
else range(num_frames)
for channel in channels: for channel in channels:
f = [] f = []
for frame in frames: for frame in frames:
@ -95,32 +101,29 @@ def _main():
board, dac = divmod(channel, dev.num_dacs) board, dac = divmod(channel, dev.num_dacs)
dev.write_data(dev.add_mem_header(board, dac, dev.map_frames(f))) dev.write_data(dev.add_mem_header(board, dac, dev.map_frames(f)))
elif args.bit: elif args.bit:
map = [0] * dev.num_frames map = [0] * num_frames
t = np.arange(2*16) * 1. t = np.arange(2*16) * 1.
v = [-1, 0, -1] v = [-1, 0, -1]
for i in range(15): for i in range(15):
vi = 1 << i vi = 1 << i
v.extend([vi - 1, vi]) 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] t, v = t[:3], v[:3]
# print(t, v) # print(t, v)
for channel in range(dev.num_channels): for channel in range(num_channels):
dev.write_data(dev.multi_frame([(t, v)], channel=channel, dev.multi_frame([(t, v)], channel=channel, order=0, map=map,
order=0, map=map, shift=15, shift=15, stop=False, trigger=False)
stop=False, trigger=False))
else: else:
tv = [(times, voltages)] tv = [(times, voltages)]
map = [None] * dev.num_frames map = [None] * num_frames
map[args.frame] = 0 map[args.frame] = 0
dev.write_data(dev.multi_frame(tv, channel=args.channel, dev.multi_frame(tv, channel=args.channel, order=args.order, map=map)
order=args.order, map=map))
dev.write_cmd("START_EN") dev.write_cmd("START_EN")
if not args.disarm: if not args.disarm:
dev.write_cmd("ARM_EN") dev.write_cmd("ARM_EN")
if args.free: if args.free:
dev.write_cmd("TRIGGER_EN") dev.write_cmd("TRIGGER_EN")
dev.close()
if args.plot: if args.plot:
from matplotlib import pyplot as plt from matplotlib import pyplot as plt
@ -128,7 +131,7 @@ def _main():
ax0.plot(times, voltages, "xk", label="points") ax0.plot(times, voltages, "xk", label="points")
if args.order: if args.order:
spline = interpolate.splrep(times, voltages, k=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) vvoltages = interpolate.splev(ttimes, spline)
ax0.plot(ttimes, vvoltages, ",b", label="interpolation") ax0.plot(ttimes, vvoltages, ",b", label="interpolation")
fig.savefig(args.plot) fig.savefig(args.plot)

View File

@ -1,12 +1,17 @@
# Robert Jordens <jordens@gmail.com>, 2012 #!/usr/bin/env python3
# Based on code by Robert Jordens <jordens@gmail.com>, 2012
import logging import logging
import struct import struct
import warnings import argparse
import asyncio
from scipy import interpolate from scipy import interpolate
import numpy as np import numpy as np
from artiq.management.pc_rpc import Server, WaitQuit
logger = logging.getLogger("Pdq2") logger = logging.getLogger("Pdq2")
@ -74,26 +79,14 @@ if Ftdi is None:
self.fil.close() self.fil.close()
del self.fil del self.fil
warnings.warn("no ftdi library found. writing to files") logger.warning("no ftdi library found. writing to files")
Ftdi = FileFtdi Ftdi = FileFtdi
class Pdq2: class Pdq2(WaitQuit):
""" """
PDQ DAC (a.k.a. QC_Waveform) 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 = { commands = {
"RESET_EN": b"\x00", "RESET_EN": b"\x00",
@ -109,24 +102,51 @@ class Pdq2:
} }
def __init__(self, serial=None): def __init__(self, serial=None):
WaitQuit.__init__(self)
self.serial = serial self.serial = serial
self.dev = Ftdi(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): def close(self):
self.dev.close() self.dev.close()
del self.dev 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] return self.escape_char + self.commands[cmd]
def write_cmd(self, cmd): def _escape(self, data):
return self.write(self.cmd(cmd))
def escape(self, data):
return data.replace(self.escape_char, return data.replace(self.escape_char,
self.escape_char + self.escape_char) self.escape_char + self.escape_char)
def write(self, *segments): def _write(self, *segments):
""" """
writes data segments to device writes data segments to device
""" """
@ -135,17 +155,23 @@ class Pdq2:
if written != len(segment): if written != len(segment):
raise IOError("wrote %i of %i" % (written, len(segment))) raise IOError("wrote %i of %i" % (written, len(segment)))
def write_data(self, *segments): def flush_escape(self):
return self.write(*(self.escape(seg) for seg in segments)) 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 scale = self.freq/2**shift
t = t*scale t = t*scale
tr = np.rint(t) tr = np.rint(t)
dt = np.diff(tr) dt = np.diff(tr)
return t, tr, dt 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 calculate spline interpolation derivatives for data
according to interpolation order according to interpolation order
@ -171,7 +197,7 @@ class Pdq2:
dv[i] -= c*dv[j] dv[i] -= c*dv[j]
return dv return dv
def pack_frame(self, *parts_dtypes): def _pack_frame(self, *parts_dtypes):
frame = [] frame = []
for part, dtype in parts_dtypes: for part, dtype in parts_dtypes:
if dtype == "i6": if dtype == "i6":
@ -185,9 +211,9 @@ class Pdq2:
frame, frame.dtype, frame.shape, len(bytes(frame.data))) frame, frame.dtype, frame.shape, len(bytes(frame.data)))
return bytes(frame.data) return bytes(frame.data)
def frame(self, t, v, p=None, f=None, def _frame(self, t, v, p=None, f=None,
order=3, aux=None, shift=0, trigger=True, end=True, order=3, aux=None, shift=0, trigger=True, end=True,
silence=False, stop=True, clear=True, wait=False): silence=False, stop=True, clear=True, wait=False):
""" """
serialize frame data serialize frame data
voltages in volts, times in seconds voltages in volts, times in seconds
@ -217,7 +243,7 @@ class Pdq2:
head[-1] |= (not stop and wait) << 15 # 1 head[-1] |= (not stop and wait) << 15 # 1
parts.append((head, "u2")) 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*2**shift > 1 + length), (dt, length)
assert np.all(dt < self.max_time), dt assert np.all(dt < self.max_time), dt
@ -226,20 +252,20 @@ class Pdq2:
v = np.clip(v/self.max_out, -1, 1) v = np.clip(v/self.max_out, -1, 1)
if p is not None: if p is not None:
v /= self.cordic_gain 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))) parts.append((np.rint(dv*(2**(16*w - 1))), "i%i" % (2*w)))
if p is not None: if p is not None:
p = p/(2*np.pi) 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))) parts.append((np.rint(dv*(2**(16*w))), "u%i" % (2*w)))
if f is not None: if f is not None:
f = f/self.freq 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))) 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 stop:
if p is not None: if p is not None:
@ -255,9 +281,9 @@ class Pdq2:
1, int(v[-1]*2**15)) 1, int(v[-1]*2**15))
return frame return frame
def line(self, dt, v=(), a=(), p=(), f=(), typ=0, def _line(self, dt, v=(), a=(), p=(), f=(), typ=0,
silence=False, end=False, trigger=False, aux=False, silence=False, end=False, trigger=False, aux=False,
clear=False): clear=False):
raise NotImplementedError raise NotImplementedError
fmt = "<HH" fmt = "<HH"
parts = [0, int(round(dt*self.freq))] parts = [0, int(round(dt*self.freq))]
@ -272,7 +298,7 @@ class Pdq2:
if p is not None: if p is not None:
typ = 1 typ = 1
def map_frames(self, frames, map=None): def _map_frames(self, frames, map=None):
table = [] table = []
adr = self.num_frames adr = self.num_frames
for frame in frames: for frame in frames:
@ -291,15 +317,59 @@ class Pdq2:
t = struct.pack("<" + "H"*self.num_frames, *t) t = struct.pack("<" + "H"*self.num_frames, *t)
return t + b"".join(frames) return t + b"".join(frames)
def add_mem_header(self, board, dac, data, adr=0): def _add_mem_header(self, board, dac, data, adr=0):
assert dac in range(self.num_dacs) assert dac in range(self.num_dacs)
head = struct.pack("<HHH", (board << 4) | dac, head = struct.pack("<HHH", (board << 4) | dac,
adr, adr + len(data)//2 - 1) adr, adr + len(data)//2 - 1)
return head + data return head + data
def multi_frame(self, times_voltages, channel, map=None, **kwargs): def multi_frame(self, times_voltages, channel, map=None, **kwargs):
frames = [self.frame(t, v, **kwargs) for t, v in times_voltages] frames = [self._frame(t, v, **kwargs) for t, v in times_voltages]
data = self.map_frames(frames, map) data = self._map_frames(frames, map)
board, dac = divmod(channel, self.num_dacs) board, dac = divmod(channel, self.num_dacs)
data = self.add_mem_header(board, dac, data) data = self._add_mem_header(board, dac, data)
return data self._write_data(data)
def _get_args():
parser = argparse.ArgumentParser(description="PDQ2 controller")
parser.add_argument(
"--bind", default="::1",
help="hostname or IP address to bind to")
parser.add_argument(
"--port", default=8889, type=int,
help="TCP port to listen to")
parser.add_argument(
"-s", "--serial", default=None,
help="device (FT245R) serial string [first]")
parser.add_argument(
"-d", "--debug", default=False, action="store_true",
help="debug communications")
return parser.parse_args()
def main():
args = _get_args()
if args.debug:
logging.basicConfig(level=logging.DEBUG)
else:
logging.basicConfig(level=logging.WARNING)
dev = Pdq2(serial=args.serial)
try:
loop = asyncio.get_event_loop()
try:
server = Server(dev)
loop.run_until_complete(server.start(args.bind, args.port))
try:
loop.run_until_complete(dev.wait_quit())
finally:
loop.run_until_complete(server.stop())
finally:
loop.close()
finally:
dev.close()
if __name__ == "__main__":
main()