Post Go back to editing

ADALM-PLUTO dual-port mod: Can't use DDS on both Tx Ports in Python

Category: Hardware
Product Number: ADALM-PLUTO

I've been trying to use the DDS to set up two CW outputs on the ADALM-PLUTO (modified for 2 tx and 2 rx ports).

I've been finding that only the second output to be declared will output a tone.  Is this a limitation of the firmware, or am I missing something?

That is, if I use

  sdr.dds_single_tone(freq, scale, 0)

...followed by

  sdr.dds_single_tone(freq, scale, 1)

 

...there will only be a tone at the second port.

But if I switch the order of the lines, there will only be a tone at the first port.  Is there something more I need to do?

Here's a script that illustrates my issue (if you loop the Tx back to the Rx ports)

import adi
import numpy as np
import matplotlib.pyplot as plt

sdr = adi.ad9361("ip:192.168.2.1")

sdr.rx_enabled_channels = [0, 1]
sdr.tx_enabled_channels = [0, 1]

sample_rate = int(30.72e6)
lo_freq     = int(1e9)
cw_freq     = int(1e6)

sdr.sample_rate     = sample_rate
sdr.rx_rf_bandwidth = sample_rate
sdr.tx_rf_bandwidth = sample_rate

# set up Tx
sdr.tx_lo = lo_freq

sdr.tx_hardwaregain_chan0 = -10
sdr.tx_hardwaregain_chan1 = -10

## Only the second DDS tone will generate an output ##
######################################################
sdr.dds_single_tone(cw_freq,     0.9, channel=0)
sdr.dds_single_tone(0 - cw_freq, 0.9, channel=1)
######################################################

# set up Rx
sdr.rx_lo = lo_freq
sdr.gain_control_mode_chan0 = 'manual'
sdr.gain_control_mode_chan1 = 'manual'
sdr.rx_hardwaregain_chan0 = 0
sdr.rx_hardwaregain_chan1 = 0

# Clear Buffer
for i in range (0, 10):
    sdr.rx()

# Receive samples
rx_samples = sdr.rx()


# Calculate Power Spectral Density (Frequency Domain)
psd_1 = np.abs(np.fft.fftshift(np.fft.fft(rx_samples[0])))**2
psd_dB_1 = 10*np.log10(psd_1)
psd_2 = np.abs(np.fft.fftshift(np.fft.fft(rx_samples[1])))**2
psd_dB_2 = 10*np.log10(psd_2)

# generate frequency span
f = np.linspace(sample_rate/-2, sample_rate/2, sdr.rx_buffer_size)
f += sdr.rx_lo

# Plot
full_fig = plt.figure(0)
time_plot = full_fig.add_subplot(211)
freq_plot = full_fig.add_subplot(212)

# Time Plot
time_plot.plot(np.real(rx_samples[0][:100]), label='Rx 1 Real')
time_plot.plot(np.imag(rx_samples[0][:100]), label='Rx 1 Imag')
time_plot.plot(np.real(rx_samples[1][:100]), label='Rx 2 Real')
time_plot.plot(np.imag(rx_samples[1][:100]), label='Rx 2 Imag')

time_plot.set_xlabel("Samples")
time_plot.legend()
time_plot.grid()

# Freq Plot
freq_plot.plot(f/1e6, psd_dB_1, alpha=0.7, label='Rx 1')
freq_plot.plot(f/1e6, psd_dB_2, alpha=0.7, label='Rx 2')
freq_plot.legend()

freq_plot.set_xlabel("Frequency [MHz]")
freq_plot.set_ylabel("PSD")
freq_plot.grid()

plt.show()

