Hello,

we are currently trying to implement a simple optical intensity stabilization PID loop based on an AOM, photodiode and ARTIQ SUServo sampler + Urukul DDS. This doesn't work at this point, and we want to figure out if it is a software issue (i.e. a code error, as discussed in this message) or an hardware issue (incorrectly set DIP switches on the relevant boards, which we didn't check yet).

To get started we want to ask the SUServo Urukul DDS to output a fixed signal (at 110MHz in the exemple below), in absence of running PID loop. This doesn't work. We based our code on the working code of [(https://forum.m-labs.hk/d/19-suservo-sample-code/3)], which has a device.db file very similar to ours and runs a SUServo PID loop. The code we are using is:

from artiq.experiment import *                      #imports everything from ARTIQ experiment library

class SUServoMinimal(EnvExperiment):
    def build(self):
        self.setattr_device("core")
        self.setattr_device("urukul0_cpld")         # most likely useless, to be checked
        self.setattr_device("suservo0")
        self.dds0 = self.get_device("suservo0_ch1") # use DDS channel 1 of Urukul board 0
    
    @kernel
    def run(self):
        # Prepare core
        self.core.reset()

        #Initialize and activate SUServo

        self.suservo0.init()
        self.suservo0.set_config(enable=1)
        
        # Set Sampler gain and Urukul attenuation
        g = 0
        A = 0.0
        self.suservo0.set_pgia_mu(0, g)         # set gain on Sampler channel 0 to 10^g
        self.suservo0.cpld0.set_att(0, A)       # set attenuation on Urukul channel 0 to 0
        
        
        # Set physical parameters
        v_t = 0.0             			# target input voltage (V) for Sampler channel
        f = 110000000.0                         # frequency (Hz) of Urukul output
        o = 0.0                 		# offset to assign to servo to reach target voltage

        # Set PI loop parameters 
        kp = 0.005                              # proportional gain in loop
        ki = -10.0                              # integrator gain
        gl = 0.0                                # integrator gain limit
        adc_ch = 0                              # Sampler channel to read from
        
        # Input parameters, activate Urukul output (en_out=1),
        # Deactivates PI loop (en_iir=0)
        self.dds0.set_iir(profile=0, adc=adc_ch, kp=kp, ki=ki, g=gl)
        self.dds0.set_dds(profile=0, frequency=f, offset=o)
        self.dds0.set(en_out=1, en_iir=0, profile=0)              # en_out=1 (=0) closes (opens) the RF switch for dds0        
                                                                  # en_iir=0 deactivates the feedback loop
 
        delay(1*ms)
        self.dds0.set_y(0, 0.8)

and the relevant part of our device.db file is:

device_db["suservo0_ch0"] = {
    "type": "local",
    "module": "artiq.coredevice.suservo",
    "class": "Channel",
    "arguments": {"channel": 0x00001e, "servo_device": "suservo0"}
}

device_db["suservo0_ch1"] = {
    "type": "local",
    "module": "artiq.coredevice.suservo",
    "class": "Channel",
    "arguments": {"channel": 0x00001f, "servo_device": "suservo0"}
}

device_db["suservo0_ch2"] = {
    "type": "local",
    "module": "artiq.coredevice.suservo",
    "class": "Channel",
    "arguments": {"channel": 0x000020, "servo_device": "suservo0"}
}

device_db["suservo0_ch3"] = {
    "type": "local",
    "module": "artiq.coredevice.suservo",
    "class": "Channel",
    "arguments": {"channel": 0x000021, "servo_device": "suservo0"}
}

[...]

device_db["suservo0"] = {
    "type": "local",
    "module": "artiq.coredevice.suservo",
    "class": "SUServo",
    "arguments": {
        "channel": 0x000026,
        "pgia_device": "spi_sampler2_pgia",
        "cpld0_device": "urukul0_cpld",
        "cpld1_device": "urukul1_cpld",
        "dds0_device": "urukul0_dds",
        "dds1_device": "urukul1_dds"
    }
}

device_db["spi_sampler2_pgia"] = {
    "type": "local",
    "module": "artiq.coredevice.spi2",
    "class": "SPIMaster",
    "arguments": {"channel": 0x000027}
}

