LimeSDR different and floating gain on RX channels

Dear guys,
I want to use my LimeSDR to measure the signal amplitude at a given frequency (here 245 MHz) with small bandwidth, around 1-2 Mhz. But signals which I have from the LimeSDR differ in time despite the fact I use the same settings on signal generators which connected to RX1_L and RX2_L ports. I send -60 dBm, but I receive sometimes -41 dBm, sometimes -39 dBm, and this happens randomly. What I have noticed, is that in general signal from RX2_L is a bit higher than from RX1_L. Even when I switch off the amplifiers by setting amplification to 0 or give them exact amplification coefficient (how does it really gains? is LNA 10 equal to +10dB gain???) I see the same picture - different amplification on time and different amplifications on each channel.

I program LimeSDR with Python 3.6.8 using SoapySDR
Lib Version: v0.7.1-myriadrf1~ubuntu18.04
API Version: v0.7.1
ABI Version: v0.7

I call the command
python3 lime.py --center 246e6 --time 1 --samprate 8e6 --bw 4e6 --channel 1 2 --filename test12.bin test22.bin

where bandwidth and sampling rate are in MHz, measurement time is in seconds.

Here is my code and two plots from measurement of the -60 dBm signals which result as -40.9 dBm and -42.3 dBm.

import numpy as np
import argparse
from matplotlib import pyplot as plt
import SoapySDR
from SoapySDR import SOAPY_SDR_RX, SOAPY_SDR_CS16

