Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: m-labs/artiq
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 6d4b9e5fcaa0
Choose a base ref
...
head repository: m-labs/artiq
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: 17611e9899dc
Choose a head ref
  • 6 commits
  • 10 files changed
  • 2 contributors

Commits on Feb 15, 2015

  1. Copy the full SHA
    7299a2c View commit details
  2. Copy the full SHA
    79a5d8a View commit details
  3. Copy the full SHA
    17596f4 View commit details
  4. lda_controller: style

    sbourdeauducq committed Feb 15, 2015
    Copy the full SHA
    e196ef9 View commit details

Commits on Feb 16, 2015

  1. add Novatech 409B controller

    Joe Britton authored and sbourdeauducq committed Feb 16, 2015
    Copy the full SHA
    a3494c5 View commit details
  2. Copy the full SHA
    17611e9 View commit details
Empty file.
262 changes: 262 additions & 0 deletions artiq/devices/novatech409b/driver.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,262 @@
#!/usr/bin/python3

# Written by Joe Britton, 2015

import time
import math
import logging

import serial


logger = logging.getLogger(__name__)


class UnexpectedResponse(Exception):
pass


class Novatech409B:
"""Driver for Novatech 409B 4-channel DDS"""

# maximum frequency of Novatech 409B when using PLL and external reference
max_freq_with_pll = 171.1276031

def __init__(self, serial_dev="/dev/ttyUSB0"):
if serial_dev == "sim":
self.simulation = True
else:
self.simulation = False
self.port = serial.serial_for_url(
serial_dev,
baudrate=19200,
bytesize=8,
parity="N",
stopbits=1,
xonxoff=0,
timeout=0.2)
self.setup()

def close(self):
"""Close the serial port"""
if not self.simulation:
self.port.close()

def _ser_send(self, cmd, get_response=True):
"""send a string to the serial port
Routine for sending serial commands to device. It sends strings
and listens for a response terminated by a carriage return.
example:
ser_send("F0 1.0") # sets the freq of channel 0 to 1.0 MHz
:param str cmd: a character string to send to device
:returns: None
"""
if self.simulation:
print(cmd)
else:
self.port.flush()
self.port.write((cmd + "\r\n").encode())
if get_response:
result = self.port.readline().rstrip().decode()
if result != "OK":
raise UnexpectedResponse(result)

def reset(self):
"""command hardware reset of 409B
returns: None
"""
self._ser_send("R", get_response=False)
time.sleep(1)
self.setup()

def setup(self):
"""initial setup of 409B
Setup the Novatech 409B with the following defaults.
* command echo off ("E d")
* external clock ("") 10 MHz sinusoid -1 to +7 dBm
:returns: None
"""
# disable command echo
self._ser_send("E d", get_response=False)
self.set_phase_continuous(True)
self.set_simultaneous_update(False)

def save_state_to_eeprom(self):
"""save current state to EEPROM
Saves current state into EEPROM and sets valid flag.
State used as default upon next power up or reset. """
self._ser_send("S")

def set_phase_continuous(self, is_continuous):
"""toggle phase continuous mode
Sends the "M n" command. This turns off the automatic
clearing of the phase register. In this mode, the phase
register is left intact when a command is performed.
Use this mode if you want frequency changes to remain
phase synchronous, with no phase discontinuities.
:param bool is_continuous: True or False
"""
if is_continuous:
self._ser_send("M n")
else:
self._ser_send("M a")

def set_simultaneous_update(self, simultaneous):
"""Sends the "I m" command. In this mode an update
pulse will not be sent to the DDS chip until
an "I p" command is sent. This is useful when it is
important to change all the outputs to new values
simultaneously.
"""
if simultaneous:
self._ser_send("I m")
else:
self._ser_send("I a")

def set_freq(self, ch_no, freq):
"""set_freq(ch_no,freq):
Set ch_no to frequency freq MHz"""
if ch_no < 0 or ch_no > 3:
raise ValueError("Incorrect channel number {}".format(ch_no))
if freq < 0.0 or freq > self.max_freq_with_pll:
raise ValueError("Incorrect frequency {}".format(freq))
# do this immediately, disable SimultaneousUpdate mode
self.set_simultaneous_update(False)
self._ser_send("F{:d} {:f}".format(ch_no, freq))

