LimeSDR: Bad IQ sample data running singleRx example in MS Visual C++

Hello,

I’m trying to get our LimeSDR v1.4 board up and running in MS VS 2013 environment. I cloned last git version of LimeSuite, compiled and built LimeSuite without GUI, uploaded the latest firmware images to the board and try to run Rx examples and get some IQ trace. Everything seems to be working (the board is responding, streaming goes OK), except for the sample data looks strange, like something is wrong with nibble/byte ordering. With unplugged Rx input, having set gain = 0 dB and turned off test gen by using LMS_TESTSIG_NONE, I expect to see some small numbers around 0 level, but instead I get some strange values. I dumped couple first samples returned by LMS_RecvStream() routine, having set packing mode to LMS_FMT_I16:

-32512:-32  32480:0    -32512:-32   -32:0    -32512:-17440 -32480:0     -64:-1056 32480:-48  32448:32     32:0
  0081:E0FF  E07E:0000   0081:E0FF E0FF:0000   0081:E0BB     2081:0000 C0FF:E0FB   E07E:D0FF  C07E:2000 2000:0000

Here, the upper line has decimal I:Q value pairs, and below is hex representation of the same values in little endian format, which gives actual byte data sequence. When I set LMS_FMT_I12, I get similar numbers:

32448:-32   -16:16   32448:-32    48:16      0:-17440  -16:16400    0:1056   48:16   32448:-32   -16:-48
 C07E:E0FF F0FF:1000  C07E:E0FF 3000:1000 0000:E0BB   F0FF:1040  0000:2004 3000:1000  C07E:E0FF F0FF:D0FF

For me, it very well resembles swapped bytes/nibbles of ADC data stream, but simply looking at it I can’t guess what should be correct ordering. Did anyone encounter such kind of problem? Though I understand that it’s not mainstream scenario, but we need to use LimeSuite.dll in our application like a plug-in module, and generally LimeSuite lib would fit our needs very well. Is this right place at all to ask such questions, or there is more direct tech support place/address?

Thank you!
Vladimir Pavlenko

P.S. Below is the console log from one of my runs, may be there could be some clue in it. For some reason, it’s got auto-formatted bold in the middle, don’t know how to cancel this…

Devices found: 1
0: USB 3.0 (LimeSDR-USB), media=USB, module=STREAM, serial=0009062000C43919, index=0

Estimated reference clock 30.7197 MHz
Reference clock 30.720 MHz
CGEN: Freq=80 MHz, VCO=2.56 GHz, INT=82, FRAC=349525, DIV_OUTCH_CGEN=15
M=156, N=3, Fvco=1040.000 MHz
16: 01 A0 AA
16: 01 A8 2A
16: AB FA 5C
phase: min 31.2; max 197.3; selected 114.2)
M=156, N=3, Fvco=1040.000 MHz
M=156, N=3, Fvco=1040.000 MHz
16: A6 DC 37
16: AA 58 75
16: AA 5A D5
phase: min 15.6; max 171.3; selected 93.5)
M=156, N=3, Fvco=1040.000 MHz

Center frequency: 1000 MHz
Available antennae:
0: NONE
1: LNA_H
2: LNA_L
3: LNA_W
Automatically selected antenna: 1: LNA_H
Manually selected antenna: 3: LNA_W
CGEN: Freq=16 MHz, VCO=2.432 GHz, INT=78, FRAC=174762, DIV_OUTCH_CGEN=75

Host interface sample rate: 2 MHz
RF ADC sample rate: 2MHz

RX center freq range: 30 - 3800 MHz
RX sample rate range: 0.1 - 80 MHz
RX LPF bandwitdh range: 1.4 - 130 MHz

MCU algorithm time: 10 ms

MCU Programming finished, 930 ms
MCU Ref. clock: 30.72 MHz
MCU algorithm time: 188 ms
RX LPF configured
MCU algorithm time: 0 ms
MCU Ref. clock: 30.72 MHz
MCU algorithm time: 221 ms
RX LPF configured
Normalized RX Gain: 0
RX Gain: 0 dB
############################################
Rx calibration using RSSI INTERNAL ON BOARD loopback
Rx ch.A @ 1000 MHz, BW: 4 MHz, RF input: LNAW, PGA: 0, LNA: 2, TIA: 1
Performed by: MCU

MCU algorithm time: 0 ms
Current MCU firmware: 3, DC/IQ calibration full
MCU Ref. clock: 30.72 MHz
MCU algorithm time: 273 ms
###########################################
Rx calibration using RSSI INTERNAL ON BOARD loopback
Rx ch.B @ 1000 MHz, BW: 4 MHz, RF input: LNAW, PGA: 0, LNA: 2, TIA: 1
Performed by: MCU

MCU algorithm time: 0 ms
Current MCU firmware: 3, DC/IQ calibration full
MCU Ref. clock: 30.72 MHz
MCU algorithm time: 239 ms
Rx pktLoss: ts diff: 6630 pktLoss: 12
32448:-32 -16:16 32448:-32 48:16 0:-17440 -16:16400 0:1056 48:16 32448:-32 -16:-48
C07E:E0FF F0FF:1000 C07E:E0FF 3000:1000 0000:E0BB F0FF:1040 0000:2004 3000:1000 C07E:E0FF F0FF:D0FF

