Microwave Spin echo (IQ modulation w/ ADF4351 & ADL5375)

In this example we connect a Red Pitaya 125-14 to an EVAL-CN0285-E1BZ containing a microwave synthesizer (ADF4351) & IQ modulator (ADL5375). We use DigitalIOs for the SPI configuration of the carrier and the RF outputs for fast quadrature modulation.

Required hardware connection (see README for IO Names and Pin Mapping):

  • digital_io_2[0] –> CE

  • digital_io_2[1] –> PDRF

  • digital_io_2[2] <– MUXOUT

  • digital_io_2[3] <– LD

  • digital_io_3[0] –> CLK

  • digital_io_3[1] –> LE

  • digital_io_3[2] –> DATA

The generated pulse sequence is a microwave spin echo (pi/2 + pi + pi/2) with variable delay in between.

NOTE:

  • The following resistors of the EVAL-CN0285-E1BZ need to be removed (if populated):

    • Connectivity to on-board controller: R46, R51, R52, R53, R54, R55 R56, R57

    • Pull-ups: R13, R14, R15, R16, R17, R18, R19

  • We use two AD8132 mounted on generic EVAL-FDA-1RZ-8 boards to perform the single-ended to differential pair conversion with common mode voltage of 0.5V.

Imports

import time 
import numpy as np
from matplotlib import pyplot as plt
from openlabctrl.device import Rp_125_14_Z7010
from openlabctrl.sequence import IoSequence
from openlabctrl.frame import ParamIoSyncFrame
from openlabctrl.io.scope import ScopeSource

Device instances

rp_0 = Rp_125_14_Z7010(ip="192.168.1.143", label="rp_0")

IO Sequences & IO Frames instances

seq = IoSequence(device_list=[rp_0])
fr_rf_config = ParamIoSyncFrame(device_type=Rp_125_14_Z7010, trig=None)
fr_mw_config = ParamIoSyncFrame(device_type=Rp_125_14_Z7010, trig=None)
fr_pi_half = ParamIoSyncFrame(device_type=Rp_125_14_Z7010, trig=None)
fr_pi = ParamIoSyncFrame(device_type=Rp_125_14_Z7010, trig=None)
fr_wait = ParamIoSyncFrame(device_type=Rp_125_14_Z7010, trig=None)

Parameter

param_dict = {
    "mw_config" : {
        "frequency_mhz" : 106.1
    },

    "rf_config" : {
        "freq_mhz" : 0,
        "offset_i" : 0, #compensate for minor deviation of zero point (I modulation)
        "offset_q" : 0  #compensate for minor deviation of zero point (Q modulation)
    },
    
    "pi_half" : {
        "width_samples" : 20, 
        "ampl_i" : 0.5,
        "ampl_q" : 0.5
    },
    "pi" : {
        "width_samples" : 40, 
        "ampl_i" : 0.5,
        "ampl_q" : 0.5
    },
    "wait" : {
        "wait_time_samples" : 500
    }
}

Frame functions

from devices import ADF4351

#MW config
def fr_func_mw_config(fr, param):
    fr.reset()
    mw = ADF4351(fr)
    mw.io_config()
    mw.rf_disable()
    mw.chip_enable()
    fr.delay(125000) #1ms
    mw.frequency(param["frequency_mhz"] * 1e6) 
    fr.delay(125000) #1ms


#RF config
def fr_func_rf_config(fr, param):
    fr.reset()
    fr.rf_out_0.frequency(param["freq_mhz"] * 1e6)
    fr.rf_out_1.frequency(param["freq_mhz"] * 1e6)
    fr.rf_out_0.amplitude(param["offset_i"])
    fr.rf_out_1.amplitude(param["offset_q"])
    fr.rf_out_0.offset(0)
    fr.rf_out_1.amplitude(0)
    fr.rf_out_0.phase(0)
    fr.rf_out_1.phase(90)
    fr.rf_out_0.phase_reset()
    fr.rf_out_1.phase_reset()


#Triangular Pulse
def fr_func_triangular_pulse(fr, param):
    ampl_i_list = np.concatenate(
        (np.linspace(0, param["ampl_i"], param["width_samples"]//2),
         np.linspace(param["ampl_i"], 0,  param["width_samples"]//2)))
    
    ampl_q_list = np.concatenate(
        (np.linspace(0, param["ampl_q"], param["width_samples"]//2),
         np.linspace(param["ampl_q"], 0,  param["width_samples"]//2)))

    fr.reset()
    mw = ADF4351(fr)
    mw.rf_enable() 
    fr.rsync()
    for ampl_i, ampl_q in zip(ampl_i_list, ampl_q_list):
        fr.rf_out_0.amplitude(ampl_i)
        fr.rf_out_1.amplitude(ampl_q)

    fr.delay(10) #80us (compensate for RF out latency) 
    fr.rsync()
    mw.rf_disable()


#Wait
def fr_func_wait(fr, param):
    fr.reset()
    fr.delay(param["wait_time_samples"])

Map frame function and parameters

fr_mw_config.set_frame_function(fr_func_mw_config)
fr_mw_config.set_frame_parameter(param_dict["mw_config"])

fr_rf_config.set_frame_function(fr_func_rf_config)
fr_rf_config.set_frame_parameter(param_dict["rf_config"])

fr_pi_half.set_frame_function(fr_func_triangular_pulse)
fr_pi_half.set_frame_parameter(param_dict["pi_half"])

fr_pi.set_frame_function(fr_func_triangular_pulse)
fr_pi.set_frame_parameter(param_dict["pi"])

fr_wait.set_frame_function(fr_func_wait)
fr_wait.set_frame_parameter(param_dict["wait"])

Sequence definition

seq.reset()
seq.add_frame(frame=fr_mw_config, device=rp_0, label="mw_config")
seq.add_frame(frame=fr_rf_config, device=rp_0, label="rf_config")
seq.add_frame(frame=fr_pi_half, device=rp_0, label="pi_half (I)")
seq.add_frame(frame=fr_wait, device=rp_0, label="wait (I)")
seq.add_frame(frame=fr_pi, device=rp_0, label="pi")
seq.add_frame(frame=fr_wait, device=rp_0, label="wait (II)")
seq.add_frame(frame=fr_pi_half, device=rp_0, label="pi_half (II)")

print(seq.sequence_description())
+--------------------+
| rp_0@192.168.1.143 |
+--------------------+
| mw_config          |
| rf_config          |
| pi_half (I)        |
| wait (I)           |
| pi                 |
| wait (II)          |
| pi_half (II)       |
+--------------------+
NOTE: Frames with (*) are triggered by external trigger source.

Upload & Run sequence

if not seq.is_done():
    seq.stop()

repeat = 5
wait_list = np.arange(0, 50, 2)

for r in range(repeat):
    for w in wait_list: 
        param_dict["wait"]["wait_time_samples"] = w
        seq.upload()
        seq.start()
        seq.wait()
        
        
seq.get_status()
seq.stop()