def set_phase(self, ch_no, phase):
"""set DDS phase
:param int ch_no: 0 to 3
:param float phase: phase angle in cycles [0,1]
:returns: None
"""
if ch_no < 0 or ch_no > 3:
raise ValueError("Incorrect channel number {}".format(ch_no))
if phase < 0 or phase > 1:
raise ValueError("Incorrect phase {}".format(phase))
# do this immediately, disable SimultaneousUpdate mode
self.set_simultaneous_update(False)
# phase word is required by device
# N is an integer from 0 to 16383. Phase is set to
# N*360/16384 deg; in ARTIQ represent phase in cycles [0,1]
phase_word = round(phase*16384)
if phase_word >= 16384:
phase_word -= 16384
cmd = "P{:d} {:d}".format(ch_no, phase_word)
self._ser_send(cmd)

def set_freq_all_phase_continuous(self, freq):
"""set frequency of all channels simultaneously
Set frequency of all channels simultaneously.
1) all DDSs are set to phase continuous mode
2) all DDSs are simultaneously set to new frequency
Together 1 and 2 ensure phase continuous frequency switching.
:param float freq: frequency in MHz
:returns: None
"""
self.set_simultaneous_update(True)
self.set_phase_continuous(True)
for channel_num in range(4):
self.set_freq(channel_num, freq)
# send command necessary to update all channels at the same time
self._ser_send("I p")

def set_phase_all(self, phase):
"""set phase of all DDS channels simultaneously
Set phase of all DDS channels at the same time. For example,::
set_phase_all([0, .25, 0.5, 0.75])
:param float phase: vector of four phases (in cycles [0,1])
:returns: None
"""
self.set_simultaneous_update(True)
# Note that this only works if the continuous
# phase switching is turned off.
self.set_phase_continuous(False)
for ch_no in range(4):
self.set_phase(ch_no, phase[ch_no])
# send command necessary to update all channels at the same time
self._ser_send("I p")

def freq_sweep_all_phase_continuous(self, f0, f1, t):
""" sweep phase of all DDSs, phase continuous
Sweep frequency in a phase continuous fashion.
:param float f0: starting frequency (MHz)
:param float f1: ending frequency (MHz)
:param float t: sweep duration (seconds)
:returns: None
"""
# TODO: consider using artiq.language.units
if f0 == f1:
return
# get sign of sweep
if f1 > f0:
df_sign = 1
else:
df_sign = -1

self.set_phase_continuous(True)
self.set_simultaneous_update(True)
# calculate delay
# note that a single call to self.set_freq_all_phase_continuous()
# takes time t_for_one_freq_set; fix duration empirically
t_for_one_freq_set = 0.264
dt = t_for_one_freq_set
n_steps = int(math.ceil(t/dt))
df = abs(f0-f1)/n_steps
for n in range(n_steps):
fnow = f0+n*df_sign*df
self.set_freq_all_phase_continuous(fnow)
self.set_freq_all_phase_continuous(f1)

def output_scale(self, ch_no, frac):
"""changes amplitude of a DDS
:param int ch_no: DDS channel 0, 1, 2 or 3
:param float frac: 0 to 1 (full attenuation to no attenuation)
:returns: None
"""
self.set_simultaneous_update(False)
dac_ch_no = int(math.floor(frac*1024))
s = "V{:d} {:d}".format(ch_no, dac_ch_no)
self._ser_send(s)

def output_scale_all(self, frac):
"""changes amplitude of all DDSs
:param float frac: 0 to 1 (full attenuation to no attenuation)
"""
for ch_no in range(4):
self.output_scale(ch_no, frac)

def output_on_off(self, ch_no, on):
"""turns on or off the DDS
:param int ch_no: DDS channel 0, 1, 2 or 3
"""
if on:
self.output_scale(ch_no, 1.0)
else:
self.output_scale(ch_no, 0.0)

def output_on_off_all(self, on):
"""turns on or off the all the DDSs"""
if on:
self.output_scale_all(1.0)
else:
self.output_scale_all(0.0)
11 changes: 6 additions & 5 deletions artiq/frontend/artiq_master.py
Original file line number Diff line number Diff line change
@@ -16,15 +16,16 @@

