Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
65 commits
Select commit Hold shift + click to select a range
a658582
Initial draft of DDS sweeper
carterturn Mar 31, 2025
9e946f7
updating worker functions to match current firmware
Json-To-String Apr 15, 2025
1a42e34
Merge branch 'labscript-suite:master' into carterturn-dds-sweeper
carterturn Apr 17, 2025
2ffb2ba
added logic for max instructions per channel, getting pico board, and…
Json-To-String Apr 18, 2025
24e00ae
change assert_OK() calls in __init__ to match current firmware
Json-To-String Apr 21, 2025
4048745
Some initial docs additions
Json-To-String Apr 23, 2025
b02ec9b
Indenting python block properly
Json-To-String Apr 23, 2025
71e107b
fix uf2 link rendering and renamed runviewer parser to plural
Json-To-String Apr 23, 2025
03352fd
adding support for simultaneous dynamic and static instructions
Json-To-String Apr 24, 2025
5630a47
Add check that device is ready for the number of bytes we will send i…
carterturn Apr 27, 2025
4387a42
Add overview and specifications for docs.
carterturn Apr 27, 2025
0595dd4
Add option for external reference clock
carterturn Apr 27, 2025
68c25c5
Expand docstring for labscript device
carterturn Apr 27, 2025
9469d0b
removing internal timing, added a parentheses to table size check
Json-To-String Apr 28, 2025
7b20811
pluralism
Json-To-String Apr 28, 2025
1f6bc6b
Some documentation and docstring updates
carterturn Apr 29, 2025
fb5acda
min version and status map update
Json-To-String Apr 29, 2025
a8adf76
no s in readline
Json-To-String Apr 29, 2025
43b4821
Add Pico2 instruction counts to docs.
carterturn Apr 29, 2025
aed9873
saving progress, all static ins works, final value update works, stil…
Json-To-String Apr 29, 2025
eebf6f6
Graceful aborts and docstring updates
Json-To-String Apr 30, 2025
995a0f4
status string format specifier fix
Json-To-String Apr 30, 2025
b821fea
transition_to_buffered supports smart cache, borrowing logic from Pra…
Json-To-String May 1, 2025
0c02e78
adding clock_limit class attribute
Json-To-String May 2, 2025
c2ec2b9
limit calculation uses dynamic channels, not all, and ref clk freq be…
Json-To-String May 2, 2025
146157a
removed unnecessary if statement checks for both dyn and stat DDSs
Json-To-String May 6, 2025
d3671af
More docstrings
carterturn May 6, 2025
46bcc9f
Default connection table example
Json-To-String May 8, 2025
4580b06
Cleaned up extra imports in the examples
Json-To-String May 8, 2025
f1985c5
smart cache logic cleanup, removed inaccurate debug msg, moved explic…
Json-To-String May 8, 2025
d7b0dfc
Conversion to DDS units is done on Pi Pico for set_output.
carterturn May 14, 2025
5377b59
Placeholder values for scale factor when only static channels are used.
carterturn May 14, 2025
b0797fe
Don't need to do unit conversions for set output
carterturn May 14, 2025
adfa697
Remove stray? factor of 10 from tuning_words_to_SI dictionary.
carterturn May 14, 2025
3f55bde
removing explicit break, is the same as returning self.final_values
Json-To-String May 19, 2025
fe811ef
program manual will invalidate static cache
Json-To-String May 19, 2025
e0fe6ee
Add dynamic channels to constructor, and add checks in add_device to …
carterturn May 20, 2025
273c42d
calculate max freq
Json-To-String May 20, 2025
10911d6
program manual should only invalidate static cache
Json-To-String May 22, 2025
0fe0504
update examples with dynamic_channels parameter
Json-To-String May 22, 2025
9f37751
Compute scale factors separately from quantising data.
carterturn May 23, 2025
9854b3c
Cache scale factor calculations and fully separate for quantization f…
dihm May 29, 2025
34436d5
Move `generate_code` break if no channels are connected earlier in th…
dihm May 29, 2025
b3aee0a
Tidy up some lints
dihm May 29, 2025
4527466
program_manual should not erase dds_data from smart cache.
carterturn Jun 10, 2025
33e1b61
Ensure data table is constructed in correct order.
carterturn Jun 24, 2025
2c0e678
Compare original cache length to DDS data length.
carterturn Sep 21, 2025
2efc496
Prevent cache from being extended with stale data from np.empty
Json-To-String Nov 17, 2025
8b37013
Add old blob documentation for the novatech dds9m, now that the blog …
dihm Apr 21, 2025
fee14bb
Bump sphinx pins and fix RTD-stable versioning
dihm Aug 3, 2025
5df9c73
Use correct mock for h5_lock, tweak makefiles, and fix cross-ref
dihm Aug 3, 2025
a8b3edc
Ensure intersphinx mapping does not include current project
dihm Aug 19, 2025
7546556
Changed np.string_ to np_bytes to work with numpy>=2
rogding Nov 16, 2025
cd510db
Replace other `np.string_` references in the package.
dihm Nov 18, 2025
b52d051
Remove deprecated `np.asfarray` function.
dihm Nov 18, 2025
ec0df34
Add board awareness to devices for pico2 support
Json-To-String Jul 7, 2025
ee4fdd9
Use dedicated send commands where possible
Json-To-String Jul 11, 2025
c76d88e
Handle differences between pico1 and pico2 board capabilities
Json-To-String Jul 11, 2025
d847002
Add version check for pico board num
Json-To-String Dec 9, 2025
0bdead9
Add min version to prawnblaster to match prawndo check
Json-To-String Dec 9, 2025
497b559
Remove blaster fast serial check in favor of min version to insist on…
Json-To-String Dec 9, 2025
a421f9a
Check to see if board specified in connection table matches board res…
Json-To-String Dec 9, 2025
b21235a
Swap assert for LabscriptError, separate board checking and version l…
Json-To-String Dec 10, 2025
7f1f8fb
Add a status readout at initialization to the PB to match DO
Json-To-String Dec 10, 2025
20898b6
Merge branch 'master' into carterturn-dds-sweeper
Json-To-String Dec 18, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions labscript_devices/AD9959DDSSweeper/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#####################################################################
# #
# /labscript_devices/AD9959DDSSweeper/blacs_tabs.py #
# #
# Copyright 2025, Carter Turnbaugh #
# #
# This file is part of the module labscript_devices, in the #
# labscript suite (see http://labscriptsuite.org), and is #
# licensed under the Simplified BSD License. See the license.txt #
# file in the root of the project for the full license. #
# #
#####################################################################
56 changes: 56 additions & 0 deletions labscript_devices/AD9959DDSSweeper/blacs_tabs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
#####################################################################
# #
# /labscript_devices/AD9959DDSSweeper/blacs_tabs.py #
# #
# Copyright 2025, Carter Turnbaugh #
# #
# This file is part of the module labscript_devices, in the #
# labscript suite (see http://labscriptsuite.org), and is #
# licensed under the Simplified BSD License. See the license.txt #
# file in the root of the project for the full license. #
# #
#####################################################################

