Reducing USB3 Latency for RFID reader application

Hi all,

A bit of background for what I’m trying to do: I’m currently attempting to use this Gen2 UHF RFID Reader library to read Gen 2 RFID tags using the LimeSDR.

When I run the program, the reader transmits a reasonable signal, and the tag responds with an RN16, which is good news. However, to meet the RFID Gen 2 standard, the Lime reader needs to begin to acknowledge (ACK) this tag response within 500us of the response. Currently, the reader only responds between 7-10 ms after the tag response, which is clearly far too slow (see picture below):

Whilst researching the issue, I came across this paper in which the researches seemed to have similar latency issues; they were using USB2 with the USRP1. In order to resolve the issue, they reduced the USB transfer packet size, so that packets would be sent sooner from the SDR to the computer - the relevant paragraph of the paper is copied below:

“Our initial reader implementation had a latency of more than 20 ms. We reduced this latency in two main ways. First, a large fraction of the latency was because the USB interface transferred samples to the receive chain in chunks of 4096 samples. When using a 250 ksps input signal, one chunk represented more than 16 ms worth of signal and introduced, on average, 8 ms worth of latency. By reducing this block size to the minimum allowed by the Linux kernel (128 samples), and using a high rate input stream which is decimated by the Matched Filter block we achieved latencies of around 400 μs.”

My question is: how do I do this? I’m fairly new to both linux (currently running Ubuntu 16.04 LTS), so I don’t really know where to begin. Alternatively; am I looking in the wrong place; does USB3 (as opposed to the USB2 used in the paper) perform better than this, and I should be looking for a different solution? I’m inclined to believe that the USB transfer size is the problem as when I increase my sampling rate, the delay decreases; however I don’t seem to be able to increase my sampling rate very much before I start getting errors (segmentation faults).

When I use ‘uname -r’ to find my Kernel version, I get ‘4.10.0-38-generic’.

I’m using GNU Radio v3.7.11, with the following code:

from gnuradio import eng_notation
from gnuradio import gr
from gnuradio import blocks
from gnuradio import filter
from gnuradio.eng_option import eng_option
from gnuradio.filter import firdes
from optparse import OptionParser
from gnuradio import analog
from gnuradio import digital
import osmosdr
import sip
import sys
import time
import rfid

SINE_TEST = False