def get_argparser():
parser = argparse.ArgumentParser(description="ARTIQ master")
parser.add_argument(
group = parser.add_argument_group("network")
group.add_argument(
"--bind", default="::1",
help="hostname or IP address to bind to")
parser.add_argument(
group.add_argument(
"--port-notify", default=3250, type=int,
help="TCP port to listen to for notifications")
parser.add_argument(
help="TCP port to listen to for notifications (default: 3250)")
group.add_argument(
"--port-control", default=3251, type=int,
help="TCP port to listen to for control")
help="TCP port to listen to for control (default: 3251)")
verbosity_args(parser)
return parser

14 changes: 6 additions & 8 deletions artiq/frontend/lda_controller.py
Original file line number Diff line number Diff line change
@@ -4,18 +4,16 @@

from artiq.devices.lda.driver import Lda, Ldasim
from artiq.protocols.pc_rpc import simple_server_loop
from artiq.tools import verbosity_args, init_logger
from artiq.tools import verbosity_args, simple_network_args, init_logger


def get_argparser():
parser = argparse.ArgumentParser()
parser.add_argument('-d', '--device', default="LDA-102",
parser = argparse.ArgumentParser(
description="ARTIQ controller for the Lab Brick Digital Attenuator")
parser.add_argument("-d", "--device", default="LDA-102",
choices=["LDA-102", "LDA-602", "sim"])
parser.add_argument('--bind', default="::1",
help="hostname or IP address to bind to")
parser.add_argument('-p', '--port', default=3253, type=int,
help="TCP port to listen to")
parser.add_argument('-s', '--serial', default=None,
simple_network_args(parser, 3253)
parser.add_argument("-s", "--serial", default=None,
help="USB serial number of the device")
verbosity_args(parser)
return parser
42 changes: 42 additions & 0 deletions artiq/frontend/novatech409b_controller.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#!/usr/bin/python3

# Written by Joe Britton, 2015

import argparse
import importlib
import logging

from artiq.devices.novatech409b.driver import Novatech409B
from artiq.protocols.pc_rpc import simple_server_loop
from artiq.tools import verbosity_args, simple_network_args, init_logger


logger = logging.getLogger(__name__)


def get_argparser():
parser = argparse.ArgumentParser(
description="ARTIQ controller for the Novatech"
" 409B 4-channel DDS box")
simple_network_args(parser, 3254)
parser.add_argument(
"-s", "--serial-dev",
default="/dev/ttyUSB0", type=str,
help="serial port: on Windows \"COMx\","
" on Linux a device path (e.g. \"/dev/ttyUSB0\")")
verbosity_args(parser)
return parser

def main():
args = get_argparser().parse_args()
init_logger(args)

dev = Novatech409B(args.serial_dev)
try:
simple_server_loop(
{"novatech409b": dev}, args.bind, args.port)
finally:
dev.close()

if __name__ == "__main__":
main()
8 changes: 2 additions & 6 deletions artiq/frontend/pdq2_controller.py
Original file line number Diff line number Diff line change
@@ -4,15 +4,12 @@

from artiq.devices.pdq2.driver import Pdq2
from artiq.protocols.pc_rpc import simple_server_loop
from artiq.tools import verbosity_args, init_logger
from artiq.tools import verbosity_args, init_logger, simple_network_args


def get_argparser():
parser = argparse.ArgumentParser(description="PDQ2 controller")
parser.add_argument("--bind", default="::1",
help="hostname or IP address to bind to")
parser.add_argument("-p", "--port", default=3252, type=int,
help="TCP port to listen to")
simple_network_args(parser, 3252)
parser.add_argument(
"-s", "--serial", default=None,
help="device (FT245R) serial string [first]")
@@ -25,7 +22,6 @@ def get_argparser():

def main():
args = get_argparser().parse_args()

init_logger(args)

dev = Pdq2(serial=args.serial)
9 changes: 9 additions & 0 deletions artiq/tools.py
Original file line number Diff line number Diff line change
@@ -38,5 +38,14 @@ def verbosity_args(parser):
help="decrease logging level")


def simple_network_args(parser, default_port):
group = parser.add_argument_group("network")
group.add_argument("--bind", default="::1",
help="hostname or IP address to bind to")
group.add_argument("-p", "--port", default=default_port, type=int,
help="TCP port to listen to (default: {})"
.format(default_port))


def init_logger(args):
logging.basicConfig(level=logging.WARNING + args.quiet*10 - args.verbose*10)
2 changes: 2 additions & 0 deletions doc/manual/drivers_reference.rst
Original file line number Diff line number Diff line change
@@ -51,3 +51,5 @@ When writing a new driver, choose a free TCP port and add it to this list.
+--------------------------+--------------+
| LDA | 3253 |
+--------------------------+--------------+
| Novatech 409B | 3254 |
+--------------------------+--------------+
28 changes: 21 additions & 7 deletions doc/manual/management_system.rst
Original file line number Diff line number Diff line change
@@ -4,27 +4,41 @@ Management system
Master
------

The master is responsible for managing the parameter and device databases, the experiment repository, scheduling and running experiments, archiving results, and distributing real-time results.

The master is a headless component, and one or several clients (command-line or GUI) use the network to interact with it.

.. argparse::
:ref: artiq.frontend.artiq_master.get_argparser
:prog: artiq_master

Controller manager
------------------

Controller managers are responsible for running and stopping controllers on a machine. There is one controller manager per network node that runs controllers.

A controller manager connects to the master and uses the device database to determine what controllers need to be run. Changes in the device database are tracked by the manager and controllers are started and stopped accordingly.

Controller managers use the local network address of the connection to the master to filter the device database and run only those controllers that are allocated to the current node. Hostname resolution is supported.

.. argparse::
:ref: artiq.frontend.artiq_ctlmgr.get_argparser
:prog: artiq_ctlmgr

Command-line client
-------------------

The command-line client connects to the master and permits modification and monitoring of the databases, monitoring the experiment schedule, and submitting experiments.

.. argparse::
:ref: artiq.frontend.artiq_client.get_argparser
:prog: artiq_client

GUI client
----------

The GUI client connects to the master and is the main way of interacting with it.

.. argparse::
:ref: artiq.frontend.artiq_gui.get_argparser
:prog: artiq_gui

Controller manager
------------------

.. argparse::
:ref: artiq.frontend.artiq_ctlmgr.get_argparser
:prog: artiq_ctlmgr
30 changes: 16 additions & 14 deletions setup.py
Original file line number Diff line number Diff line change
@@ -5,25 +5,27 @@


requirements = [
"sphinx", "sphinx-argparse", "pyserial", "numpy", "scipy",
"python-dateutil", "prettytable", "h5py"
]
"sphinx", "sphinx-argparse", "pyserial", "numpy", "scipy",
"python-dateutil", "prettytable", "h5py"
]

scripts = [
"artiq_client=artiq.frontend.artiq_client:main",
"artiq_ctlmgr=artiq.frontend.artiq_ctlmgr:main",
"artiq_master=artiq.frontend.artiq_master:main",
"artiq_rpctool=artiq.frontend.artiq_rpctool:main",
"artiq_run=artiq.frontend.artiq_run:main",
"lda_controller=artiq.frontend.lda_controller:main",
"pdq2_client=artiq.frontend.pdq2_client:main",
"pdq2_controller=artiq.frontend.pdq2_controller:main",
]
"artiq_client=artiq.frontend.artiq_client:main",
"artiq_ctlmgr=artiq.frontend.artiq_ctlmgr:main",
"artiq_master=artiq.frontend.artiq_master:main",
"artiq_rpctool=artiq.frontend.artiq_rpctool:main",
"artiq_run=artiq.frontend.artiq_run:main",
"lda_controller=artiq.frontend.lda_controller:main",
"novatech409b_controller=artiq.frontend.novatech409b_controller:main",
"pdq2_client=artiq.frontend.pdq2_client:main",
"pdq2_controller=artiq.frontend.pdq2_controller:main",
]

if os.getenv("ARTIQ_GUI") == "1":
requirements += ["pygobject", "gbulb", "cairoplot"]
scripts += [
"artiq_gui=artiq.frontend.artiq_gui:main"
]
"artiq_gui=artiq.frontend.artiq_gui:main"
]


setup(