from blacs.device_base_class import DeviceTab

class AD9959DDSSweeperTab(DeviceTab):
def initialise_GUI(self):
# Capabilities
self.base_units = {'freq':'Hz', 'amp':'Arb', 'phase':'Degrees'}
self.base_min = {'freq':0.0, 'amp':0, 'phase':0}
self.base_max = {'freq':250.0*10.0**6, 'amp':1, 'phase':360}
Comment thread
carterturn marked this conversation as resolved.
Outdated
self.base_step = {'freq':10**6, 'amp':1/1023., 'phase':1}
self.base_decimals = {'freq':1, 'amp':4, 'phase':3}
self.num_DDS = 4

dds_prop = {}
for i in range(self.num_DDS):
dds_prop['channel %d' % i] = {}
for subchnl in ['freq', 'amp', 'phase']:
dds_prop['channel %d' % i][subchnl] = {'base_unit':self.base_units[subchnl],
'min':self.base_min[subchnl],
'max':self.base_max[subchnl],
'step':self.base_step[subchnl],
'decimals':self.base_decimals[subchnl]
}

self.create_dds_outputs(dds_prop)
dds_widgets, _, _ = self.auto_create_widgets()
self.auto_place_widgets(('DDS Outputs', dds_widgets))

device = self.settings['connection_table'].find_by_name(self.device_name)

