/** @file N pulses @author Andrin Doll @brief N pulses on TX and collection on RX requirements: LimeSuite HDF5 library compilation: g++ limedriver.cpp -std=c++11 $(pkg-config --cflags --libs LimeSuite) -o limedriver \ $(h5c++ -show) */ #include "H5Cpp.h" #include "lime/LimeSuite.h" #include #include #include #include #include #include #include #include #include // errno, ENOENT, EEXIST #include // stat #include #if defined(_WIN32) #include // _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* 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; }