Calibration failures

Hi, when I run the program below, there are a couple problems with the calibration. The RX calibration turns on the DC bias removal, even though I explicitly turned it off beforehand. It doesn’t appear to calibrate the RX DC zero offset. The TX calibration fails completely.

import sys
import math
import SoapySDR
from SoapySDR import * #SOAPY_SDR_ constants
import numpy #use numpy for buffers

frequency = 2.45e9
frequencyOffset = 0.
amplitude = 0.5

#enumerate devices
results = SoapySDR.Device.enumerate()
print("Devices: ")
for result in results: print(result)

#create device instance
#args can be user defined or from the enumeration result
#args = dict(driver="lime")
args = results[0]
print("Instantiating device: ")
sdr = SoapySDR.Device(args)

#apply settings
rxSampleRate = 1.0e5
print("Setting RX sample rate to %g" % rxSampleRate)
sdr.setSampleRate(SOAPY_SDR_RX, 0, rxSampleRate)
rxFrequency = frequency
print("Setting RX frequency to %g" % (rxFrequency))
sdr.setFrequency(SOAPY_SDR_RX, 0, rxFrequency)
rxAntenna = "LNAH"
print("Setting RX antenna to " + str(rxAntenna))
sdr.setAntenna(SOAPY_SDR_RX, 0, rxAntenna)
rxGain = 20.0
print("Setting RX gain to " + str(rxGain))
sdr.setGain(SOAPY_SDR_RX, 0, rxGain)
#set dc offset mode to False to disable automatic DC bias removal
sdr.setDCOffsetMode(SOAPY_SDR_RX, 0, False)
print("Getting RX DC offset mode: " + str(sdr.getDCOffsetMode(SOAPY_SDR_RX, 0)))
print("Getting RX DC offset: " + str(sdr.getDCOffset(SOAPY_SDR_RX, 0)))
sdr.setBandwidth(SOAPY_SDR_RX, 0, 5e6)
print("Getting RX bandwidth: " + str(sdr.getBandwidth(SOAPY_SDR_RX, 0)))

#setup a stream (complex floats)
print("Setting up RX stream")
#args = dict(skipCal="true")
args = dict(skipCal="false")
rxStream = sdr.setupStream(SOAPY_SDR_RX, SOAPY_SDR_CF32, [0], args)

txSampleRate = 1.0e5
print("Setting TX sample rate to %g" % txSampleRate)
sdr.setSampleRate(SOAPY_SDR_TX, 0, txSampleRate)
txFrequency = frequency + frequencyOffset
print("Setting TX frequency to %g" % (txFrequency))
sdr.setFrequency(SOAPY_SDR_TX, 0, txFrequency)
txAntenna = "BAND1"
print("Setting TX antenna to " + str(txAntenna))
sdr.setAntenna(SOAPY_SDR_TX, 0, txAntenna)
txGain = 20.0
print("Setting TX gain to " + str(txGain))
sdr.setGain(SOAPY_SDR_TX, 0, txGain)
print("Getting TX DC offset: " + str(sdr.getDCOffset(SOAPY_SDR_TX, 0)))
sdr.setBandwidth(SOAPY_SDR_TX, 0, 5e6)
print("Getting TX bandwidth: " + str(sdr.getBandwidth(SOAPY_SDR_TX, 0)))

print("Setting up TX stream")
args = dict(skipCal="true")
#args = dict(skipCal="false")
txStream = sdr.setupStream(SOAPY_SDR_TX, SOAPY_SDR_CF32, [0], args)

rxBuffer = numpy.array([0]*0x20000, numpy.complex64)

txBuffer = numpy.array([0]*0x20000, numpy.complex64)
for i in range(len(txBuffer)):
	txBuffer[i] = numpy.complex64(amplitude)

print("Activating RX stream")
sdr.activateStream(rxStream) #start streaming

print("Getting RX DC offset mode: " + str(sdr.getDCOffsetMode(SOAPY_SDR_RX, 0)))
print("Getting RX DC offset: " + str(sdr.getDCOffset(SOAPY_SDR_RX, 0)))

print("Activating TX stream")
sdr.activateStream(txStream) #start streaming

