mirror of
https://github.com/nqrduck/LimeDriver.git
synced 2024-11-25 11:32:27 +00:00
1848 lines
64 KiB
C++
1848 lines
64 KiB
C++
/**
|
|
@file N pulses
|
|
@author Andrin Doll
|
|
@brief N pulses on TX and collection on RX
|
|
|
|
|
|
requirements:
|
|
LimeSuite
|
|
HDF5 library
|
|
|
|
compilation:
|
|
$(h5c++ -show) limedriver.cpp -std=c++11 $(pkg-config --cflags --libs LimeSuite) -o limedriver
|
|
|
|
*/
|
|
|
|
#include "H5Cpp.h"
|
|
#include "lime/LimeSuite.h"
|
|
#include <chrono>
|
|
#include <fstream>
|
|
#include <iomanip>
|
|
#include <iostream>
|
|
#include <math.h>
|
|
#include <sstream>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
#include <errno.h> // errno, ENOENT, EEXIST
|
|
#include <sys/stat.h> // stat
|
|
#include <sys/types.h>
|
|
#if defined(_WIN32)
|
|
#include <direct.h> // _mkdir
|
|
#endif
|
|
|
|
using namespace std;
|
|
|
|
// structure that holds all the relevant parameters for a N-pulse experiment.
|
|
// See initialization below for description
|
|
const int maxNpulse = 50;
|
|
struct LimeConfig_t {
|
|
|
|
float srate;
|
|
float frq;
|
|
float frq_set;
|
|
float RX_LPF;
|
|
float TX_LPF;
|
|
int RX_gain;
|
|
int TX_gain;
|
|
int TX_IcorrDC;
|
|
int TX_QcorrDC;
|
|
int RX_gain_rback[4];
|
|
int TX_gain_rback[3];
|
|
|
|
int Npulses;
|
|
double *p_dur;
|
|
int *p_dur_smp;
|
|
int *p_offs;
|
|
double *p_amp;
|
|
double *p_frq;
|
|
double *p_frq_smp;
|
|
double *p_pha;
|
|
int *p_phacyc_N;
|
|
int *p_phacyc_lev;
|
|
|
|
int *p_c0_en;
|
|
int *p_c1_en;
|
|
int *p_c2_en;
|
|
int *p_c3_en;
|
|
|
|
int c0_tim[4];
|
|
int c1_tim[4];
|
|
int c2_tim[4];
|
|
int c3_tim[4];
|
|
|
|
int c0_synth[5];
|
|
int c1_synth[5];
|
|
int c2_synth[5];
|
|
int c3_synth[5];
|
|
|
|
int averages;
|
|
int repetitions;
|
|
bool pcyc_bef_avg;
|
|
double reptime_secs;
|
|
double rectime_secs;
|
|
int reptime_smps;
|
|
int rectime_smps;
|
|
int buffersize;
|
|
|
|
string file_pattern;
|
|
string file_stamp;
|
|
string save_path;
|
|
int override_save;
|
|
|
|
string stamp_start;
|
|
string stamp_end;
|
|
};
|
|
|
|
// structure that will be used to map LimeConfig to HDF attribute
|
|
struct Config2HDFattr_t {
|
|
string arg;
|
|
H5std_string Name;
|
|
H5::DataType dType;
|
|
void *Value;
|
|
hsize_t dim;
|
|
};
|
|
|
|
// Device structure, should be initialize to NULL
|
|
static lms_device_t *device = NULL;
|
|
|
|
// LMS error function
|
|
int error() {
|
|
if (device != NULL)
|
|
LMS_Close(device);
|
|
exit(-1);
|
|
}
|
|
|
|
// portable way to check and create directory (from stackexchange)
|
|
// https://stackoverflow.com/questions/675039/how-can-i-create-directory-tree-in-c-linux
|
|
bool isDirExist(const std::string &path) {
|
|
#if defined(_WIN32)
|
|
struct _stat info;
|
|
if (_stat(path.c_str(), &info) != 0) {
|
|
return false;
|
|
}
|
|
return (info.st_mode & _S_IFDIR) != 0;
|
|
#else
|
|
struct stat info;
|
|
if (stat(path.c_str(), &info) != 0) {
|
|
return false;
|
|
}
|
|
return (info.st_mode & S_IFDIR) != 0;
|
|
#endif
|
|
}
|
|
|
|
bool makePath(const std::string &path) {
|
|
#if defined(_WIN32)
|
|
int ret = _mkdir(path.c_str());
|
|
#else
|
|
mode_t mode = 0755;
|
|
int ret = mkdir(path.c_str(), mode);
|
|
#endif
|
|
if (ret == 0)
|
|
return true;
|
|
|
|
switch (errno) {
|
|
case ENOENT:
|
|
// parent didn't exist, try to create it
|
|
{
|
|
int pos = path.find_last_of('/');
|
|
if (pos == std::string::npos)
|
|
#if defined(_WIN32)
|
|
pos = path.find_last_of('\\');
|
|
if (pos == std::string::npos)
|
|
#endif
|
|
return false;
|
|
if (!makePath(path.substr(0, pos)))
|
|
return false;
|
|
}
|
|
// now, try to create again
|
|
#if defined(_WIN32)
|
|
return 0 == _mkdir(path.c_str());
|
|
#else
|
|
return 0 == mkdir(path.c_str(), mode);
|
|
#endif
|
|
|
|
case EEXIST:
|
|
// done!
|
|
return isDirExist(path);
|
|
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
inline bool file_exists(const std::string &name) {
|
|
struct stat buffer;
|
|
return (stat(name.c_str(), &buffer) == 0);
|
|
}
|
|
|
|
// Custom function to read back the gain of the RX/TX channels. The API function
|
|
// GetGaindB has to be avoided, as it also modifies the gain, which is useless
|
|
// and dangerous..
|
|
// int GetGainRXTX(array< int, 4>* RXgain, array<int,3>* TXgain) {
|
|
int GetGainRXTX(int *RXgain, int *TXgain) {
|
|
// RX gain: LNA, TIA and PGA
|
|
uint16_t gain_lna, gain_tia, gain_pga;
|
|
if (LMS_ReadParam(device, LMS7_G_LNA_RFE, &gain_lna) != 0)
|
|
error();
|
|
if (LMS_ReadParam(device, LMS7_G_TIA_RFE, &gain_tia) != 0)
|
|
error();
|
|
if (LMS_ReadParam(device, LMS7_G_PGA_RBB, &gain_pga) != 0)
|
|
error();
|
|
// convert to actual gain
|
|
|
|
// TX gain: PAD and TBB
|
|
uint16_t gain_pad, gain_tbb;
|
|
if (LMS_ReadParam(device, LMS7_LOSS_LIN_TXPAD_TRF, &gain_pad) != 0)
|
|
error();
|
|
if (LMS_ReadParam(device, LMS7_CG_IAMP_TBB, &gain_tbb) != 0)
|
|
error();
|
|
|
|
// convert to actual gain
|
|
// TXpad
|
|
const int pmax = 52;
|
|
if (gain_pad > 10)
|
|
TXgain[1] = pmax - 10 - 2 * (gain_pad - 10);
|
|
else
|
|
TXgain[1] = pmax - gain_pad;
|
|
|
|
// TBB gain: linear to dB. Impossible to obtain like this. It is calibrated to
|
|
// an optimum value, called opt_gain_tbb. This is only available if a
|
|
// calibration is done. However, as long as the TXgain is below 43+12 = 55 dB,
|
|
// gain is fully determined by TX PAD
|
|
TXgain[2] = gain_tbb;
|
|
// TXgain[2] = 20.0*log10((float_type)gain_tbb / (float_type) opt_gain_tbb);
|
|
// from LMS7002.cpp
|
|
|
|
// RFE LNA
|
|
int gmax = 30;
|
|
if (gain_lna >= 9)
|
|
RXgain[1] = gmax + (gain_lna - 15);
|
|
else
|
|
RXgain[1] = gmax + 3 * (gain_lna - 11);
|
|
|
|
// RFE TIA
|
|
gmax = 12;
|
|
switch (gain_tia) {
|
|
case 3:
|
|
RXgain[2] = gmax - 0;
|
|
break;
|
|
case 2:
|
|
RXgain[2] = gmax - 3;
|
|
break;
|
|
case 1:
|
|
RXgain[2] = gmax - 12;
|
|
break;
|
|
}
|
|
|
|
// RBB PGA
|
|
RXgain[3] = gain_pga - 12;
|
|
|
|
// Sum to first element, adding that mysterious +12+0.5 as it is done in the
|
|
// API
|
|
RXgain[0] = RXgain[1] + RXgain[2] + RXgain[3] + 12 + 0.5;
|
|
TXgain[0] = TXgain[1] + 0 * TXgain[2] + 12 + 0.5;
|
|
|
|
// Print in function to ease debugging
|
|
cout << "TX: " << TXgain[0] << " dB : " << TXgain[1] << " dB PAD, "
|
|
<< TXgain[2] << " setting of BB + 12 dB" << endl;
|
|
cout << "RX: " << RXgain[0] << " dB : " << RXgain[1] << " dB LNA, "
|
|
<< RXgain[2] << " dB TIA, " << RXgain[3] << " dB PGA + 12 dB" << endl;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int main(int argc, char **argv) {
|
|
const double pi = acos(-1);
|
|
|
|
LimeConfig_t LimeCfg;
|
|
|
|
LimeCfg.Npulses = 2; // Number of pulses, default value
|
|
|
|
// check if nPulses has been given as argument, so that all the arrays are
|
|
// initialized with proper size
|
|
for (int ii_arg = 1; ii_arg < argc; ii_arg++) {
|
|
if (strcmp(argv[ii_arg], "-npu") == 0 && ii_arg + 1 < argc) {
|
|
LimeCfg.Npulses = atoi(argv[ii_arg + 1]);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------------
|
|
// set all the DEFAULT parameters. Command line arguments allow for
|
|
// modification!
|
|
LimeCfg.srate = 30.72e6; // sample rate of the IF DAC/ADC
|
|
LimeCfg.frq = 50e6; // LO carrier frequency
|
|
LimeCfg.RX_gain = 20; // total gain of the receiver
|
|
LimeCfg.TX_gain = 30; // total gain of the transmitter
|
|
LimeCfg.RX_LPF = 5e6; // IF lowpass of the receiver
|
|
LimeCfg.TX_LPF = 130e6; // IF lowpass of the transmitter
|
|
|
|
LimeCfg.TX_IcorrDC =
|
|
-32; // DC corr to TX mixer at IF (evaluate with LimeSuiteGUI)
|
|
LimeCfg.TX_QcorrDC = 50; // DC corr to TX mixer at IF
|
|
|
|
// Allocate the arrays with pulse parametes
|
|
LimeCfg.p_dur = new double[LimeCfg.Npulses]; // pulse duration (secs)
|
|
LimeCfg.p_offs = new int[LimeCfg.Npulses]; // pulse time offset
|
|
LimeCfg.p_amp = new double[LimeCfg.Npulses]; // pulse digital IF amplitude
|
|
LimeCfg.p_frq =
|
|
new double[LimeCfg.Npulses]; // pulse digital IF frequency (unit: Hz)
|
|
LimeCfg.p_pha = new double[LimeCfg.Npulses]; // pulse digital IF phase
|
|
LimeCfg.p_phacyc_N =
|
|
new int[LimeCfg.Npulses]; // number of pulse phases (cycled within 2*pi,
|
|
// must be at least 1)
|
|
LimeCfg.p_phacyc_lev =
|
|
new int[LimeCfg.Npulses]; // stacking level of phase cycle (for eventual
|
|
// coupling)
|
|
LimeCfg.p_c0_en = new int[LimeCfg.Npulses]; // pulse-wise enable of marker c0
|
|
LimeCfg.p_c1_en = new int[LimeCfg.Npulses]; // pulse-wise enable of marker c1
|
|
LimeCfg.p_c2_en = new int[LimeCfg.Npulses]; // pulse-wise enable of marker c2
|
|
LimeCfg.p_c3_en = new int[LimeCfg.Npulses]; // pulse-wise enable of marker c3
|
|
|
|
// and set standard values
|
|
for (int ii = 0; ii < LimeCfg.Npulses; ii++) {
|
|
|
|
LimeCfg.p_dur[ii] = 2e-6;
|
|
LimeCfg.p_offs[ii] =
|
|
(4080 * 3) /
|
|
(LimeCfg.Npulses + 1); // distribute them evenly within the buffer...
|
|
LimeCfg.p_amp[ii] = 1.0;
|
|
LimeCfg.p_frq[ii] = 4.0 / LimeCfg.p_dur[0];
|
|
LimeCfg.p_pha[ii] = 0.0;
|
|
LimeCfg.p_phacyc_N[ii] = 1;
|
|
LimeCfg.p_phacyc_lev[ii] = 0;
|
|
LimeCfg.p_c0_en[ii] = 1;
|
|
LimeCfg.p_c1_en[ii] = 1;
|
|
LimeCfg.p_c2_en[ii] = 1;
|
|
LimeCfg.p_c3_en[ii] = 1;
|
|
}
|
|
|
|
// Timing of TTL controls: [enabled? , pre, offs, post]
|
|
int c0_tim[4] = {0, 70, 56, -5};
|
|
int c1_tim[4] = {0, 70, 56, -5};
|
|
int c2_tim[4] = {0, 70, 56, -5};
|
|
int c3_tim[4] = {0, 70, 56, -5};
|
|
|
|
// Use TTL channel as synth: [enabled? , half-period, strt, PSK shift, PSK
|
|
// adv]
|
|
int c0_synth[5] = {0, 500, 0, 0, 0};
|
|
int c1_synth[5] = {0, 500, 0, 0, 0};
|
|
int c2_synth[5] = {0, 500, 0, 0, 0};
|
|
int c3_synth[5] = {0, 500, 0, 0, 0};
|
|
|
|
LimeCfg.averages = 6; // number of averages
|
|
LimeCfg.repetitions = 4; // number of repetions
|
|
LimeCfg.reptime_secs = 4e-3; // repetition time
|
|
LimeCfg.rectime_secs = 0.2e-3; // duration of acquisition window
|
|
LimeCfg.buffersize = 4080 * 3; // number of samples in buffer
|
|
LimeCfg.pcyc_bef_avg = 0; // phase cycle before average
|
|
|
|
LimeCfg.file_pattern = "test"; // identifier when saving the file
|
|
LimeCfg.save_path = "./asdf/"; // path to save the file to
|
|
LimeCfg.override_save = 0; // default: save data
|
|
|
|
// that's it for the parameters
|
|
// ----------------------------------------------------------------------------------
|
|
|
|
// .. copy here those arrays ...
|
|
memcpy(LimeCfg.c0_tim, c0_tim, 4 * sizeof *LimeCfg.c0_tim);
|
|
memcpy(LimeCfg.c1_tim, c1_tim, 4 * sizeof *LimeCfg.c1_tim);
|
|
memcpy(LimeCfg.c2_tim, c2_tim, 4 * sizeof *LimeCfg.c2_tim);
|
|
memcpy(LimeCfg.c3_tim, c3_tim, 4 * sizeof *LimeCfg.c3_tim);
|
|
memcpy(LimeCfg.c0_synth, c0_synth, 5 * sizeof *LimeCfg.c0_synth);
|
|
memcpy(LimeCfg.c1_synth, c1_synth, 5 * sizeof *LimeCfg.c1_synth);
|
|
memcpy(LimeCfg.c2_synth, c2_synth, 5 * sizeof *LimeCfg.c2_synth);
|
|
memcpy(LimeCfg.c3_synth, c3_synth, 5 * sizeof *LimeCfg.c3_synth);
|
|
|
|
// and add the timestamp for the file
|
|
auto now = std::chrono::system_clock::now();
|
|
auto itt = std::chrono::system_clock::to_time_t(now);
|
|
std::ostringstream stringstream;
|
|
stringstream << std::put_time(localtime(&itt), "%G%m%d_%H%M%S");
|
|
LimeCfg.file_stamp = stringstream.str();
|
|
LimeCfg.stamp_start = stringstream.str();
|
|
LimeCfg.stamp_end =
|
|
stringstream.str(); // will be overwritten just before data is written
|
|
|
|
// allocate other variables that depend on Npulses
|
|
LimeCfg.p_dur_smp = new int[LimeCfg.Npulses];
|
|
LimeCfg.p_frq_smp = new double[LimeCfg.Npulses];
|
|
|
|
// LimeCfg as attributes for writing to HDF and for parsing command line input
|
|
// This is all done 'manually', since there is no reflection in cpp.. at least
|
|
// not by default
|
|
struct Config2HDFattr_t HDFattr[] = {
|
|
{"sra", "SampleRate [Hz]", H5::PredType::IEEE_F32LE, &LimeCfg.srate, 1},
|
|
{"lof", "LO Frequency [Hz]", H5::PredType::IEEE_F32LE, &LimeCfg.frq, 1},
|
|
{"rlp", "RX LowPass BW [Hz]", H5::PredType::IEEE_F32LE, &LimeCfg.RX_LPF,
|
|
1},
|
|
{"tlp", "TX LowPass BW [Hz]", H5::PredType::IEEE_F32LE, &LimeCfg.TX_LPF,
|
|
1},
|
|
{"rgn", "RX Gain [dB]", H5::PredType::NATIVE_INT, &LimeCfg.RX_gain, 1},
|
|
{"tgn", "TX Gain [dB]", H5::PredType::NATIVE_INT, &LimeCfg.TX_gain, 1},
|
|
{"///", "RX Gain readback [dB]", H5::PredType::NATIVE_INT,
|
|
&LimeCfg.RX_gain_rback, 4},
|
|
{"///", "TX Gain readback [dB]", H5::PredType::NATIVE_INT,
|
|
&LimeCfg.TX_gain_rback, 3},
|
|
{"tdq", "TX DC-correction Q", H5::PredType::NATIVE_INT,
|
|
&LimeCfg.TX_QcorrDC, 1},
|
|
{"tdi", "TX DC-correction I", H5::PredType::NATIVE_INT,
|
|
&LimeCfg.TX_IcorrDC, 1},
|
|
{"npu", "Number of Pulses", H5::PredType::NATIVE_INT, &LimeCfg.Npulses,
|
|
1},
|
|
{"pdr", "Pulse Duration [s]", H5::PredType::IEEE_F64LE, LimeCfg.p_dur,
|
|
(hsize_t)LimeCfg.Npulses},
|
|
{"pof", "Pulse Offset [Sa]", H5::PredType::NATIVE_INT, LimeCfg.p_offs,
|
|
(hsize_t)LimeCfg.Npulses},
|
|
{"pam", "IF Pulse Amplitude", H5::PredType::IEEE_F64LE, LimeCfg.p_amp,
|
|
(hsize_t)LimeCfg.Npulses},
|
|
{"pfr", "IF Pulse Frequency [Hz]", H5::PredType::IEEE_F64LE,
|
|
LimeCfg.p_frq, (hsize_t)LimeCfg.Npulses},
|
|
{"pph", "IF Pulse Phase", H5::PredType::IEEE_F64LE, LimeCfg.p_pha,
|
|
(hsize_t)LimeCfg.Npulses},
|
|
{"pcn", "Nmbr of Phasecycles", H5::PredType::NATIVE_INT,
|
|
LimeCfg.p_phacyc_N, (hsize_t)LimeCfg.Npulses},
|
|
{"pcl", "Level of Phasecycle", H5::PredType::NATIVE_INT,
|
|
LimeCfg.p_phacyc_lev, (hsize_t)LimeCfg.Npulses},
|
|
{"///", "Pulse Duration [Sa]", H5::PredType::NATIVE_INT,
|
|
LimeCfg.p_dur_smp, (hsize_t)LimeCfg.Npulses},
|
|
{"///", "IF Pulse Frequency [1/Sa]", H5::PredType::IEEE_F64LE,
|
|
LimeCfg.p_frq_smp, (hsize_t)LimeCfg.Npulses},
|
|
{"t0d", "Trigger0 Timing [Sa]", H5::PredType::NATIVE_INT, &LimeCfg.c0_tim,
|
|
4},
|
|
{"t1d", "Trigger1 Timing [Sa]", H5::PredType::NATIVE_INT, &LimeCfg.c1_tim,
|
|
4},
|
|
{"t2d", "Trigger2 Timing [Sa]", H5::PredType::NATIVE_INT, &LimeCfg.c2_tim,
|
|
4},
|
|
{"t3d", "Trigger3 Timing [Sa]", H5::PredType::NATIVE_INT, &LimeCfg.c3_tim,
|
|
4},
|
|
{"t0s", "Trigger0 Synth [Sa]", H5::PredType::NATIVE_INT,
|
|
&LimeCfg.c0_synth, 5},
|
|
{"t1s", "Trigger1 Synth [Sa]", H5::PredType::NATIVE_INT,
|
|
&LimeCfg.c1_synth, 5},
|
|
{"t2s", "Trigger2 Synth [Sa]", H5::PredType::NATIVE_INT,
|
|
&LimeCfg.c2_synth, 5},
|
|
{"t3s", "Trigger3 Synth [Sa]", H5::PredType::NATIVE_INT,
|
|
&LimeCfg.c3_synth, 5},
|
|
{"t0p", "Trigger0 Enable", H5::PredType::NATIVE_INT, LimeCfg.p_c0_en,
|
|
(hsize_t)LimeCfg.Npulses},
|
|
{"t1p", "Trigger1 Enable", H5::PredType::NATIVE_INT, LimeCfg.p_c1_en,
|
|
(hsize_t)LimeCfg.Npulses},
|
|
{"t2p", "Trigger2 Enable", H5::PredType::NATIVE_INT, LimeCfg.p_c2_en,
|
|
(hsize_t)LimeCfg.Npulses},
|
|
{"t3p", "Trigger3 Enable", H5::PredType::NATIVE_INT, LimeCfg.p_c3_en,
|
|
(hsize_t)LimeCfg.Npulses},
|
|
{"nrp", "Nmbr of Repetitions", H5::PredType::NATIVE_INT,
|
|
&LimeCfg.repetitions, 1},
|
|
{"nav", "Nmbr of Averages", H5::PredType::NATIVE_INT, &LimeCfg.averages,
|
|
1},
|
|
{"trp", "Repetition Time [s]", H5::PredType::IEEE_F64LE,
|
|
&LimeCfg.reptime_secs, 1},
|
|
{"tac", "Acquisition Time [s]", H5::PredType::IEEE_F64LE,
|
|
&LimeCfg.rectime_secs, 1},
|
|
{"///", "Repetition Time [Sa]", H5::PredType::NATIVE_INT,
|
|
&LimeCfg.reptime_smps, 1},
|
|
{"///", "Acquisition Time [Sa]", H5::PredType::NATIVE_INT,
|
|
&LimeCfg.rectime_smps, 1},
|
|
{"bsz", "Buffersize", H5::PredType::NATIVE_INT, &LimeCfg.buffersize, 1},
|
|
{"pba", "Pcyc before Avg if >0", H5::PredType::NATIVE_INT,
|
|
&LimeCfg.pcyc_bef_avg, 1},
|
|
{"fpa", "Filename Pattern",
|
|
H5::StrType(H5::PredType::C_S1, LimeCfg.file_pattern.length() + 1),
|
|
(void *)LimeCfg.file_pattern.c_str(), 1},
|
|
{"spt", "Save Path",
|
|
H5::StrType(H5::PredType::C_S1, LimeCfg.save_path.length() + 1),
|
|
(void *)LimeCfg.save_path.c_str(), 1},
|
|
{"nos", "Don't save if >0", H5::PredType::NATIVE_INT,
|
|
&LimeCfg.override_save, 1},
|
|
{"fst", "Filename Timestamp",
|
|
H5::StrType(H5::PredType::C_S1, LimeCfg.file_stamp.length() + 1),
|
|
(void *)LimeCfg.file_stamp.c_str(), 1},
|
|
{"///", "Exp Start Timestamp",
|
|
H5::StrType(H5::PredType::C_S1, LimeCfg.stamp_start.length() + 1),
|
|
(void *)LimeCfg.stamp_start.c_str(), 1},
|
|
{"///", "Exp End Timestamp",
|
|
H5::StrType(H5::PredType::C_S1, LimeCfg.stamp_end.length() + 1),
|
|
(void *)LimeCfg.stamp_end.c_str(), 1}};
|
|
int no_of_attr = sizeof(HDFattr) / sizeof(Config2HDFattr_t);
|
|
|
|
// iterate through arguments to parse eventual user input
|
|
// (exposing the actual content of the struct to python would be nicer...)
|
|
bool parse_prob = false;
|
|
int curr_attr = -1;
|
|
int curr_attr_last = -1;
|
|
int attr2read = 0;
|
|
int attr2read_last = 0;
|
|
int attr_read = 0;
|
|
for (int ii_arg = 1; ii_arg < argc; ii_arg++) {
|
|
|
|
// get the attribute for the argument based on '-' (which also is there for
|
|
// negative numbers..)
|
|
if (argv[ii_arg][0] == '-') {
|
|
|
|
if ((strlen(argv[ii_arg] + 1) != 3) && (attr2read == 0)) {
|
|
cout << "Invalid argument " << ii_arg << ": " << argv[ii_arg] << endl;
|
|
parse_prob = true;
|
|
continue;
|
|
}
|
|
// find matching attribute
|
|
curr_attr_last = curr_attr;
|
|
attr2read_last = attr2read;
|
|
curr_attr = -1;
|
|
for (int ii_attr = 0; ii_attr < no_of_attr; ii_attr++) {
|
|
if (strcmp(argv[ii_arg] + 1, HDFattr[ii_attr].arg.c_str()) == 0) {
|
|
curr_attr = ii_attr;
|
|
attr2read = HDFattr[ii_attr].dim;
|
|
attr_read = 0;
|
|
cout << "Found argument " << HDFattr[curr_attr].arg << ": "
|
|
<< HDFattr[curr_attr].Name << endl;
|
|
}
|
|
}
|
|
// found nothing
|
|
if (curr_attr == -1 && attr2read_last == 0) {
|
|
cout << "Could not find valid attribute for argument " << ii_arg << ": "
|
|
<< argv[ii_arg] << endl;
|
|
parse_prob = true;
|
|
// found something, but did not read the previous arguments
|
|
} else if (curr_attr > -1 && attr2read_last > 0) {
|
|
cout << "Missing argument: " << attr2read_last
|
|
<< " value missing for argument " << HDFattr[curr_attr_last].arg
|
|
<< endl;
|
|
parse_prob = true;
|
|
}
|
|
// found nothing and did not read the previous arguments: a negative
|
|
// number
|
|
if (curr_attr == -1 && attr2read_last > 0) {
|
|
// restore the attribute and it as number
|
|
curr_attr = curr_attr_last;
|
|
attr2read = attr2read_last;
|
|
} else
|
|
// all other cases: jump to the next argument
|
|
continue;
|
|
}
|
|
|
|
// parse the value from the current attribute
|
|
if (curr_attr != -1 && attr2read != 0) {
|
|
|
|
// differentiate between the different types of input based on the
|
|
// H5::DataType float values
|
|
if (HDFattr[curr_attr].dType == H5::PredType::IEEE_F32LE) {
|
|
*((float *)HDFattr[curr_attr].Value + attr_read) = atof(argv[ii_arg]);
|
|
attr2read--;
|
|
attr_read++;
|
|
// cout << "Got value " << atof(argv[ii_arg]) << " from " <<
|
|
// argv[ii_arg] << endl;
|
|
}
|
|
// double values
|
|
if (HDFattr[curr_attr].dType == H5::PredType::IEEE_F64LE) {
|
|
*((double *)HDFattr[curr_attr].Value + attr_read) =
|
|
(double)atof(argv[ii_arg]);
|
|
attr2read--;
|
|
attr_read++;
|
|
// cout << "Got value " << (double) atof(argv[ii_arg]) << " from " <<
|
|
// argv[ii_arg] << endl;
|
|
}
|
|
// integer values
|
|
if (HDFattr[curr_attr].dType == H5::PredType::NATIVE_INT) {
|
|
*((int *)HDFattr[curr_attr].Value + attr_read) = atoi(argv[ii_arg]);
|
|
attr2read--;
|
|
attr_read++;
|
|
// cout << "Got value " << atoi(argv[ii_arg]) << " from " <<
|
|
// argv[ii_arg] << endl;
|
|
}
|
|
// strings: stored as std::string in LimeCfg and as Cstring in HDFattr..
|
|
// --> explicitly treat strings, these are anyhow just a few for file/path
|
|
// info
|
|
if (strcmp(HDFattr[curr_attr].arg.c_str(), "spt") == 0) {
|
|
LimeCfg.save_path = argv[ii_arg];
|
|
HDFattr[curr_attr].dType =
|
|
H5::StrType(H5::PredType::C_S1, LimeCfg.save_path.length() + 1);
|
|
HDFattr[curr_attr].Value = (void *)LimeCfg.save_path.c_str();
|
|
attr2read--;
|
|
attr_read++;
|
|
}
|
|
if (strcmp(HDFattr[curr_attr].arg.c_str(), "fpa") == 0) {
|
|
LimeCfg.file_pattern = argv[ii_arg];
|
|
HDFattr[curr_attr].dType =
|
|
H5::StrType(H5::PredType::C_S1, LimeCfg.file_pattern.length() + 1);
|
|
HDFattr[curr_attr].Value = (void *)LimeCfg.file_pattern.c_str();
|
|
attr2read--;
|
|
attr_read++;
|
|
}
|
|
if (strcmp(HDFattr[curr_attr].arg.c_str(), "fst") == 0) {
|
|
LimeCfg.file_stamp = argv[ii_arg];
|
|
HDFattr[curr_attr].dType =
|
|
H5::StrType(H5::PredType::C_S1, LimeCfg.file_stamp.length() + 1);
|
|
HDFattr[curr_attr].Value = (void *)LimeCfg.file_stamp.c_str();
|
|
attr2read--;
|
|
attr_read++;
|
|
}
|
|
} else if (attr2read == 0) {
|
|
cout << "Problem with argument " << HDFattr[curr_attr].arg
|
|
<< ": There is an input that is not clear, probably one input more "
|
|
"than required! "
|
|
<< endl;
|
|
parse_prob = true;
|
|
}
|
|
}
|
|
// check if the last argument had all the values
|
|
if (attr2read > 0) {
|
|
cout << "Missing argument: " << attr2read << " value missing for argument "
|
|
<< HDFattr[curr_attr].arg << endl;
|
|
parse_prob = true;
|
|
}
|
|
if (parse_prob) {
|
|
cout << "Exiting due to problem with provided arguments! Valid arguments "
|
|
"are (exept -///, which cannot be set by the user):"
|
|
<< endl;
|
|
string datatype;
|
|
for (int ii_attr = 0; ii_attr < no_of_attr; ii_attr++) {
|
|
|
|
// get the datatype
|
|
if (HDFattr[ii_attr].dType == H5::PredType::IEEE_F32LE)
|
|
datatype = "float";
|
|
else if (HDFattr[ii_attr].dType == H5::PredType::IEEE_F64LE)
|
|
datatype = "double";
|
|
else if (HDFattr[ii_attr].dType == H5::PredType::NATIVE_INT)
|
|
datatype = "int";
|
|
else
|
|
datatype = "string";
|
|
cout << "-" << HDFattr[ii_attr].arg << " " << left << setw(30)
|
|
<< HDFattr[ii_attr].Name << ": " << HDFattr[ii_attr].dim << "x "
|
|
<< datatype << endl;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
// convert input in seconds/Hz to samples
|
|
for (int ii = 0; ii < LimeCfg.Npulses; ii++) {
|
|
LimeCfg.p_dur_smp[ii] = round(LimeCfg.p_dur[ii] * LimeCfg.srate);
|
|
LimeCfg.p_frq_smp[ii] = LimeCfg.p_frq[ii] / LimeCfg.srate;
|
|
}
|
|
|
|
// check directory first
|
|
if (makePath(LimeCfg.save_path) == 0) {
|
|
cout << "Problem entering the specified path: " << LimeCfg.save_path
|
|
<< endl;
|
|
return 1;
|
|
}
|
|
|
|
// Find devices
|
|
int n;
|
|
lms_info_str_t list[8]; // should be large enough to hold all detected devices
|
|
if ((n = LMS_GetDeviceList(list)) <
|
|
0) // NULL can be passed to only get number of devices
|
|
error();
|
|
|
|
cout << "Devices found: " << n << endl; // print number of devices
|
|
if (n < 1)
|
|
return -1;
|
|
|
|
// open the first device
|
|
if (LMS_Open(&device, list[0], NULL))
|
|
error();
|
|
|
|
/*
|
|
//print available antennae names
|
|
//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();
|
|
|
|
// get and print antenna index and name
|
|
if ((n = LMS_GetAntenna(device, LMS_CH_RX, 0)) < 0) error();
|
|
cout << "Automatically selected RX LNA: " << n << ": " <<
|
|
antenna_list[n] << endl;
|
|
*/
|
|
|
|
// Get number of channels
|
|
if ((n = LMS_GetNumChannels(device, LMS_CH_RX)) < 0)
|
|
error();
|
|
cout << "Number of RX channels: " << n << endl;
|
|
if ((n = LMS_GetNumChannels(device, LMS_CH_TX)) < 0)
|
|
error();
|
|
cout << "Number of TX channels: " << n << endl;
|
|
|
|
// check if the settings are already there
|
|
float_type frq_read;
|
|
if (LMS_GetLOFrequency(device, LMS_CH_RX, 0, &frq_read) != 0)
|
|
error();
|
|
|
|
float_type srate_read, rf_rate;
|
|
if (LMS_GetSampleRate(device, LMS_CH_RX, 0, &srate_read, &rf_rate) != 0)
|
|
error();
|
|
|
|
bool frqdev = fabs(frq_read - LimeCfg.frq) > 1.0;
|
|
bool sratedev = fabs(srate_read - LimeCfg.srate) > 1.0;
|
|
|
|
// Getting the gain
|
|
int RXgain[4];
|
|
int TXgain[3];
|
|
GetGainRXTX(RXgain, TXgain);
|
|
|
|
bool rgndev = RXgain[0] != LimeCfg.RX_gain;
|
|
bool tgndev = TXgain[0] != LimeCfg.TX_gain;
|
|
if (TXgain[0] > 55 && LimeCfg.TX_gain > 55) {
|
|
tgndev = false;
|
|
cout << "Unable to check for variation in TXgain setting, since it is "
|
|
"impossible to retrieve it for TXgain > 55 dB without altering the "
|
|
"RF performance. Eventual changes in the TXgain are thus not taken "
|
|
"into account."
|
|
<< endl;
|
|
}
|
|
|
|
/*
|
|
// Similar as with the built in GetGaindB function, the GetLPFBW function is
|
|
also affecting the actual reading of the LPF. It is actually not entirely
|
|
clear why this happens, as compared to the GetGaindB function, where it is
|
|
evident that some calibration is done..
|
|
// Accordingly, one must take care that the LPFBW is set right at the
|
|
beginning when opening the device float_type LPFBW, LPFBW2; // lowpass
|
|
bandwidth if (LMS_GetLPFBW(device, LMS_CH_RX, 0, &LPFBW) != 0) error(); bool
|
|
rlpfdev = LPFBW != LimeCfg.RX_LPF; if (LMS_GetLPFBW(device, LMS_CH_TX, 0,
|
|
&LPFBW2) != 0) error(); bool tlpfdev = LPFBW2 != LimeCfg.TX_LPF;
|
|
*/
|
|
|
|
// initialize if the frequency is different
|
|
// if (frqdev || sratedev || tgndev || rgndev || rlpfdev || tlpfdev || true) {
|
|
if (frqdev || sratedev || tgndev || rgndev) {
|
|
|
|
// just to re-assure why there is another setup
|
|
cout << "Re-initialization of parameters ... " << endl;
|
|
if (frqdev)
|
|
cout << "... due to LOfrequency deviation by " << frq_read - LimeCfg.frq
|
|
<< " from " << LimeCfg.frq << endl;
|
|
if (sratedev)
|
|
cout << "... due to samplerate deviation by "
|
|
<< srate_read - LimeCfg.srate << " from " << LimeCfg.srate << endl;
|
|
if (rgndev)
|
|
cout << "... due to RX gain deviation by " << RXgain[0] - LimeCfg.RX_gain
|
|
<< " from " << LimeCfg.RX_gain << endl;
|
|
if (tgndev)
|
|
cout << "... due to TX gain deviation by " << TXgain[0] - LimeCfg.TX_gain
|
|
<< " from " << LimeCfg.TX_gain << endl;
|
|
// if (rlpfdev) cout << "... due to RX LPF deviation by " << LPFBW -
|
|
// LimeCfg.RX_LPF << " from " << LimeCfg.RX_LPF << endl; if (tlpfdev) cout
|
|
// << "... due to TX LPF deviation by " << LPFBW2 - LimeCfg.TX_LPF << " from
|
|
// " << LimeCfg.TX_LPF << endl;
|
|
|
|
// First mute the TX output, as the init commands create a lot of garbage
|
|
if (LMS_WriteParam(device, LMS7_PD_TLOBUF_TRF, 1) != 0)
|
|
error();
|
|
if (LMS_SetGaindB(device, LMS_CH_TX, 0, 0) != 0) {
|
|
|
|
cout << "Initializing device first!" << endl;
|
|
|
|
// this might fail for a freshly connected device
|
|
// --> init the device
|
|
if (LMS_Init(device) != 0)
|
|
error();
|
|
// retry
|
|
if (LMS_SetGaindB(device, LMS_CH_TX, 0, 0) != 0)
|
|
error();
|
|
}
|
|
if (LMS_SetNormalizedGain(device, LMS_CH_TX, 0, 0.0) != 0)
|
|
error();
|
|
|
|
// Set RX center frequency
|
|
if (LMS_SetLOFrequency(device, LMS_CH_RX, 0, LimeCfg.frq) != 0)
|
|
error();
|
|
|
|
// Set TX center frequency
|
|
if (LMS_SetLOFrequency(device, LMS_CH_TX, 0, LimeCfg.frq) != 0)
|
|
error();
|
|
|
|
// Read back the updated frequency for later storage
|
|
if (LMS_GetLOFrequency(device, LMS_CH_RX, 0, &frq_read) != 0)
|
|
error();
|
|
|
|
// Enable RX channel
|
|
// Channels are numbered starting at 0
|
|
if (LMS_EnableChannel(device, LMS_CH_RX, 0, true) != 0)
|
|
error();
|
|
// Enable TX channels
|
|
if (LMS_EnableChannel(device, LMS_CH_TX, 0, true) != 0)
|
|
error();
|
|
|
|
// apply DC offset in TxTSP
|
|
uint16_t DC_I, DC_Q, DC_EN;
|
|
DC_EN = 0;
|
|
if (LMS_WriteParam(device, LMS7_DCCORRI_TXTSP, LimeCfg.TX_IcorrDC) != 0)
|
|
error();
|
|
if (LMS_WriteParam(device, LMS7_DCCORRQ_TXTSP, LimeCfg.TX_QcorrDC) != 0)
|
|
error();
|
|
if (LMS_WriteParam(device, LMS7_DC_BYP_TXTSP, DC_EN) != 0)
|
|
error();
|
|
|
|
/*
|
|
// read back DC offset in TxTSP
|
|
if (LMS_ReadParam(device, LMS7_DCCORRI_TXTSP, &DC_I) != 0) error();
|
|
if (LMS_ReadParam(device, LMS7_DCCORRQ_TXTSP, &DC_Q) != 0) error();
|
|
if (LMS_ReadParam(device, LMS7_DC_BYP_TXTSP, &DC_EN) != 0) error();
|
|
cout << "TxTSP DC corr (EN, I, Q): " << DC_EN << ", " << DC_I << ", " <<
|
|
DC_Q << endl;
|
|
*/
|
|
|
|
// print available antennae names
|
|
// 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 RX LNAs:\n"; // print available antennae names
|
|
for (int i = 0; i < n; i++)
|
|
cout << i << ": " << antenna_list[i] << endl;
|
|
// get and print antenna index and name
|
|
if ((n = LMS_GetAntenna(device, LMS_CH_RX, 0)) < 0)
|
|
error();
|
|
cout << "Automatically selected RX LNA: " << n << ": " << antenna_list[n]
|
|
<< endl;
|
|
|
|
// manually select antenna
|
|
if (LMS_SetAntenna(device, LMS_CH_RX, 0, LMS_PATH_LNAL) != 0)
|
|
error();
|
|
|
|
// get and print antenna index and name
|
|
if ((n = LMS_GetAntenna(device, LMS_CH_RX, 0)) < 0)
|
|
error();
|
|
cout << "Manually selected RX LNA: " << n << ": " << antenna_list[n]
|
|
<< endl;
|
|
|
|
// select antenna port
|
|
// Alternatively, NULL can be passed to LMS_GetAntennaList() to obtain
|
|
// number of antennae
|
|
if ((n = LMS_GetAntennaList(device, LMS_CH_TX, 0, antenna_list)) < 0)
|
|
error();
|
|
|
|
cout << "Available TX pathways:\n"; // print available antennae names
|
|
for (int i = 0; i < n; i++)
|
|
cout << i << ": " << antenna_list[i] << endl;
|
|
|
|
// get and print print antenna index and name
|
|
if ((n = LMS_GetAntenna(device, LMS_CH_TX, 0)) < 0)
|
|
error();
|
|
cout << "Automatically selected TX pathway: " << n << ": "
|
|
<< antenna_list[n] << endl;
|
|
|
|
// manually select antenna
|
|
int mychoice = LMS_PATH_TX1;
|
|
if (LimeCfg.frq > 1500e6)
|
|
mychoice = LMS_PATH_TX2;
|
|
if (LMS_SetAntenna(device, LMS_CH_TX, 0, mychoice) != 0)
|
|
error();
|
|
|
|
// get and print print antenna index and name
|
|
if ((n = LMS_GetAntenna(device, LMS_CH_TX, 0)) < 0)
|
|
error();
|
|
cout << "Manually selected TX pathway: " << n << ": " << antenna_list[n]
|
|
<< endl;
|
|
|
|
// Set sample rate, w/o oversampling, so that we can remove the invsinc
|
|
// filter
|
|
if (LMS_SetSampleRate(device, LimeCfg.srate, 1) != 0)
|
|
error();
|
|
// Invsinc, which removes that non-causal wiggle in timedomain
|
|
if (LMS_WriteParam(device, LMS7_ISINC_BYP_TXTSP, 1) != 0)
|
|
error();
|
|
// CMIX: Disable, as it is not used
|
|
if (LMS_WriteParam(device, LMS7_CMIX_BYP_TXTSP, 1) != 0)
|
|
error();
|
|
if (LMS_WriteParam(device, LMS7_CMIX_BYP_RXTSP, 1) != 0)
|
|
error();
|
|
|
|
// experiment with the GFIR filters
|
|
// if (LMS_SetGFIRLPF(device, LMS_CH_RX, 0, 1, 0.5e6) != 0) error(); //
|
|
// Works nicely. Allows, for instance, to perform narrowband observation
|
|
// together with CMIX
|
|
|
|
// Remute the TX output here, as the init commands create a lot of garbage
|
|
if (LMS_WriteParam(device, LMS7_PD_TLOBUF_TRF, 1) != 0)
|
|
error();
|
|
|
|
// Set RX and TX to the gain values
|
|
if (LMS_SetGaindB(device, LMS_CH_TX, 0, LimeCfg.TX_gain) != 0)
|
|
error();
|
|
if (LMS_SetGaindB(device, LMS_CH_RX, 0, LimeCfg.RX_gain) != 0)
|
|
error();
|
|
|
|
cout << "After gain setting: " << endl;
|
|
GetGainRXTX(RXgain, TXgain);
|
|
|
|
// special for low frequency operation: LNA gain saturates rather early ->
|
|
// reduce lna gain and increase pga ( and even though we have that function
|
|
// GetGainRXTX(), we need to re-read the values here explicitly, since
|
|
// we need to operate on the actual settings of the LMS Parameter, and not
|
|
// the gain values )
|
|
uint16_t gain_lna, gain_tia, gain_pga;
|
|
if (LMS_ReadParam(device, LMS7_G_LNA_RFE, &gain_lna) != 0)
|
|
error();
|
|
if (LMS_ReadParam(device, LMS7_G_TIA_RFE, &gain_tia) != 0)
|
|
error();
|
|
if (LMS_ReadParam(device, LMS7_G_PGA_RBB, &gain_pga) != 0)
|
|
error();
|
|
// cout << "Indiv gain addr: " << gain_lna << " LNA, " << gain_tia << " TIA,
|
|
// " << gain_pga << " PGA" << endl; gain_lna > 7 means a gain beyond
|
|
// gmax-12. Convert that to gains in dB
|
|
uint16_t crit_val = 7;
|
|
uint16_t gain_corr = (gain_lna - crit_val);
|
|
if (gain_corr > 2)
|
|
gain_corr += 4; // gain steps of 1 dB for gain_lna > 9
|
|
else
|
|
gain_corr *= 3; // gain steps of 3 dB for gain_lna <= 9
|
|
// eventually put this to the pga gain
|
|
if (gain_corr > 0) {
|
|
if (LMS_WriteParam(device, LMS7_G_LNA_RFE, crit_val) != 0)
|
|
error();
|
|
if (LMS_WriteParam(device, LMS7_G_PGA_RBB, gain_pga + gain_corr) != 0)
|
|
error();
|
|
}
|
|
GetGainRXTX(RXgain, TXgain);
|
|
|
|
// 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";
|
|
|
|
if (LMS_GetLPFBWRange(device, LMS_CH_TX, &range) != 0)
|
|
error();
|
|
cout << "TX LPF bandwitdh range: " << range.min / 1e6 << " - "
|
|
<< range.max / 1e6 << " MHz\n\n";
|
|
|
|
if (LMS_SetLPFBW(device, LMS_CH_RX, 0, LimeCfg.RX_LPF) != 0)
|
|
error();
|
|
if (LMS_SetLPFBW(device, LMS_CH_TX, 0, LimeCfg.TX_LPF) != 0)
|
|
error();
|
|
|
|
float_type LPFBW; // lowpass bandwidth
|
|
if (LMS_GetLPFBW(device, LMS_CH_RX, 0, &LPFBW) != 0)
|
|
error();
|
|
cout << "RX LPFBW: " << LPFBW / 1e6 << " MHz" << endl;
|
|
if (LMS_GetLPFBW(device, LMS_CH_TX, 0, &LPFBW) != 0)
|
|
error();
|
|
cout << "TX LPFBW: " << LPFBW / 1e6 << " MHz" << endl;
|
|
|
|
// Set limelight interface to TRXIQ, as the std value (JESD) will not
|
|
// communicate
|
|
if (LMS_WriteParam(device, LMS7_LML1_MODE, 0) != 0)
|
|
error();
|
|
if (LMS_WriteParam(device, LMS7_LML2_MODE, 0) != 0)
|
|
error();
|
|
|
|
// Unmute the TX output, as the init commands are now written
|
|
if (LMS_WriteParam(device, LMS7_PD_TLOBUF_TRF, 0) != 0)
|
|
error();
|
|
}
|
|
|
|
// read back values that tend to depend on the specific configuration
|
|
memcpy(LimeCfg.TX_gain_rback, TXgain, 3 * sizeof *LimeCfg.TX_gain_rback);
|
|
memcpy(LimeCfg.RX_gain_rback, RXgain, 4 * sizeof *LimeCfg.RX_gain_rback);
|
|
|
|
const int chCount = 1; // number of RX/TX streams
|
|
|
|
// Initialize acquisition data buffer
|
|
int buffersize = LimeCfg.buffersize; // complex samples per buffer
|
|
// note that proper scheduling requires buffersize that is a multiple of 1360
|
|
// (12bit RX) and 1020 (16bit TX) accordingly, buffersize needs to be a
|
|
// multiple of 4080, which is 3*1360 and 4*1020
|
|
if (buffersize % 4080 != 0) {
|
|
|
|
cout << "Problem with requested buffersize of " << LimeCfg.buffersize
|
|
<< ", as it is not a multiple of 4080." << endl;
|
|
LMS_Close(device);
|
|
return 1;
|
|
}
|
|
|
|
int timestampOffset = 0; // for offsets between TX and RX timestamps
|
|
int bufferOffset = 0; // to correct for those offsets
|
|
int16_t *buffers[chCount];
|
|
for (int ii = 0; ii < chCount; ++ii) {
|
|
buffers[ii] = new int16_t[buffersize *
|
|
2]; // buffer to hold complex values (2*samples)
|
|
}
|
|
|
|
// Streaming Setup
|
|
lms_stream_t rx_streams[chCount];
|
|
lms_stream_t tx_streams[chCount];
|
|
|
|
int N_buffers_per_fifo =
|
|
96; // Number of buffers that can be put onto the fifo
|
|
|
|
// Initialize streams
|
|
// All streams setups should be done before starting streams. New streams
|
|
// cannot be set-up if at least stream is running.
|
|
for (int ii = 0; ii < chCount; ++ii) {
|
|
rx_streams[ii].channel = ii; // channel number
|
|
rx_streams[ii].fifoSize =
|
|
buffersize * N_buffers_per_fifo; // fifo size in samples
|
|
rx_streams[ii].throughputVsLatency = 0.5; // some middle ground
|
|
rx_streams[ii].isTx = false; // RX channel
|
|
rx_streams[ii].dataFmt = lms_stream_t::LMS_FMT_I12; // 12-bit integers
|
|
|
|
if (LMS_SetupStream(device, &rx_streams[ii]) != 0)
|
|
error();
|
|
|
|
tx_streams[ii].channel = ii; // channel number
|
|
tx_streams[ii].fifoSize =
|
|
buffersize * N_buffers_per_fifo; // fifo size in samples
|
|
tx_streams[ii].throughputVsLatency = 0.5; // some middle ground
|
|
tx_streams[ii].isTx = true; // TX channel
|
|
tx_streams[ii].dataFmt = lms_stream_t::LMS_FMT_I16; // 16-bit float
|
|
|
|
if (LMS_SetupStream(device, &tx_streams[ii]) != 0)
|
|
error();
|
|
}
|
|
|
|
// gather parameters for the TX pulse
|
|
|
|
// first get all the phase-cycles
|
|
// which at first requires few maximum quantities....
|
|
// .... the number of levels ...
|
|
int max_lev = 0;
|
|
for (int ii_pls = 0; ii_pls < LimeCfg.Npulses; ii_pls++)
|
|
if (LimeCfg.p_phacyc_lev[ii_pls] > max_lev)
|
|
max_lev = LimeCfg.p_phacyc_lev[ii_pls];
|
|
|
|
// check if there are no gaps in the level specification
|
|
bool found_level[max_lev + 1];
|
|
bool level_problem = false;
|
|
for (int ii = 0; ii < max_lev + 1; ii++) {
|
|
found_level[ii] = false;
|
|
for (int ii_pls = 0; ii_pls < LimeCfg.Npulses; ii_pls++)
|
|
if (LimeCfg.p_phacyc_lev[ii_pls] == ii)
|
|
found_level[ii] = true;
|
|
if (!found_level[ii])
|
|
level_problem = true;
|
|
}
|
|
if (level_problem) {
|
|
cout << "Problem with specified phase cycle levels: ";
|
|
for (int ii_pls = 0; ii_pls < LimeCfg.Npulses; ii_pls++)
|
|
cout << setw(5) << left << LimeCfg.p_phacyc_lev[ii_pls];
|
|
cout << endl;
|
|
cout << "A consecutive level numbering is required, but level/s ";
|
|
for (int ii = 0; ii < max_lev + 1; ii++)
|
|
if (!found_level[ii])
|
|
cout << setw(2) << left << ii;
|
|
cout << " is/are missing!" << endl;
|
|
|
|
LMS_Close(device);
|
|
return 1;
|
|
}
|
|
// ... the maximum number of phase cycles per level ...
|
|
int *steps_per_lev = new int[max_lev + 1];
|
|
for (int ii = 0; ii < max_lev + 1; ii++)
|
|
steps_per_lev[ii] = 0;
|
|
|
|
int curr_lev_steps; // to make the code more readable...
|
|
for (int ii = 0; ii < LimeCfg.Npulses; ii++) {
|
|
curr_lev_steps = steps_per_lev[LimeCfg.p_phacyc_lev[ii]];
|
|
if (LimeCfg.p_phacyc_N[ii] > curr_lev_steps)
|
|
steps_per_lev[LimeCfg.p_phacyc_lev[ii]] = LimeCfg.p_phacyc_N[ii];
|
|
}
|
|
|
|
// ... which gives the total number of phase variations ...
|
|
int num_phavar = 1;
|
|
int steps_incr[max_lev + 1] = {
|
|
1}; // .. and the number of steps where phase is constant ...
|
|
for (int ii = 0; ii < max_lev + 1; ii++) {
|
|
if (ii > 0)
|
|
steps_incr[ii] = steps_incr[ii - 1] * steps_per_lev[ii - 1];
|
|
num_phavar *= steps_per_lev[ii];
|
|
}
|
|
|
|
// ... which allows to construct the entire phase table ...
|
|
double pha_tab[num_phavar][LimeCfg.Npulses];
|
|
|
|
double pha_incr, curr_pha;
|
|
int step_incr = 1;
|
|
|
|
for (int ii_pls = 0; ii_pls < LimeCfg.Npulses; ii_pls++) {
|
|
// retrieve the phase increment
|
|
if (LimeCfg.p_phacyc_N[ii_pls] != 0)
|
|
pha_incr = 1.0 / LimeCfg.p_phacyc_N[ii_pls];
|
|
else
|
|
pha_incr = 1.0;
|
|
|
|
curr_pha = 0;
|
|
|
|
// get the step increment
|
|
step_incr = steps_incr[LimeCfg.p_phacyc_lev[ii_pls]];
|
|
|
|
// start to fill the table
|
|
for (int ii_pha = 0; ii_pha < num_phavar; ii_pha++) {
|
|
// eventually increment the phase
|
|
if ((ii_pha > 0) && (ii_pha % step_incr == 0))
|
|
curr_pha += pha_incr;
|
|
|
|
pha_tab[ii_pha][ii_pls] = fmod(curr_pha, 1.0);
|
|
}
|
|
}
|
|
|
|
// debug: print that phase table
|
|
cout << "Phase Table : " << endl;
|
|
for (int ii_pha = 0; ii_pha < num_phavar; ii_pha++) {
|
|
for (int ii_pls = 0; ii_pls < LimeCfg.Npulses; ii_pls++) {
|
|
cout << setw(10) << left << pha_tab[ii_pha][ii_pls];
|
|
}
|
|
cout << endl;
|
|
}
|
|
|
|
// get the number of buffers that are required in order to fit the entire
|
|
// experiment
|
|
long exc_len = 0;
|
|
int exc_buffers;
|
|
int pulsedur, pulseoffs;
|
|
pulseoffs = 0;
|
|
for (int ii_pls = 0; ii_pls < LimeCfg.Npulses; ii_pls++) {
|
|
|
|
pulsedur = LimeCfg.p_dur_smp[ii_pls]; // duration of pulse in samples
|
|
pulseoffs += LimeCfg.p_offs[ii_pls]; // offset of pulse in samples
|
|
|
|
if (pulseoffs + pulsedur > exc_len)
|
|
exc_len = pulseoffs + pulsedur;
|
|
}
|
|
exc_buffers = ceil((double)exc_len / (double)buffersize);
|
|
cout << "Exc_len " << exc_len << " and buffersize " << buffersize << endl;
|
|
cout << "A total of " << exc_buffers
|
|
<< " excitation buffers is required to fit the experiment" << endl;
|
|
|
|
// TX buffers
|
|
// int16_t tx_buffer[num_phavar][exc_buffers][2*buffersize]; // buffer to
|
|
// hold complex values (2* samples), including phase cycles
|
|
// TODO: put in the same way as the acq buffer, i.e. as an array of pointers.
|
|
// Otherwise, there is a limitation in space that can be used
|
|
int16_t *tx_buffer[num_phavar][exc_buffers];
|
|
for (int ii = 0; ii < num_phavar; ++ii) {
|
|
for (int jj = 0; jj < exc_buffers; ++jj) {
|
|
tx_buffer[ii][jj] = new int16_t[2 * buffersize];
|
|
}
|
|
}
|
|
int16_t tx_buffer_1st[2 * buffersize]; // buffer to hold complex values
|
|
float fsmpI, fsmpQ;
|
|
int16_t smpI, smpQ;
|
|
|
|
// init with zero, as we add to it
|
|
for (int ii = 0; ii < 2 * buffersize; ii++) {
|
|
for (int jj = 0; jj < exc_buffers; jj++) {
|
|
for (int ll = 0; ll < num_phavar; ll++)
|
|
tx_buffer[ll][jj][ii] = 0;
|
|
}
|
|
}
|
|
|
|
// pulse parameters
|
|
double pulsefrq, pulseamp, pulsepha;
|
|
double w;
|
|
int buffoffs;
|
|
|
|
pulseoffs = 0;
|
|
for (int ii_pls = 0; ii_pls < LimeCfg.Npulses; ii_pls++) {
|
|
|
|
pulsedur = LimeCfg.p_dur_smp[ii_pls]; // duration of pulse in samples
|
|
pulseoffs += LimeCfg.p_offs[ii_pls]; // offset of pulse in samples
|
|
pulsefrq = LimeCfg.p_frq_smp[ii_pls]; // frequency of pulse in samples
|
|
pulseamp = LimeCfg.p_amp[ii_pls]; // relative amplitude of pulses
|
|
pulsepha = LimeCfg.p_pha[ii_pls]; // phase of pulse
|
|
|
|
for (int ll = 0; ll < num_phavar;
|
|
ll++) { // generate TX Pulse for different phases
|
|
buffoffs = 0;
|
|
for (int jj = 0; jj < exc_buffers;
|
|
jj++) { // distribute 'long experiment' amongst buffers
|
|
for (int ii = 0; ii < buffersize;
|
|
ii++) { // generate TX Pulse point by point
|
|
if ((ii + buffoffs >= pulseoffs) &
|
|
(ii + buffoffs < pulsedur + pulseoffs)) {
|
|
|
|
w = 2 * pi * (ii + buffoffs) * pulsefrq; // frequency*time
|
|
w = 2 * pi * (ii - pulseoffs) *
|
|
pulsefrq; // re-put absolute phase, which is better suited for
|
|
// pulsed experiments. The inclusion of buffoffs above
|
|
// was actually intended for CW type experiments,
|
|
// which were put separately in CW_AFC_engine.
|
|
|
|
fsmpI = pulseamp * cos(w + pulsepha + 2 * pi * pha_tab[ll][ii_pls]);
|
|
fsmpQ = pulseamp * sin(w + pulsepha + 2 * pi * pha_tab[ll][ii_pls]);
|
|
|
|
// convert to int16 ...
|
|
smpI = 2047.0 * fsmpI;
|
|
smpQ = 2047.0 * fsmpQ;
|
|
|
|
// ... with 4 LSB at 0 for marker
|
|
if (tx_streams[0].dataFmt == lms_stream_t::LMS_FMT_I16) {
|
|
smpI = smpI << 4;
|
|
smpQ = smpQ << 4;
|
|
}
|
|
// add to buffer
|
|
tx_buffer[ll][jj][2 * ii] += smpI;
|
|
tx_buffer[ll][jj][2 * ii + 1] += smpQ;
|
|
} else {
|
|
// fsmpI = 0.0;
|
|
// fsmpQ = 0.0;
|
|
}
|
|
}
|
|
buffoffs += buffersize; // jump to next buffer
|
|
}
|
|
}
|
|
|
|
// Pulse Marker for timing relative to pulse flanks
|
|
// TODO: we might need some gate joining and one should also warn if the
|
|
// triggers do not fit into the buffer
|
|
if (tx_streams[0].dataFmt == lms_stream_t::LMS_FMT_I16) {
|
|
int *curr_marker;
|
|
int *curr_marker_en;
|
|
// marker channel for that offset
|
|
for (int ii_c = 0; ii_c < 4;
|
|
ii_c++) { // iterate through the four marker channels
|
|
// get the proper configuration of the trigger channel
|
|
switch (ii_c) {
|
|
case 0:
|
|
curr_marker = LimeCfg.c0_tim;
|
|
curr_marker_en = LimeCfg.p_c0_en;
|
|
break;
|
|
case 1:
|
|
curr_marker = LimeCfg.c1_tim;
|
|
curr_marker_en = LimeCfg.p_c1_en;
|
|
break;
|
|
case 2:
|
|
curr_marker = LimeCfg.c2_tim;
|
|
curr_marker_en = LimeCfg.p_c2_en;
|
|
break;
|
|
case 3:
|
|
curr_marker = LimeCfg.c3_tim;
|
|
curr_marker_en = LimeCfg.p_c3_en;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
// check if the channel is activated
|
|
if (curr_marker[0] == 0 || curr_marker_en[ii_pls] == 0)
|
|
continue;
|
|
|
|
// set trigger with bitset operation
|
|
for (int ll = 0; ll < num_phavar; ll++) {
|
|
buffoffs = 0;
|
|
for (int jj = 0; jj < exc_buffers;
|
|
jj++) { // distribute 'long experiment' amongst buffers
|
|
for (int ii = 0; ii < 2 * buffersize;
|
|
ii++) { // set trigger point by point
|
|
if ((ii + buffoffs >=
|
|
2 * pulseoffs + curr_marker[2] - curr_marker[1]) &
|
|
(ii + buffoffs < 2 * pulsedur + 2 * pulseoffs +
|
|
curr_marker[2] + curr_marker[3])) {
|
|
tx_buffer[ll][jj][ii] |= 1 << ii_c;
|
|
}
|
|
}
|
|
buffoffs += 2 * buffersize; // jump to next buffer
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Synth Marker for CW sync stuff
|
|
// Use TTL channel as synth: [enabled? , half-period, strt, PSK shift, PSK
|
|
// adv]
|
|
int synthstart = 0;
|
|
int wrapped_phase = 0;
|
|
if (tx_streams[0].dataFmt == lms_stream_t::LMS_FMT_I16) {
|
|
int *curr_synth;
|
|
// marker channel for that offset
|
|
for (int ii_c = 0; ii_c < 4;
|
|
ii_c++) { // iterate through the four marker channels
|
|
// get the proper configuration of the trigger channel
|
|
switch (ii_c) {
|
|
case 0:
|
|
curr_synth = LimeCfg.c0_synth;
|
|
break;
|
|
case 1:
|
|
curr_synth = LimeCfg.c1_synth;
|
|
break;
|
|
case 2:
|
|
curr_synth = LimeCfg.c2_synth;
|
|
break;
|
|
case 3:
|
|
curr_synth = LimeCfg.c3_synth;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
cout << curr_synth[1] << endl;
|
|
// check if the channel is activated
|
|
if (curr_synth[0] == 0)
|
|
continue;
|
|
|
|
// set trigger with bitset operation
|
|
synthstart = curr_synth[2];
|
|
for (int ll = 0; ll < num_phavar; ll++) {
|
|
buffoffs = 0;
|
|
|
|
// eventually advance the synth coupled to the phase cycle
|
|
if (curr_synth[4] > 0) {
|
|
if ((ll % curr_synth[4]) == 0)
|
|
synthstart += curr_synth[3];
|
|
}
|
|
|
|
for (int jj = 0; jj < exc_buffers;
|
|
jj++) { // distribute 'long experiment' amongst buffers
|
|
for (int ii = 0; ii < 2 * buffersize;
|
|
ii++) { // set trigger point by point
|
|
// wrap the phase counter
|
|
wrapped_phase =
|
|
int(int(ii + buffoffs + synthstart) % int(2 * curr_synth[1]));
|
|
|
|
// wrapped_phase = 0;
|
|
// test = (wrapped_phase < curr_synth[1]);
|
|
if (wrapped_phase < curr_synth[1]) {
|
|
tx_buffer[ll][jj][ii] |= 1 << ii_c;
|
|
}
|
|
}
|
|
buffoffs += 2 * buffersize; // jump to next buffer
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// generate empty TX Pulse at beginning
|
|
for (int ii = 0; ii < buffersize; ii++) {
|
|
|
|
tx_buffer_1st[2 * ii] = 0.0;
|
|
tx_buffer_1st[2 * ii + 1] = 0.0;
|
|
}
|
|
|
|
// calculate the repetition and recording time in samples as mutliple of
|
|
// buffer size in this way, we do not need to do any exhaustive sample
|
|
// alignment strategies
|
|
long rep_offset, rec_len;
|
|
rep_offset = ceil(LimeCfg.reptime_secs * LimeCfg.srate / (double)buffersize) *
|
|
buffersize;
|
|
rec_len = ceil(LimeCfg.rectime_secs * LimeCfg.srate / (double)buffersize) *
|
|
buffersize;
|
|
|
|
// write back to LimeCfg so that these are stored in the file
|
|
LimeCfg.reptime_smps = rep_offset;
|
|
LimeCfg.rectime_smps = rec_len;
|
|
|
|
if (rec_len > rep_offset) {
|
|
cout << "Recording length of " << rec_len
|
|
<< " samples cannot be longer than repetition time (" << rep_offset
|
|
<< " Samples)" << endl;
|
|
error();
|
|
}
|
|
|
|
cout << "Rep and rectimes: " << rep_offset << ", " << rec_len << endl;
|
|
cout << "Rep and rectimes in bufperiods: " << rep_offset / buffersize << ", "
|
|
<< rec_len / buffersize << endl;
|
|
|
|
// Buffer for acqisition signal: integer datatype, so that we can have a
|
|
// sufficient number of averages of acquired 16 bit data into the 32 bit
|
|
// buffer for some reason, there are segfaults here when acqbuf_size becomes
|
|
// something on the order of 100
|
|
int acqbuf_size = LimeCfg.repetitions * num_phavar;
|
|
// int acqbuf[acqbuf_size][2*rec_len]; // causes segfaults with large
|
|
// arrays...
|
|
|
|
// init as non-contiguous memory (which will require writing as chunks to
|
|
// HDF5)
|
|
int *acqbuf[acqbuf_size];
|
|
for (int ii = 0; ii < acqbuf_size; ++ii) {
|
|
acqbuf[ii] = new int[2 * rec_len];
|
|
}
|
|
|
|
// brute-force initialization by zero ( memset(acqbuf, 0,
|
|
// acqbuf_size*2*rec_len); ) did not work...
|
|
for (int ii = 0; ii < acqbuf_size; ii++) {
|
|
for (int jj = 0; jj < 2 * rec_len; jj++) {
|
|
acqbuf[ii][jj] = 0;
|
|
}
|
|
}
|
|
|
|
// Streaming
|
|
lms_stream_meta_t rx_metadata; // Use metadata for additional control over
|
|
// sample receive function behavior
|
|
rx_metadata.flushPartialPacket = false; // currently has no effect in RX
|
|
rx_metadata.waitForTimestamp = false; // currently has no effect in RX
|
|
|
|
lms_stream_meta_t tx_metadata; // Use metadata for additional control over
|
|
// sample send function behavior
|
|
tx_metadata.flushPartialPacket =
|
|
false; // do not force sending of incomplete packet
|
|
tx_metadata.waitForTimestamp = true; // Enable synchronization to HW timestamp
|
|
|
|
lms_stream_status_t status; // To check the FIFO
|
|
|
|
// counters to keep track of the transmission FIFO
|
|
int TXFIFO_slots = N_buffers_per_fifo;
|
|
int ii_TXavg = 0;
|
|
int ii_TXpcyc = 0;
|
|
int ii_TXoffset = 0;
|
|
int ii_TXrep = 0;
|
|
int ii_sent = 0;
|
|
double init_delay_s = 10e-3; // delay in seconds until TX packets are
|
|
// forwarded from FPGA to the FPRF
|
|
long next_TXtimestamp =
|
|
ceil((init_delay_s * LimeCfg.srate) / (double)buffersize) * buffersize;
|
|
long last_TXtimestamp =
|
|
0; // this one stores the last timestamp of the beginning of a repetition
|
|
|
|
// Timestamps to schedule the acquisition
|
|
long next_RXtimestamp = 0;
|
|
long last_RXtimestamp = 0;
|
|
|
|
// Acquisition loop
|
|
auto t1 = chrono::high_resolution_clock::now();
|
|
auto t2 = t1;
|
|
|
|
int samplesRead;
|
|
int samplesReadSum = 0;
|
|
int rcvattempts = 0;
|
|
|
|
int ii_rep = 0; // number of repetions
|
|
int ii_pcyc = 0; // number of phase cycle
|
|
int ii_avg = 0; // number of averages
|
|
int ii_acq = 0; // number of complete acquisitions
|
|
int samples2Acquire =
|
|
0; // number of samples to acquire in current acquisition
|
|
int validSamples = 0; // number of valid samples in current datapacket
|
|
int *acqbuf_pos; // pointer to acqbuffer
|
|
int *delayedacqbuf_pos; // pointer to acqbuffer for delayed fwd
|
|
|
|
bool acquiring = false; // RX stream to acqbuffer?
|
|
bool acquire = true; // disable one single acquisition in acq loop
|
|
bool delayedAcqbufFwd = false; // to delay the forwarding of the acqbuffer
|
|
|
|
int ndebug = 100;
|
|
|
|
// Start streaming
|
|
for (int i = 0; i < chCount; ++i) {
|
|
LMS_StartStream(&rx_streams[i]);
|
|
LMS_StartStream(&tx_streams[i]);
|
|
}
|
|
|
|
// pre-fill the TX fifo
|
|
for (int ii_TXbuff = 0; ii_TXbuff < N_buffers_per_fifo; ii_TXbuff++) {
|
|
|
|
// save the TX timestamp to the current packet
|
|
tx_metadata.timestamp = next_TXtimestamp;
|
|
|
|
// First packet is special, since it is cut off in some weird way
|
|
if (ii_TXbuff == 0) {
|
|
LMS_SendStream(&tx_streams[0], tx_buffer_1st, buffersize, &tx_metadata,
|
|
1000); // so we put zeros
|
|
TXFIFO_slots--;
|
|
|
|
// advance TX timestamp for the next packet
|
|
next_TXtimestamp += rep_offset;
|
|
|
|
next_RXtimestamp = next_TXtimestamp; // ... and do not wait for it
|
|
last_TXtimestamp = next_TXtimestamp;
|
|
continue; // proceed the for loop with the first actual TX packet
|
|
}
|
|
|
|
// Put data to FIFO
|
|
LMS_SendStream(&tx_streams[0], tx_buffer[ii_TXpcyc][ii_TXoffset],
|
|
buffersize, &tx_metadata, 1000);
|
|
|
|
// Update TX FIFO counters
|
|
TXFIFO_slots--;
|
|
ii_sent++;
|
|
|
|
// advance the tx_buffer counter
|
|
ii_TXoffset++;
|
|
next_TXtimestamp += buffersize;
|
|
if (ii_TXoffset == exc_buffers) {
|
|
ii_TXoffset = 0;
|
|
next_TXtimestamp = last_TXtimestamp + rep_offset;
|
|
last_TXtimestamp = next_TXtimestamp;
|
|
|
|
if (LimeCfg.pcyc_bef_avg > 0) {
|
|
ii_TXpcyc++;
|
|
if (ii_TXpcyc == num_phavar) {
|
|
ii_TXpcyc = 0;
|
|
ii_TXavg++;
|
|
if (ii_TXavg == LimeCfg.averages) {
|
|
ii_TXavg = 0;
|
|
ii_TXrep++;
|
|
// in case the entire experiment fits within the TX FIFO
|
|
if (ii_TXrep == LimeCfg.repetitions)
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
ii_TXavg++;
|
|
if (ii_TXavg == LimeCfg.averages) {
|
|
ii_TXavg = 0;
|
|
ii_TXpcyc++;
|
|
if (ii_TXpcyc == num_phavar) {
|
|
ii_TXpcyc = 0;
|
|
ii_TXrep++;
|
|
// in case the entire experiment fits within the TX FIFO
|
|
if (ii_TXrep == LimeCfg.repetitions)
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
// if there is still data to be put on the buffer
|
|
}
|
|
}
|
|
|
|
/*
|
|
// Check for the TX buffer and keep it filled
|
|
LMS_GetStreamStatus(tx_streams, &status); //Obtain TX stream stats
|
|
if (status.fifoFilledCount != 0) cout << TXFIFO_slots <<" TXFIFO slots
|
|
free before start: " << status.fifoFilledCount << " samples of " <<
|
|
status.fifoSize << " with HW stamp " << status.timestamp <<" at RX
|
|
timestamp" << rx_metadata.timestamp << endl;
|
|
*/
|
|
|
|
// Main acquisition loop
|
|
while (ii_acq < LimeCfg.repetitions * LimeCfg.averages * num_phavar) {
|
|
|
|
// Receive samples
|
|
if (acquire) {
|
|
samplesRead = LMS_RecvStream(&rx_streams[0], buffers[0], buffersize,
|
|
&rx_metadata, 1000);
|
|
rcvattempts++;
|
|
samplesReadSum += samplesRead;
|
|
}
|
|
|
|
if (ndebug < 10) {
|
|
cout << rx_metadata.timestamp << ", " << samplesReadSum << endl;
|
|
ndebug++;
|
|
LMS_GetStreamStatus(rx_streams, &status); // Obtain RX stream stats
|
|
cout << "Rx stream info: " << status.overrun << ", " << status.underrun
|
|
<< status.droppedPackets << ", " << status.overrun << ", actually "
|
|
<< status.fifoFilledCount << endl;
|
|
}
|
|
|
|
// check if the scheduled timestamp is coming here
|
|
// if (rx_metadata.timestamp >= next_RXtimestamp) {
|
|
if ((rx_metadata.timestamp >= next_RXtimestamp - samplesRead + 1) &&
|
|
acquire) {
|
|
|
|
// Advance acqbuf in case that there is not an ongoing acquisition (just
|
|
// in the case of gap-free acquisition, that has usually a timestamp
|
|
// offset)
|
|
if (acquiring == false) {
|
|
acqbuf_pos = acqbuf[ii_rep * num_phavar + ii_pcyc];
|
|
samples2Acquire = rec_len;
|
|
} else {
|
|
delayedacqbuf_pos = acqbuf[ii_rep * num_phavar + ii_pcyc];
|
|
delayedAcqbufFwd = true;
|
|
}
|
|
acquiring = true;
|
|
|
|
// advance counters
|
|
if (LimeCfg.pcyc_bef_avg > 0) {
|
|
ii_pcyc++;
|
|
if (ii_pcyc == num_phavar) {
|
|
ii_pcyc = 0;
|
|
ii_avg++;
|
|
if (ii_avg == LimeCfg.averages) {
|
|
ii_avg = 0;
|
|
ii_rep++;
|
|
}
|
|
}
|
|
} else {
|
|
ii_avg++;
|
|
if (ii_avg == LimeCfg.averages) {
|
|
ii_avg = 0;
|
|
ii_pcyc++;
|
|
if (ii_pcyc == num_phavar) {
|
|
ii_pcyc = 0;
|
|
ii_rep++;
|
|
}
|
|
}
|
|
}
|
|
|
|
// store the offset
|
|
timestampOffset =
|
|
(signed long)next_RXtimestamp - (signed long)rx_metadata.timestamp;
|
|
|
|
// Important debug message in general, will however only be printed in
|
|
// case the offset gets positive (which will inevidably run into a
|
|
// segfault and is related to a too fast samplingrate combined with a too
|
|
// small rectime_secs)
|
|
if (timestampOffset < 0) {
|
|
cout << "Next acq: rep " << ii_rep << ", avg " << ii_avg << ", pcyc "
|
|
<< ii_pcyc << " : sched/act tstamp: " << next_RXtimestamp << ", "
|
|
<< rx_metadata.timestamp
|
|
<< " Diff to last: " << next_RXtimestamp - last_RXtimestamp
|
|
<< " Offset: "
|
|
<< (signed long)rx_metadata.timestamp -
|
|
(signed long)next_RXtimestamp
|
|
<< " currently processing " << samplesRead
|
|
<< " RX samples with delayed fwd " << delayedAcqbufFwd << endl;
|
|
}
|
|
|
|
// advance to the forthcoming RX timestamp and keep the current one
|
|
next_RXtimestamp += rep_offset;
|
|
last_RXtimestamp = rx_metadata.timestamp;
|
|
}
|
|
acquire = true;
|
|
|
|
// copy RX data into acquisition buffer
|
|
if (acquiring) {
|
|
|
|
// standard case: copy everything, without offset
|
|
bufferOffset = 0;
|
|
validSamples = buffersize;
|
|
|
|
// first packet: consider eventual timestamp offset
|
|
if (samples2Acquire == rec_len) {
|
|
bufferOffset = timestampOffset;
|
|
validSamples = buffersize - bufferOffset;
|
|
// last packet with timestamp offset: just get the tail without offset
|
|
} else if (samples2Acquire < buffersize) {
|
|
validSamples = samples2Acquire;
|
|
}
|
|
|
|
for (int ii_acqbuf = 0; ii_acqbuf < 2 * (validSamples); ii_acqbuf++)
|
|
acqbuf_pos[ii_acqbuf] += (int)buffers[0][ii_acqbuf + 2 * bufferOffset];
|
|
samples2Acquire -= validSamples;
|
|
|
|
// advance position in acquisition buffer
|
|
acqbuf_pos += 2 * validSamples;
|
|
|
|
if (samples2Acquire == 0) {
|
|
ii_acq++;
|
|
|
|
// check for continuous RX with timestamp offset, where we would
|
|
// actually still have valid samples to copy in the buffer
|
|
if (delayedAcqbufFwd) {
|
|
// put pointer to right place
|
|
acqbuf_pos = delayedacqbuf_pos;
|
|
samples2Acquire = rec_len;
|
|
|
|
delayedAcqbufFwd = false;
|
|
// important: rerun this entire block without getting a new data or
|
|
// forwarding the timestamp packet. We still need to copy the part
|
|
// beyond the validsamples to the next acqbuf position
|
|
acquire = false;
|
|
} else {
|
|
// standard case: signal that the acquisition is finished and wait for
|
|
// the next scheduled timestamp
|
|
acquiring = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check for the TX buffer and keep it filled
|
|
if (ii_TXrep < LimeCfg.repetitions) {
|
|
LMS_GetStreamStatus(tx_streams, &status); // Obtain TX stream stats
|
|
TXFIFO_slots = (status.fifoSize - status.fifoFilledCount) / buffersize;
|
|
}
|
|
|
|
/*
|
|
// debug
|
|
if (TXFIFO_slots > 0) cout << TXFIFO_slots << " free fifo slots to fill" <<
|
|
endl;
|
|
*/
|
|
|
|
// re-fill the TX fifo
|
|
while (TXFIFO_slots > 0) {
|
|
|
|
// save the TX timestamp to the current packet
|
|
tx_metadata.timestamp = next_TXtimestamp;
|
|
|
|
// Put data to FIFO
|
|
LMS_SendStream(&tx_streams[0], tx_buffer[ii_TXpcyc][ii_TXoffset],
|
|
buffersize, &tx_metadata, 1000);
|
|
|
|
// Update TX counters
|
|
TXFIFO_slots--;
|
|
ii_sent++;
|
|
|
|
// advance the tx_buffer counter
|
|
ii_TXoffset++;
|
|
next_TXtimestamp += buffersize;
|
|
if (ii_TXoffset == exc_buffers) {
|
|
ii_TXoffset = 0;
|
|
next_TXtimestamp = last_TXtimestamp + rep_offset;
|
|
last_TXtimestamp = next_TXtimestamp;
|
|
|
|
if (LimeCfg.pcyc_bef_avg > 0) {
|
|
ii_TXpcyc++;
|
|
if (ii_TXpcyc == num_phavar) {
|
|
ii_TXpcyc = 0;
|
|
ii_TXavg++;
|
|
if (ii_TXavg == LimeCfg.averages) {
|
|
ii_TXavg = 0;
|
|
ii_TXrep++;
|
|
// in case the experiment is finished
|
|
if (ii_TXrep == LimeCfg.repetitions)
|
|
;
|
|
TXFIFO_slots = 0;
|
|
}
|
|
}
|
|
} else {
|
|
ii_TXavg++;
|
|
if (ii_TXavg == LimeCfg.averages) {
|
|
ii_TXavg = 0;
|
|
ii_TXpcyc++;
|
|
if (ii_TXpcyc == num_phavar) {
|
|
ii_TXpcyc = 0;
|
|
ii_TXrep++;
|
|
// in case the experiment is finished
|
|
if (ii_TXrep == LimeCfg.repetitions)
|
|
TXFIFO_slots = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Stop streaming
|
|
for (int i = 0; i < chCount; ++i) {
|
|
LMS_StopStream(&rx_streams[i]); // stream is stopped but can be started
|
|
// again with LMS_StartStream()
|
|
LMS_StopStream(&tx_streams[i]);
|
|
}
|
|
for (int i = 0; i < chCount; ++i) {
|
|
LMS_DestroyStream(
|
|
device,
|
|
&rx_streams[i]); // stream is deallocated and can no longer be used
|
|
LMS_DestroyStream(device, &tx_streams[i]);
|
|
delete[] buffers[i];
|
|
}
|
|
|
|
//-------------------------------------------------------------------------------------
|
|
// SAVE TO HDF5
|
|
//-------------------------------------------------------------------------------------
|
|
|
|
if (LimeCfg.override_save == 0) {
|
|
|
|
// Open HDF5 file
|
|
string filename;
|
|
// check for save_path delimiter
|
|
if (LimeCfg.save_path.back() == '/')
|
|
filename = LimeCfg.save_path + LimeCfg.file_stamp + "_" +
|
|
LimeCfg.file_pattern + ".h5";
|
|
else
|
|
filename = LimeCfg.save_path + '/' + LimeCfg.file_stamp + "_" +
|
|
LimeCfg.file_pattern + ".h5";
|
|
cout << filename << endl;
|
|
H5::H5File *h5f;
|
|
|
|
// create or open
|
|
if (file_exists(filename))
|
|
h5f = new H5::H5File(filename, H5F_ACC_RDWR);
|
|
else
|
|
h5f = new H5::H5File(filename, H5F_ACC_EXCL);
|
|
|
|
// Check for the number of datasets already in there (only >0 if an external
|
|
// file_stamp is provided or if the program is called more than once within
|
|
// a second..)
|
|
hsize_t num_obj = 0;
|
|
H5Gget_num_objs(h5f->getId(),
|
|
&num_obj); // if success, num_obj will be assigned the
|
|
// number of objects in the group
|
|
|
|
// write dataset to HDF5 file
|
|
// 1. specify datatype and dimensions and dataset name
|
|
H5::DataType saveDataType(H5::PredType::NATIVE_INT);
|
|
hsize_t saveDataDim[] = {(hsize_t)acqbuf_size, (hsize_t)2 * rec_len};
|
|
string DataName = "Acqbuf_";
|
|
std::ostringstream oss;
|
|
oss << setfill('0') << setw(2) << num_obj;
|
|
DataName += oss.str();
|
|
|
|
H5std_string saveDataName(DataName.c_str());
|
|
|
|
// 2. allocate dataspace and init
|
|
// set parameters to have a chunked file, so that each acquired trace is one
|
|
// chunk
|
|
H5::DSetCreatPropList cparms;
|
|
hsize_t chunk_dims[2] = {1, saveDataDim[1]};
|
|
cparms.setChunk(2, chunk_dims);
|
|
|
|
int fill_val = 0;
|
|
cparms.setFillValue(H5::PredType::NATIVE_INT, &fill_val);
|
|
|
|
H5::DataSpace mspace1(2, saveDataDim);
|
|
H5::DataSet dataset =
|
|
h5f->createDataSet(saveDataName, saveDataType, mspace1, cparms);
|
|
|
|
// write with standard procedure
|
|
// dataset.write( acqbuf, saveDataType); // requires contiguous memory of
|
|
// the entire acqbuf, which does not work for large buffers
|
|
// write row-wise
|
|
H5::DataSpace fspace_row = dataset.getSpace();
|
|
hsize_t offset[2] = {0, 0};
|
|
hsize_t dims_row[2] = {1, saveDataDim[1]};
|
|
H5::DataSpace mspace_row(
|
|
1, &saveDataDim[1]); // contiguous memory space of data to write
|
|
for (int ii = 0; ii < acqbuf_size; ii++) {
|
|
fspace_row.selectHyperslab(H5S_SELECT_SET, dims_row, offset);
|
|
dataset.write(acqbuf[ii], saveDataType, mspace_row, fspace_row);
|
|
offset[0] += 1; // advance to next row
|
|
}
|
|
|
|
// get the timestamp at the end of the experiment ...
|
|
now = std::chrono::system_clock::now();
|
|
itt = std::chrono::system_clock::to_time_t(now);
|
|
stringstream.str("");
|
|
stringstream.clear();
|
|
stringstream << std::put_time(localtime(&itt), "%G%m%d_%H%M%S");
|
|
|
|
// ... and write it to the appropriate index
|
|
for (int ii_attr = 0; ii_attr < no_of_attr; ii_attr++) {
|
|
if (strcmp("Exp End Timestamp", HDFattr[ii_attr].Name.c_str()) == 0) {
|
|
LimeCfg.stamp_end = stringstream.str();
|
|
HDFattr[ii_attr].dType =
|
|
H5::StrType(H5::PredType::C_S1, LimeCfg.stamp_end.length() + 1);
|
|
HDFattr[ii_attr].Value = (void *)LimeCfg.stamp_end.c_str();
|
|
}
|
|
}
|
|
|
|
// write the attributes
|
|
for (int ii = 0; ii < no_of_attr; ii++) {
|
|
|
|
H5::DataSpace *tmpSpace = new H5::DataSpace();
|
|
// special case: arrays
|
|
if (HDFattr[ii].dim > 1) {
|
|
delete tmpSpace;
|
|
H5::DataSpace *tmpSpace = new H5::DataSpace(1, &HDFattr[ii].dim);
|
|
}
|
|
H5std_string concat("-" + HDFattr[ii].arg + " " + HDFattr[ii].Name);
|
|
// H5::Attribute attribute = h5f->createAttribute(concat,
|
|
// HDFattr[ii].dType, *tmpSpace); // write the attribute to the file
|
|
H5::Attribute attribute = dataset.createAttribute(
|
|
concat, HDFattr[ii].dType,
|
|
*tmpSpace); // write the attribute to the dataset
|
|
attribute.write(HDFattr[ii].dType, HDFattr[ii].Value);
|
|
delete tmpSpace;
|
|
}
|
|
|
|
// special attribute for N pulses: the phase table, which is certainly of
|
|
// use in evaluation
|
|
hsize_t phatab_len = LimeCfg.Npulses * num_phavar;
|
|
H5::DataSpace *tmpSpace = new H5::DataSpace(1, &phatab_len);
|
|
H5std_string concat("-/// Phase Table");
|
|
H5::Attribute attribute = dataset.createAttribute(
|
|
concat, H5::PredType::IEEE_F64LE,
|
|
*tmpSpace); // write the attribute to the dataset
|
|
attribute.write(H5::PredType::IEEE_F64LE, &pha_tab);
|
|
delete tmpSpace;
|
|
|
|
// close file
|
|
h5f->close();
|
|
delete h5f;
|
|
|
|
cout << "Written to HDFfile as " << saveDataDim[0] << " by "
|
|
<< saveDataDim[1] << " array" << endl;
|
|
}
|
|
|
|
// Close device
|
|
LMS_Close(device);
|
|
|
|
return 0;
|
|
}
|