Skip to content

Commit 3df7e0b

Browse files
committed
Add frequency reference control and general cleanup and docs.
1 parent 21bd62e commit 3df7e0b

File tree

6 files changed

+238
-32
lines changed

6 files changed

+238
-32
lines changed

Diff for: docs/source/devices.rst

+1
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ These devices cover various frequency sources that provide either hardware-timed
5959

6060
devices/novatechDDS9m
6161
devices/phasematrixquicksyn
62+
devices/windfreak
6263

6364

6465
Miscellaneous

Diff for: docs/source/devices/windfreak.rst

+120
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
Windfreak Synth
2+
===============
3+
4+
This labscript device controls the Windfreak SynthHD and SynthHD Pro signal generators.
5+
6+
At present only static frequencies and DDS gating is supported.
7+
This driver also supports external referencing.
8+
9+
10+
Installation
11+
~~~~~~~~~~~~
12+
13+
This driver requires the `windfreak` package available on pip.
14+
If using a version of Windows older than 10,
15+
you will need to install the usb driver available from windfreak.
16+
17+
Usage
18+
~~~~~
19+
20+
Below is a basic script using the driver.
21+
22+
.. code-block:: python
23+
24+
from labscript import *
25+
26+
from labscript_devices.PrawnBlaster.labscript_devices import PrawnBlaster
27+
from labscript_devices.Windfreak.labscript_devices import WindfreakSynthHDPro
28+
29+
PrawnBlaster(name='prawn', com_port='COM6', num_pseudoclocks=1)
30+
31+
WindfreakSynthHDPro(name='WF', com_port="COM7")
32+
33+
StaticDDS('WF_A', WF, 'channel 0')
34+
35+
if __name__ == '__main__':
36+
37+
WF.enable_output(0) # enables channel A (0)
38+
WF_A.setfreq(10, units = 'GHz')
39+
WF_A.setamp(-24) # in dBm
40+
WF_A.setphase(45) # in deg
41+
42+
start(0)
43+
stop(1)
44+
45+
This driver supports the DDS Gate feature which can provide dynamic TTL control of the outputs.
46+
This is done by enabling the `rf_enable` triggering mode on the synth,
47+
as well as setting the correct `digital_gate` on the output.
48+
Note that both outputs will be toggled on/off when using `rf_enable` modulation.
49+
50+
It also supports external referencing of the device.
51+
The below script uses external clocking and gating features.
52+
53+
.. code-block:: python
54+
55+
from labscript import *
56+
57+
from labscript_devices.PrawnBlaster.labscript_devices import PrawnBlaster
58+
from labscript_devices.Windfreak.labscript_devices import WindfreakSynthHDPro
59+
from labscript_devices.NI_DAQmx.Models.NI_USB_6343 import NI_USB_6343
60+
61+
PrawnBlaster(name='prawn', com_port='COM6', num_pseudoclocks=1)
62+
63+
NI_USB_6343(name='ni_6343', parent_device=prawn.clocklines[0],
64+
clock_terminal='/ni_usb_6343/PFI0',
65+
MAX_name='ni_usb_6343',
66+
)
67+
68+
WindfreakSynthHDPro(name='WF', com_port="COM7",
69+
trigger_mode='rf enable',
70+
reference_mode='external',
71+
reference_frequency=10e6)
72+
73+
StaticDDS('WF_A', WF, 'channel 0',
74+
digital_gate={'device':ni_6343, 'connection':'port0/line0'})
75+
76+
if __name__ == '__main__':
77+
78+
WF.enable_output(0) # enables channel A (0)
79+
WF_A.setfreq(10, units = 'GHz')
80+
WF_A.setamp(-24) # in dBm
81+
WF_A.setphase(45) # in deg
82+
83+
t = 0
84+
start(t)
85+
86+
# enable rf via digital gate for 1 ms at 10 ms
87+
t = 10e-3
88+
WF_A.enable(t)
89+
t += 1e-3
90+
WF_A.disable(t)
91+
92+
stop(t+1e-3)
93+
94+
95+
Detailed Documentation
96+
~~~~~~~~~~~~~~~~~~~~~~
97+
98+
.. automodule:: labscript_devices.Windfreak
99+
:members:
100+
:undoc-members:
101+
:show-inheritance:
102+
:private-members:
103+
104+
.. automodule:: labscript_devices.Windfreak.labscript_devices
105+
:members:
106+
:undoc-members:
107+
:show-inheritance:
108+
:private-members:
109+
110+
.. automodule:: labscript_devices.Windfreak.blacs_tabs
111+
:members:
112+
:undoc-members:
113+
:show-inheritance:
114+
:private-members:
115+
116+
.. automodule:: labscript_devices.Windfreak.blacs_workers
117+
:members:
118+
:undoc-members:
119+
:show-inheritance:
120+
:private-members:

