I’m trying to consistently send and receive a signal on a LimeSDR-mini, but the received signal is randomly shift by one sample, even though I use timed commands. I really cannot figure out what is wrong and so my project has been dead in the water for weeks now.
For example, I transmitted and received a step signal 20 times in a row using timed commands, and here is a plot of the received signals:
Whole plot:
Zoomed in:
As you can see, the signals arrive with either one sample offset or another, but I cannot figure out why.
Please let me know if there’s any more information that I can supply to help diagnose this. Thanks!
Code:
import argparse
import os
import time
import numpy as np
import SoapySDR
from SoapySDR import * #SOAPY_SDR_ constants
from bokeh.plotting import figure, output_file, output_notebook, show
from bokeh.models.tools import HoverTool
from bokeh.models import Legend
from bokeh import palettes
output_notebook()
def simple_plots(x, ys, color='limegreen'):
p = figure(sizing_mode='scale_width', height=150)
palette = palettes.Category20[min(20, len(ys))]
for i, y in enumerate(ys):
p.line(x=x, y=y, color=palette[i % len(palette)])
p.background_fill_alpha = 0
p.border_fill_alpha = 0
p.axis.major_label_text_color = 'grey'
p.toolbar.logo = None
p.add_tools(HoverTool(tooltips='@y @ iteration @x', show_arrow=True, point_policy='snap_to_data'))
show(p)
def get_receptions(
args,
rate,
freq=None,
rx_bw=None,
tx_bw=None,
rx_chan=0,
tx_chan=0,
rx_ant=None,
tx_ant=None,
rx_gain=None,
tx_gain=None,
clock_rate=None,
dump_dir=None):
sdr = SoapySDR.Device(args)
if not sdr.hasHardwareTime():
raise Exception('this device does not support timed streaming')
#set clock rate first
if clock_rate is not None:
sdr.setMasterClockRate(clock_rate)
actual_clock_rate = sdr.getMasterClockRate()
if actual_clock_rate != clock_rate:
print('Clock rate set to', actual_clock_rate/1e6,'MHz instead of requested', clock_rate / 1e6, 'MHz')
actual_clock_rate = sdr.getMasterClockRate()
#set sample rate
sdr.setSampleRate(SOAPY_SDR_RX, rx_chan, rate)
sdr.setSampleRate(SOAPY_SDR_TX, tx_chan, rate)
print("Actual Rx Rate %f Msps"%(sdr.getSampleRate(SOAPY_SDR_RX, rx_chan) / 1e6))
print("Actual Tx Rate %f Msps"%(sdr.getSampleRate(SOAPY_SDR_TX, tx_chan) / 1e6))
#set antenna
if rx_ant is not None:
sdr.setAntenna(SOAPY_SDR_RX, rx_chan, rx_ant)
if tx_ant is not None:
sdr.setAntenna(SOAPY_SDR_TX, tx_chan, tx_ant)
#set overall gain
if rx_gain is not None:
sdr.setGain(SOAPY_SDR_RX, rx_chan, rx_gain)
if tx_gain is not None:
sdr.setGain(SOAPY_SDR_TX, tx_chan, tx_gain)
#tune frontends
if freq is not None:
sdr.setFrequency(SOAPY_SDR_RX, rx_chan, freq)
if freq is not None:
sdr.setFrequency(SOAPY_SDR_TX, tx_chan, freq)
#set bandwidth
if rx_bw is not None:
sdr.setBandwidth(SOAPY_SDR_RX, rx_chan, rx_bw)
if tx_bw is not None:
sdr.setBandwidth(SOAPY_SDR_TX, tx_chan, tx_bw)
timeout_us = int(5e5) #500 ms >> stream time
tx_signal = np.zeros(1024)
tx_signal[int(len(tx_signal)/2):] = 0.3
tx_signal = tx_signal.astype(np.complex64)
delay_ms = 1000
delay_clocks = int(delay_ms * 1e-3 * actual_clock_rate)
delay_s = delay_clocks / rate
delay_ns = int(delay_s * 1e9)
num_rx_samps = len(tx_signal)
tx_flags = SOAPY_SDR_HAS_TIME | SOAPY_SDR_END_BURST
rx_flags = SOAPY_SDR_HAS_TIME | SOAPY_SDR_END_BURST
receptions = []
#create rx and tx streams
rx_stream = sdr.setupStream(SOAPY_SDR_RX, SOAPY_SDR_CF32, [rx_chan])
tx_stream = sdr.setupStream(SOAPY_SDR_TX, SOAPY_SDR_CF32, [tx_chan])
#let things settle
time.sleep(1)
for _ in range(20):
sdr.activateStream(tx_stream)
tx_time_0 = int(sdr.getHardwareTime() + delay_ns)
receive_time = int(tx_time_0)
status = sdr.writeStream(tx_stream, [tx_signal], len(tx_signal), tx_flags, tx_time_0)
if status.ret != len(tx_signal):
raise Exception('transmit failed %s' % str(status))
rx_buffs = np.array([], np.complex64)
sdr.activateStream(rx_stream, rx_flags, receive_time, num_rx_samps)
rx_time_0 = None
#accumulate receive buffer into large contiguous buffer
while True:
rx_buff = np.array([0]*1024, np.complex64)
status = sdr.readStream(rx_stream, [rx_buff], len(rx_buff), timeoutUs=timeout_us)
#stash time on first buffer
if status.ret > 0 and len(rx_buffs) == 0:
rx_time_0 = status.timeNs # Need this line
if (status.flags & SOAPY_SDR_HAS_TIME) == 0:
raise Exception('receive fail - no timestamp on first readStream %s'%(str(status)))
#accumulate buffer or exit loop
if status.ret > 0:
rx_buffs = np.concatenate((rx_buffs, rx_buff[:status.ret]))
else:
break
#cleanup streams
sdr.deactivateStream(rx_stream)
sdr.deactivateStream(tx_stream)
receptions.append(rx_buffs)
sdr.closeStream(rx_stream)
sdr.closeStream(tx_stream)
#check resulting buffer
if len(rx_buffs) != num_rx_samps:
raise Exception(
'receive fail - captured samples %d out of %d'%(len(rx_buffs), num_rx_samps))
if rx_time_0 is None:
raise Exception('receive fail - no valid timestamp')
return receptions
receptions = get_receptions(
args='driver=lime',
rate=samp_rate,
freq=1e9,
tx_gain=50,
rx_gain=10,
clock_rate=samp_rate
)
simple_plots(x=np.arange(len(receptions[0])), ys=[np.abs(y) for y in receptions])