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]
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:
There is a dual tone method available https://analogdevicesinc.github.io/pyadi-iio/fpga/index.html#adi.dds.dds.dds_dual_tone
-Travis
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()
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
-Travis