Diff for: labscript_devices/Windfreak/blacs_tabs.py

+7-3
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,11 @@
1414
from blacs.device_base_class import DeviceTab
1515

1616

17-
class WindfreakSynthTab(DeviceTab):
17+
class WindfreakSynthHDTab(DeviceTab):
1818

1919
def __init__(self, *args, **kwargs):
2020
if not hasattr(self,'device_worker_class'):
21-
self.device_worker_class = 'labscript_devices.Windfreak.blacs_workers.WindfreakSynthWorker'
21+
self.device_worker_class = 'labscript_devices.Windfreak.blacs_workers.WindfreakSynthHDWorker'
2222
DeviceTab.__init__(self, *args, **kwargs)
2323

2424
def initialise_GUI(self):
@@ -69,9 +69,13 @@ def initialise_workers(self):
6969
conn_obj = self.settings['connection_table'].find_by_name(self.device_name).properties
7070
self.com_port = conn_obj.get('com_port',None)
7171
self.trigger_mode = conn_obj.get('trigger_mode','disabled')
72+
self.reference_mode = conn_obj['reference_mode']
73+
self.reference_frequency = conn_obj['reference_frequency']
7274

7375
self.create_worker('main_worker',self.device_worker_class,{'com_port':self.com_port,
7476
'allowed_chans':self.allowed_chans,
75-
'trigger_mode':self.trigger_mode})
77+
'trigger_mode':self.trigger_mode,
78+
'reference_mode':self.reference_mode,
79+
'reference_frequency':self.reference_frequency})
7680

7781
self.primary_worker = 'main_worker'

Diff for: labscript_devices/Windfreak/blacs_workers.py

+71-20
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
import numpy as np
1717

1818

19-
class WindfreakSynthWorker(Worker):
19+
class WindfreakSynthHDWorker(Worker):
2020

2121
def init(self):
2222
# hide import of 3rd-party library in here so docs don't need it
@@ -32,12 +32,41 @@ def init(self):
3232
# connect to synth
3333
self.synth = windfreak.SynthHD(self.com_port)
3434
self.valid_modes = self.synth.trigger_modes
35+
self.valid_ref_modes = self.synth.reference_modes
36+
# set reference mode
37+
self.set_reference_mode(self.reference_mode, self.reference_frequency)
3538
# set trigger mode from connection_table_properties
3639
self.set_trigger_mode(self.trigger_mode)
3740

3841
# populate smart chache
3942
self.smart_cache['STATIC_DATA'] = self.check_remote_values()
4043

