kopia lustrzana https://github.com/cariboulabs/cariboulite
248 wiersze
8.1 KiB
C++
248 wiersze
8.1 KiB
C++
#include <iostream>
|
|
#include <string>
|
|
#include <thread>
|
|
#include <complex>
|
|
#include <cmath>
|
|
|
|
#include <CaribouLite.hpp> // CPP API for CaribouLite
|
|
#include "circular_buffer.hpp" // for circular sample buffer
|
|
|
|
#define SAMPLE_RATE (4000000)
|
|
#define SAMPLE_RATE_CLOSEST (4194304)
|
|
#define MAX_FIFO_SECONDS (2)
|
|
#define MAX_FIFO_SIZE (SAMPLE_RATE_CLOSEST * MAX_FIFO_SECONDS)
|
|
#define TIME_BETWEEN_EPOCHS (5)
|
|
#define TIME_OF_SAMPLING (1)
|
|
#define RX_CHUNK_SAMPLES (8192)
|
|
|
|
// ==========================================================================================
|
|
// The Application context
|
|
// ==========================================================================================
|
|
|
|
typedef enum
|
|
{
|
|
app_state_setup = 0,
|
|
app_state_sampling = 1,
|
|
app_state_sleeping = 2,
|
|
} appState_en;
|
|
|
|
typedef struct
|
|
{
|
|
// device
|
|
CaribouLite* cl;
|
|
CaribouLiteRadio* radio;
|
|
|
|
// operational parameters
|
|
bool running;
|
|
float freq;
|
|
float gain;
|
|
size_t num_samples_read_so_far;
|
|
size_t epoch;
|
|
appState_en state;
|
|
bool requested_to_quit;
|
|
|
|
// buffers & threads
|
|
circular_buffer<std::complex<float>> *rx_fifo;
|
|
std::thread *dsp_thread;
|
|
} appContext_st;
|
|
|
|
static appContext_st app = {0};
|
|
|
|
// ==========================================================================================
|
|
// General printing and detection of the board
|
|
// ==========================================================================================
|
|
void printInfo(CaribouLite& cl)
|
|
{
|
|
std::cout << "Initialized CaribouLite: " << cl.IsInitialized() << std::endl;
|
|
std::cout << "API Versions: " << cl.GetApiVersion() << std::endl;
|
|
std::cout << "Hardware Serial Number: " << std::hex << cl.GetHwSerialNumber() << std::endl;
|
|
std::cout << "System Type: " << cl.GetSystemVersionStr() << std::endl;
|
|
std::cout << "Hardware Unique ID: " << cl.GetHwGuid() << std::endl;
|
|
}
|
|
|
|
// Detect boards
|
|
void detectBoard()
|
|
{
|
|
CaribouLite::SysVersion ver;
|
|
std::string name;
|
|
std::string guid;
|
|
|
|
if (CaribouLite::DetectBoard(&ver, name, guid))
|
|
{
|
|
std::cout << "Detected Version: " << CaribouLite::GetSystemVersionStr(ver) << ", Name: " << name << ", GUID: " << guid << std::endl;
|
|
}
|
|
else
|
|
{
|
|
std::cout << "Undetected CaribouLite!" << std::endl;
|
|
}
|
|
}
|
|
|
|
// Print radio information
|
|
void printRadioInformation(CaribouLiteRadio* radio)
|
|
{
|
|
std::cout << "Radio Name: " << radio->GetRadioName() << " MtuSize: " << std::dec << radio->GetNativeMtuSample() << " Samples" << std::endl;
|
|
|
|
std::vector<CaribouLiteFreqRange> range = radio->GetFrequencyRange();
|
|
std::cout << "Frequency Regions:" << std::endl;
|
|
for (int i = 0; i < range.size(); i++)
|
|
{
|
|
std::cout << " " << i << ": " << range[i] << std::endl;
|
|
}
|
|
}
|
|
|
|
// ==========================================================================================
|
|
// DSP Thread - The thread that processes data whenever it is available and its
|
|
// helper sub-functions
|
|
// ==========================================================================================
|
|
|
|
// Helper DSP function example: calculate the RSSI
|
|
float RSSI(const std::complex<float>* signal, size_t num_of_samples)
|
|
{
|
|
if (num_of_samples == 0)
|
|
{
|
|
return 0.0f;
|
|
}
|
|
|
|
float sum_of_squares = 0.0f;
|
|
for (size_t i = 0; i < num_of_samples && i < num_of_samples; ++i)
|
|
{
|
|
float vrms = std::norm(signal[i]);
|
|
sum_of_squares += vrms * vrms / 100.0;
|
|
}
|
|
|
|
float mean_of_squares = sum_of_squares / num_of_samples;
|
|
|
|
// Convert RMS value to dBm
|
|
return 10 * log10(mean_of_squares);
|
|
}
|
|
|
|
// Consumer (parallel DSP) thread
|
|
void dataConsumerThread(appContext_st* app)
|
|
{
|
|
std::cout << "Data consumer thread started" << std::endl;
|
|
std::complex<float> local_dsp_buffer[RX_CHUNK_SAMPLES * 4];
|
|
|
|
|
|
while (app->running)
|
|
{
|
|
// get the number of elements in the fifo
|
|
size_t num_lements = app->rx_fifo->size();
|
|
if (num_lements == 0)
|
|
{
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
|
continue;
|
|
}
|
|
|
|
size_t actual_read = app->rx_fifo->get(local_dsp_buffer, (num_lements>(RX_CHUNK_SAMPLES*4)) ? (RX_CHUNK_SAMPLES * 4) : num_lements);
|
|
if (actual_read)
|
|
{
|
|
// here goes the DSP
|
|
app->num_samples_read_so_far += actual_read;
|
|
float rssi = RSSI(local_dsp_buffer, actual_read);
|
|
printf("DSP EPOCH: %ld, NUM_READ: %ld, RSSI: %.3f\n", app->epoch, app->num_samples_read_so_far, rssi);
|
|
}
|
|
}
|
|
|
|
std::cout << "Data consumer thread exitting" << std::endl;
|
|
}
|
|
|
|
// ==========================================================================================
|
|
// Asynchronous API for receiving data and managing data flow in RX
|
|
// ==========================================================================================
|
|
// Rx Callback (async)
|
|
void receivedSamples(CaribouLiteRadio* radio, const std::complex<float>* samples, CaribouLiteMeta* sync, size_t num_samples)
|
|
{
|
|
/*for (int i = 0; i < 6; i ++)
|
|
{
|
|
std::cout << "[" << samples[i].real() << ", " << samples[i].imag() << "]";
|
|
}
|
|
std::cout << std::endl;
|
|
*/
|
|
// push the received samples in the fifo
|
|
app.rx_fifo->put(samples, num_samples);
|
|
}
|
|
|
|
// ==========================================================================================
|
|
// A simple asynchronous DSP flow
|
|
// ==========================================================================================
|
|
int main ()
|
|
{
|
|
// try detecting the board before getting the instance
|
|
detectBoard();
|
|
CaribouLite &cl = CaribouLite::GetInstance();
|
|
printInfo(cl);
|
|
|
|
// get the radios
|
|
CaribouLiteRadio *s1g = cl.GetRadioChannel(CaribouLiteRadio::RadioType::S1G);
|
|
printRadioInformation(s1g);
|
|
|
|
// create the application context
|
|
app.cl = &cl;
|
|
app.radio = s1g;
|
|
app.freq = 900000000;
|
|
app.gain = 69;
|
|
app.rx_fifo = new circular_buffer<std::complex<float>>(MAX_FIFO_SIZE);
|
|
app.num_samples_read_so_far = 0;
|
|
app.state = app_state_sampling;
|
|
app.epoch = 0;
|
|
app.requested_to_quit = false;
|
|
app.running = true;
|
|
app.dsp_thread = new std::thread(dataConsumerThread, &app);
|
|
|
|
// time management
|
|
while (1)
|
|
{
|
|
switch(app.state)
|
|
{
|
|
//----------------------------------------------
|
|
case app_state_setup:
|
|
std::cout << "Starting Epoch " << app.epoch << std::endl;
|
|
// an example periodic radio setup stage
|
|
app.radio->SetRxGain(app.gain);
|
|
app.radio->SetFrequency(app.freq);
|
|
app.num_samples_read_so_far = 0;
|
|
|
|
// and now go directly to the next sampling stage
|
|
app.state = app_state_sampling;
|
|
break;
|
|
|
|
//----------------------------------------------
|
|
case app_state_sampling:
|
|
// start receiving
|
|
app.radio->StartReceiving(receivedSamples, RX_CHUNK_SAMPLES);
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(TIME_OF_SAMPLING * 1000));
|
|
|
|
// stop receiving
|
|
app.radio->StopReceiving();
|
|
app.state = app_state_sleeping;
|
|
break;
|
|
|
|
//----------------------------------------------
|
|
case app_state_sleeping:
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(TIME_BETWEEN_EPOCHS * 1000));
|
|
app.epoch ++;
|
|
|
|
// reset the fifo
|
|
app.rx_fifo->reset();
|
|
|
|
// either go the next epoch or quit the program
|
|
// this is an example flow but other possibilities exist
|
|
if (!app.requested_to_quit) app.state = app_state_setup;
|
|
else break;
|
|
break;
|
|
|
|
//----------------------------------------------
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
// cleanup - done by the creator
|
|
app.running = false;
|
|
app.dsp_thread->join();
|
|
delete app.dsp_thread;
|
|
delete app.rx_fifo;
|
|
|
|
return 0;
|
|
}
|