class reader_top_block(gr.top_block):

  def __init__(self):
    gr.top_block.__init__(self)


    rt = gr.enable_realtime_scheduling()

    ######## Variables #########
    self.dac_rate = 1e6                 # DAC rate
    self.adc_rate = 100e6/50            # ADC rate (2MS/s complex samples)
    self.decim     = 5                    # Decimation (downsampling factor)
    self.ampl     = 0.6                  # Output signal amplitude (signal power vary for different RFX900 cards)
    self.freq     = 910e6                # Modulation frequency (can be set between 902-920)
    self.rx_gain   = 20                   # RX Gain (gain at receiver)
    self.tx_gain   = 0                    # RFX900 no Tx gain option



    # Each FM0 symbol consists of ADC_RATE/BLF samples (2e6/40e3 = 50 samples)
    # 10 samples per symbol after matched filtering and decimation
    self.num_taps     = [1] * 25 # matched to half symbol period

   ######## File sinks for debugging (1 for each block) #########
    self.file_sink_source         = blocks.file_sink(gr.sizeof_gr_complex*1, "../misc/data/source", False)
    self.file_sink_matched_filter = blocks.file_sink(gr.sizeof_gr_complex*1, "../misc/data/matched_filter", False)
    self.file_sink_gate           = blocks.file_sink(gr.sizeof_gr_complex*1, "../misc/data/gate", False)
    self.file_sink_decoder        = blocks.file_sink(gr.sizeof_gr_complex*1, "../misc/data/decoder", False)
    self.file_sink_reader         = blocks.file_sink(gr.sizeof_float*1,      "../misc/data/reader", False)

    if SINE_TEST :
        #File to dump signal that we want to disconnect
        self.file_sink_dump           = blocks.file_sink(gr.sizeof_gr_complex*1,      "../misc/data/dump", False)



    ######## Blocks #########
    self.matched_filter = filter.fir_filter_ccf(self.decim, self.num_taps)
    self.gate            = rfid.gate(int(self.adc_rate/self.decim))
    self.tag_decoder    = rfid.tag_decoder(int(self.adc_rate/self.decim))
    self.reader          = rfid.reader(int(self.adc_rate/self.decim),int(self.dac_rate))
    self.amp              = blocks.multiply_const_ff(self.ampl)
    self.to_complex      = blocks.float_to_complex(1)

    if SINE_TEST :
        self.sine_source = analog.sig_source_c(self.dac_rate, analog.GR_SIN_WAVE, 440, .5, 0)

    # Osmocom blocks
    self.u_source = osmosdr.source(args="numchan=" + str(1) + " " + 'driver=lime,soapy=0'  )
    self.u_source.set_sample_rate(self.adc_rate)
    self.u_source.set_center_freq(self.freq, 0)
    self.u_source.set_freq_corr(0, 0)
    #self.u_source.set_dc_offset_mode(0, 0)
    self.u_source.set_iq_balance_mode(0, 0)
    self.u_source.set_gain_mode(True, 0)
    self.u_source.set_gain(self.rx_gain, 0)
    self.u_source.set_if_gain(10, 0)
    self.u_source.set_bb_gain(10, 0)
    self.u_source.set_antenna("LNAL", 0)
    self.u_source.set_bandwidth(0, 1000000)

    self.u_sink =  osmosdr.sink(args="numchan=" + str(1) + " " + 'driver=lime,soapy=0' )
    self.u_sink.set_sample_rate(self.dac_rate)
    self.u_sink.set_center_freq(self.freq, 0)
    self.u_sink.set_freq_corr(0, 0)
    self.u_sink.set_gain(self.tx_gain, 0)
    self.u_sink.set_if_gain(15, 0)
    self.u_sink.set_bb_gain(15, 0)
    self.u_sink.set_antenna("BAND1", 0)
    self.u_sink.set_bandwidth(0, 1000000)

    ######## Connections #########
    self.connect(self.u_source, self.matched_filter)
    self.connect(self.matched_filter, self.gate)
    self.connect(self.gate, self.tag_decoder)
    self.connect((self.tag_decoder,0), self.reader)
    self.connect(self.reader, self.amp)
    self.connect(self.amp, self.to_complex)

    if SINE_TEST : 
        self.connect(self.to_complex, self.file_sink_dump)
        self.connect(self.sine_source, self.u_sink)
    else :
        self.connect(self.to_complex, self.u_sink)

    self.connect(self.u_source, self.file_sink_source)
    self.connect(self.gate, self.file_sink_gate)
    self.connect((self.tag_decoder,1), self.file_sink_decoder) # (Do not comment this line)
    self.connect(self.reader, self.file_sink_reader)
    self.connect(self.matched_filter, self.file_sink_matched_filter)

if __name__ == '__main__':

  main_block = reader_top_block()
  main_block.start()

  while(1):
    inp = raw_input("'Q' to quit \n")
    if (inp == "q" or inp == "Q"):
      break

  main_block.reader.print_results()
  main_block.stop()

Thanks,

DasSidG

I’ve done a bit more looking around on this and I think I’ve mostly solved the issue. I don’t think USB3.0 is necessarily the bottleneck; there’s a latency parameter (which I wasn’t previously aware of) in the SoapyLMS7 api: look in https://github.com/myriadrf/LimeSuite/blob/stable/SoapyLMS7/Streaming.cpp and see the config.performanceLatency parameter; forcing this to 1 seemed to mostly resolve my latency issues, to bring it down to about 500us from around 8ms (which is probably just about sufficient for what I need).

1 Like