44+
def set_reference_mode(self, mode, ext_freq):
45+
"""Sets the synth reference mode.
46+
47+
Provides basic error checking that setting is valid.
48+
49+
Args:
50+
mode (str): Valid reference modes are `external`, `internal 27mhz`
51+
and `internal 10mhz`. If mode is external, ext_freq must be provided.
52+
ext_freq (float): Frequency of external reference.
53+
If using internal reference, pass `None`.
54+
55+
Raises:
56+
ValueError: if `mode` is not a valid setting or `ext_ref` not provided
57+
when using an external reference.
58+
"""
59+
60+
if mode == 'external' and ext_freq is None:
61+
raise ValueError('Must specify external reference frequency')
62+
63+
if mode in self.valid_ref_modes:
64+
self.synth.reference_mode = mode
65+
if mode == 'external':
66+
self.synth.reference_frequency = ext_freq
67+
else:
68+
raise ValueError(f'{mode} not in {self.valid_ref_modes}')
69+
4170
def set_trigger_mode(self,mode):
4271
"""Sets the synth trigger mode.
4372
@@ -81,6 +110,16 @@ def program_manual(self, front_panel_values):
81110
return self.check_remote_values()
82111

83112
def check_remote_value(self,channel,typ):
113+
"""Checks the remote value of a parameter for a channel.
114+
115+
Args:
116+
channel (int): Which channel to check. Must be 0 or 1.
117+
typ (str): Which parameter to get. Must be `freq`, `amp`, `phase`
118+
or `gate`.
119+
120+
Raises:
121+
ValueError: If `typ` is not a valid parameter type for the channel.
122+
"""
84123

85124
if typ == 'freq':
86125
return self.synth[channel].frequency
@@ -91,9 +130,21 @@ def check_remote_value(self,channel,typ):
91130
elif typ == 'gate':
92131
return self.synth[channel].rf_enable and self.synth[channel].pll_enable
93132
else:
94-
raise ValueError(type)
133+
raise ValueError(typ)
95134

96135
def program_static_value(self,channel,typ,value):
136+
"""Program a value for the specified parameter of the channel.
137+
138+
Args:
139+
channel (int): Channel to program. Must be 0 or 1.
140+
typ (str): Parameter to program. Must be `freq`, `amp`, `phase`,
141+
or `gate`.
142+
value (float or bool): Value to program. `gate` takes a boolean type,
143+
all others take a float.
144+
145+
Raises:
146+
ValueError: If requested parameter type is not valid.
147+
"""
97148

98149
if typ == 'freq':
99150
self.synth[channel].frequency = value
@@ -104,11 +155,12 @@ def program_static_value(self,channel,typ,value):
104155
elif typ == 'gate':
105156
# windfreak API does not like np.bool_
106157
# convert to native python bool
107-
if isinstance(value, np.bool_): value = value.item()
158+
if isinstance(value, np.bool_):
159+
value = value.item()
108160
self.synth[channel].rf_enable = value
109161
self.synth[channel].pll_enable = value
110162
else:
111-
raise ValueError(type)
163+
raise ValueError(typ)
112164

113165
def transition_to_buffered(self, device_name, h5file, initial_values, fresh):
114166

@@ -122,21 +174,20 @@ def transition_to_buffered(self, device_name, h5file, initial_values, fresh):
122174
static_data = group['STATIC_DATA'][:][0]
123175

124176
if static_data is not None:
125-
data = static_data
126-
if fresh or data != self.smart_cache['STATIC_DATA']:
127-
128-
# need to infer which channels are programming
129-
num_chan = len(data)//len(self.subchnls)
130-
channels = [int(name[-1]) for name in data.dtype.names[0:num_chan]]
131-
132-
for i in channels:
133-
for sub in self.subchnls:
134-
if initial_values[f'channel {i:d}'][sub] != data[sub+str(i)]:
135-
self.program_static_value(i,sub,data[sub+str(i)])
136-
# update smart cache to reflect programmed values
137-
self.smart_cache['STATIC_DATA'][f'channel {i:d}'][sub] = data[sub+str(i)]
138-
# update final values to reflect programmed values
139-
self.final_values[f'channel {i:d}'][sub] = data[sub+str(i)]
177+
178+
# need to infer which channels are programming
179+
num_chan = len(static_data)//len(self.subchnls)
180+
channels = [int(name[-1]) for name in static_data.dtype.names[0:num_chan]]
181+
182+
for i in channels:
183+
for sub in self.subchnls:
184+
desired_value = static_data[sub+str(i)]
185+
if self.smart_cache['STATIC_DATA'][f'channel {i:d}'][sub] != desired_value or fresh:
186+
self.program_static_value(i,sub,desired_value)
187+
# update smart cache to reflect programmed values
188+
self.smart_cache['STATIC_DATA'][f'channel {i:d}'][sub] = desired_value
189+
# update final values to reflect programmed values
190+
self.final_values[f'channel {i:d}'][sub] = desired_value
140191

141192
return self.final_values
142193

@@ -161,5 +212,5 @@ def transition_to_manual(self,abort = False):
161212
# If we're aborting the run, reset to original value
162213
self.program_manual(self.initial_values)
163214
# If we're not aborting the run, stick with buffered value. Nothing to do really!
164-
# return the current values in the device
215+
165216
return True

Diff for: labscript_devices/Windfreak/labscript_devices.py

+31-7
Original file line numberDiff line numberDiff line change
@@ -11,22 +11,22 @@
1111
# #
1212
#####################################################################
1313

14-
from labscript import LabscriptError, set_passed_properties, config, StaticDDS, IntermediateDevice, Device
14+
from labscript import LabscriptError, set_passed_properties, config, StaticDDS, Device
1515
from labscript_utils import dedent
1616
from labscript_utils.unitconversions.generic_frequency import FreqConversion
1717

1818
import numpy as np
1919

2020

21-
class WindfreakSynth(Device):
22-
description = 'Windfreak HDPro Synthesizer'
21+
class WindfreakSynthHD(Device):
22+
description = 'Windfreak HD Synthesizer'
2323
allowed_children = [StaticDDS]
2424
# note, box labels 'A', 'B' map to programming channels 0, 1
2525
allowed_chans = [0, 1]
2626
enabled_chans = []
2727

2828
# define output limitations for the SynthHDPro
29-
freq_limits = (10e6, 24e9) # set in Hz
29+
freq_limits = (10e6, 15e9) # set in Hz
3030
freq_res = 1 # number of sig digits after decimal
3131
amp_limits = (-40.0, 20.0) # set in dBm
3232
amp_res = 2
@@ -44,27 +44,39 @@ class WindfreakSynth(Device):
4444
'phase_limits',
4545
'phase_res',
4646
'trigger_mode',
47+
'reference_mode',
48+
'reference_frequency',
4749
]
4850
})
49-
def __init__(self, name, com_port="", trigger_mode='disabled', **kwargs):
51+
def __init__(self, name, com_port="", trigger_mode='disabled',
52+
reference_mode='internal 27mhz',
53+
reference_frequency=None, **kwargs):
5054
"""Creates a Windfreak HDPro Synthesizer
5155
5256
Args:
5357
name (str): python variable name to assign the device to.
5458
com_port (str): COM port connection string.
5559
Must take the form of 'COM d', where d is an integer.
5660
trigger_mode (str): Trigger mode for the device to use.
57-
Currently, labscript only directly programs 'rf enable',
61+
Currently, labscript only directly supports `'rf enable'`,
5862
via setting DDS gates.
5963
labscript could correctly program other modes with some effort.
6064
Other modes can be correctly programmed externally,
6165
with the settings saved to EEPROM.
62-
**kwargs: Keyword arguments passed to :obj:`labscript:labscript.Device.__init__`.
66+
reference_mode (str): Frequency reference mode to use.
67+
Valid options are 'external', 'internal 27mhz', and 'internal 10mhz'.
68+
Default is 'internal 27mhz'.
69+
reference_frequency (float): Reference frequency (in Hz)
70+
when using an external frequency.
71+
Valid values are between 10 and 100 MHz.
72+
**kwargs: Keyword arguments passed to :obj:`labscript:labscript.Device.__init__`.
6373
"""
6474

6575
Device.__init__(self, name, None, com_port, **kwargs)
6676
self.BLACS_connection = com_port
6777
self.trigger_mode = trigger_mode
78+
self.reference_mode = reference_mode
79+
self.reference_frequency = reference_frequency
6880

6981
def add_device(self, device):
7082
Device.add_device(self, device)
@@ -157,3 +169,15 @@ def enable_output(self, channel):
157169
self.enabled_chans.append(channel)
158170
else:
159171
raise LabscriptError(f'Channel {channel} is not a valid option for {self.device.name}.')
172+
173+
174+
class WindfreakSynthHDPro(WindfreakSynthHD):
175+
description = 'Windfreak HDPro Synthesizer'
176+
177+
# define output limitations for the SynthHDPro
178+
freq_limits = (10e6, 24e9) # set in Hz
179+
freq_res = 1 # number of sig digits after decimal
180+
amp_limits = (-40.0, 20.0) # set in dBm
181+
amp_res = 2
182+
phase_limits = (0.0, 360.0) # in deg
183+
phase_res = 2

0 commit comments

Comments
 (0)