L
RX_1 data rate: 0 MB/s
RX_1 sample rate: 1.91382 MSamples/s
RX_1 fifo: 0%

Hello @wowa,

Share your register settings (ini file), please.

I have just tried it with MS VS 2013. For me it works fine. After setting LMS_TESTSIG_NONE and gain to 0, I get values from -2 to 2 (mostly -1, 0, 1) when using LMS_FMT_I12 format. Also, LMS_FMT_I12 should never give you values outside of range [-2048, 2047].
Could you share your singleRX code?

Zack,

Is there any standard version in some folder? If you mean my custom settings, I don’t use any, just initialize with default LMS_Init(device) call, as singleRX.cpp file has.

Vladimir

OK, I see.
Could you share your code, please as per IgnasJ request.

IgnasJ,

Thanks for forcing me to compare my differences from original code. I doublechecked my settings and output, you are right - FMT_I12 does not give large numbers. But it still gives out values like 2029/-2032 with unplugged input.
But I found out the main reason that leads to extremal values - it’s that I try to use oversampling factor of 1. Now in the included code, the critical is “LMS_SetSampleRate(device, 2e6, 2)” line. If I set oversample to 1, it starts producing values near three basic levels: 0, ±1000 and ±2000.

Now, the question is if this is expected behavior? If yes, then is it possible to get sampling rates above 40 MHz? Can you please clarify this point?

Try to attach the code, but don’t get how to do it right. Somehow it’s getting autoformatted. What is the correct way to attach/include files here? Uploader says it can upload only images…

#include “lime/LimeSuite.h”
#include
#include
#ifdef USE_GNU_PLOT
#include “gnuPlotPipe.h”
#endif

using namespace std;

//Device structure, should be initialize to NULL
lms_device_t* device = NULL;

int error()
{
//print last error message
cout << “ERROR:” << LMS_GetLastErrorMessage();
if (device != NULL)
LMS_Close(device);
exit(-1);
}

int main(int argc, char** 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;

//Open the first device
if (LMS_Open(&device, list[0], NULL))
    error();

delete [] list;                                 //free device list

//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();

//Enable RX channel
//Channels are numbered starting at 0
if (LMS_EnableChannel(device, LMS_CH_RX, 0, true) != 0)
    error();

//Set center frequency to 800 MHz
if (LMS_SetLOFrequency(device, LMS_CH_RX, 0, 800e6) != 0)
    error();

//print currently set center frequency
float_type freq;
if (LMS_GetLOFrequency(device, LMS_CH_RX, 0, &freq) != 0)
    error();
cout << "\nCenter frequency: " << freq / 1e6 << " MHz\n";

//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();

cout << "Available antennae:\n";            //print available antennae names
for (int i = 0; i < n; i++)
    cout << i << ": " << antenna_list[i] << endl;

if ((n = LMS_GetAntenna(device, LMS_CH_RX, 0)) < 0) //get currently selected antenna index
    error();
//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();

if ((n = LMS_GetAntenna(device, LMS_CH_RX, 0)) < 0) //get currently selected antenna index
    error();

//print antenna index and name
cout << "Manually selected antenna: " << n << ": " << antenna_list[n] << endl;

//Set sample rate to 8 MHz, preferred oversampling in RF 8x
//This set sampling rate for all channels
if (LMS_SetSampleRate(device, 2e6, 1) != 0)
    error();
//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();
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();

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, 8e6) != 0)
    error();

//Set RX gain
if (LMS_SetNormalizedGain(device, LMS_CH_RX, 0, 0) != 0)
    error();
//Print RX gain
float_type gain; //normalized gain
if (LMS_GetNormalizedGain(device, LMS_CH_RX, 0, &gain) != 0)
    error();
cout << "Normalized RX Gain: " << gain << endl;

unsigned int gaindB; //gain in dB
if (LMS_GetGaindB(device, LMS_CH_RX, 0, &gaindB) != 0)
    error();
cout << "RX Gain: " << gaindB << " dB" << endl;

//Perform automatic calibration
if (LMS_Calibrate(device, LMS_CH_RX, 0, 8e6, 0) != 0)
    error();

//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();

//Streaming Setup

//Initialize stream
lms_stream_t streamId;
streamId.channel = 0; //channel number
streamId.fifoSize = 1024 * 1024; //fifo size in samples
streamId.throughputVsLatency = 1.0; //optimize for max throughput
streamId.isTx = false; //RX channel
streamId.dataFmt = lms_stream_t::LMS_FMT_I12; //32-bit floats
if (LMS_SetupStream(device, &streamId) != 0)
    error();

//Data buffers
const int bufersize = 10000; //complex samples per buffer
int16_t buffer[bufersize * 2]; //must hold I+Q values of each sample
//Start streaming
LMS_StartStream(&streamId);

