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

Conversation

carterturn
Copy link
Contributor

No description provided.

@carterturn
Copy link
Contributor Author

A result from testing in the lab: we should either remove timing mode or implement it fully. At the moment, the labscript device just sends an instruction table without timings (regardless of whether timing mode is 1 or 0), which fails. I think I am leaning towards removing it, but I could go either way.

On a related note, we should check that the number of bytes we want to send matches the bytes the Pico is ready for.

…n binary mode.

At the moment, if it fails it will simply send all zeros and throw an error.
I think this is the best option, as it prevents the device from getting locked up waiting for bytes (if we restart the blacs tab).
@carterturn
Copy link
Contributor Author

I added the "ready for ... bytes" check and an option to use an external reference clock for the labscript device. These should be tested before merging.

@Json-To-String
Copy link

Talked a tad with David and we settled on not supporting internal timing at all since the prawnblaster clockline is much better for less work. I'll remove those and try to add any remaining docs

Copy link
Contributor

@dihm dihm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mostly minor tweaks, with two more significant things

  1. I am now noticing that we basically have a firmware limitation that there must be at least one dynamic output to use this device. I get why and it isn't totally unreasonable, I do think it could be worth allowing for all the outputs to be StaticDDS.
  2. The BLACS worker transition functions feel a bit rough. They don't quite respect the conventional boundaries (which are admittedly poorly documented) and I think transition_to_buffered has a number of edge case issues that need to be tested and fixed. I would appreciate testing confirmation that everything works as expected if you do the following things:
  • Program no outputs
  • Only use StaticDDS
  • Only Use DDS
  • That the BLACS tab updates to the final values of all outputs correctly
  • That aborts leave the device in a functional state

)

def program_manual(self, values):
self.intf.abort()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure how I feel about calling abort all the time. I'd much rather it only be called when we actually need to abort a shot. If we are having troubles with synchronization of device state with the worker, I'd just as soon solve them.


with h5py.File(h5file, 'r') as hdf5_file:
group = hdf5_file['devices'][device_name]
dds_data = group['dds_data']
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is dds_data guaranteed to always be present? I suppose it is since you can't actually set the number of dynamic channels to 0...

Comment on lines 74 to 75
#chann2 = DDS( 'chann2', AD9959, 'channel 2')
chann3 = DDS( 'chann3', AD9959, 'channel 3')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should remove commented lines from the example.

Also, this configuration is wrong, since you can't specify a dynamic channel after a Static one (at the firmware level at least).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is resolved in latest commit.

Comment on lines 37 to 39
def __init__(self, name, parent_device, com_port,
sweep_mode=0,
ref_clock_external=0, ref_clock_frequency=125e6, pll_mult=4, **kwargs):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A somewhat unclear limitation that is only implicitly enforced is that there must be at least one dynamic channel for the device to work correctly.

While we could just document that and move on, I think I'd just as soon make a minor tweak to the firmware to allow for the Sweeper outputs to all be static (if desired). Maybe not a common request, but I think it will actually make handling static and dynamic channels a little easier as there won't be any strange edge cases to handle.

Comment on lines +114 to +115
def set_output(self, channel, frequency, amplitude, phase):
'''Set frequency, amplitude, and phase of a channel.'''
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While I can maybe be persuaded that not all functions in parameter and return docstrings, functions like this one would benefit from being more explicit about types and units of the inputs.

Other mandatory ones for me include set, set_channels, set_batch, stop, and __init__, though I personally think all functions of this interface class should be fully documented, if at all.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is resolved in latest commit.

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):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should add smart programming support (basically copied from PrawnDO)

@carterturn
Copy link
Contributor Author

I found a rather insidious bug: if two instructions are spaced by less than the reprogramming time (10us for 4 channels), the trigger for the second instruction arrives before the DDS Sweeper has finished reprogramming the AD9959. I think the result of this is a partial update of the AD9959 parameters.

For an example of this, we were trying to use the DDS Sweeper to pulse on an AOM for 4us, 8us, and 12us. In the first two cases, the second instruction doesn't run, and the AOM never turns off.

@dihm
Copy link
Contributor

dihm commented Apr 30, 2025

I found a rather insidious bug: if two instructions are spaced by less than the reprogramming time (10us for 4 channels), the trigger for the second instruction arrives before the DDS Sweeper has finished reprogramming the AD9959. I think the result of this is a partial update of the AD9959 parameters.

For an example of this, we were trying to use the DDS Sweeper to pulse on an AOM for 4us, 8us, and 12us. In the first two cases, the second instruction doesn't run, and the AOM never turns off.

Ah, good catch. The firmware definitely doesn't do sensible things when a trigger is missed (since it doesn't even know it missed it). It's on the labscript_device to enforce maximum update rates, which I am now noticing we have not specified.

@Json-To-String I believe the NovaTechDDS has the core infrastructure necessary to handle this (though we'll need to update the rate based on mode and number of dynamic channels). If I remember correctly, you only need to define the right class variable and the labscript machinery will catch the error automatically at compile time.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants