Dual RX Phase difference inconsistency

#1

Hi,

I am developing an application that calculate the phase difference between a signal coming from the two RX channels of a limeSDR. So far, I have the code that calculates it and have fed a Sine using a splitter. The main problem here is that when I execute the program several times, the phase difference stops being the same between executions. Here is the code:

SoapySDR::Device *device(nullptr);
device = SoapySDR::Device::make();

//device->setMasterClockRate(8*sampleRate);
device->setBandwidth(SOAPY_SDR_RX, 0, 20e6);
device->setBandwidth(SOAPY_SDR_RX, 1, 20e6);

device->setSampleRate(SOAPY_SDR_RX, 0, sampleRate);
device->setSampleRate(SOAPY_SDR_RX, 1, sampleRate);

std::string format = SOAPY_SDR_CF32;//device->getNativeStreamFormat(SOAPY_SDR_RX, 0, fullScale);
const size_t elemSize = SoapySDR::formatToSize(format);
std::vector<size_t> channel(2);
channel[0] = 0;
channel[1] = 1;
auto rxStream = device->setupStream(SOAPY_SDR_RX, format, channel);

device->setFrequency(SOAPY_SDR_RX, 0, frequency);
device->setAntenna(SOAPY_SDR_RX, 0, "LNAL");
device->setGain(SOAPY_SDR_RX, 0, gain);

device->setFrequency(SOAPY_SDR_RX, 1, frequency);
device->setAntenna(SOAPY_SDR_RX, 1, "LNAL");
device->setGain(SOAPY_SDR_RX, 1, gain);

cout << "Rx 0 Frequency " << device->getFrequency(SOAPY_SDR_RX, 0)*1e-6 << " MHz" << endl;
cout << "Rx 1 Frequency " << device->getFrequency(SOAPY_SDR_RX, 1)*1e-6 << " MHz" << endl;

cout << "Rx 0 Antenna " << device->getAntenna(SOAPY_SDR_RX,0) << endl;
cout << "Rx 1 Antenna " << device->getAntenna(SOAPY_SDR_RX,1) << endl;

cout << "Rx 0 Sampling Rate " << device->getSampleRate(SOAPY_SDR_RX,0)*1e-6 << " Ms" << endl;
cout << "Rx 1 Sampling Rate " << device->getSampleRate(SOAPY_SDR_RX,1)*1e-6 << " Ms" << endl;

cout << "Rx 0 Bw " << device->getBandwidth(SOAPY_SDR_RX,0)*1e-6 << " MHz" << endl;
cout << "Rx 1 Bw " << device->getBandwidth(SOAPY_SDR_RX,1)*1e-6 << " MHz" << endl;

cout << "Clock rate: " << device->getMasterClockRate()*1e-6 << " MHz" << endl;

std::cout << "Starting stream loop, press Ctrl+C to exit..." << std::endl;
signal(SIGINT, sigIntHandler);

fftwf_plan p0;
fftwf_plan p1;

fftwf_complex *in0 = (fftwf_complex*) fftwf_malloc(sizeof(fftwf_complex) * receiveWindow);
fftwf_complex *in1 = (fftwf_complex*) fftwf_malloc(sizeof(fftwf_complex) * receiveWindow);

std::vector<void *> rxBuffs(2);
rxBuffs[0] = in0;
rxBuffs[1] = in1;

while(!loopDone){
    //counter++;

    int flags(0);
    long long timeNs(0);

    device->activateStream(rxStream);
    int rxRet = device->readStream(rxStream, rxBuffs.data(), receiveWindow, flags, timeNs);
    device->deactivateStream(rxStream);

    cout << "Ret: " << rxRet << endl;

    if (rxRet == SOAPY_SDR_TIMEOUT){
        cout << "T" << endl;
        continue;
    }
    if (rxRet == SOAPY_SDR_OVERFLOW)
    {
        cout << "O";
        continue;
    }
    if (rxRet == SOAPY_SDR_UNDERFLOW)
    {
        cout << "U";
        continue;
    }

    if (rxRet < 0)
    {
        std::cerr << "Unexpected stream error " << SoapySDR::errToStr(rxRet) << std::endl;
        break;
    }

    
    if (rxRet == receiveWindow) {

        vector<double> phaseDifference(receiveWindow, 0);

        size_t histogramSpan = 720;
        vector<double> histogram(histogramSpan, 0);

        size_t i = 0;

        double max = 0;
        double maxPos = 0;
        int angPos;
        for (i = 0; i < receiveWindow; ++i) {

            phaseDifference[i] = 
                    std::arg(complex<float>(in0[i][0], in0[i][1]))
                    - std::arg(complex<float>(in1[i][0], in1[i][1]));

            angPos = floor(phaseDifference[i] * 180.0 / M_PI) + histogramSpan*0.5;

            histogram[angPos]++;

            if (histogram[angPos] > max){
                maxPos = angPos;
                max = histogram[angPos];
            }


        }
        cout << "-------------------------" << endl;
        cout << "Max: " << max << endl;
        cout << "MaxPos: " << maxPos << endl;
        cout << "MaxPosAngle: " << maxPos - histogramSpan*0.5 << endl;
        cout << "-------------------------" << endl;

        sleep(1);
    }
}