Sanity checks we performed :

  • using self.dds0.set(en_out=1, en_iir=0, profile=0) we can turn green the RF switch LED of the Urukul DDS channel of interest (channel 1 in code above), which indicates that the RF switch is correctly closed. Conversely we can turn the LED off using self.dds0.set(en_out=0, en_iir=0, profile=0). This indicates additionally that we are looking at the right output channel for the RF signal.
  • we tried the code above for all suservo0_ch0, suservo0_ch1, suservo0_ch2 and suservo0_ch3 channels.
  • we used the command print(self.dds0.get_y(0)) to check that self.dds0.set_y(0, 0.8) indeed writes the value 0.8 for the amplitude of the RF signal output, which it does.

Do you see anything evidently wrong in the code above? Thanks in advance for your help,

Vincent Barbé, LaserLaB Amsterdam

Thanks for your fast answer. Using the Github code given by you (with no sampler input and a spectrum analyzer plugged onto the ch0 output of Urukul 0), I get the following error:

artiq.coredevice.exceptions.RTIOUnderflow: RTIO underflow at 82431204873504 mu, channel 38, slack -7600 mu

and no DDS output. I'll keep trying based on the code posted on the forum and the Github code.

In the three years ARTIQ probably diverged a bit and the code now needs more slack in some parts.

Problem solved. The problem was the hardware, the DIP switches of the SUServo Urukul board had been set to 1000 instead of 1100. With the DIP switches correctly set to 1100 we successfully obtain a 110MHz, 9.2dBm DDS output with the code I posted above.

Thanks for the time you took to help; we'll post the servo loop code once it is working.

    Ok. Thanks for the update. Those DIP switches should have been set since manufacturing. I'll check why they weren't.

    a month later

    VincentB Hi Vincent

    Have you been able to get the PID Servo loop working. I am also looking to make use of it for optical intensity stabilisation.

    Hi han94ros,

    yes we got it working. The code we use is almost identical to the working code given in [https://forum.m-labs.hk/d/19-suservo-sample-code/3]. It is the following:

    from artiq.experiment import *                      #imports everything from ARTIQ experiment library
    
    class SUServoMinimal_ON(EnvExperiment):
    
        def build(self):
            self.setattr_device("core")
            self.setattr_device("urukul0_cpld")         # most likely useless, to be checked
            self.setattr_device("suservo0")
            self.dds0 = self.get_device("suservo0_ch0")
    
        @kernel
        def run(self):
    
            # Prepare core
            self.core.reset()
    
            #Initialize and activate SUServo
            #self.init()
    
            self.suservo0.init()
            #self.suservo0.set_config(enable=1)
            
            # Set Sampler gain and Urukul attenuation
            g = 0
            A = 0.0
            self.suservo0.set_pgia_mu(0, g)         # set gain on Sampler channel 0 to 10^g
            self.suservo0.cpld0.set_att(0, A)       # set attenuation on Urukul channel 0 to 0
            
            # Set physical parameters
            v_t = .075                      # target input voltage (V) for Sampler channel
            f = 110000000.0                 # frequency (Hz) of Urukul output
            o =  -v_t*(10.0**(g-1))                     # offset to assign to servo to reach target voltage
    
            # Set PI loop parameters 
            kp = -30.                            # proportional gain in loop
            ki = 3000./s                              # low integrator gain
            gl = 0.                                # integrator gain limit
            adc_ch = 0                             # Sampler channel to read from
    
            # Servo is done and disabled
            #assert self.suservo0.get_status() & 0xff == 2
            # set up profile 0 on channel 0:
            # delay(120*us)
            # self.dds0.set_y(
            #     profile=0,
            #     y=1.  # clear integrator
            # )
    
            # Input parameters, activate Urukul output (en_out=1),
            # Deactivates PI loop (en_iir=0)
            self.dds0.set_iir(profile=0, adc=adc_ch, kp=kp, ki=ki, g=gl)
            self.dds0.set_dds(profile=0, frequency=f, offset=o)
            self.dds0.set(en_out=1, en_iir=1, profile=0)        # en_out=1 (=0) closes (opens) the RF switch for dds0        
    
            # en_iir=0 deactivates the feedback loop
            # # enable global servo iterations
    
            self.suservo0.set_config(enable=1)

    Best,

    Vincent

    Thank you for the help Vincent.

    2 years later

    VincentB
    Hi Vincent,

    I have been trying to get DDS output in suservo configuration without the sampler input, I have a code very similar to what you used. Please have a look at my thread
    https://forum.m-labs.hk/d/655-suservo-unable-to-get-dds-output/7
    I got a comment from @rjo that we cannot get the dds output with en_iir = 0. At the same time you suggest that it is indeed possible. Please let me know your opinion.