for i in range(4):
	#load tx
	print("Loading TX stream")
	writeResult = sdr.writeStream(txStream, [txBuffer], len(txBuffer))
	print("Write return value: " + str(writeResult.ret)) 

	#receive some samples
	print("Reading RX stream")
	readResult = sdr.readStream(rxStream, [rxBuffer], len(rxBuffer))
	print("Read return value: " + str(readResult.ret)) #num samples or error code
	print("flags: " + str(readResult.flags)) #flags set by receive operation
	print("Timestamp: " + str(readResult.timeNs)) #timestamp for receive buffer
	for i in range(4):
		print("Value[%d]: " % i)
		print(str(rxBuffer[i]) + " magnitude: " + str(abs(rxBuffer[i])))
#shutdown the stream
print("Deactivating TX stream")
sdr.deactivateStream(txStream) #stop streaming
print("Closing TX stream")

print("Deactivating RX stream")
sdr.deactivateStream(rxStream) #stop streaming
print("Closing RX stream")

It produces the following output when run:

{addr=24607:1027, driver=lime, label=LimeSDR Mini [USB 2.0] 1D40ECE6B10A17, media=USB 2.0, module=FT601, name=LimeSDR Mini, serial=1D40ECE6B10A17}
Instantiating device: 
[INFO] Make connection: 'LimeSDR Mini [USB 2.0] 1D40ECE6B10A17'
[INFO] Reference clock 40.00 MHz
[INFO] Device name: LimeSDR-Mini
[INFO] Reference: 40 MHz
[INFO] LMS7002M calibration values caching Disable
Setting RX sample rate to 100000
Setting RX frequency to 2.45e+09
Setting RX antenna to LNAH
Setting RX gain to 20.0
Getting RX DC offset mode: False
Getting RX DC offset: 0j
[INFO] RX LPF configured
Getting RX bandwidth: 5000000.0
Setting up RX stream
Setting TX sample rate to 100000
Setting TX frequency to 2.45e+09
Setting TX antenna to BAND1
Setting TX gain to 20.0
Getting TX DC offset: 0j
[INFO] Filter calibrated. Filter order-4th, filter bandwidth set to 5 MHz.Real pole 1st order filter set to 2.5 MHz. Preemphasis filter not active
[INFO] TX LPF configured
Getting TX bandwidth: 5000000.0
Setting up TX stream
Activating RX stream
e[1me[31m[ERROR] Tx Calibration: MCU error 5 (Loopback signal weak: not connected/insufficient gain?)e[0m
[INFO] Rx calibration finished
Getting RX DC offset mode: True
Getting RX DC offset: 0j
Activating TX stream
Loading TX stream
Write return value: 131072

Reading RX stream
Read return value: 131072
flags: 4
Timestamp: 0
(0.196783-0.143559j) magnitude: 0.243583
(0.196783-0.143071j) magnitude: 0.243296
(0.196783-0.143071j) magnitude: 0.243296
(0.196783-0.143559j) magnitude: 0.243583

Loading TX stream
Write return value: 131072

Reading RX stream
Read return value: 131072
flags: 4
Timestamp: 1310720000
-0.000488296j magnitude: 0.000488296
(-0.000488296-0.000488296j) magnitude: 0.000690555
(-0.000488296+0j) magnitude: 0.000488296
0j magnitude: 0.0

Loading TX stream
Write return value: 131072

Reading RX stream
Read return value: 131072
flags: 4
Timestamp: 2621440000
-0.000488296j magnitude: 0.000488296
-0.000488296j magnitude: 0.000488296
-0.000488296j magnitude: 0.000488296
0j magnitude: 0.0

Loading TX stream
Write return value: 131072

Reading RX stream
Read return value: 131072
flags: 4
Timestamp: 3932160000
-0.000488296j magnitude: 0.000488296
-0.000488296j magnitude: 0.000488296
(-0.000488296-0.000488296j) magnitude: 0.000690555
-0.000488296j magnitude: 0.000488296

Deactivating TX stream
e[1me[33m[WARNING] popping from TX, samples popped 8/1020e[0m
Closing TX stream
Deactivating RX stream
Closing RX stream

You can see getDCOffsetMode() returns True after the RX calibration runs even though I set it False earlier.

I tried increasing the gain to make the TX calibration work, but it didn’t help.

I haven’t gotten any responses, so I’m starting to look into this myself. The MCU calibration code changes the DC bias removal setting (DC_BYP_RXTSP where 0 enables bias removal and 1 disables) in two functions in lms7002m_calibrations.c. In the function CheckSaturationTxRx() it turns enables the DC bias removal on entry and disables it on exit. In the function CalibrateRxDCAuto() it does the opposite, disabling DC bias removal on entry and enabling on exit. I suspect the intention was to do the opposite in CheckSaturationTxRx(), perhaps this is why my TX calibration is broken?

Also, at some level the code needs to save/restore the original DC bias removal setting before/after running the calibration.

Rx DC correction enabling at the end is by design:

Looks like calibration fails due to low Rx TIA gain, which should be overriden during tx calibration, but instead user set value is used. Probably this is the cause 0x0113 register I’ll check it later.

Thank you for lending your attention and expertise, it is much appreciated.

However, even if enabling DC bias removal after calibration is part of the lime design, it is not part of the soapysdr API design (at least I hope it is not!), which is the API I am using. All I am doing is calling “setDCOffsetMode” followed by a later call to “activateStream”. It is the lime soapy driver that is deciding it needs to do a calibration in its implementation of “activateStream”. Then it is the lime calibration code that decides it is a good idea to just ignore and overwrite the DC bias removal setting. So at the very least, lime’s SoapyLMS7 driver needs to save/restore the setting made with “setDCOffsetMode” before/after it calls the lime calibration code.

Really though, lime should just respect the user setting all the way down. Calibrating the DC offset and entirely removing the DC component are two distinct concepts. Conflating them was a design mistake.

On a related note, I noticed the SoapyLMS7 implementation of setDCOffsetMode only sets DC_BYP_RXTSP which is inconsistent with LMS7002M::SetRxDCRemoval in LMS7002M.cpp, which also sets DCCORR_AVG_RXTSP.

Oh, and at risk of being overly critical, it would be nice if the lines you highlighted were changed to use named constants instead of a bunch of un-grep-able magic numbers. It would make the code more comprehensible to those of us less familiar with the hardware.

Ok, so I’ve fixed 5ba685c1a8 the issue I’ve previously mentioned.
But in your case the issue is that indeed the Tx gain is set pretty low txGain = 20.0 , with my board I need at least 35 to work.

Your critique is totally understandable, and I wish I could do it that way. But as the calibrations has to be squeezed in to 16KB of MCU memory, every byte counts, so comprehensability has to be sacrificed and the “magic numbers” have to be used to fit everything. As of now there is only 110 free bytes left for additional code.

Ok, I’ve built the sources from github that include your fix. Now I see behavior as you describe, where the TX calibration error goes away after increasing txGain to 35. I’m a newbie to the RF stuff, but I don’t understand why the calibration fails at txGain 20? If I increase the rxGain to 35, and put the txGain at 20, then (with a coax connecting rx to tx) I can see a signal about 75% of full scale on the rx. So I don’t understand why the calibration cannot work at txGain 20.

Also, I happened to notice the TX puts out a large signal even if you never call “activateStream” for the TX. My naive expectation would be it would output zero until you activate the stream and write tx data to it.

Disclaimer: I don’t work on this stuff for several years now, so my knowledge might be obsolete now.

The first difference is that calibrations are done using chip’s internal loopback not external/cable loopback like you did. Further more during Tx calibration depending on it’s band different rx path will be used (LNAL or LNAW, LNAH is not available as internal loopback), you are using LNAH, therefore paths can have different sensitivity depending on frequency. The calibration could be done with any gain level, but if the signal strength is only at the noise floor level then the measurement results would be unstable and could make things perform worse, therefore low gain (received signal lower than around -30 dBFS) error has been added to indicate that something is not right. The error now stops the calibrations, but maybe I’ll make it to continue till the end so it would still give some results, but still return error that it might be unreliable.
The gain settings are not absolute units, they are more of a relative scale regarding max power at current frequency. Also there are several hardware parameters for adjusting gains, I’m not sure on what principle they are tied together in the API to a single value.
When connecting Tx to Rx using cable, I think you are suppose to use some kind of attenuator, as you could be overloading/saturating receiver. I think LimeSDR mini has some board controls to make and external loopback with attenuation without using cables.
activateStream is responsible only for managing data transfering between PC and on board FPGA. It does not affect transmitter settings. So if transmitter is configured to be powered on it will transmit something, and if stream is not active, FPGA does not provide any data to the chip, I guess chip will just continuously transmit the last value that was in the interface.