Is there anything else I should be doing? Reseting some configuration or something like that?

I will also try to make a similar program using the LimeSuite API and post it with the results.

Kind regards,
Aridane

Application for using the 2 RX channels simultaneously
Application for using the 2 RX channels simultaneously
#2

Ok, I have performed some tests with LMS API and I get the same result. There is also an issue with me not knowing if both streams are actually reading simultaneously. But here it goes:

int configureChannel(lms_device_t* device, int channel, const bool channelType, double sampleRate,
                     double frequency, int oversampling, double gain){
    int n;
    //Enable RX channel
    //Channels are numbered starting at 0
    if (LMS_EnableChannel(device, channelType, channel, true) != 0)
        error(device);

    //Set center frequency
    if (LMS_SetLOFrequency(device, channelType, channel, frequency) != 0)
        error(device);


    //print currently set center frequency
    float_type freq;
    if (LMS_GetLOFrequency(device, channelType, channel, &freq) != 0)
        error(device);
    cout << "\nRX Center 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, channelType, channel, antenna_list)) < 0)
        error(device);

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


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


    if (LMS_SetAntenna(device, channelType, channel, LMS_PATH_LNAL) != 0) // manually select antenna
         error(device);


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

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



    //print resulting sampling rates (interface to host , and ADC)
    float_type rate, rf_rate;
    if (LMS_GetSampleRate(device, channelType, channel, &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,channelType,&range)!=0)
        error(device);

    cout << "LPF bandwitdh range: " << range.min / 1e6 << " - " << range.max / 1e6 << " MHz\n\n";

    //Configure LPF, bandwidth 20 MHz
    if (LMS_SetLPFBW(device, channelType, channel, 15e6) != 0)
        error(device);

    double bw;
    if (LMS_GetLPFBW(device, channelType, channel, &bw) != 0)
        error(device);

    cout << "LPF bandwitdh: " << bw / 1e6 << " MHz\n\n";

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

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



    //Perform automatic calibration
    if (LMS_Calibrate(device, channelType, channel, 20e6, 0) != 0)
        error(device);
    }
}

Main program:

lms_device_t* device = NULL;
int n;
if ((n = LMS_GetDeviceList(NULL)) < 0)//Pass NULL to only obtain number of devices
    error(device);
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(device);

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

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


configureChannel(device, 0, LMS_CH_RX, sampleRate, frequency, oversampling, gain);
configureChannel(device, 1, LMS_CH_RX, sampleRate, frequency, oversampling, gain);

//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_NONE, 0, 0) != 0)
    error(device);
if (LMS_SetTestSignal(device, LMS_CH_RX, 1, LMS_TESTSIG_NONE, 0, 0) != 0)
    error(device);

//Streaming Setup

//Initialize stream
lms_stream_t stream0;

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


lms_stream_t stream1;

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


//Initialize data buffers
const size_t receiveWindow = 102400;

//Streaming
fftwf_plan p;

fftwf_complex *in0 = (fftwf_complex*) fftwf_malloc(sizeof(fftwf_complex) * receiveWindow);
fftwf_complex *in1 = (fftwf_complex*) fftwf_malloc(sizeof(fftwf_complex) * receiveWindow);

cout << "Start Reading" << endl;

lms_stream_meta_t md0, md1;

md0.flushPartialPacket = false;
md0.waitForTimestamp = false;

md1.flushPartialPacket = false;
md1.waitForTimestamp = false;

