No samples with example code

Since I haven’t written any C++ since the primordial epoch of the late '90s, I had to use Microsoft’s new Coding Wizard, Github Copilot, to help me write something to fix the buffer overrun issue.
Now I get a WAV file, but it skips time. I think this is implemented kind of backwards. I would write an app and put the sampling in a background thread. I think I need to do it continuously.
Progress, though.

/**
    @file   singleRX.cpp
    @author Lime Microsystems (www.limemicro.com)
    @brief  RX example
 */
// Try to get this to demodulate FM radio with the blog entry at:
// https://witestlab.poly.edu/blog/capture-and-decode-fm-radio/
// #define USE_GNU_PLOT
#include "lime/LimeSuite.h"
#include <iostream>
#include <chrono>
#include <fstream>
#ifdef USE_GNU_PLOT
#include "gnuPlotPipe.h"
#endif
#include <complex>
#include <thread>
#include <queue>
#include <mutex>
#include <condition_variable>
#include <boost/lockfree/queue.hpp>

using namespace std;

lms_device_t *device = NULL;
std::mutex mtx;
boost::lockfree::queue<float *> sampleQueue(128);
std::atomic<bool> dataAvailable(false);
std::atomic<bool> exitFlag(false);
std::condition_variable cv;

const int buffersize = 1000000; // complex samples per buffer

int error()
{
    if (device != NULL)
        LMS_Close(device);
    exit(-1);
}

void writeSamplesToFile(const std::string &filename)
{
    std::ofstream samplecsv(filename);
    while (true)
    {
        float *buffer = nullptr;
        while (!sampleQueue.pop(buffer))
        {
            // Wait for data to become available
            std::unique_lock<std::mutex> lock(mtx);
            cv.wait(lock, []
                    { return dataAvailable.load() || exitFlag.load(); }); // Check the flag here
            dataAvailable.store(false);
        }

        for (int i = 0; i < buffersize; i++)
        {
            samplecsv << buffer[i];
            if (i % 2 == 0)
            {
                samplecsv << ",";
            }
            else
            {
                samplecsv << "\n";
            }
        }

        if (exitFlag.load())
            break;

        // Don't forget to delete the buffer when you're done with it
        delete[] buffer;
    }
}

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

    if (LMS_EnableChannel(device, LMS_CH_TX, 0, true) != 0) // Fix for v2
        error();

    // Set center frequency to 800 MHz
    int F_offset = 250000;
    int desired_freq = 985 * 1e5 - F_offset;
    if (LMS_SetLOFrequency(device, LMS_CH_RX, 0, desired_freq) != 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, 8e6, 8) != 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 bandwidth 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.7) != 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_F32; // 32-bit floats
    if (LMS_SetupStream(device, &streamId) != 0)
        error();

    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;

    // In your main function or wherever you're receiving the samples:
    string filename = "samples_" + to_string(freq / 1e6) + ".csv";
    std::thread writerThread(writeSamplesToFile, filename);

    while (chrono::high_resolution_clock::now() - t1 < chrono::seconds(10)) // run for 10 seconds
    {
        int samplesRead;
        // Allocate a new buffer
        float *buffer = new float[buffersize * 2];

        lms_stream_status_t status;
        if (LMS_GetStreamStatus(&streamId, &status) != 0)
        {
            // Handle error
            cout << "Stream Status error" << endl;
        }

        // Receive samples
        samplesRead = LMS_RecvStream(&streamId, buffer, buffersize, NULL, 1000);
        if (samplesRead <= 0)
        {
            cout << "error: " << samplesRead << endl;
        }
        //        cout << "Read " << samplesRead << " samples." << endl;
        while (!sampleQueue.push(buffer))
            ;

        dataAvailable.store(true);
        cv.notify_one();

        // I and Q samples are interleaved in buffer: IQIQIQ...
        /*
            INSERT CODE FOR PROCESSING RECEIVED SAMPLES
        */

        // 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 fifo: " << 100 * status.fifoFilledCount / status.fifoSize << "%" << endl; // percentage of FIFO filled
        }
    }

    cout << "Finished sampling" << endl;

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

    cout << "Waiting for samples to be written to file" << endl;

    while (!sampleQueue.empty())
    {
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
    }

    cout << "Complete" << endl;

    // Don't forget to join the thread at the end of your program:
    exitFlag.store(true);
    cv.notify_one();
    writerThread.join();

    return 0;
}

I could probably decimate it down to the 200KHz that FM broadcast uses in the C++ code rather than in the Python code. I’d really like to end up with an iOS app.
Is there a way to sample at 200KHz in the first place?

I suspect that your problem is with the “sampleQueue.push” and “sampleQueue.pop”. I alway have to put in about a 10 msec or so thread sleep when that type of routine fails to do the push or pop - calling them too often overwhelms them - the I/O buffering can handle slight delay with no problem.

If I want to decimate the samples to the 200KHz channel bandwidth (actually more like 100KHz) before transferring them to the host, do I need to do that in the FPGA?
I’d like to be able to use a Raspberry Pi.

You can nicely down sample to practically any sample rate that you please, Using the liquid-dsp routines msresamp_crcf_create, msresamp_crcf_execute, msresamp_crcf_destroy. I have actually gone down to 50Khz with no problems .These routines do the antialiasing - so you avoid the false signal problem of a straight down sample.

The LimeMini and LimeMini 2.0 run nicely on the Bookworm 64 bit version of the PI OS with the Raspberry pi 4 computer. The PI 4 can easily handle what you want to do.

Thanks.
What got me interested in the LMS7002 is the high bandwidth.
I want to build a very flexible comms box.
I want to have 20MHz of bandwidth, so sampling 40MHz and picking several 8KHz bandwidth voice channels out of it.
I don’t know if a Pi, even a Pi5 is capable enough.

My three year old MacBook Pro M1 is at only 90 % of one processor running at 40 MSPS and a bandwidth of 40 MHZ on the FM band with the limeMini 2.0 using SdrGlut. My new Pi 4 does 30MSPS with power spectrum and waterfall, but if I turn on the audio it starts erroring.

Here are some examples of SdrGlut scanning frequencies (the limeMini 2.0 will work as well as the SDRplay Rspduo used) -

SdrGlut uses DSD to decode Public Service Channels with the RSPduo

A P25 reception contest - dsd-louis vs. dsd-dme

Nice. I just ordered an Orange Pi 5B.
I have wanted a Pi with at least 16GB of memory and a Type C Display Port output for my AR glasses.
Plus it beats the Broadcom 2712 in the RPi5 handily which has twice the performance of an RPi4.
I might even see what I can do with the AI coprocessor

Is it necessary to sample 32 but floats for audio?
I was looking at the docs and it said that it’s not built into the hardware.
Looking to reduce the work the system has to do if I’m going to try sampling at 40MSPS.

You could request the LimeMini to give you the native short ints and I have seen some very old programs do all of the processing using the ints. All of the newer programs use floats until they extract the audio as short ints or float - it avoids all of the scaling problems. A simple oscillator using floats needs to be corrected often or it wanders to heck - think of the problems with ints. I would try the floats first and then shift to ints if necessary.