Skip to content

Commit af2db0d

Browse files
committed
Initial boiler plate for controlling Windfreak Synthesizers.
Only tested for SynthHDPro HWv2.06
1 parent 137a057 commit af2db0d

File tree

5 files changed

+323
-0
lines changed

5 files changed

+323
-0
lines changed
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#####################################################################
2+
# #
3+
# /labscript_devices/Windfreak/__init__.py #
4+
# #
5+
# Copyright 2022, Monash University and contributors #
6+
# #
7+
# This file is part of labscript_devices, in the labscript suite #
8+
# (see http://labscriptsuite.org), and is licensed under the #
9+
# Simplified BSD License. See the license.txt file in the root of #
10+
# the project for the full license. #
11+
# #
12+
#####################################################################
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
#####################################################################
2+
# #
3+
# /labscript_devices/Windfreak/blacs_tabs.py #
4+
# #
5+
# Copyright 2022, Monash University and contributors #
6+
# #
7+
# This file is part of labscript_devices, in the labscript suite #
8+
# (see http://labscriptsuite.org), and is licensed under the #
9+
# Simplified BSD License. See the license.txt file in the root of #
10+
# the project for the full license. #
11+
# #
12+
#####################################################################
13+
14+
from blacs.device_base_class import DeviceTab
15+
16+
import labscript_utils.properties
17+
18+
class WindfreakSynthTab(DeviceTab):
19+
20+
def __init__(self, *args, **kwargs):
21+
if not hasattr(self,'device_worker_class'):
22+
self.device_worker_class = 'labscript_devices.Windfreak.blacs_workers.WindfreakSynthWorker'
23+
DeviceTab.__init__(self, *args, **kwargs)
24+
25+
def initialise_GUI(self):
26+
27+
print(self.settings)
28+
conn_obj = self.settings['connection_table'].find_by_name(self.device_name).properties
29+
30+
self.allowed_chans = conn_obj.get('allowed_chans',None)
31+
32+
# finish populating these from device properties
33+
chan_prop = {'freq':{},'amp':{},'phase':{}}
34+
freq_limits = conn_obj.get('freq_limits',None)
35+
chan_prop['freq']['min'] = freq_limits[0]
36+
chan_prop['freq']['max'] = freq_limits[1]
37+
chan_prop['freq']['decimals'] = conn_obj.get('freq_res',None)
38+
chan_prop['freq']['base_unit'] = 'Hz'
39+
chan_prop['freq']['step'] = 100
40+
amp_limits = conn_obj.get('amp_limits',None)
41+
chan_prop['amp']['min'] = amp_limits[0]
42+
chan_prop['amp']['max'] = amp_limits[1]
43+
chan_prop['amp']['decimals'] = conn_obj.get('amp_res',None)
44+
chan_prop['amp']['base_unit'] = 'dBm'
45+
chan_prop['amp']['step'] = 1
46+
phase_limits = conn_obj.get('phase_limits',None)
47+
chan_prop['phase']['min'] = phase_limits[0]
48+
chan_prop['phase']['max'] = phase_limits[1]
49+
chan_prop['phase']['decimals'] = conn_obj.get('phase_res',None)
50+
chan_prop['phase']['base_unit'] = 'deg'
51+
chan_prop['phase']['step'] = 1
52+
53+
dds_prop = {}
54+
for chan in self.allowed_chans:
55+
dds_prop[f'channel {chan:d}'] = chan_prop
56+
57+
self.create_dds_outputs(dds_prop)
58+
dds_widgets,ao_widgets,do_widgets = self.auto_create_widgets()
59+
self.auto_place_widgets(('Synth Outputs',dds_widgets))
60+
61+
DeviceTab.initialise_GUI(self)
62+
63+
# set capabilities
64+
self.supports_remote_value_check(True)
65+
self.supports_smart_programming(True)
66+
#self.statemachine_timeout_add(5000,self.status_monitor)
67+
68+
def initialise_workers(self):
69+
70+
conn_obj = self.settings['connection_table'].find_by_name(self.device_name).properties
71+
self.com_port = conn_obj.get('com_port',None)
72+
73+
self.create_worker('main_worker',self.device_worker_class,{'com_port':self.com_port,
74+
'allowed_chans':self.allowed_chans})
75+
76+
self.primary_worker = 'main_worker'
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
#####################################################################
2+
# #
3+
# /labscript_devices/Windfreak/blacs_workers.py #
4+
# #
5+
# Copyright 2022, Monash University and contributors #
6+
# #
7+
# This file is part of labscript_devices, in the labscript suite #
8+
# (see http://labscriptsuite.org), and is licensed under the #
9+
# Simplified BSD License. See the license.txt file in the root of #
10+
# the project for the full license. #
11+
# #
12+
#####################################################################
13+
14+
from blacs.tab_base_classes import Worker
15+
import labscript_utils.h5_lock, h5py
16+
17+
class WindfreakSynthWorker(Worker):
18+
19+
def init(self):
20+
# hide import of 3rd-party library in here so docs don't need it
21+
global windfreak; import windfreak
22+
23+
# init smart cache to a known point
24+
self.smart_cache = {'STATIC_DATA':None}
25+
self.subchnls = ['freq','amp','phase']
26+
27+
# connect to synth
28+
self.synth = windfreak.SynthHD(self.com_port)
29+
30+
Worker.init(self)
31+
32+
# populate smart chache
33+
self.smart_cache['STATIC_DATA'] = self.check_remote_values()
34+
35+
def check_remote_values(self):
36+
37+
results = {}
38+
for i in self.allowed_chans:
39+
chan = f'channel {i:d}'
40+
results[chan] = {}
41+
for sub in self.subchnls:
42+
results[chan][sub] = self.check_remote_value(i,sub)
43+
44+
return results
45+
46+
def program_manual(self, front_panel_values):
47+
48+
for i in self.allowed_chans:
49+
chan = f'channel {i:d}'
50+
for sub in self.subchnls:
51+
if self.smart_cache['STATIC_DATA'][chan][sub] == front_panel_values[chan][sub]:
52+
# don't program if desired setting already present
53+
continue
54+
self.program_static_value(i,sub,front_panel_values[chan][sub])
55+
# invalidate smart cache upon manual programming
56+
self.smart_cache['STATIC_DATA'][chan][sub] = None
57+
58+
return self.check_remote_values()
59+
60+
def check_remote_value(self,channel,type):
61+
62+
if type == 'freq':
63+
return self.synth[channel].frequency
64+
elif type == 'amp':
65+
return self.synth[channel].power
66+
elif type == 'phase':
67+
return self.synth[channel].phase
68+
else:
69+
raise ValueError(type)
70+
71+
def program_static_value(self,channel,type,value):
72+
73+
if type == 'freq':
74+
self.synth[channel].frequency = value
75+
elif type == 'amp':
76+
self.synth[channel].power = value
77+
elif type == 'phase':
78+
self.synth[channel].phase = value
79+
else:
80+
raise ValueError(type)
81+
82+
def transition_to_buffered(self, device_name, h5file, initial_values, fresh):
83+
84+
self.initial_values = initial_values
85+
self.final_values = initial_values
86+
87+
static_data = None
88+
with h5py.File(h5file,'r') as hdf5_file:
89+
group = hdf5_file['/devices/'+device_name]
90+
if 'STATIC_DATA' in group:
91+
static_data = group['STATIC_DATA'][:][0]
92+
93+
if static_data is not None:
94+
data = static_data
95+
if fresh or data != self.smart_cache['STATIC_DATA']:
96+
97+
# need to infer which channels are programming
98+
num_chan = len(data)//len(self.subchnls)
99+
channels = [int(name[-1]) for name in data.dtype.names[0:num_chan]]
100+
101+
for i in channels:
102+
for sub in self.subchnls:
103+
self.program_static_value(i,sub,data[sub+str(i)])
104+
105+
# update smart cache to reflect programmed values
106+
self.smart_cache['STATIC_DATA'] = data
107+
108+
109+
return self.final_values
110+
111+
def shutdown(self):
112+
# save current state the memory
113+
self.synth.save()
114+
self.synth.close()
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
#####################################################################
2+
# #
3+
# /labscript_devices/Windfreak/labscript_devices.py #
4+
# #
5+
# Copyright 2022, Monash University and contributors #
6+
# #
7+
# This file is part of labscript_devices, in the labscript suite #
8+
# (see http://labscriptsuite.org), and is licensed under the #
9+
# Simplified BSD License. See the license.txt file in the root of #
10+
# the project for the full license. #
11+
# #
12+
#####################################################################
13+
14+
from labscript import LabscriptError, set_passed_properties, config, StaticDDS, IntermediateDevice, Device
15+
from labscript_utils import dedent
16+
17+
import numpy as np
18+
19+
class WindfreakSynth(Device):
20+
description = 'Windfreak HDPro Synthesizer'
21+
allowed_children = [StaticDDS]
22+
# note, box labels 'A', 'B' map to programming channels 0, 1
23+
allowed_chans = [0,1]
24+
25+
# define output limitations for the SynthHDPro
26+
freq_limits = (10e6,24e9) # set in Hz
27+
freq_res = 1 # number of sig digits after decimal
28+
amp_limits = (-40.0,20.0) # set in dBm
29+
amp_res = 2
30+
phase_limits = (0.0,360.0) # in deg
31+
phase_res = 2
32+
33+
@set_passed_properties(property_names={
34+
'connection_table_properties': [
35+
'com_port',
36+
'allowed_chans',
37+
'freq_limits',
38+
'freq_res',
39+
'amp_limits',
40+
'amp_res',
41+
'phase_limits',
42+
'phase_res',
43+
]
44+
})
45+
def __init__(self, name, com_port="", **kwargs):
46+
47+
Device.__init__(self, name, None, com_port, **kwargs)
48+
self.BLACS_connection = com_port
49+
50+
def add_device(self, device):
51+
Device.add_device(self, device)
52+
# ensure a valid default value
53+
device.frequency.default_value = 10e6
54+
55+
def validate_data(self, data, limits, device):
56+
if not isinstance(data, np.ndarray):
57+
data = np.array(data,dtype=np.float64)
58+
if np.any(data < limits[0]) or np.any(data > limits[1]):
59+
msg = f'''{device.description} {device.name} can only have frequencies between
60+
{limits[0]:E}Hz and {limits[1]:E}Hz, {data} given
61+
'''
62+
raise LabscriptError(dedent(msg))
63+
return data
64+
65+
def generate_code(self, hdf5_file):
66+
DDSs = {}
67+
68+
69+
for output in self.child_devices:
70+
71+
try:
72+
prefix, channel = output.connection.split()
73+
if channel not in self.allowed_chans:
74+
LabscriptError(f"Channel {channel} must be 0 or 0")
75+
except:
76+
msg = f"""{output.description}:{output.name} has invalid connection string.
77+
Only 'channel 0' or 'channel 1' is allowed.
78+
"""
79+
raise LabscriptError(dedent(msg))
80+
81+
DDSs[channel] = output
82+
83+
for connection in DDSs:
84+
dds = DDSs[connection]
85+
dds.frequency.raw_output = self.validate_data(dds.frequency.static_value,self.freq_limits,dds)
86+
dds.amplitude.raw_output = self.validate_data(dds.amplitude.static_value,self.amp_limits,dds)
87+
dds.phase.raw_output = self.validate_data(dds.phase.static_value,self.phase_limits,dds)
88+
89+
static_dtypes = [(f'freq{i:d}',np.float64) for i in self.allowed_chans] +\
90+
[(f'amp{i:d}',np.float64) for i in self.allowed_chans] +\
91+
[(f'phase{i:d}',np.float64) for i in self.allowed_chans]
92+
static_table = np.zeros(1,dtype=static_dtypes)
93+
94+
for connection in DDSs:
95+
static_table[f'freq{connection}'] = dds.frequency.raw_output
96+
static_table[f'amp{connection}'] = dds.amplitude.raw_output
97+
static_table[f'phase{connection}'] = dds.phase.raw_output
98+
99+
grp = self.init_device_group(hdf5_file)
100+
grp.create_dataset('STATIC_DATA',compression=config.compression,data=static_table)
101+
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
#####################################################################
2+
# #
3+
# /labscript_devices/Windfreak/register_classes.py #
4+
# #
5+
# Copyright 2022, Monash University and contributors #
6+
# #
7+
# This file is part of labscript_devices, in the labscript suite #
8+
# (see http://labscriptsuite.org), and is licensed under the #
9+
# Simplified BSD License. See the license.txt file in the root of #
10+
# the project for the full license. #
11+
# #
12+
#####################################################################
13+
14+
import labscript_devices
15+
16+
labscript_devices.register_classes(
17+
'WindfreakSynth',
18+
BLACS_tab='labscript_devices.Windfreak.blacs_tabs.WindfreakSynthTab',
19+
runviewer_parser=None
20+
)

0 commit comments

Comments
 (0)