class LimeSDR:
  #at first one has to initialize SDR. Init method sets all preferences and setups the data streams
  def __init__(self, cent_freq, meas_time, rx_bw, fs, channel): #rx_bw - bandwidth, fs - sampling rate
    self.channel = channel
    use_agc = True          # Use or don't use the AGC
    self.freq = cent_freq            # LO tuning frequency in Hz
    self.timeout_us = int(5e6)
    self.N = int(fs * meas_time)               # Number of complex samples per transfer
    self.rx_bits = 12            # The Lime's ADC is 12 bits
    RX1 = 0             # RX1 = 0, RX2 = 1
    RX2 = 1
    self.sdr = SoapySDR.Device(dict(driver="lime")) # Create AIR-T instance
      
    if (len(self.channel) == 2):
      self.sdr.setSampleRate(SOAPY_SDR_RX, RX1, fs)   # Set sample rate
      self.sdr.setSampleRate(SOAPY_SDR_RX, RX2, fs)          
      self.sdr.setGainMode(SOAPY_SDR_RX, RX1, False)   # Set the gain mode
      self.sdr.setGainMode(SOAPY_SDR_RX, RX2, False)
      self.sdr.setGain(SOAPY_SDR_RX, RX1, "TIA", 0) # Set TransImpedance Amplifier gain
      self.sdr.setGain(SOAPY_SDR_RX, RX2, "TIA", 0)    
      self.sdr.setGain(SOAPY_SDR_RX, RX1, "LNA", 0) # Set Low-Noise Amplifier gain
      self.sdr.setGain(SOAPY_SDR_RX, RX2, "LNA", 0)      
      self.sdr.setGain(SOAPY_SDR_RX, RX1, "PGA", 0) # programmable-gain amplifier (PGA)
      self.sdr.setGain(SOAPY_SDR_RX, RX2, "PGA", 0) 
      self.sdr.setDCOffsetMode(SOAPY_SDR_RX, 0, False)  
      self.sdr.setDCOffsetMode(SOAPY_SDR_RX, 1, False)
      self.sdr.setFrequency(SOAPY_SDR_RX, RX1, self.freq)         # Tune the LO
      self.sdr.setFrequency(SOAPY_SDR_RX, RX2, self.freq)         # Tune the LO
      self.RX1_buff = np.empty(2 * self.N, np.int16)                 # Create memory buffer for data stream
      self.RX2_buff = np.empty(2 * self.N, np.int16)                 # Create memory buffer for data stream
      self.sdr.setBandwidth(SOAPY_SDR_RX, RX1, rx_bw)
      self.sdr.setBandwidth(SOAPY_SDR_RX, RX2, rx_bw)
      self.sdr.setAntenna(SOAPY_SDR_RX, RX1, "LNAL")
      self.sdr.setAntenna(SOAPY_SDR_RX, RX2, "LNAL")
      # Create data buffer and start streaming samples to it
      self.rx_stream = self.sdr.setupStream(SOAPY_SDR_RX, SOAPY_SDR_CS16, [RX1, RX2])  # Setup data stream
    
    elif(self.channel[0] == 1):
      self.sdr.setSampleRate(SOAPY_SDR_RX, RX1, fs)          # Set sample rate
      self.sdr.setGainMode(SOAPY_SDR_RX, RX1, True)       # Set the gain mode
      self.sdr.setGain(SOAPY_SDR_RX, RX1, "TIA", 0)
      self.sdr.setGain(SOAPY_SDR_RX, RX1, "LNA", 0)
      self.sdr.setGain(SOAPY_SDR_RX, RX1, "PGA", 0) # programmable-gain amplifier (PGA)
      self.sdr.setFrequency(SOAPY_SDR_RX, RX1, self.freq)         # Tune the LO    
      self.RX1_buff = np.empty(2 * self.N, np.int16)                 # Create memory buffer for data stream
      self.sdr.setBandwidth(SOAPY_SDR_RX, RX1, rx_bw)
      self.sdr.setAntenna(SOAPY_SDR_RX, RX1, "LNAL")
      self.rx_stream = self.sdr.setupStream(SOAPY_SDR_RX, SOAPY_SDR_CS16, [RX1])  # Setup data stream
     
   
    elif(self.channel[0] == 2):
      self.sdr.setSampleRate(SOAPY_SDR_RX, RX2, fs)          # Set sample rate
      self.sdr.setGainMode(SOAPY_SDR_RX, RX2, True)       # Set the gain mode
      self.sdr.setGain(SOAPY_SDR_RX, RX2, "TIA", 0)
      self.sdr.setGain(SOAPY_SDR_RX, RX2, "LNA", 0)
      self.sdr.setGain(SOAPY_SDR_RX, RX2, "PGA", 0) # programmable-gain amplifier (PGA)
      self.sdr.setFrequency(SOAPY_SDR_RX, RX2, self.freq)         # Tune the LO    
      self.RX2_buff = np.empty(2 * self.N, np.int16)                 # Create memory buffer for data stream
      self.sdr.setBandwidth(SOAPY_SDR_RX, RX2, rx_bw)
      self.sdr.setAntenna(SOAPY_SDR_RX, RX2, "LNAL")
      self.rx_stream = self.sdr.setupStream(SOAPY_SDR_RX, SOAPY_SDR_CS16, [RX2])  # Setup data stream
   
    else:
      print("Channel amount has to be 1 or 2")

  def get_signal(self): #this function reads stream of data and puts it in self.rx_stream numpy array  
    
    self.sdr.activateStream(self.rx_stream)  #  Initialize the  receiver using Soapy. This turns the radio on
    if (len(self.channel) == 2):  # Read the samples from the data buffer
      sr = self.sdr.readStream(self.rx_stream, [self.RX1_buff, self.RX2_buff], self.N, timeoutUs = self.timeout_us)
    elif(self.channel[0] == 1):
      sr = self.sdr.readStream(self.rx_stream, [self.RX1_buff], self.N, timeoutUs = self.timeout_us)    
    elif(self.channel[0] == 2):
      sr = self.sdr.readStream(self.rx_stream, [self.RX2_buff], self.N, timeoutUs = self.timeout_us)
  
    rc = sr.ret # number of samples read or the error code
    assert rc == self.N, 'Error Reading Samples from Device (error code = %d)!' % rc
    
    self.sdr.deactivateStream(self.rx_stream)# Stop streaming
    self.sdr.closeStream(self.rx_stream)
    
  def make_iq(self, filenames): #this function makes complex64 from ADC bits and saves it as binary file 
    if (len(self.channel) == 2):
      RX1bits = self.RX1_buff.astype(float) / np.power(2.0, self.rx_bits-1)# Convert interleaved shorts (received signal) to numpy.complex64 normalized between [-1, 1]
      RX2bits = self.RX2_buff.astype(float) / np.power(2.0, self.rx_bits-1)
      RX1complex = (RX1bits[::2] + 1j*RX1bits[1::2]) 
      RX2complex = (RX2bits[::2] + 1j*RX2bits[1::2]) 
      RX1complex = np.insert(RX1complex, 0, (self.N + 1j*self.freq) )
      RX2complex = np.insert(RX2complex, 0, (self.N + 1j*self.freq) )
      RX1complex = np.complex64(RX1complex)
      RX2complex = np.complex64(RX2complex)
      RX1complex.tofile(filenames[0])
      RX2complex.tofile(filenames[1])
   
    elif(self.channel[0] == 1):
      RX1bits = self.RX1_buff.astype(float) / np.power(2.0, self.rx_bits-1)
      RX1complex = (RX1bits[::2] + 1j*RX1bits[1::2]) 
      RX1complex = np.insert(RX1complex, 0, (self.N + 1j*self.freq) )
      RX1complex = np.complex64(RX1complex)
      RX1complex.tofile(filenames[0])
   
    elif(self.channel[0] == 2):
      RX2bits = self.RX2_buff.astype(float) / np.power(2.0, self.rx_bits-1)
      RX2complex = (RX2bits[::2] + 1j*RX2bits[1::2]) 
      RX2complex = np.insert(RX2complex, 0, (self.N + 1j*self.freq) )
      RX2complex = np.complex64(RX2complex)
      RX2complex.tofile(filenames[0])

