I have my modified version of the RX app trying to sample from the 2m Ham band, and I’m having trouble getting things to match up.
My tinySA Spectrum Analyzer sees the right peak.
/**
@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>
#include <fftw3.h>
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 double bandStart = 144e6; // Start from the Nyquist frequency
const double bandEnd = 148e6; // End at the Nyquist frequency
const double startFrequency = bandStart + (bandEnd - bandStart) / 2; // Start at the center of the FM band
const int bandWidth = bandEnd - bandStart;
const int sampleRate = bandWidth * 2;
const int bufferSize = 4 * 1024; // complex samples per buffer
std::vector<double> calculatePower(const std::vector<float> &samples)
{
// Calculate the number of channels
int binCount = samples.size() / 2;
// Create a vector to hold the power of each channel
std::vector<double> power(binCount);
// Create input array
fftw_complex *input = (fftw_complex *)fftw_malloc(sizeof(fftw_complex) * samples.size() / 2);
int n = 0;
// Copy the samples for this channel to the input array
for (long unsigned int i = 0; i < samples.size(); i += 2)
{
input[n][0] = samples[i];
input[n][1] = samples[i + 1];
n++;
}
auto start = std::chrono::high_resolution_clock::now();
// Create the FFTW plan
fftw_plan plan = fftw_plan_dft_1d(binCount, input, input, FFTW_FORWARD, FFTW_ESTIMATE);
// Execute the FFT
fftw_execute(plan);
auto end = std::chrono::high_resolution_clock::now();
// Calculate the power of the signal for this channel
for (int i = 0; i < binCount; i++)
{
double real = input[i][0];
double imag = input[i][1];
power[i] += std::round(real * real + imag * imag);
}
// Clean up
fftw_destroy_plan(plan);
fftw_free(input);
// Calculate and print the elapsed time in microseconds
auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
std::cout << "FFT took " << duration.count() << " microseconds\n";
for (long unsigned int i = 0; i < power.size(); i++)
{
double freq = startFrequency + i * bandWidth / power.size(); // Subtract the width of each band
if (power[i] > 100000)
{
cout << "Band " << i << " starts at " << freq / 1e6 << " MHz and has power " << power[i] << "\n";
}
}
return power;
}
int error()
{
if (device != NULL)
LMS_Close(device);
exit(-1);
}
void waterfallThread()
{
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);
}
cout << "Got buffer\n";
std::vector<double> power = calculatePower(std::vector<float>(buffer, buffer + bufferSize));
double binWidth = bandWidth / power.size();
cout << "Bin width: " << binWidth << "\n";
for (long unsigned int i = 0; i < power.size(); i++)
{
double freq = bandStart + i * binWidth; // Subtract the width of each band
if (power[i] > 1000)
{
cout << "Band " << i << " starts at " << freq / 1e6 << " MHz and has power " << power[i] << "\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 = 98 * 1e5 - F_offset;
int desired_freq = startFrequency;
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_LNAH) != 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, sampleRate, 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, sampleRate / 2) != 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, sampleRate, 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 = bufferSize * bufferSize; // 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);
std::thread writerThread(waterfallThread);
// long long timeForBuffer = (1 / sampleRate) * bufferSize * 1e6; // time in microseconds
while (chrono::high_resolution_clock::now() - t1 < chrono::milliseconds(5)) // 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.\n";
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;
}
When I key the mike on my Yaesu on 145MHz, I get this output from the program:
Band 0 starts at 144 MHz and has power 1077
Band 107 starts at 144.209 MHz and has power 1372
Band 112 starts at 144.219 MHz and has power 2117
Band 129 starts at 144.252 MHz and has power 1121
Band 130 starts at 144.254 MHz and has power 2473
Band 135 starts at 144.264 MHz and has power 1448
Band 136 starts at 144.266 MHz and has power 1108
Band 137 starts at 144.268 MHz and has power 1137
Band 138 starts at 144.27 MHz and has power 1203
Band 139 starts at 144.271 MHz and has power 1205
Band 140 starts at 144.273 MHz and has power 5078
Band 141 starts at 144.275 MHz and has power 2566
Band 142 starts at 144.277 MHz and has power 2350
Band 143 starts at 144.279 MHz and has power 1734
Band 145 starts at 144.283 MHz and has power 16861
Band 146 starts at 144.285 MHz and has power 12249
Band 147 starts at 144.287 MHz and has power 13518
Band 148 starts at 144.289 MHz and has power 17202
Band 149 starts at 144.291 MHz and has power 8001
Band 150 starts at 144.293 MHz and has power 38635
Band 151 starts at 144.295 MHz and has power 72927
Band 152 starts at 144.297 MHz and has power 187574
Band 153 starts at 144.299 MHz and has power 1.23797e+06
Band 154 starts at 144.301 MHz and has power 3.54064e+06
Band 155 starts at 144.303 MHz and has power 260385
Band 156 starts at 144.305 MHz and has power 90073
Band 157 starts at 144.307 MHz and has power 48924
Band 158 starts at 144.309 MHz and has power 69522
Band 159 starts at 144.311 MHz and has power 9174
Band 160 starts at 144.312 MHz and has power 8282
Band 161 starts at 144.314 MHz and has power 6145
Band 162 starts at 144.316 MHz and has power 3649
Band 163 starts at 144.318 MHz and has power 31159
Band 164 starts at 144.32 MHz and has power 4938
Band 165 starts at 144.322 MHz and has power 3310
Band 166 starts at 144.324 MHz and has power 2349
Band 167 starts at 144.326 MHz and has power 1152
Band 168 starts at 144.328 MHz and has power 6467
Band 169 starts at 144.33 MHz and has power 2921
Band 170 starts at 144.332 MHz and has power 2108
Band 171 starts at 144.334 MHz and has power 1687
Band 172 starts at 144.336 MHz and has power 1405
Band 173 starts at 144.338 MHz and has power 2480
Band 174 starts at 144.34 MHz and has power 1700
Band 175 starts at 144.342 MHz and has power 1461
Band 176 starts at 144.344 MHz and has power 1831
Band 178 starts at 144.348 MHz and has power 1040
Band 179 starts at 144.35 MHz and has power 1084
Band 180 starts at 144.352 MHz and has power 1190
Band 181 starts at 144.353 MHz and has power 3003
Band 186 starts at 144.363 MHz and has power 1150
Band 191 starts at 144.373 MHz and has power 1027
Band 196 starts at 144.383 MHz and has power 1058
Band 214 starts at 144.418 MHz and has power 1021
Band 759 starts at 145.482 MHz and has power 2543
Band 763 starts at 145.49 MHz and has power 4453
Band 764 starts at 145.492 MHz and has power 4017
Band 768 starts at 145.5 MHz and has power 10502
Band 772 starts at 145.508 MHz and has power 1019
Band 773 starts at 145.51 MHz and has power 7152
Band 777 starts at 145.517 MHz and has power 1639
Band 778 starts at 145.519 MHz and has power 1212
Band 973 starts at 145.9 MHz and has power 1246
Band 977 starts at 145.908 MHz and has power 1029
Band 1564 starts at 147.054 MHz and has power 1523
Band 1573 starts at 147.072 MHz and has power 1120
Band 1578 starts at 147.082 MHz and has power 6582
Band 1582 starts at 147.09 MHz and has power 7136
Band 1583 starts at 147.092 MHz and has power 7090
Band 1586 starts at 147.097 MHz and has power 2092
Band 1587 starts at 147.099 MHz and has power 194056
Band 1588 starts at 147.101 MHz and has power 4924
Band 1589 starts at 147.103 MHz and has power 1669
Band 1590 starts at 147.105 MHz and has power 1394
Band 1591 starts at 147.107 MHz and has power 2970
Band 1592 starts at 147.109 MHz and has power 10582
Band 1596 starts at 147.117 MHz and has power 3848
Band 1597 starts at 147.119 MHz and has power 1436