self.com_port = device.properties['com_port']

self.supports_remote_value_check(False)
self.supports_smart_programming(True)

def initialise_workers(self):
self.create_worker(
"main_worker",
"labscript_devices.AD9959DDSSweeper.blacs_workers.AD9959DDSSweeperWorker",
{
'com_port': self.com_port,
},
)
self.primary_worker = "main_worker"
201 changes: 201 additions & 0 deletions labscript_devices/AD9959DDSSweeper/blacs_workers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
#####################################################################
# #
# /labscript_devices/AD9959DDSSweeper/blacs_workers.py #
# #
# Copyright 2025, Carter Turnbaugh #
# #
# This file is part of the module labscript_devices, in the #
# labscript suite (see http://labscriptsuite.org), and is #
# licensed under the Simplified BSD License. See the license.txt #
# file in the root of the project for the full license. #
# #
#####################################################################

from blacs.tab_base_classes import Worker
import labscript_utils.h5_lock, h5py

class AD9959DDSSweeperInterface(object):
def __init__(
Comment thread
dihm marked this conversation as resolved.
self,
com_port,
sweep_mode,
timing_mode,
ref_clock_frequency,
pll_mult
):
global serial; import serial

self.timeout = 0.1
self.conn = serial.Serial(com_port, 10000000, timeout=self.timeout)

version = self.get_version()
print(f'Connected to version: {version}')

board = self.get_board()
print(f'Connected to board: {board}')

current_status = self.get_status()
print(f'Current status is {current_status}')

self.conn.write(b'reset\n')
self.assert_OK()
self.conn.write(b'setclock 0 %d %d\n' % (ref_clock_frequency, pll_mult))
# self.assert_OK()
Comment thread
carterturn marked this conversation as resolved.
Outdated
self.conn.write(b'mode %d %d\n' % (sweep_mode, timing_mode))
self.assert_OK()
self.assert_OK()
self.conn.write(b'debug off\n')
self.assert_OK()

def assert_OK(self):
Comment thread
dihm marked this conversation as resolved.
resp = self.conn.readline().decode().strip()
assert resp == "ok", 'Expected "ok", received "%s"' % resp

def get_version(self):
'''Sends 'version' command, which retrieves the Pico firmware version.
Returns response, throws serial exception on disconnect.'''
self.conn.write(b'version\n')
version_str = self.conn.readline().decode()
version = tuple(int(i) for i in version_str.split('.'))
assert len(version) == 3

# may be better logic for semantic versioning w/o version pkg
assert version[1] >= 4, f'Version {version} too low'
Comment thread
dihm marked this conversation as resolved.
Outdated
return version

def abort(self):
'''Stops buffered execution immediately.'''
self.conn.write(b'abort\n')
self.assert_OK()

def start(self):
'''Starts buffered execution.'''
self.conn.write(b'start\n')
self.assert_OK()

def get_status(self):
'''Reads the status of the AD9959 DDS Sweeper
Returns int status code.`'''
self.conn.write(b'status\n')
status_str = int(self.conn.readline().decode())
status_map = {
0: 'STOPPED',
1: 'TRANSITION_TO_RUNNING',
2: 'RUNNING',
3: 'ABORTING',
4: 'ABORTED',
5: 'TRANSITION_TO_STOPPED'
}
self.conn.write(b'status\n')
status_str = int(self.conn.readline().decode())
if status_str in status_map:
return status_map[status_str]
Comment thread
dihm marked this conversation as resolved.
Outdated
else:
raise LabscriptError(f'Invalid status, returned {status_str}')