if __name__ == "__main__":
  parser = argparse.ArgumentParser()
  parser.add_argument("--center", type=float, nargs='?', help="Set local oscillator frequency")
  parser.add_argument("--time", type=int, nargs='?', help="Set measurement time")
  parser.add_argument("--samprate", type=float, nargs='?', help="Set sampling rate")
  parser.add_argument("--bw", type=float, nargs='?', help="Set bandwidth")
  parser.add_argument("--channel", type=int, nargs='+', help="Set channels to read data from")
  parser.add_argument("--filename", type=str, action='append', nargs='+', help="Path to saved file")
  args = parser.parse_args()
  
  if args.time and args.samprate and args.bw and args.filename and args.channel:
    print ("Central frequency set to ", args.center, " Hz.")
    print ("Measurement time set to ", args.time, " sec.")
    print ("Sampling rate set to ", args.samprate, " Samples/sec.")
    print ("Bandwidth set to ", args.bw, " Hz.")

    if ( len(args.channel) == len(args.filename[0]) and len(args.channel) == 1 ):
      print ("Reading from channels ", args.channel[0])
      print ("file saved as: ", args.filename[0][0])
      #initialize Lime SDR with entered measure time, bandwidth, sampling rate
      Lime = LimeSDR(args.center, args.time, args.bw, args.samprate, args.channel)
      Lime.get_signal()
      Lime.make_iq(args.filename[0])
    
    elif ( len(args.channel) == len(args.filename[0]) and len(args.channel) == 2 ):
      print ("Reading from channels ", args.channel[0],args.channel[1])
      print ("file saved as: ", args.filename[0][0], args.filename[0][1])
      #initialize Lime SDR with entered measure time, bandwidth, sampling rate
      Lime = LimeSDR(args.center, args.time, args.bw, args.samprate, args.channel)
      Lime.get_signal()
      Lime.make_iq(args.filename[0])
    
    else:
      print ("channels amount not equal to amount of files")     
  else:
    print ("No parameters were given")

Any comments or ideas would be kindly appreciated, because I have no idea what to do. The only restriction is that measurements have to be done using Python3 script.

Bests,
Skye

Update: I have tried to do the calibration, then make a time.sleep(1) and during the sleep switch on the signal generator. Because maybe different gain could be caused by the signal from generator during the calibration. But this did not help. Moreover, if sleep command between activateStream and readStream will be longer than 0.8 seconds - program will hang and one has to manually switch off and on LimeSDR board. More questions than answers… Any ideas?

Hi @Skye,

What results do you get when measuring it with LimeSuiteGUI and FFTViewer?

Dear Zack, many thanks for your reaction.

Unfortunately I was not able to re-measure the signal because of MCU error 5 (described in RF Loopback Test Failed in my message). As far as I will be able to solve this I will try to use LimeSuiteGUI and FFTViewer

Best Regards

Hello

I have the same problem: namely I want to make sure that the gain between the two chains is relatively the same. How did you do?
An answer would be appreciated.