while (!stop)
{
    LMS_StartStream(&stream0);
    LMS_StartStream(&stream1);

    //Receive samples
    int samplesRead = LMS_RecvStream(&stream0, in0, receiveWindow, &md0, 1000);
    samplesRead = LMS_RecvStream(&stream1, in1, receiveWindow, &md1, 1000);

    LMS_StopStream(&stream0);
    LMS_StopStream(&stream1);

    if (samplesRead == receiveWindow) {
      vector<double> phaseDifference(receiveWindow, 0);

      size_t histogramSpan = 720;
      vector<double> histogram(histogramSpan);

      size_t i = 0;

      double max = 0;
      double maxPos = 0;

      for (i = 0; i < receiveWindow; ++i) {

          phaseDifference[i] = /*constrainAngle(*/
                  std::arg(complex<float>(in0[i][0], in0[i][1]))
                  - std::arg(complex<float>(in1[i][0], in1[i][1]))/*)*/;

          int angPos = floor(phaseDifference[i] * 180.0 / M_PI) + histogramSpan*0.5;

          histogram[angPos]++;

          if (histogram[angPos] > max){
              maxPos = angPos;
              max = histogram[angPos];
          }
      }

      cout << "Max " << max << endl;
      cout << "MaxPos: " << maxPos - histogramSpan*0.5 << endl;

      for (auto it = histogram.begin(); it != histogram.end(); ++it) {
        *it = *it / receiveWindow;
      }

      vector<double> axis(histogramSpan);
      for (int i = 0; i < histogramSpan; ++i)
        axis[i] = i - histogramSpan*0.5;

      plt::clf();
      plt::ylim(0,1);
      plt::plot(axis, histogram);
      plt::pause(0.000000001);
    }
}
//Stop streaming
LMS_DestroyStream(device, &stream0); //stream is deallocated and can no longer be used
LMS_DestroyStream(device, &stream1); //stream is deallocated and can no longer be used
LMS_Reset(device);
//Close device
LMS_Close(device);
#3

I encountered the same thing. My best guess is ADC sampling time misalignment (in subsample steps). So far no working solution was offered.

#4

I observed the same!!
It seemes that LimeSDR is not really 2x2MIMO …
:scream::scream::scream:

@Zack, tell me it’s not true, please!!
Is there a way to obtain a repeatable phase offset between channels in different executions?
Any information about subsampling timing?

bye

#5

Hi Modimo,
one question.
You affirm that the time misalignement is in subsampling steps. How do you discover it?

Is it possible to bypass the subsampling, and sample directly?
Would it solve the problem?
Thanks

#6

I tried many things to solve this. Check this thread:


I calculated phase shift between channels for chirped signal (spanning over 20MHz). At each offset frequency phase shift was different(linear with frequency offset). Just as if samples were misaligned. However 1 sample shift caused to big changes. My conclusion is since ADC is sigma delta type it is possible that there would be subsample shift(since in fact ADC works at higher frequency). Resetting of the logic as was mentioned by @ricardas did not help.

#7

Hi all, it’s an old topic but maybe there are some news.
I’m doing the exact thing of @Aridane. My goal was to check from where a transmitting signal (roughly) comes.
I noticed, as @modimo stated above, that the samples coming from the two antennas are misaligned. I think that I need a sort of preamble to try aligning it.

Before doing that I tried a simpler test: I use a transmitter (433MHz) and put the LimeSDR antennas perpendicularly a couple of meters away, something like:
________________ A1
T ------------------->
________________ A2
(T=transmitter, A=antenna)

After some seconds and without quitting the sampling, I changed the position of the antennas by putting them aligned with the transmitter, something like:

T-----------------------> A1 ----- A2

After some seconds I quit sampling.

By doing so, I should have the same sampling time misalignment on both the antennas position cases. So, by computing the difference between the phase differences of, let’s say, the first N samples of the two antennas and the last N samples of the two antennas (saying that I have M samples, M>>N) I should obtain 180 (or at least a value close to it, as the phase difference of the perpendicular case should be 0 and the phase difference of the align case should be 180). By computing the difference, the misalignment effect should be canceled.

Well, I obtained 0. I don’t know if it’s something related only on the sampling time misalignment or if it’s a mix of more things. What do you thing about it?

I hope I have been clear. I didn’t know if i should start another thread, but the problem is the same of this one.