-
Notifications
You must be signed in to change notification settings - Fork 79
AD9959 DDS Sweeper #126
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
carterturn
wants to merge
48
commits into
labscript-suite:master
Choose a base branch
from
carterturn:carterturn-dds-sweeper
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
AD9959 DDS Sweeper #126
Changes from 4 commits
Commits
Show all changes
48 commits
Select commit
Hold shift + click to select a range
a658582
Initial draft of DDS sweeper
carterturn 9e946f7
updating worker functions to match current firmware
Json-To-String 1a42e34
Merge branch 'labscript-suite:master' into carterturn-dds-sweeper
carterturn 2ffb2ba
added logic for max instructions per channel, getting pico board, and…
Json-To-String 24e00ae
change assert_OK() calls in __init__ to match current firmware
Json-To-String 4048745
Some initial docs additions
Json-To-String b02ec9b
Indenting python block properly
Json-To-String 71e107b
fix uf2 link rendering and renamed runviewer parser to plural
Json-To-String 03352fd
adding support for simultaneous dynamic and static instructions
Json-To-String 5630a47
Add check that device is ready for the number of bytes we will send i…
carterturn 4387a42
Add overview and specifications for docs.
carterturn 0595dd4
Add option for external reference clock
carterturn 68c25c5
Expand docstring for labscript device
carterturn 9469d0b
removing internal timing, added a parentheses to table size check
Json-To-String 7b20811
pluralism
Json-To-String 1f6bc6b
Some documentation and docstring updates
carterturn fb5acda
min version and status map update
Json-To-String a8adf76
no s in readline
Json-To-String 43b4821
Add Pico2 instruction counts to docs.
carterturn aed9873
saving progress, all static ins works, final value update works, stil…
Json-To-String eebf6f6
Graceful aborts and docstring updates
Json-To-String 995a0f4
status string format specifier fix
Json-To-String b821fea
transition_to_buffered supports smart cache, borrowing logic from Pra…
Json-To-String 0c02e78
adding clock_limit class attribute
Json-To-String c2ec2b9
limit calculation uses dynamic channels, not all, and ref clk freq be…
Json-To-String 146157a
removed unnecessary if statement checks for both dyn and stat DDSs
Json-To-String d3671af
More docstrings
carterturn 46bcc9f
Default connection table example
Json-To-String 4580b06
Cleaned up extra imports in the examples
Json-To-String f1985c5
smart cache logic cleanup, removed inaccurate debug msg, moved explic…
Json-To-String d7b0dfc
Conversion to DDS units is done on Pi Pico for set_output.
carterturn 5377b59
Placeholder values for scale factor when only static channels are used.
carterturn b0797fe
Don't need to do unit conversions for set output
carterturn adfa697
Remove stray? factor of 10 from tuning_words_to_SI dictionary.
carterturn 3f55bde
removing explicit break, is the same as returning self.final_values
Json-To-String fe811ef
program manual will invalidate static cache
Json-To-String e0fe6ee
Add dynamic channels to constructor, and add checks in add_device to …
carterturn 273c42d
calculate max freq
Json-To-String 10911d6
program manual should only invalidate static cache
Json-To-String 0fe0504
update examples with dynamic_channels parameter
Json-To-String 9f37751
Compute scale factors separately from quantising data.
carterturn 9854b3c
Cache scale factor calculations and fully separate for quantization f…
dihm 34436d5
Move `generate_code` break if no channels are connected earlier in th…
dihm b3aee0a
Tidy up some lints
dihm 4527466
program_manual should not erase dds_data from smart cache.
carterturn 33e1b61
Ensure data table is constructed in correct order.
carterturn 2c0e678
Compare original cache length to DDS data length.
carterturn 2efc496
Prevent cache from being extended with stale data from np.empty
Json-To-String File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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. # | ||
| # # | ||
| ##################################################################### |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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} | ||
| 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" | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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__( | ||
dihm marked this conversation as resolved.
Show resolved
Hide 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() | ||
carterturn marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| 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): | ||
dihm marked this conversation as resolved.
Show resolved
Hide 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' | ||
dihm marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| 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] | ||
dihm marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| 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) | ||
dihm marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| 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.''' | ||
dihm marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| 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): | ||
dihm marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| '''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() | ||
dihm marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| 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): | ||
dihm marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| self.final_values = initial_values | ||
dihm marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| with h5py.File(h5file, 'r') as hdf5_file: | ||
| group = hdf5_file['devices'][device_name] | ||
| dds_data = group['dds_data'] | ||
dihm marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| 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) | ||
dihm marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| return True | ||
|
|
||
| def abort_buffered(self): | ||
| return self.transition_to_manual() | ||
dihm marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| def abort_transition_to_buffered(self): | ||
| return self.transition_to_manual() | ||
dihm marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| def shutdown(self): | ||
| self.intf.close() | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.