Skip to content

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
wants to merge 21 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 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
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
1 change: 1 addition & 0 deletions docs/source/devices.rst
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ These devices cover various frequency sources that provide either hardware-timed
.. toctree::
:maxdepth: 2

devices/AD9959DDSSweeper
devices/novatechDDS9m
devices/phasematrixquicksyn

Expand Down
123 changes: 123 additions & 0 deletions docs/source/devices/AD9959DDSSweeper.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
AD9959DDSSweeper
================

This labscript device controls the `DDSSweeper <https://github.com/qtc-umd/dds-sweeper>`_, an interface to the `AD9959 eval board <https://www.analog.com/en/resources/evaluation-hardware-and-software/evaluation-boards-kits/eval-ad9959.html>`_ four channel direct digital synthesizer (DDS) using the `Raspberry Pi Pico <https://www.raspberrypi.org/documentation/rp2040/getting-started/>`_ platform.

The DDS Sweeper is described in more detail in E. Huegler, J. C. Hill, and D. H. Meyer, An agile radio-frequency source using internal sweeps of a direct digital synthesizer, *Review of Scientific Instruments*, **94**, 094705 (2023) https://doi.org/10.1063/5.0163342 .

Specifications
~~~~~~~~~~~~~~

The AD9959 evaluation board provides the following:

* 4 DDS channels

- 100 kHz - 250 MHz output frequency with 32 bit frequency resolution (~0.1 Hz)
- Up to 0 dBm output power with 10 bit amplitude resolution
- Phase control with 16 bit resolution (~48 uRad)

The Pico interface allows the evaluation board parameters to be reprogrammed during a sequence.
At this time, stepping of frequency, amplitude, and phase parameters is supported.
Parameter ramping is possible, but not currently supported by the labscript device (if support for this is of interest, please `open an issue <https://github.com/labscript-suite/labscript-devices/issues>`).
The Pico interface provides the following:

* For the Pico 1: 16,656 instructions distributed evenly among the configured channels; 16,656, 8,615, 5,810, and 4,383 for 1, 2, 3, 4 channels respectively.
* For the Pico 2: 34,132 instructions distributed evenly among the configured channels; 34,132, 17,654, 11,905, and 8,981 for 1, 2, 3, 4 channels respectively.
* External timing via a pseudoclock clockline.
* By default, the AD9959 system reference clock is taken from the Pi Pico. If a higher quality clock is needed, the user can provide an external system reference clock to the AD9959. For more details on clocking, see the Usage section.

Installation
~~~~~~~~~~~~

- **For Pi Pico (RP2040)**:
`dds-sweeper_rp2040.uf2 <https://github.com/QTC-UMD/dds-sweeper/releases/latest/download/dds-sweeper_rp2040.uf2>`_

- **For Pi Pico 2 (RP2350)**:
`dds-sweeper_rp2350.uf2 <https://github.com/QTC-UMD/dds-sweeper/releases/latest/download/dds-sweeper_rp2350.uf2>`_

On your Raspberry Pi Pico, hold down the "bootsel" button while plugging the Pico into USB port on a PC (that must already be turned on).
The Pico should mount as a mass storage device (if it doesn't, try again or consult the Pico documentation).
Drag and drop the `.uf2` file into the mounted mass storage device.
The mass storage device should unmount after the copy completes. Your Pico is now running the DDS Sweeper firmware!

Note that this device communicates using a virtual COM port.
The number is assigned by the controlling computer and will need to be determined in order for BLACS to connect to the PrawnDO.


Usage
~~~~~


An example connection table that uses the PrawnBlaster and sweeper with an external, 100 MHz clock:

.. code-block:: python

from labscript import start, stop, add_time_marker, AnalogOut, DigitalOut, DDS, StaticDDS
from labscript_devices.PrawnBlaster.labscript_devices import PrawnBlaster
from labscript_devices.AD9959DDSSweeper.labscript_devices import AD9959DDSSweeper

# prawnblaster for external timing
prawn = PrawnBlaster(
name='prawn',
com_port='COM7',
num_pseudoclocks=1
)

AD9959 = AD9959DDSSweeper(
name='AD9959',
parent_device=prawn.clocklines[0],
com_port='COM11',
ref_clock_external=1,
ref_clock_frequency=100e6,
pll_mult=5
)


chann0 = DDS( 'chann0', AD9959, 'channel 0')
chann1 = DDS( 'chann1', AD9959, 'channel 1')
chann2 = DDS( 'chann2', AD9959, 'channel 2')
chann3 = StaticDDS( 'chann3', AD9959, 'channel 3')


start()

stop(1)

.. note::

**Clocking**

If the Pi Pico is used as the AD9959 system reference clock, pin 21 of the Pi Pico should be connected to the REF CLK input (J9) of the AD9959 eval board. Otherwise, another clock source should be connected to REF CLK input and its frequency provided as the ref_clock_frequency.

Detailed Documentation
~~~~~~~~~~~~~~~~~~~~~~

.. automodule:: labscript_devices.AD9959DDSSweeper
:members:
:undoc-members:
:show-inheritance:
:private-members:

.. automodule:: labscript_devices.AD9959DDSSweeper.labscript_devices
:members:
:undoc-members:
:show-inheritance:
:private-members:

.. automodule:: labscript_devices.AD9959DDSSweeper.blacs_tabs
:members:
:undoc-members:
:show-inheritance:
:private-members:

.. automodule:: labscript_devices.AD9959DDSSweeper.blacs_workers
:members:
:undoc-members:
:show-inheritance:
:private-members:

.. automodule:: labscript_devices.AD9959DDSSweeper.runviewer_parsers
:members:
:undoc-members:
:show-inheritance:
:private-members:
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}
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"
Loading