def get_board(self):
'''Responds with pico board version.'''
self.conn.write(b'board\n')
resp = self.conn.readline().decode()
return(resp)
Comment thread
dihm marked this conversation as resolved.
Outdated

def get_freqs(self):
'''Responds with a dictionary containing
the current operating frequencies (in kHz) of various clocks.'''
self.conn.write(b'getfreqs\n')
freqs = {}
while True:
resp = self.conn.readline().decode()
if resp == "ok":
break
resp = resp.split('=')
freqs[resp[0].strip()] = int(resp[1].strip()[:-3])
return freqs

def set_output(self, channel, frequency, amplitude, phase):
'''Set frequency, amplitude, and phase of a channel.'''
Comment thread
dihm marked this conversation as resolved.
Outdated
self.conn.write(b'setfreq %d %f\n' % (channel, frequency))
self.assert_OK()
self.conn.write(b'setamp %d %f\n' % (channel, amplitude))
self.assert_OK()
self.conn.write(b'setphase %d %f\n' % (channel, phase))
self.assert_OK()

def set_channels(self, channels):
'''Set number of channels to use in buffered sequence.'''
self.conn.write(b'setchannels %d\n' % channels)
self.assert_OK()

def set(self, channel, addr, frequency, amplitude, phase):
Comment thread
dihm marked this conversation as resolved.
Outdated
'''Set frequency, phase, and amplitude of a channel
for address addr in buffered sequence from integer values.'''
self.conn.write(b'seti %d %d %f %f %f\n' % (channel, addr, frequency, amplitude, phase))
self.assert_OK()

def set_batch(self, table):
'''Set frequency, phase, and amplitude of a channel
for address addr in buffered sequence.'''
self.conn.write(b'setb 0 %d\n' % len(table))
resp = self.conn.readline().decode()
if not resp.startswith('ready'):
resp += ''.join([r.decode() for r in self.conn.readlines()])
raise LabscriptError(f'setb command failed, got response {repr(resp)}')
self.conn.write(table.tobytes())
self.assert_OK()

def stop(self, count):
self.conn.write(b'set 4 %d\n' % count)
self.assert_OK()

def close(self):
self.conn.close()

class AD9959DDSSweeperWorker(Worker):
def init(self):
self.intf = AD9959DDSSweeperInterface(
self.com_port,
self.sweep_mode,
self.timing_mode,
self.ref_clock_frequency,
self.pll_mult
)
def program_manual(self, values):
self.intf.abort()
Comment thread
dihm marked this conversation as resolved.
Outdated

for chan in values:
chan_int = int(chan[8:])
self.intf.set_output(chan_int, values[chan]['freq'], values[chan]['amp'], values[chan]['phase'])

def transition_to_buffered(self, device_name, h5file, initial_values, fresh):
Comment thread
dihm marked this conversation as resolved.
self.final_values = initial_values
Comment thread
dihm marked this conversation as resolved.

with h5py.File(h5file, 'r') as hdf5_file:
group = hdf5_file['devices'][device_name]
dds_data = group['dds_data']
Comment thread
dihm marked this conversation as resolved.
Outdated

if len(dds_data) == 0:
# Don't bother transitioning to buffered if no data
return {}

channels = set([int(n[4:]) for n in dds_data.dtype.names if n.startswith('freq')])
self.intf.set_channels(max(channels) + 1)
self.intf.set_batch(dds_data[()])
self.intf.stop(len(dds_data[()]))

self.intf.start()

return {}

def transition_to_manual(self):
if self.final_values:
self.program_manual(self.final_values)
Comment thread
dihm marked this conversation as resolved.
Outdated
return True

def abort_buffered(self):
return self.transition_to_manual()
Comment thread
dihm marked this conversation as resolved.
Outdated

def abort_transition_to_buffered(self):
return self.transition_to_manual()
Comment thread
dihm marked this conversation as resolved.
Outdated

def shutdown(self):
self.intf.close()
Loading