Hi,
I am trying to read from two LimeSDR mini v2 devices in one and the same app. But I observe instability in the received samples and I am trying to figure out what is causing it - software, the SDR or the USB communication.
For this purpose I extended the “singleRX” example in LimeSuite to support multiple devices and to measure the execution time of LMS_RecvStream(). I ran it with 2,3 and even 5 SDRs connected to my laptop and to another machine. And in any of the cases I observe the following:
1166. Radio 0 1D90F53077C892: Read 7680 samples for 19.00us. {On=1 FIFO= 120360/ 7679580(1.5673%) URun=0 ORun=0 Dropped=0 Rate=30.8675MB/s timestamp=9087180}
1166. Radio 1 1D90F5FD139436: Read 7680 samples for 19.00us. {On=1 FIFO= 87720/ 7679580(1.1422%) URun=0 ORun=0 Dropped=0 Rate=30.8675MB/s timestamp=9053520}
1166. Radio 2 1D90EE19E2E8F7: Read 7680 samples for 19.00us. {On=1 FIFO= 63240/ 7679580(0.8235%) URun=0 ORun=0 Dropped=0 Rate=30.8675MB/s timestamp=9027000}
1166. Radio 3 1D90F687323423: Read 7680 samples for 39.00us. {On=1 FIFO= 30600/ 7679580(0.3985%) URun=0 ORun=0 Dropped=0 Rate=30.8675MB/s timestamp=8996400}
1166. Radio 4 1D90F62803335C: Read 7680 samples for 935.00us. {On=1 FIFO= 6120/ 7679580(0.0797%) URun=0 ORun=0 Dropped=0 Rate=30.8675MB/s timestamp=8969880}
One of the SDRs, usually the last one, executes the call of LMS_RecvStream() much much slower then the rest. Depending on the sample rate, this execution seems to be between x10 and x50 slower. In the quoted text above the first 4 SDR need 19 or 39us, while the last one 935us.
Have you observed such behavior? What could be causing it and what can I do to solve it?
My setup:
Laptop: Intel(R) Core™ i7-9850H CPU @ 2.60GHz-4.60GHz 6 cores, 12 threads, RAM:16GB, Ubuntu 20.04
PC: AMD Ryzen 9 7950X @ 3.00GHz-5.879GHz, 16 cores, 32 threads, RAM:64GB, Ubuntu 22.04
LimeSuite: based on master branch, Parent: 38efe9602a7945717a371a1bb9e19d075284f445 (Fix RFE panel checkbox for Rx DC corrector)
Since I can’t find way to upload the code I use for the test, I will paste it here. To build it, simply replace the existing singleRX.cpp example with the code below. Once compiled, you can run it like this, just replace the list of SDR serial numbers (-r option):
./singleRX -f 1300000000 -g 40 -a LNAH -s 7680000 -n 7680 -t 3 -o 1 -r "1D90F53077C892,1D90F5FD139436" -p 0
/**
@file singleRX.cpp
@author Lime Microsystems (www.limemicro.com)
@brief RX example
*/
#include "lime/LimeSuite.h"
#include <iostream>
#include <cstdlib>
#include <unistd.h>
#include <chrono>
#include <vector>
#include <sstream>
#ifdef USE_GNU_PLOT
#include "gnuPlotPipe.h"
#endif
using namespace std;
double freq = 2630e6;
float rf_gain = -1.0;
std::vector<std::string> radios = {};
std::string antenna = "auto";
double srate = 30.72e6;
uint32_t samples_per_frame = 30.72e6/1000;
int32_t exec_time = 5; /* in seconds*/
int32_t oversample = 1;
int32_t pstatus = 1000;
void usage(char* prog)
{
printf("Usage: %s [fgasntorph]\n", prog);
printf("\t-f Carrier frequency in Hz [Default %.2f]\n", freq);
printf("\t-g RF gain [Default AGC(%.2f)]\n", rf_gain);
printf("\t-a Antenna to use [Default %s]\n", antenna.c_str());
printf("\t-s Sampling rate [Default %.2f]\n", srate);
printf("\t-n Samples per frame [Default %d]\n", samples_per_frame);
printf("\t-t Execution time [Default %d]\n", exec_time);
printf("\t-o Oversample factor [Default %d]\n", exec_time);
printf("\t-r Comma separated list of serial numbers to use [Default All detected]\n");
printf("\t-p Print stream status interval us [Default %d]\n", pstatus);
printf("\t-h show this message\n");
}
void parse_args(int argc, char** argv)
{
int opt;
while ((opt = getopt(argc, argv, "fgasntorph")) != -1) {
switch (opt) {
case 'f':
freq = strtof(argv[optind], NULL);
break;
case 'g':
rf_gain = strtof(argv[optind], NULL);
break;
case 'a':
antenna = argv[optind];
break;
case 's':
srate = strtof(argv[optind], NULL);
break;
case 'n':
samples_per_frame = strtof(argv[optind], NULL);
break;
case 't':
exec_time = strtol(argv[optind], NULL, 10);
break;
case 'p':
pstatus = strtol(argv[optind], NULL, 10);
break;
case 'o':
oversample = strtol(argv[optind], NULL, 10);
break;
case 'r':
{
std::stringstream ss(argv[optind]);
std::string token;
while (getline(ss, token, ','))
radios.push_back(token);
}
break;
case 'h':
default:
usage(argv[0]);
exit(-1);
}
}
}
int error(lms_device_t* device = nullptr)
{
if (device != NULL)
LMS_Close(device);
exit(-1);
}
int setupRX(lms_device_t** device, lms_info_str_t sdev)
{
int r = 0;
//Open the device
if (LMS_Open(device, sdev, NULL))
error(*device);
//Initialize device with default configuration
//Do not use if you want to keep existing configuration
//Use LMS_LoadConfig(device, "/path/to/file.ini") to load config from INI
if (LMS_Init(*device) != 0)
error(*device);
if (LMS_EnableChannel(*device, LMS_CH_TX, 0, true)!=0) // Fix for v2
error(*device);
//Enable RX channel
//Channels are numbered starting at 0
if (LMS_EnableChannel(*device, LMS_CH_RX, 0, true) != 0)
error(*device);
//Set center frequency to 800 MHz
if (LMS_SetLOFrequency(*device, LMS_CH_RX, 0, freq) != 0)
error(*device);
//print currently set center frequency
float_type freq;
if (LMS_GetLOFrequency(*device, LMS_CH_RX, 0, &freq) != 0)
error(*device);
cout << "\nCenter frequency: " << freq / 1e6 << " MHz\n";
int n = 0;
//select antenna port
lms_name_t antenna_list[10]; //large enough list for antenna names.
//Alternatively, NULL can be passed to LMS_GetAntennaList() to obtain number of antennae
if ((n = LMS_GetAntennaList(*device, LMS_CH_RX, 0, antenna_list)) < 0)
error(*device);
cout << "Available antennas:\n"; //print available antennae names
for (int i = 0; i < n; i++)
cout << i << ": " << antenna_list[i] << endl;
if ("auto" == antenna) {
if ((n = LMS_GetAntenna(*device, LMS_CH_RX, 0)) < 0) //get currently selected antenna index
error(*device);
//print antenna index and name
//cout << "Automatically selected antenna: " << n << ": " << antenna_list[n] << endl;
if (LMS_SetAntenna(*device, LMS_CH_RX, 0, LMS_PATH_LNAW) != 0) // manually select antenna
error(*device);
}
else {
for (int i = 0; i < n; i++) {
if (antenna_list[i] == antenna) {
if (LMS_SetAntenna(*device, LMS_CH_RX, 0, i) != 0) // manually select antenna
error(*device);
break;
}
}
}
if ((n = LMS_GetAntenna(*device, LMS_CH_RX, 0)) < 0) //get currently selected antenna index
error(*device);
//print antenna index and name
cout << "Selected antenna: " << n << " " << antenna_list[n] << endl;
//This set sampling rate for all channels
if (LMS_SetSampleRate(*device, srate, oversample) != 0)
error(*device);
//print resulting sampling rates (interface to host , and ADC)
float_type rate, rf_rate;
if (LMS_GetSampleRate(*device, LMS_CH_RX, 0, &rate, &rf_rate) != 0) //NULL can be passed
error(*device);
cout << "\nHost interface sample rate: " << rate / 1e6 << " MHz\nRF ADC sample rate: " << rf_rate / 1e6 << "MHz\n\n";
//Example of getting allowed parameter value range
//There are also functions to get other parameter ranges (check LimeSuite.h)
//Get allowed LPF bandwidth range
lms_range_t range;
if (LMS_GetLPFBWRange(*device,LMS_CH_RX,&range)!=0)
error(*device);
cout << "RX LPF bandwitdh range: " << range.min / 1e6 << " - " << range.max / 1e6 << " MHz\n\n";
//Configure LPF, bandwidth 8 MHz
if (LMS_SetLPFBW(*device, LMS_CH_RX, 0, srate) != 0)
error(*device);
//Set RX gain
if (0 > rf_gain) {
if (LMS_SetNormalizedGain(*device, LMS_CH_RX, 0, 0.7) != 0)
error(*device);
//Print RX gain
float_type gain; //normalized gain
if (LMS_GetNormalizedGain(*device, LMS_CH_RX, 0, &gain) != 0)
error(*device);
cout << "Normalized RX Gain: " << gain << endl;
}
else {
if (LMS_SetGaindB(*device, LMS_CH_RX, 0, rf_gain) != 0)
error(*device);
}
unsigned int gaindB; //gain in dB
if (LMS_GetGaindB(*device, LMS_CH_RX, 0, &gaindB) != 0)
error(*device);
cout << "RX Gain: " << gaindB << " dB" << endl;
//Perform automatic calibration
if (LMS_Calibrate(*device, LMS_CH_RX, 0, srate, 0) != 0)
error(*device);
return r;
}
int setupRXStream(lms_device_t* device, lms_stream_t* streamId, bool run = false)
{
//Enable test signal generation
//To receive data from RF, remove this line or change signal to LMS_TESTSIG_NONE
//if (LMS_SetTestSignal(device, LMS_CH_RX, 0, LMS_TESTSIG_NCODIV8, 0, 0) != 0)
// error(device);
//Streaming Setup - Initialize stream
streamId->channel = 0; //channel number
//streamId->fifoSize = 1024 * 1024; //fifo size in samples
streamId->fifoSize = srate; //set fifo to contain ~1 second of data
streamId->throughputVsLatency = 1.0; //optimize for max throughput
streamId->isTx = false; //RX channel
streamId->dataFmt = lms_stream_t::LMS_FMT_F32; //32-bit floats
int r = LMS_SetupStream(device, streamId);
if (!r && run)
r = LMS_StartStream(streamId);
return r;
}
int setupRXStream_Start(lms_stream_t* streamId)
{
return LMS_StartStream(streamId);
}
int main(int argc, char** argv)
{
int r = 0;
parse_args(argc, argv);
//Find devices
//First we find number of devices, then allocate large enough list, and then populate the list
int n;
if ((n = LMS_GetDeviceList(NULL)) < 0)//Pass NULL to only obtain number of devices
error();
cout << "Devices found: " << n << endl;
if (n < 1)
return -1;
lms_info_str_t* list = new lms_info_str_t[n]; //allocate device list
if (LMS_GetDeviceList(list) < 0) //Populate device list
error();
for (int i = 0; i < n; i++) //print device list
cout << i << ": " << list[i] << endl;
cout << endl;
//Device structure, should be initialize to NULL
std::vector<lms_device_t*> devices(radios.size(), nullptr);
std::vector<lms_stream_t> streamIds(radios.size());
//Data buffers
//const int bufersize = frame_ms*sr/1000; //complex samples per buffer - holds frame_ms of data
//float buffer[bufersize * 2]; //must hold I+Q values of each sample
std::vector<std::vector<float>> buffers(radios.size());
for (auto&& buffer : buffers)
buffer.resize(samples_per_frame*2);
for (size_t i=0; i<radios.size() && !r; i++)
{
bool found = false;
int j = 0;
//find the desired sdr in the list
for (j=0; j<n && !found; j++) {
std::string available(list[j]);
if ((found = string::npos != available.find(radios[i]))) {
cout << "Looking for " << radios[i] << " Found: " << available << " Match:" << found << endl;
break;
}
}
if (!found) {
cout << "Device: " << radios[i] << " not found!" << endl;
exit(-1);
}
cout << "Constructing device " << i << " "<< radios[i] <<" List[" << j << "]" << list[j] << endl;
r = setupRX(&devices[i], list[j]);
cout << "Created device: " << devices[i] << " List:" << list[j] << endl;
}
if (r)
cout << "Failed to construct LMS Devices!" << endl;
for (size_t i=0; i<radios.size() && !r; i++)
{
cout << "Constructing stream " << i << " "<< radios[i] << " streamId:" << streamIds[i].handle << endl;
r = setupRXStream(devices[i], &streamIds[i], false);
cout << "Created stream: " << devices[i] << " streamId:" << streamIds[i].handle << endl;
}
if (r)
cout << "Failed to construct LMS Read Stream!" << endl;
usleep(200000); //seem we need some time to settle
for (size_t i=0; i<radios.size() && !r; i++)
r = setupRXStream_Start(&streamIds[i]);
if (r)
cout << "Failed to start LMS Read Stream!" << endl;
std::vector<std::pair<int32_t, double>> read_samples(radios.size(), {0,0.0});
auto t1 = chrono::high_resolution_clock::now();
auto t2 = t1;
int loop = 0;
while (!r && chrono::high_resolution_clock::now() - t1 < chrono::seconds(int(exec_time))) //run for 10 seconds
{
for (size_t i=0; i<radios.size() && !r; i++)
{
auto beg = chrono::high_resolution_clock::now();
//Receive samples
r = LMS_RecvStream(&streamIds[i], buffers[i].data(), samples_per_frame, NULL, 3000);
if (0 <= r) {
read_samples[i].first += r;
r = 0;
auto end = chrono::high_resolution_clock::now();
std::chrono::duration< double > fs = end- beg;
read_samples[i].second += std::chrono::duration_cast< std::chrono::microseconds >( fs ).count();
}
}
if (!r && (!pstatus || chrono::high_resolution_clock::now() - t2 > chrono::microseconds(pstatus)))
{
t2 = chrono::high_resolution_clock::now();
for (size_t i=0; i<radios.size() && !r; i++)
{
lms_stream_status_t status;
//Get stream status
r = LMS_GetStreamStatus(&streamIds[i], &status);
std::string s(5120, 0);
snprintf((char*)s.data(), s.size(), "%d. Radio %ld %s: Read %d samples for %.2fus. {On=%d FIFO=%8d/%8d(%4.4f%%) URun=%d ORun=%d Dropped=%d Rate=%4.4fMB/s timestamp=%ld}",
loop, i, radios[i].c_str(), read_samples[i].first, read_samples[i].second,
status.active, status.fifoFilledCount, status.fifoSize, 100 * (float)status.fifoFilledCount / (float)status.fifoSize,
status.underrun, status.overrun, status.droppedPackets, status.linkRate/1e6, status.timestamp);
cout << s << endl;
read_samples[i].first = 0;
read_samples[i].second = 0;
}
}
loop++;
}
for (size_t i=0; i<radios.size(); i++) {
//Stop streaming
LMS_StopStream(&streamIds[i]); //stream is stopped but can be started again with LMS_StartStream()
LMS_DestroyStream(devices[i], &streamIds[i]); //stream is deallocated and can no longer be used
//Close device
LMS_Close(devices[i]);
}
return 0;
}