vs.

  • Hi,

    You can replace the 2 sdr.dds_single_tone calls with something similar to the following:

    #### [TX1_I_F1, TX1_I_F2, TX1_Q_F1, TX1_Q_F2, TX2_I_F1, TX2_I_F2, TX2_Q_F1, TX2_Q_F2]
    sdr.dds_frequencies = [cw_freq, 0, cw_freq, 0, cw_freq, 0, cw_freq, 0]
    sdr.dds_scales = [0.9, 0, 0.9, 0, 0.9, 0, 0.9, 0]
    #sdr.dds_phases = []
    sdr.dds_enabled = [1, 1, 1, 1, 1, 1, 1, 1]
    


    You need to change the values to match what you need for scale, frequency and phase values.
    The "dds_single_tone" function generates the tone on the required channel but resets all the values that were previously setup for other channels.
    The way described above should allow for more flexibility in generating what you need.
    The config lists have 8 elements due to both TX channels being dual tone. In order to mute the DDS at startup you need to set the scale to 0.

    Additionally, at the end of the script you could use sdr.disable_dds() to ensure all get disabled.


    -Alexandra

  • Thanks Alexandra!
    That worked great.

    I added your extra hint about the sdr.dds_phases to accomplish a positive and negative frequency as well.

    Here's my updated code:

    import adi
    import numpy as np
    import matplotlib.pyplot as plt
    
    sdr = adi.ad9361("ip:192.168.2.1")
    
    sdr.rx_enabled_channels = [0, 1]
    sdr.tx_enabled_channels = [0, 1]
    
    sample_rate = int(30.72e6)
    lo_freq     = int(1e9)
    cw_freq     = int(1e6)
    
    sdr.sample_rate     = sample_rate
    sdr.rx_rf_bandwidth = sample_rate
    sdr.tx_rf_bandwidth = sample_rate
    
    # set up Tx
    sdr.tx_lo = lo_freq
    
    sdr.tx_hardwaregain_chan0 = -10
    sdr.tx_hardwaregain_chan1 = -10
    
    ## Only the second DDS tone will generate an output ##
    ######################################################
    # sdr.dds_single_tone(0 - cw_freq, 0.9, channel=1)
    # sdr.dds_single_tone(cw_freq,     0.9, channel=0)
    ######################################################
    
    ## generates output on both ports ##
    ####################################
    sdr.dds_frequencies = [cw_freq, 0, cw_freq, 0, cw_freq, 0, cw_freq, 0]
    sdr.dds_scales      = [    0.9, 0,     0.9, 0,     0.9, 0,     0.9, 0]
    sdr.dds_phases      = [      0, 0,   90000, 0,   90000, 0,       0, 0]
    sdr.dds_enabled     = [      1, 1,       1, 1,       1, 1,       1, 1]
    ####################################
    # set up Rx
    sdr.rx_lo = lo_freq
    sdr.gain_control_mode_chan0 = 'manual'
    sdr.gain_control_mode_chan1 = 'manual'
    sdr.rx_hardwaregain_chan0 = 0
    sdr.rx_hardwaregain_chan1 = 0
    
    # Clear Buffer
    for i in range (0, 10):
        sdr.rx()
    
    # Receive samples
    rx_samples = sdr.rx()
    
    # Disable Outputs
    sdr.disable_dds()
    
    
    # Calculate Power Spectral Density (Frequency Domain)
    psd_1 = np.abs(np.fft.fftshift(np.fft.fft(rx_samples[0])))**2
    psd_dB_1 = 10*np.log10(psd_1)
    psd_2 = np.abs(np.fft.fftshift(np.fft.fft(rx_samples[1])))**2
    psd_dB_2 = 10*np.log10(psd_2)
    
    # generate frequency span
    f = np.linspace(sample_rate/-2, sample_rate/2, sdr.rx_buffer_size)
    f += sdr.rx_lo
    
    # Plot
    full_fig = plt.figure(0)
    time_plot = full_fig.add_subplot(211)
    freq_plot = full_fig.add_subplot(212)
    
    # Time Plot
    time_plot.plot(np.real(rx_samples[0][:100]), label='Rx 1 Real')
    time_plot.plot(np.imag(rx_samples[0][:100]), label='Rx 1 Imag')
    time_plot.plot(np.real(rx_samples[1][:100]), label='Rx 2 Real')
    time_plot.plot(np.imag(rx_samples[1][:100]), label='Rx 2 Imag')
    
    time_plot.set_xlabel("Samples")
    time_plot.legend()
    time_plot.grid()
    
    # Freq Plot
    freq_plot.plot(f/1e6, psd_dB_1, alpha=0.7, label='Rx 1')
    freq_plot.plot(f/1e6, psd_dB_2, alpha=0.7, label='Rx 2')
    freq_plot.legend()
    
    freq_plot.set_xlabel("Frequency [MHz]")
    freq_plot.set_ylabel("PSD")
    freq_plot.grid()
    
    plt.show()
    

    Which yields both tones at once:

  • Thanks for that link, Travis.
    But as I dig into that setting, I'm finding it be very inconsistent.

    1.) As with dds_single_tone, it only outputs one Tx port at a time.

    2.) More importantly, it appears to change which Tx port it's using.

    I found that using dds_dual_tone -- even on only one channel -- sometimes it would come out Tx 0 and sometimes it would come out Tx 1.

    I put my whole configuration in a loop to test this:

    import adi
    import numpy as np
    import matplotlib.pyplot as plt
    
    for a in range(10):
        sdr = adi.ad9361("ip:192.168.2.1")
    
        sdr.rx_enabled_channels = [0, 1]
        sdr.tx_enabled_channels = [0, 1]
    
        sample_rate = int(30.72e6)
        lo_freq     = int(1e9)
        cw_freq     = int(1e6)
    
        sdr.sample_rate     = sample_rate
        sdr.rx_rf_bandwidth = sample_rate
        sdr.tx_rf_bandwidth = sample_rate
    
        # set up Tx
        sdr.tx_lo = lo_freq
    
        sdr.tx_hardwaregain_chan0 = -10
        sdr.tx_hardwaregain_chan1 = -10
    
        ## Dual Tone port output is inconsistent ##
        ######################################################
        sdr.dds_dual_tone(    cw_freq, 0.5,     cw_freq + 1e6, 0.3, channel=0)
        # sdr.dds_dual_tone(0 - cw_freq, 0.5, 0 - cw_freq - 1e6, 0.3, channel=1)
        ######################################################
    
        # set up Rx
        sdr.rx_lo = lo_freq
        sdr.gain_control_mode_chan0 = 'manual'
        sdr.gain_control_mode_chan1 = 'manual'
        sdr.rx_hardwaregain_chan0 = 0
        sdr.rx_hardwaregain_chan1 = 0
    
        # Clear Buffer
        for i in range (0, 10):
            sdr.rx()
    
        # Receive samples
        rx_samples = sdr.rx()
    
        # Disable Outputs
        sdr.disable_dds()
    
    
        # Calculate Power Spectral Density (Frequency Domain)
        psd_1 = np.abs(np.fft.fftshift(np.fft.fft(rx_samples[0])))**2
        psd_dB_1 = 10*np.log10(psd_1)
        psd_2 = np.abs(np.fft.fftshift(np.fft.fft(rx_samples[1])))**2
        psd_dB_2 = 10*np.log10(psd_2)
    
        # generate frequency span
        f = np.linspace(sample_rate/-2, sample_rate/2, sdr.rx_buffer_size)
        f += sdr.rx_lo
    
        # Plot
        full_fig = plt.figure() # plt.figure(0)
        time_plot = full_fig.add_subplot(211)
        freq_plot = full_fig.add_subplot(212)
    
        # Time Plot
        time_plot.plot(np.real(rx_samples[0][:100]), label='Rx 1 Real')
        time_plot.plot(np.imag(rx_samples[0][:100]), label='Rx 1 Imag')
        time_plot.plot(np.real(rx_samples[1][:100]), label='Rx 2 Real')
        time_plot.plot(np.imag(rx_samples[1][:100]), label='Rx 2 Imag')
    
        time_plot.set_xlabel("Samples")
        time_plot.legend()
        time_plot.grid()
    
        # Freq Plot
        freq_plot.plot(f/1e6, psd_dB_1, alpha=0.7, label='Rx 1')
        freq_plot.plot(f/1e6, psd_dB_2, alpha=0.7, label='Rx 2')
        freq_plot.legend()
    
        freq_plot.set_xlabel("Frequency [MHz]")
        freq_plot.set_ylabel("PSD")
        freq_plot.grid()
    
    plt.show()
    

  • Maybe dds_dual_tone is not totally what you want. It will generate 2 tones from 1 channel. And disable all others, like dds_single_tone.

    -Travis

  • I agree it isn't what I'm looking for in this case.  But the part where it fully switches outputs, even with just this line:

    sdr.dds_dual_tone(    cw_freq, 0.5,     cw_freq + 1e6, 0.3, channel=0)

    ...is concerning.

    Should I open a different thread to discuss that?

  • I would put an enhancement request on the github repo and/or send a PR if you want to add the change yourself Slight smile

    -Travis