#ifdef USE_GNU_PLOT
GNUPlotPipe gp;
gp.write(“set size square\n set xrange[-1:1]\n set yrange[-1:1]\n”);
#endif
auto t1 = chrono::high_resolution_clock::now();
auto t2 = t1;
bool First = true;

while (chrono::high_resolution_clock::now() - t1 < chrono::seconds(2)) //run for 10 seconds
{
    int samplesRead;
    //Receive samples
    samplesRead = LMS_RecvStream(&streamId, buffer, bufersize, NULL, 1000);
    //I and Q samples are interleaved in buffer: IQIQIQ...
    if (First) {
      First = false;
      int16_t *p = buffer;
      printf("%4d:%-4d %4d:%-4d %4d:%-4d %4d:%-4d %4d:%-4d %4d:%-4d %4d:%-4d %4d:%-4d %4d:%-4d %4d:%-4d\n",
        p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7], p[8], p[9],
        p[10], p[11], p[12], p[13], p[14], p[15], p[16], p[17], p[18], p[19]);
      byte *b = (byte*)buffer;
      printf("%02X%02X:%02X%02X %02X%02X:%02X%02X %02X%02X:%02X%02X %02X%02X:%02X%02X %02X%02X:%02X%02X %02X%02X:%02X%02X %02X%02X:%02X%02X %02X%02X:%02X%02X %02X%02X:%02X%02X %02X%02X:%02X%02X\n\n",
        b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7], b[8], b[9],
        b[10], b[11], b[12], b[13], b[14], b[15], b[16], b[17], b[18], b[19],
        b[20], b[21], b[22], b[23], b[24], b[25], b[26], b[27], b[28], b[29],
        b[30], b[31], b[32], b[33], b[34], b[35], b[36], b[37], b[38], b[39]);
    } // if

    //Plot samples

#ifdef USE_GNU_PLOT
gp.write(“plot ‘-’ with points\n”);
for (int j = 0; j < samplesRead; ++j)
gp.writef("%f %f\n", buffer[2 * j], buffer[2 * j + 1]);
gp.write(“e\n”);
gp.flush();
#endif
//Print stats (once per second)
if (chrono::high_resolution_clock::now() - t2 > chrono::seconds(1))
{
t2 = chrono::high_resolution_clock::now();
lms_stream_status_t status;
//Get stream status
LMS_GetStreamStatus(&streamId, &status);
cout << “RX data rate: " << status.linkRate / 1e6 << " MB/s\n”; //link data rate
cout << “RX sample rate: " << status.sampleRate / 1e6 << " MSamples/s\n”; //link data rate
cout << "RX fifo: " << 100 * status.fifoFilledCount / status.fifoSize << “%” << endl; //percentage of FIFO filled
}
}

//Stop streaming
LMS_StopStream(&streamId); //stream is stopped but can be started again with LMS_StartStream()
LMS_DestroyStream(device, &streamId); //stream is deallocated and can no longer be used

//Close device
LMS_Close(device);

return 0;

}

Forgot to mention that initially our goal was to get two 50 MSps streams off two LimeSDR 's rx channels. Then I realized that in real life it (400MB/s) gets close to usb3 practical limit. Do you have any data about if it’s achievable with existing usb3 controllers? Is it realistic to get at least 2x40MSps rx?

Vladimir

Oversampling factor of 1 bypasses decimator. This configuration is not tested much as it offers no benefits compared to decimation of factor 2. Adjustment of some other chips settings may be required to make it work correctly.
You can set sample rate over 40MHz simply by e.g. LMS_SetSampleRate(device, 60e6, 2). Generally you should not set sampling rate higher than 60-65MHz as at higher sampling rates bit errors start appearing in digital interface.

It is possible to get 2x40MSps. It results in about 320 MB/s when LMS_FMT_I16 format is used, or about 240 MB/s when LMS_FMT_I12 format is used. I think upper limit for Rx only on my PC is something like 2x60MSps (~360MB/s with LMS_FMT_I12 format) but this is without doing any processing on samples.

When trying high sampling rates, make sure that you compile Release version.

1 Like

Thank you for the info. Can you please clarify a little bit the limits and relation between sample rate and oversampling factor in LMS_SetSampleRate()? I thought that [host rate * oversampling] shouldn’t exceed [max ADC freq]. So what is this maximum ADC freq value? I thought it was 80 MHz, but now I cannot find this explicitly mentioned in docs.

Another question is about SNR / sampling accuracy: if I set oversampling say to 8, how is one host sample produced now: is it just one of the 8 “RF” samples, or are those 8 RF samples averaged to get one host sample? In the latter case, could it be possible to increase the effective ADC bit count?

ADC maximum sampling rate is 160 MHz. You can find it in LMS7002m datasheet:
https://wiki.myriadrf.org/LimeMicro:LMS7002M_Datasheet#ADCs_electrical_specification
And you are right about that host_rate*oversampling should not exceed max ADC rate (160 MHz).

Maybe @Zack can tell you more about inner workings of the chip.

It is averaged.

Thanks. It’s an interesting subject, about the ability to get more effective bits out of the same ADC, I’ll probably start a new topic on it later…

Sorry, my fault, overlooked that :slight_smile: