From 450c1e1d29a08326f4a370005bacafd528cd25e7 Mon Sep 17 00:00:00 2001 From: "Matthias P. Braendli" Date: Thu, 2 Nov 2017 14:11:26 +0100 Subject: Create new SDR output abstraction and port Soapy --- src/output/UHD.h | 244 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 244 insertions(+) create mode 100644 src/output/UHD.h (limited to 'src/output/UHD.h') diff --git a/src/output/UHD.h b/src/output/UHD.h new file mode 100644 index 0000000..e3f8174 --- /dev/null +++ b/src/output/UHD.h @@ -0,0 +1,244 @@ +/* + Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Her Majesty the + Queen in Right of Canada (Communications Research Center Canada) + + Copyright (C) 2017 + Matthias P. Braendli, matthias.braendli@mpb.li + + http://opendigitalradio.org + +DESCRIPTION: + It is an output driver for the USRP family of devices, and uses the UHD + library. +*/ + +/* + This file is part of ODR-DabMod. + + ODR-DabMod is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as + published by the Free Software Foundation, either version 3 of the + License, or (at your option) any later version. + + ODR-DabMod is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with ODR-DabMod. If not, see . + */ + +#pragma once + +#ifdef HAVE_CONFIG_H +# include +#endif + +#ifdef HAVE_OUTPUT_UHD + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Log.h" +#include "output/SDR.h" +#include "TimestampDecoder.h" +#include "RemoteControl.h" +#include "ThreadsafeQueue.h" +#include "OutputUHDFeedback.h" + +#include +#include + +//#define MDEBUG(fmt, args...) fprintf(LOG, fmt , ## args) +#define MDEBUG(fmt, args...) + +// If the timestamp is further in the future than +// 100 seconds, abort +#define TIMESTAMP_ABORT_FUTURE 100 + +// Add a delay to increase buffers when +// frames are too far in the future +#define TIMESTAMP_MARGIN_FUTURE 0.5 + +namespace Output { + +enum refclk_lock_loss_behaviour_t { CRASH, IGNORE }; + +/* This structure is used as initial configuration for OutputUHD. + * It must also contain all remote-controllable settings, otherwise + * they will get lost on a modulator restart. */ +struct OutputUHDConfig { + std::string device; + std::string usrpType; // e.g. b100, b200, usrp2 + + // The USRP1 can accept two daughterboards + std::string subDevice; // e.g. A:0 + + long masterClockRate = 32768000; + unsigned sampleRate = 2048000; + double frequency = 0.0; + double lo_offset = 0.0; + double txgain = 0.0; + double rxgain = 0.0; + bool enableSync = false; + + // When working with timestamps, mute the frames that + // do not have a timestamp + bool muteNoTimestamps = false; + unsigned dabMode = 0; + unsigned maxGPSHoldoverTime = 0; + + /* allowed values : auto, int, sma, mimo */ + std::string refclk_src; + + /* allowed values : int, sma, mimo */ + std::string pps_src; + + /* allowed values : pos, neg */ + std::string pps_polarity; + + /* What to do when the reference clock PLL loses lock */ + refclk_lock_loss_behaviour_t refclk_lock_loss_behaviour; + + // muting can only be changed using the remote control + bool muting = false; + + // TCP port on which to serve TX and RX samples for the + // digital pre distortion learning tool + uint16_t dpdFeedbackServerPort = 0; +}; + +class OutputUHD: public ModOutput, public RemoteControllable { + public: + OutputUHD(OutputUHDConfig& config); + OutputUHD(const OutputUHD& other) = delete; + OutputUHD operator=(const OutputUHD& other) = delete; + ~OutputUHD(); + + int process(Buffer* dataIn); + + const char* name() { return "OutputUHD"; } + + void setETISource(EtiSource *etiSource); + + /*********** REMOTE CONTROL ***************/ + + /* Base function to set parameters. */ + virtual void set_parameter(const std::string& parameter, + const std::string& value); + + /* Getting a parameter always returns a string. */ + virtual const std::string get_parameter( + const std::string& parameter) const; + + protected: + EtiSource *myEtiSource = nullptr; + OutputUHDConfig& myConf; + uhd::usrp::multi_usrp::sptr myUsrp; + std::shared_ptr mySyncBarrier; + bool first_run = true; + bool gps_fix_verified = false; + std::shared_ptr uhdFeedback; + + private: + // Each frame contains one OFDM frame, and its + // associated timestamp + struct UHDWorkerFrameData { + // Buffer holding frame data + std::vector buf; + + // A full timestamp contains a TIST according to standard + // and time information within MNSC with tx_second. + struct frame_timestamp ts; + }; + + // Resize the internal delay buffer according to the dabMode and + // the sample rate. + void SetDelayBuffer(unsigned int dabMode); + + // data + // The remote-controllable static delay is in the OutputUHDConfig + int myTFDurationMs; // TF duration in milliseconds + std::vector myDelayBuf; + size_t lastLen = 0; + + // GPS Fix check variables + int num_checks_without_gps_fix = 1; + struct timespec first_gps_fix_check; + struct timespec last_gps_fix_check; + struct timespec time_last_frame; + boost::packaged_task gps_fix_pt; + boost::unique_future gps_fix_future; + boost::thread gps_fix_task; + + // Wait time in seconds to get fix + static const int initial_gps_fix_wait = 180; + + // Interval for checking the GPS at runtime + static constexpr double gps_fix_check_interval = 10.0; // seconds + + // Asynchronous message statistics + size_t num_underflows = 0; + size_t num_late_packets = 0; + size_t num_underflows_previous = 0; + size_t num_late_packets_previous = 0; + + size_t num_frames_modulated = 0; + + uhd::tx_metadata_t md; + bool last_tx_time_initialised = false; + uint32_t last_tx_second = 0; + uint32_t last_tx_pps = 0; + + // Used to print statistics once a second + std::chrono::steady_clock::time_point last_print_time; + + bool sourceContainsTimestamp = false; + + ThreadsafeQueue frames; + + // Returns true if we want to verify loss of refclk + bool refclk_loss_needs_check(void) const; + bool suppress_refclk_loss_check = false; + + // Returns true if we want to check for the gps_timelock sensor + bool gpsfix_needs_check(void) const; + + // Return true if the gpsdo is from ettus, false if it is the ODR + // LEA-M8F board is used + bool gpsdo_is_ettus(void) const; + + std::atomic running; + boost::thread uhd_thread; + boost::thread async_rx_thread; + void stop_threads(void); + + uhd::tx_streamer::sptr myTxStream; + + // The worker thread decouples the modulator from UHD + void workerthread(); + void handle_frame(const struct UHDWorkerFrameData *frame); + void tx_frame(const struct UHDWorkerFrameData *frame, bool ts_update); + + // Poll asynchronous metadata from UHD + void print_async_thread(void); + + void check_gps(); + + void set_usrp_time(); + + void initial_gps_check(); +}; + +} // namespace Output + +#endif // HAVE_OUTPUT_UHD + -- cgit v1.2.3 From 53c3963b8a47d485e6776caa06e86d14a8520c0c Mon Sep 17 00:00:00 2001 From: "Matthias P. Braendli" Date: Fri, 3 Nov 2017 10:40:09 +0100 Subject: Port UHD to new SDR abstraction --- src/ConfigParser.cpp | 76 +++--- src/ConfigParser.h | 15 +- src/DabMod.cpp | 47 ++-- src/output/SDR.cpp | 3 + src/output/SDR.h | 1 + src/output/UHD.cpp | 737 ++++++++++++++------------------------------------- src/output/UHD.h | 158 +++-------- 7 files changed, 303 insertions(+), 734 deletions(-) (limited to 'src/output/UHD.h') diff --git a/src/ConfigParser.cpp b/src/ConfigParser.cpp index c9ed212..c532ab5 100644 --- a/src/ConfigParser.cpp +++ b/src/ConfigParser.cpp @@ -210,65 +210,73 @@ static void parse_configfile( } #if defined(HAVE_OUTPUT_UHD) else if (output_selected == "uhd") { - OutputUHDConfig outputuhd_conf; + Output::SDRDeviceConfig sdr_device_config; - outputuhd_conf.device = pt.get("uhdoutput.device", ""); - outputuhd_conf.usrpType = pt.get("uhdoutput.type", ""); - outputuhd_conf.subDevice = pt.get("uhdoutput.subdevice", ""); - outputuhd_conf.masterClockRate = pt.get("uhdoutput.master_clock_rate", 0); + string device = pt.get("uhdoutput.device", ""); + const auto usrpType = pt.get("uhdoutput.type", ""); + if (usrpType != "") { + if (not device.empty()) { + device += ","; + } + device += "type=" + usrpType; + } + sdr_device_config.device = device; + + sdr_device_config.subDevice = pt.get("uhdoutput.subdevice", ""); + sdr_device_config.masterClockRate = pt.get("uhdoutput.master_clock_rate", 0); - if (outputuhd_conf.device.find("master_clock_rate") != std::string::npos) { + if (sdr_device_config.device.find("master_clock_rate") != std::string::npos) { std::cerr << "Warning:" "setting master_clock_rate in [uhd] device is deprecated !\n"; } - if (outputuhd_conf.device.find("type=") != std::string::npos) { + if (sdr_device_config.device.find("type=") != std::string::npos) { std::cerr << "Warning:" "setting type in [uhd] device is deprecated !\n"; } - outputuhd_conf.txgain = pt.get("uhdoutput.txgain", 0.0); - outputuhd_conf.rxgain = pt.get("uhdoutput.rxgain", 0.0); - outputuhd_conf.frequency = pt.get("uhdoutput.frequency", 0); + sdr_device_config.txgain = pt.get("uhdoutput.txgain", 0.0); + sdr_device_config.rxgain = pt.get("uhdoutput.rxgain", 0.0); + sdr_device_config.frequency = pt.get("uhdoutput.frequency", 0); std::string chan = pt.get("uhdoutput.channel", ""); - outputuhd_conf.dabMode = mod_settings.dabMode; + sdr_device_config.dabMode = mod_settings.dabMode; - if (outputuhd_conf.frequency == 0 && chan == "") { + if (sdr_device_config.frequency == 0 && chan == "") { std::cerr << " UHD output enabled, but neither frequency nor channel defined.\n"; throw std::runtime_error("Configuration error"); } - else if (outputuhd_conf.frequency == 0) { - outputuhd_conf.frequency = parseChannel(chan); + else if (sdr_device_config.frequency == 0) { + sdr_device_config.frequency = parseChannel(chan); } - else if (outputuhd_conf.frequency != 0 && chan != "") { + else if (sdr_device_config.frequency != 0 && chan != "") { std::cerr << " UHD output: cannot define both frequency and channel.\n"; throw std::runtime_error("Configuration error"); } - outputuhd_conf.lo_offset = pt.get("uhdoutput.lo_offset", 0); + sdr_device_config.lo_offset = pt.get("uhdoutput.lo_offset", 0); - outputuhd_conf.refclk_src = pt.get("uhdoutput.refclk_source", "internal"); - outputuhd_conf.pps_src = pt.get("uhdoutput.pps_source", "none"); - outputuhd_conf.pps_polarity = pt.get("uhdoutput.pps_polarity", "pos"); + sdr_device_config.refclk_src = pt.get("uhdoutput.refclk_source", "internal"); + sdr_device_config.pps_src = pt.get("uhdoutput.pps_source", "none"); + sdr_device_config.pps_polarity = pt.get("uhdoutput.pps_polarity", "pos"); std::string behave = pt.get("uhdoutput.behaviour_refclk_lock_lost", "ignore"); if (behave == "crash") { - outputuhd_conf.refclk_lock_loss_behaviour = CRASH; + sdr_device_config.refclk_lock_loss_behaviour = Output::CRASH; } else if (behave == "ignore") { - outputuhd_conf.refclk_lock_loss_behaviour = IGNORE; + sdr_device_config.refclk_lock_loss_behaviour = Output::IGNORE; } else { std::cerr << "Error: UHD output: behaviour_refclk_lock_lost invalid." << std::endl; throw std::runtime_error("Configuration error"); } - outputuhd_conf.maxGPSHoldoverTime = pt.get("uhdoutput.max_gps_holdover_time", 0); + sdr_device_config.maxGPSHoldoverTime = pt.get("uhdoutput.max_gps_holdover_time", 0); - outputuhd_conf.dpdFeedbackServerPort = pt.get("uhdoutput.dpd_port", 0); + sdr_device_config.dpdFeedbackServerPort = pt.get("uhdoutput.dpd_port", 0); - mod_settings.outputuhd_conf = outputuhd_conf; + mod_settings.sdr_device_config = sdr_device_config; mod_settings.useUHDOutput = 1; } #endif @@ -312,9 +320,9 @@ static void parse_configfile( } #if defined(HAVE_OUTPUT_UHD) - mod_settings.outputuhd_conf.enableSync = (pt.get("delaymanagement.synchronous", 0) == 1); - mod_settings.outputuhd_conf.muteNoTimestamps = (pt.get("delaymanagement.mutenotimestamps", 0) == 1); - if (mod_settings.outputuhd_conf.enableSync) { + mod_settings.sdr_device_config.enableSync = (pt.get("delaymanagement.synchronous", 0) == 1); + mod_settings.sdr_device_config.muteNoTimestamps = (pt.get("delaymanagement.mutenotimestamps", 0) == 1); + if (mod_settings.sdr_device_config.enableSync) { std::string delay_mgmt = pt.get("delaymanagement.management", ""); std::string fixedoffset = pt.get("delaymanagement.fixedoffset", ""); std::string offset_filename = pt.get("delaymanagement.dynamicoffsetfile", ""); @@ -389,7 +397,7 @@ void parse_args(int argc, char **argv, mod_settings_t& mod_settings) break; case 'F': #if defined(HAVE_OUTPUT_UHD) - mod_settings.outputuhd_conf.frequency = strtof(optarg, NULL); + mod_settings.sdr_device_config.frequency = strtof(optarg, NULL); #endif break; case 'g': @@ -397,7 +405,7 @@ void parse_args(int argc, char **argv, mod_settings_t& mod_settings) break; case 'G': #if defined(HAVE_OUTPUT_UHD) - mod_settings.outputuhd_conf.txgain = strtod(optarg, NULL); + mod_settings.sdr_device_config.txgain = strtod(optarg, NULL); #endif break; case 'l': @@ -406,7 +414,7 @@ void parse_args(int argc, char **argv, mod_settings_t& mod_settings) case 'o': mod_settings.tist_offset_s = strtod(optarg, NULL); #if defined(HAVE_OUTPUT_UHD) - mod_settings.outputuhd_conf.enableSync = true; + mod_settings.sdr_device_config.enableSync = true; #endif break; case 'm': @@ -424,10 +432,10 @@ void parse_args(int argc, char **argv, mod_settings_t& mod_settings) fprintf(stderr, "Options -u and -f are mutually exclusive\n"); throw std::invalid_argument("Invalid command line options"); } - mod_settings.outputuhd_conf.device = optarg; - mod_settings.outputuhd_conf.refclk_src = "internal"; - mod_settings.outputuhd_conf.pps_src = "none"; - mod_settings.outputuhd_conf.pps_polarity = "pos"; + mod_settings.sdr_device_config.device = optarg; + mod_settings.sdr_device_config.refclk_src = "internal"; + mod_settings.sdr_device_config.pps_src = "none"; + mod_settings.sdr_device_config.pps_polarity = "pos"; mod_settings.useUHDOutput = 1; #endif break; diff --git a/src/ConfigParser.h b/src/ConfigParser.h index 4d936b8..dd6c040 100644 --- a/src/ConfigParser.h +++ b/src/ConfigParser.h @@ -35,12 +35,8 @@ #include "GainControl.h" #include "TII.h" #include "output/SDR.h" -#if defined(HAVE_OUTPUT_UHD) -# include "OutputUHD.h" -#endif -#if defined(HAVE_SOAPYSDR) -# include "output/Soapy.h" -#endif +#include "output/UHD.h" +#include "output/Soapy.h" #define ZMQ_INPUT_MAX_FRAME_QUEUE 500 @@ -83,12 +79,7 @@ struct mod_settings_t { float cfrClip = 1.0f; float cfrErrorClip = 1.0f; - -#if defined(HAVE_OUTPUT_UHD) - OutputUHDConfig outputuhd_conf; -#endif - -#if defined(HAVE_SOAPYSDR) +#if defined(HAVE_OUTPUT_UHD) || defined(HAVE_SOAPYSDR) Output::SDRDeviceConfig sdr_device_config; #endif diff --git a/src/DabMod.cpp b/src/DabMod.cpp index b810f20..e1fbe4a 100644 --- a/src/DabMod.cpp +++ b/src/DabMod.cpp @@ -37,12 +37,8 @@ #include "OutputFile.h" #include "FormatConverter.h" #include "output/SDR.h" -#if defined(HAVE_OUTPUT_UHD) -# include "OutputUHD.h" -#endif -#if defined(HAVE_SOAPYSDR) -# include "output/Soapy.h" -#endif +#include "output/UHD.h" +#include "output/Soapy.h" #include "OutputZeroMQ.h" #include "InputReader.h" #include "PcDebug.h" @@ -140,15 +136,15 @@ static void printModSettings(const mod_settings_t& mod_settings) else if (mod_settings.useUHDOutput) { fprintf(stderr, " UHD\n" " Device: %s\n" - " Type: %s\n" + " Subdevice: %s\n" " master_clock_rate: %ld\n" " refclk: %s\n" " pps source: %s\n", - mod_settings.outputuhd_conf.device.c_str(), - mod_settings.outputuhd_conf.usrpType.c_str(), - mod_settings.outputuhd_conf.masterClockRate, - mod_settings.outputuhd_conf.refclk_src.c_str(), - mod_settings.outputuhd_conf.pps_src.c_str()); + mod_settings.sdr_device_config.device.c_str(), + mod_settings.sdr_device_config.subDevice.c_str(), + mod_settings.sdr_device_config.masterClockRate, + mod_settings.sdr_device_config.refclk_src.c_str(), + mod_settings.sdr_device_config.pps_src.c_str()); } #endif #if defined(HAVE_SOAPYSDR) @@ -211,9 +207,10 @@ static shared_ptr prepare_output( #if defined(HAVE_OUTPUT_UHD) else if (s.useUHDOutput) { s.normalise = 1.0f / normalise_factor; - s.outputuhd_conf.sampleRate = s.outputRate; - output = make_shared(s.outputuhd_conf); - rcs.enrol((OutputUHD*)output.get()); + s.sdr_device_config.sampleRate = s.outputRate; + auto uhddevice = make_shared(s.sdr_device_config); + output = make_shared(s.sdr_device_config, uhddevice); + rcs.enrol((Output::SDR*)output.get()); } #endif #if defined(HAVE_SOAPYSDR) @@ -329,16 +326,16 @@ int launch_modulator(int argc, char* argv[]) flowgraph.connect(modulator, output); } + if (false #if defined(HAVE_OUTPUT_UHD) - if (mod_settings.useUHDOutput) { - ((OutputUHD*)output.get())->setETISource(modulator->getEtiSource()); - } + or mod_settings.useUHDOutput #endif #if defined(HAVE_SOAPYSDR) - if (mod_settings.useSoapyOutput) { + or mod_settings.useSoapyOutput +#endif + ) { ((Output::SDR*)output.get())->setETISource(modulator->getEtiSource()); } -#endif size_t framecount = 0; @@ -423,16 +420,16 @@ int launch_modulator(int argc, char* argv[]) flowgraph.connect(modulator, output); } + if (false #if defined(HAVE_OUTPUT_UHD) - if (mod_settings.useUHDOutput) { - ((OutputUHD*)output.get())->setETISource(modulator->getEtiSource()); - } + or mod_settings.useUHDOutput #endif #if defined(HAVE_SOAPYSDR) - if (mod_settings.useSoapyOutput) { + or mod_settings.useSoapyOutput +#endif + ) { ((Output::SDR*)output.get())->setETISource(modulator->getEtiSource()); } -#endif inputReader->PrintInfo(); diff --git a/src/output/SDR.cpp b/src/output/SDR.cpp index c1183c4..2ad8e57 100644 --- a/src/output/SDR.cpp +++ b/src/output/SDR.cpp @@ -63,6 +63,9 @@ SDR::SDR(SDRDeviceConfig& config, std::shared_ptr device) : m_running(false), m_device(device) { + // muting is remote-controllable, and reset by the GPS fix check + m_config.muting = true; + m_device_thread = std::thread(&SDR::process_thread_entry, this); } diff --git a/src/output/SDR.h b/src/output/SDR.h index ab23aa1..370bc38 100644 --- a/src/output/SDR.h +++ b/src/output/SDR.h @@ -48,6 +48,7 @@ enum refclk_lock_loss_behaviour_t { CRASH, IGNORE }; * they will get lost on a modulator restart. */ struct SDRDeviceConfig { std::string device; + std::string subDevice; // For UHD long masterClockRate = 32768000; unsigned sampleRate = 2048000; diff --git a/src/output/UHD.cpp b/src/output/UHD.cpp index 4942f3f..13a0a57 100644 --- a/src/output/UHD.cpp +++ b/src/output/UHD.cpp @@ -24,7 +24,7 @@ along with ODR-DabMod. If not, see . */ -#include "OutputUHD.h" +#include "output/UHD.h" #ifdef HAVE_OUTPUT_UHD @@ -49,12 +49,12 @@ using namespace std; +namespace Output { + // Maximum number of frames that can wait in frames static const size_t FRAMES_MAX_SIZE = 8; -typedef std::complex complexf; - -std::string stringtrim(const std::string &s) +static std::string stringtrim(const std::string &s) { auto wsfront = std::find_if_not(s.begin(), s.end(), [](int c){ return std::isspace(c);} ); @@ -64,7 +64,7 @@ std::string stringtrim(const std::string &s) [](int c){ return std::isspace(c);} ).base()); } -void uhd_msg_handler(uhd::msg::type_t type, const std::string &msg) +static void uhd_msg_handler(uhd::msg::type_t type, const std::string &msg) { if (type == uhd::msg::warning) { etiLog.level(warn) << "UHD Warning: " << msg; @@ -80,38 +80,9 @@ void uhd_msg_handler(uhd::msg::type_t type, const std::string &msg) } } -static void tune_usrp_to( - uhd::usrp::multi_usrp::sptr usrp, - double lo_offset, - double frequency) -{ - if (lo_offset != 0.0) { - etiLog.level(info) << std::fixed << std::setprecision(3) << - "OutputUHD:Setting freq to " << frequency << - " with LO offset " << lo_offset << "..."; - - const auto tr = uhd::tune_request_t(frequency, lo_offset); - uhd::tune_result_t result = usrp->set_tx_freq(tr); - - etiLog.level(debug) << "OutputUHD:" << - std::fixed << std::setprecision(0) << - " Target RF: " << result.target_rf_freq << - " Actual RF: " << result.actual_rf_freq << - " Target DSP: " << result.target_dsp_freq << - " Actual DSP: " << result.actual_dsp_freq; - } - else { - //set the centre frequency - etiLog.level(info) << std::fixed << std::setprecision(3) << - "OutputUHD:Setting freq to " << frequency << "..."; - usrp->set_tx_freq(frequency); - } - - usrp->set_rx_freq(frequency); -} // Check function for GPS TIMELOCK sensor from the ODR LEA-M8F board GPSDO -bool check_gps_timelock(uhd::usrp::multi_usrp::sptr usrp) +static bool check_gps_timelock(uhd::usrp::multi_usrp::sptr usrp) { try { std::string sensor_value( @@ -131,7 +102,7 @@ bool check_gps_timelock(uhd::usrp::multi_usrp::sptr usrp) } // Check function for GPS LOCKED sensor from the Ettus GPSDO -bool check_gps_locked(uhd::usrp::multi_usrp::sptr usrp) +static bool check_gps_locked(uhd::usrp::multi_usrp::sptr usrp) { try { uhd::sensor_value_t sensor_value( @@ -151,52 +122,33 @@ bool check_gps_locked(uhd::usrp::multi_usrp::sptr usrp) } -OutputUHD::OutputUHD( - OutputUHDConfig& config) : - ModOutput(), - RemoteControllable("uhd"), - myConf(config), - // Since we don't know the buffer size, we cannot initialise - // the buffers at object initialisation. - myDelayBuf(0), - running(false) +UHD::UHD( + SDRDeviceConfig& config) : + SDRDevice(), + m_conf(config), + m_running(false) { - myConf.muting = true; // is remote-controllable, and reset by the GPS fix check - // Variables needed for GPS fix check first_gps_fix_check.tv_sec = 0; last_gps_fix_check.tv_sec = 0; time_last_frame.tv_sec = 0; - std::stringstream device; - device << myConf.device; - - if (myConf.masterClockRate != 0) { - if (device.str() != "") { - device << ","; - } - device << "master_clock_rate=" << myConf.masterClockRate; - } + device << m_conf.device; - if (myConf.usrpType != "") { + if (m_conf.masterClockRate != 0) { if (device.str() != "") { device << ","; } - device << "type=" << myConf.usrpType; + device << "master_clock_rate=" << m_conf.masterClockRate; } MDEBUG("OutputUHD::OutputUHD(device: %s) @ %p\n", device.str().c_str(), this); - /* register the parameters that can be remote controlled */ - RC_ADD_PARAMETER(txgain, "UHD analog daughterboard TX gain"); + /* TODO RC_ADD_PARAMETER(rxgain, "UHD analog daughterboard RX gain for DPD feedback"); - RC_ADD_PARAMETER(freq, "UHD transmission frequency"); - RC_ADD_PARAMETER(muting, "Mute the output by stopping the transmitter"); - RC_ADD_PARAMETER(underruns, "Read-only counter of number of underruns"); - RC_ADD_PARAMETER(latepackets, "Read-only counter of number of late packets"); - RC_ADD_PARAMETER(frames, "Read-only counter of number of frames modulated"); + */ uhd::msg::register_handler(uhd_msg_handler); @@ -205,267 +157,261 @@ OutputUHD::OutputUHD( etiLog.log(info, "OutputUHD:Creating the usrp device with: %s...", device.str().c_str()); - myUsrp = uhd::usrp::multi_usrp::make(device.str()); + m_usrp = uhd::usrp::multi_usrp::make(device.str()); etiLog.log(info, "OutputUHD:Using device: %s...", - myUsrp->get_pp_string().c_str()); + m_usrp->get_pp_string().c_str()); - if (myConf.masterClockRate != 0.0) { - double master_clk_rate = myUsrp->get_master_clock_rate(); + if (m_conf.masterClockRate != 0.0) { + double master_clk_rate = m_usrp->get_master_clock_rate(); etiLog.log(debug, "OutputUHD:Checking master clock rate: %f...", master_clk_rate); - if (fabs(master_clk_rate - myConf.masterClockRate) > - (myConf.masterClockRate * 1e-6)) { + if (fabs(master_clk_rate - m_conf.masterClockRate) > + (m_conf.masterClockRate * 1e-6)) { throw std::runtime_error("Cannot set USRP master_clock_rate. Aborted."); } } MDEBUG("OutputUHD:Setting REFCLK and PPS input...\n"); - if (myConf.refclk_src == "gpsdo-ettus") { - myUsrp->set_clock_source("gpsdo"); + if (m_conf.refclk_src == "gpsdo-ettus") { + m_usrp->set_clock_source("gpsdo"); } else { - myUsrp->set_clock_source(myConf.refclk_src); + m_usrp->set_clock_source(m_conf.refclk_src); } - myUsrp->set_time_source(myConf.pps_src); + m_usrp->set_time_source(m_conf.pps_src); - if (myConf.subDevice != "") { - myUsrp->set_tx_subdev_spec(uhd::usrp::subdev_spec_t(myConf.subDevice), + if (m_conf.subDevice != "") { + m_usrp->set_tx_subdev_spec(uhd::usrp::subdev_spec_t(m_conf.subDevice), uhd::usrp::multi_usrp::ALL_MBOARDS); } - etiLog.level(debug) << "UHD clock source is " << myUsrp->get_clock_source(0); + etiLog.level(debug) << "UHD clock source is " << m_usrp->get_clock_source(0); - etiLog.level(debug) << "UHD time source is " << myUsrp->get_time_source(0); + etiLog.level(debug) << "UHD time source is " << m_usrp->get_time_source(0); - myUsrp->set_tx_rate(myConf.sampleRate); + m_usrp->set_tx_rate(m_conf.sampleRate); etiLog.log(debug, "OutputUHD:Set rate to %d. Actual TX Rate: %f sps...", - myConf.sampleRate, myUsrp->get_tx_rate()); + m_conf.sampleRate, m_usrp->get_tx_rate()); - if (fabs(myUsrp->get_tx_rate() / myConf.sampleRate) > - myConf.sampleRate * 1e-6) { + if (fabs(m_usrp->get_tx_rate() / m_conf.sampleRate) > + m_conf.sampleRate * 1e-6) { throw std::runtime_error("Cannot set USRP sample rate. Aborted."); } - tune_usrp_to(myUsrp, myConf.lo_offset, myConf.frequency); + tune(m_conf.lo_offset, m_conf.frequency); - myConf.frequency = myUsrp->get_tx_freq(); + m_conf.frequency = m_usrp->get_tx_freq(); etiLog.level(info) << std::fixed << std::setprecision(3) << - "OutputUHD:Actual TX frequency: " << myConf.frequency; + "OutputUHD:Actual TX frequency: " << m_conf.frequency; etiLog.level(info) << std::fixed << std::setprecision(3) << - "OutputUHD:Actual RX frequency: " << myUsrp->get_tx_freq(); + "OutputUHD:Actual RX frequency: " << m_usrp->get_tx_freq(); - myUsrp->set_tx_gain(myConf.txgain); - etiLog.log(debug, "OutputUHD:Actual TX Gain: %f", myUsrp->get_tx_gain()); + m_usrp->set_tx_gain(m_conf.txgain); + m_conf.txgain = m_usrp->get_tx_gain(); + etiLog.log(debug, "OutputUHD:Actual TX Gain: %f", m_conf.txgain); etiLog.log(debug, "OutputUHD:Mute on missing timestamps: %s", - myConf.muteNoTimestamps ? "enabled" : "disabled"); + m_conf.muteNoTimestamps ? "enabled" : "disabled"); // preparing output thread worker data - sourceContainsTimestamp = false; - - SetDelayBuffer(myConf.dabMode); + // TODO sourceContainsTimestamp = false; - myUsrp->set_rx_rate(myConf.sampleRate); - etiLog.log(debug, "OutputUHD:Actual RX Rate: %f sps.", myUsrp->get_rx_rate()); + m_usrp->set_rx_rate(m_conf.sampleRate); + etiLog.log(debug, "OutputUHD:Actual RX Rate: %f sps.", m_usrp->get_rx_rate()); - myUsrp->set_rx_antenna("RX2"); + m_usrp->set_rx_antenna("RX2"); etiLog.log(debug, "OutputUHD:Set RX Antenna: %s", - myUsrp->get_rx_antenna().c_str()); + m_usrp->get_rx_antenna().c_str()); - myUsrp->set_rx_gain(myConf.rxgain); - etiLog.log(debug, "OutputUHD:Actual RX Gain: %f", myUsrp->get_rx_gain()); + m_usrp->set_rx_gain(m_conf.rxgain); + etiLog.log(debug, "OutputUHD:Actual RX Gain: %f", m_usrp->get_rx_gain()); + /* TODO uhdFeedback = std::make_shared( - myUsrp, myConf.dpdFeedbackServerPort, myConf.sampleRate); + m_usrp, m_conf.dpdFeedbackServerPort, m_conf.sampleRate); + */ MDEBUG("OutputUHD:UHD ready.\n"); } -bool OutputUHD::refclk_loss_needs_check() const +UHD::~UHD() { - if (suppress_refclk_loss_check) { - return false; - } - return myConf.refclk_src != "internal"; + stop_threads(); } -bool OutputUHD::gpsfix_needs_check() const +void UHD::tune(double lo_offset, double frequency) { - if (myConf.refclk_src == "internal") { - return false; - } - else if (myConf.refclk_src == "gpsdo") { - return (myConf.maxGPSHoldoverTime != 0); - } - else if (myConf.refclk_src == "gpsdo-ettus") { - return (myConf.maxGPSHoldoverTime != 0); + if (lo_offset != 0.0) { + etiLog.level(info) << std::fixed << std::setprecision(3) << + "OutputUHD:Setting freq to " << frequency << + " with LO offset " << lo_offset << "..."; + + const auto tr = uhd::tune_request_t(frequency, lo_offset); + uhd::tune_result_t result = m_usrp->set_tx_freq(tr); + + etiLog.level(debug) << "OutputUHD:" << + std::fixed << std::setprecision(0) << + " Target RF: " << result.target_rf_freq << + " Actual RF: " << result.actual_rf_freq << + " Target DSP: " << result.target_dsp_freq << + " Actual DSP: " << result.actual_dsp_freq; } else { - return false; + //set the centre frequency + etiLog.level(info) << std::fixed << std::setprecision(3) << + "OutputUHD:Setting freq to " << frequency << "..."; + m_usrp->set_tx_freq(frequency); } -} -bool OutputUHD::gpsdo_is_ettus() const -{ - return (myConf.refclk_src == "gpsdo-ettus"); + // TODO configure LO offset also for RX + m_usrp->set_rx_freq(frequency); } -OutputUHD::~OutputUHD() +double UHD::get_tx_freq(void) { - stop_threads(); + return m_usrp->get_tx_freq(); } -void OutputUHD::stop_threads() +void UHD::set_txgain(double txgain) { - running.store(false); - uhd_thread.interrupt(); - uhd_thread.join(); - async_rx_thread.join(); + m_usrp->set_tx_gain(txgain); + m_conf.txgain = m_usrp->get_tx_gain(); } - -void OutputUHD::setETISource(EtiSource *etiSource) +double UHD::get_txgain(void) { - myEtiSource = etiSource; + return m_usrp->get_tx_gain(); } -int transmission_frame_duration_ms(unsigned int dabMode) +void UHD::transmit_frame(const struct FrameData& frame) { - switch (dabMode) { - // could happen when called from constructor and we take the mode from ETI - case 0: return 0; - - case 1: return 96; - case 2: return 24; - case 3: return 24; - case 4: return 48; - default: - throw std::runtime_error("OutputUHD: invalid DAB mode"); - } -} + const double tx_timeout = 20.0; + const size_t sizeIn = frame.buf.size() / sizeof(complexf); + const complexf* in_data = reinterpret_cast(&frame.buf[0]); -void OutputUHD::SetDelayBuffer(unsigned int dabMode) -{ - // find out the duration of the transmission frame (Table 2 in ETSI 300 401) - myTFDurationMs = transmission_frame_duration_ms(dabMode); + size_t usrp_max_num_samps = m_tx_stream->get_max_num_samps(); + size_t num_acc_samps = 0; //number of accumulated samples + while (m_running.load() and (not m_conf.muting) and (num_acc_samps < sizeIn)) { + size_t samps_to_send = std::min(sizeIn - num_acc_samps, usrp_max_num_samps); - // The buffer size equals the number of samples per transmission frame so - // we calculate it by multiplying the duration of the transmission frame - // with the samplerate. - myDelayBuf.resize(myTFDurationMs * myConf.sampleRate / 1000); -} + uhd::tx_metadata_t md_tx = md; -int OutputUHD::process(Buffer* dataIn) -{ - if (not gps_fix_verified) { - if (gpsfix_needs_check()) { - initial_gps_check(); - - if (num_checks_without_gps_fix == 0) { - set_usrp_time(); - gps_fix_verified = true; - myConf.muting = false; - } - } - else { - set_usrp_time(); - gps_fix_verified = true; - myConf.muting = false; - } - } - else { - if (first_run) { - etiLog.level(debug) << "OutputUHD: UHD initialising..."; + // ensure the the last packet has EOB set if the timestamps has been + // refreshed and need to be reconsidered. + md_tx.end_of_burst = ( + frame.ts.timestamp_valid and + frame.ts.timestamp_refresh and + samps_to_send <= usrp_max_num_samps ); - // we only set the delay buffer from the dab mode signaled in ETI if the - // dab mode was not set in contructor - if (myTFDurationMs == 0) { - SetDelayBuffer(myEtiSource->getMode()); - } + //send a single packet + size_t num_tx_samps = m_tx_stream->send( + &in_data[num_acc_samps], + samps_to_send, md_tx, tx_timeout); + etiLog.log(trace, "UHD,sent %zu of %zu", num_tx_samps, samps_to_send); - running.store(true); - uhd_thread = boost::thread(&OutputUHD::workerthread, this); - async_rx_thread = boost::thread( - &OutputUHD::print_async_thread, this); + num_acc_samps += num_tx_samps; - lastLen = dataIn->getLength(); - first_run = false; - etiLog.level(debug) << "OutputUHD: UHD initialising complete"; - } + md_tx.time_spec = md.time_spec + + uhd::time_spec_t(0, num_tx_samps/m_conf.sampleRate); - if (lastLen != dataIn->getLength()) { - // I expect that this never happens. - etiLog.level(emerg) << - "OutputUHD: Fatal error, input length changed from " << lastLen << - " to " << dataIn->getLength(); - throw std::runtime_error("Non-constant input length!"); + if (num_tx_samps == 0) { + etiLog.log(warn, + "OutputUHD unable to write to device, skipping frame!"); + break; } + } +} - sourceContainsTimestamp = myConf.enableSync and - myEtiSource->sourceContainsTimestamp(); - if (gpsfix_needs_check()) { - try { - check_gps(); - } - catch (std::runtime_error& e) { - running.store(false); - etiLog.level(error) << e.what(); - } - } +SDRDevice::RunStatistics UHD::get_run_statistics(void) +{ + RunStatistics rs; + rs.num_underruns = num_underflows; + rs.num_overruns = num_overflows; + rs.num_late_packets = num_late_packets; + rs.num_frames_modulated = num_frames_modulated; + return rs; +} - // Prepare the frame for the worker - UHDWorkerFrameData frame; - frame.buf.resize(dataIn->getLength()); +double UHD::get_real_secs(void) +{ + return m_usrp->get_time_now().get_real_secs(); +} - std::copy(pInData, pInData + dataIn->getLength(), - frame.buf.begin()); +// Return true if GPS and reference clock inputs are ok +bool UHD::is_clk_source_ok(void) +{ + //TODO + return true; +} - myEtiSource->calculateTimestamp(frame.ts); +const char* UHD::device_name(void) +{ + return "UHD"; +} - if (not running.load()) { - uhd_thread.interrupt(); - uhd_thread.join(); - async_rx_thread.join(); - first_run = true; - etiLog.level(error) << "OutputUHD UHD worker failed"; - throw std::runtime_error("UHD worker failed"); - } +bool UHD::refclk_loss_needs_check() const +{ + if (suppress_refclk_loss_check) { + return false; + } + return m_conf.refclk_src != "internal"; +} - if (frame.ts.fct == -1) { - etiLog.level(info) << - "OutputUHD: dropping one frame with invalid FCT"; - } - else { - try { - uhdFeedback->set_tx_frame(frame.buf, frame.ts); - } - catch (const runtime_error& e) { - etiLog.level(warn) << - "OutputUHD: Feedback server failed, restarting..."; +bool UHD::gpsfix_needs_check() const +{ + if (m_conf.refclk_src == "internal") { + return false; + } + else if (m_conf.refclk_src == "gpsdo") { + return (m_conf.maxGPSHoldoverTime != 0); + } + else if (m_conf.refclk_src == "gpsdo-ettus") { + return (m_conf.maxGPSHoldoverTime != 0); + } + else { + return false; + } +} - uhdFeedback = std::make_shared( - myUsrp, myConf.dpdFeedbackServerPort, myConf.sampleRate); - } +bool UHD::gpsdo_is_ettus() const +{ + return (m_conf.refclk_src == "gpsdo-ettus"); +} - size_t num_frames = frames.push_wait_if_full(frame, - FRAMES_MAX_SIZE); - etiLog.log(trace, "UHD,push %zu", num_frames); - } +void UHD::stop_threads() +{ + m_running.store(false); + if (m_async_rx_thread.joinable()) { + m_async_rx_thread.join(); } +} - return dataIn->getLength(); + +static int transmission_frame_duration_ms(unsigned int dabMode) +{ + switch (dabMode) { + // could happen when called from constructor and we take the mode from ETI + case 0: return 0; + + case 1: return 96; + case 2: return 24; + case 3: return 24; + case 4: return 48; + default: + throw std::runtime_error("OutputUHD: invalid DAB mode"); + } } -void OutputUHD::set_usrp_time() +void UHD::set_usrp_time() { - if (myConf.enableSync and (myConf.pps_src == "none")) { + if (m_conf.enableSync and (m_conf.pps_src == "none")) { etiLog.level(warn) << "OutputUHD: WARNING:" " you are using synchronous transmission without PPS input!"; @@ -476,14 +422,14 @@ void OutputUHD::set_usrp_time() etiLog.level(error) << "OutputUHD: could not get time"; } else { - myUsrp->set_time_now(uhd::time_spec_t(now.tv_sec)); + m_usrp->set_time_now(uhd::time_spec_t(now.tv_sec)); etiLog.level(info) << "OutputUHD: Setting USRP time to " << std::fixed << uhd::time_spec_t(now.tv_sec).get_real_secs(); } } - if (myConf.pps_src != "none") { + if (m_conf.pps_src != "none") { /* handling time for synchronisation: wait until the next full * second, and set the USRP time at next PPS */ struct timespec now; @@ -509,7 +455,7 @@ void OutputUHD::set_usrp_time() /* We are now shortly after the second change. */ usleep(200000); // 200ms, we want the PPS to be later - myUsrp->set_time_unknown_pps(uhd::time_spec_t(seconds + 2)); + m_usrp->set_time_unknown_pps(uhd::time_spec_t(seconds + 2)); etiLog.level(info) << "OutputUHD: Setting USRP time next pps to " << std::fixed << uhd::time_spec_t(seconds + 2).get_real_secs(); @@ -517,11 +463,11 @@ void OutputUHD::set_usrp_time() usleep(1e6); etiLog.log(info, "OutputUHD: USRP time %f\n", - myUsrp->get_time_now().get_real_secs()); + m_usrp->get_time_now().get_real_secs()); } } -void OutputUHD::initial_gps_check() +void UHD::initial_gps_check() { if (first_gps_fix_check.tv_sec == 0) { etiLog.level(info) << "Waiting for GPS fix"; @@ -538,7 +484,8 @@ void OutputUHD::initial_gps_check() if (last_gps_fix_check.tv_sec > first_gps_fix_check.tv_sec + initial_gps_fix_wait) { stringstream ss; - ss << "GPS did not show time lock in " << initial_gps_fix_wait << " seconds"; + ss << "GPS did not show time lock in " << + initial_gps_fix_wait << " seconds"; throw std::runtime_error(ss.str()); } @@ -558,7 +505,7 @@ void OutputUHD::initial_gps_check() } long delta_us = timespecdiff_us(time_last_frame, now); - long wait_time_us = transmission_frame_duration_ms(myConf.dabMode); + long wait_time_us = transmission_frame_duration_ms(m_conf.dabMode); if (wait_time_us - delta_us > 0) { usleep(wait_time_us - delta_us); @@ -571,7 +518,7 @@ void OutputUHD::initial_gps_check() } } -void OutputUHD::check_gps() +void UHD::check_gps() { struct timespec time_now; if (clock_gettime(CLOCK_MONOTONIC, &time_now) != 0) { @@ -612,7 +559,7 @@ void OutputUHD::check_gps() } if (gps_fix_check_interval * num_checks_without_gps_fix > - myConf.maxGPSHoldoverTime) { + m_conf.maxGPSHoldoverTime) { std::stringstream ss; ss << "Lost GPS Time Lock for " << gps_fix_check_interval * num_checks_without_gps_fix << " seconds"; @@ -625,11 +572,11 @@ void OutputUHD::check_gps() // time, it has to be done in a separate thread. if (gpsdo_is_ettus()) { gps_fix_pt = boost::packaged_task( - boost::bind(check_gps_locked, myUsrp) ); + boost::bind(check_gps_locked, m_usrp) ); } else { gps_fix_pt = boost::packaged_task( - boost::bind(check_gps_timelock, myUsrp) ); + boost::bind(check_gps_timelock, m_usrp) ); } gps_fix_future = gps_fix_pt.get_future(); @@ -638,227 +585,11 @@ void OutputUHD::check_gps() } } -void OutputUHD::workerthread() -{ - // Set thread priority to realtime - if (int ret = set_realtime_prio(1)) { - etiLog.level(error) << "Could not set priority for UHD worker:" << ret; - } - - set_thread_name("uhdworker"); - - last_tx_time_initialised = false; - - uhd::stream_args_t stream_args("fc32"); //complex floats - myTxStream = myUsrp->get_tx_stream(stream_args); - - md.start_of_burst = false; - md.end_of_burst = false; - - num_underflows = 0; - num_late_packets = 0; - - size_t last_num_underflows = 0; - size_t pop_prebuffering = FRAMES_MAX_SIZE; - - while (running.load()) { - md.has_time_spec = false; - md.time_spec = uhd::time_spec_t(0.0); - - struct UHDWorkerFrameData frame; - etiLog.log(trace, "UHD,wait"); - frames.wait_and_pop(frame, pop_prebuffering); - etiLog.log(trace, "UHD,pop"); - - handle_frame(&frame); - num_frames_modulated++; - - /* Ensure we fill frames after every underrun and - * at startup to reduce underrun likelihood. */ - if (last_num_underflows < num_underflows) { - pop_prebuffering = FRAMES_MAX_SIZE; - } - else { - pop_prebuffering = 1; - } - last_num_underflows = num_underflows; - } - running.store(false); - etiLog.level(warn) << "UHD worker terminated"; -} - -void OutputUHD::handle_frame(const struct UHDWorkerFrameData *frame) -{ - // Transmit timeout - static const double tx_timeout = 20.0; - - // Check for ref_lock - if (refclk_loss_needs_check()) { - try { - if (not myUsrp->get_mboard_sensor("ref_locked", 0).to_bool()) { - etiLog.log(alert, - "OutputUHD: External reference clock lock lost !"); - if (myConf.refclk_lock_loss_behaviour == CRASH) { - throw std::runtime_error( - "OutputUHD: External reference clock lock lost."); - } - } - } - catch (uhd::lookup_error &e) { - suppress_refclk_loss_check = true; - etiLog.log(warn, "OutputUHD: This USRP does not have mboard " - "sensor for ext clock loss. Check disabled."); - } - } - - double usrp_time = myUsrp->get_time_now().get_real_secs(); - bool timestamp_discontinuity = false; - - if (sourceContainsTimestamp) { - // Tx time from MNSC and TIST - uint32_t tx_second = frame->ts.timestamp_sec; - uint32_t tx_pps = frame->ts.timestamp_pps; - - if (!frame->ts.timestamp_valid) { - /* We have not received a full timestamp through - * MNSC. We sleep through the frame. - */ - etiLog.level(info) << - "OutputUHD: Throwing sample " << frame->ts.fct << - " away: incomplete timestamp " << tx_second << - " / " << tx_pps; - usleep(20000); //TODO should this be TM-dependant ? - return; - } - - if (last_tx_time_initialised) { - const size_t sizeIn = frame->buf.size() / sizeof(complexf); - uint64_t increment = (uint64_t)sizeIn * 16384000ul / - (uint64_t)myConf.sampleRate; - // samps * ticks/s / (samps/s) - // (samps * ticks * s) / (s * samps) - // ticks - - uint32_t expected_sec = last_tx_second + increment / 16384000ul; - uint32_t expected_pps = last_tx_pps + increment % 16384000ul; - - while (expected_pps >= 16384000) { - expected_sec++; - expected_pps -= 16384000; - } - - if (expected_sec != tx_second or - expected_pps != tx_pps) { - etiLog.level(warn) << "OutputUHD: timestamp irregularity!" << - std::fixed << - " Expected " << - expected_sec << "+" << (double)expected_pps/16384000.0 << - "(" << expected_pps << ")" << - " Got " << - tx_second << "+" << (double)tx_pps/16384000.0 << - "(" << tx_pps << ")"; - - timestamp_discontinuity = true; - } - } - - last_tx_second = tx_second; - last_tx_pps = tx_pps; - last_tx_time_initialised = true; - - double pps_offset = tx_pps / 16384000.0; - - md.has_time_spec = true; - md.time_spec = uhd::time_spec_t(tx_second, pps_offset); - etiLog.log(trace, "UHD,tist %f", md.time_spec.get_real_secs()); - - // md is defined, let's do some checks - if (md.time_spec.get_real_secs() + tx_timeout < usrp_time) { - etiLog.level(warn) << - "OutputUHD: Timestamp in the past! offset: " << - std::fixed << - md.time_spec.get_real_secs() - usrp_time << - " (" << usrp_time << ")" - " frame " << frame->ts.fct << - ", tx_second " << tx_second << - ", pps " << pps_offset; - return; - } - - if (md.time_spec.get_real_secs() > usrp_time + TIMESTAMP_ABORT_FUTURE) { - etiLog.level(error) << - "OutputUHD: Timestamp way too far in the future! offset: " << - std::fixed << - md.time_spec.get_real_secs() - usrp_time; - throw std::runtime_error("Timestamp error. Aborted."); - } - } - else { // !sourceContainsTimestamp - if (myConf.muting or myConf.muteNoTimestamps) { - /* There was some error decoding the timestamp */ - if (myConf.muting) { - etiLog.log(info, - "OutputUHD: Muting sample %d requested\n", - frame->ts.fct); - } - else { - etiLog.log(info, - "OutputUHD: Muting sample %d : no timestamp\n", - frame->ts.fct); - } - usleep(20000); - return; - } - } - - tx_frame(frame, timestamp_discontinuity); -} - -void OutputUHD::tx_frame(const struct UHDWorkerFrameData *frame, bool ts_update) +void UHD::print_async_thread() { - const double tx_timeout = 20.0; - const size_t sizeIn = frame->buf.size() / sizeof(complexf); - const complexf* in_data = reinterpret_cast(&frame->buf[0]); - - size_t usrp_max_num_samps = myTxStream->get_max_num_samps(); - size_t num_acc_samps = 0; //number of accumulated samples - while (running.load() and (not myConf.muting) and (num_acc_samps < sizeIn)) { - size_t samps_to_send = std::min(sizeIn - num_acc_samps, usrp_max_num_samps); - - uhd::tx_metadata_t md_tx = md; - - //ensure the the last packet has EOB set if the timestamps has been - //refreshed and need to be reconsidered. - md_tx.end_of_burst = ( - sourceContainsTimestamp and - (frame->ts.timestamp_refresh or ts_update) and - samps_to_send <= usrp_max_num_samps ); - - - //send a single packet - size_t num_tx_samps = myTxStream->send( - &in_data[num_acc_samps], - samps_to_send, md_tx, tx_timeout); - etiLog.log(trace, "UHD,sent %zu of %zu", num_tx_samps, samps_to_send); - - num_acc_samps += num_tx_samps; - - md_tx.time_spec = md.time_spec + - uhd::time_spec_t(0, num_tx_samps/myConf.sampleRate); - - if (num_tx_samps == 0) { - etiLog.log(warn, - "OutputUHD::workerthread() unable to write to device, skipping frame!\n"); - break; - } - } -} - -void OutputUHD::print_async_thread() -{ - while (running.load()) { + while (m_running.load()) { uhd::async_metadata_t async_md; - if (myUsrp->get_device()->recv_async_msg(async_md, 1)) { + if (m_usrp->get_device()->recv_async_msg(async_md, 1)) { const char* uhd_async_message = ""; bool failure = false; switch (async_md.event_code) { @@ -895,14 +626,13 @@ void OutputUHD::print_async_thread() "Received Async UHD Message '" << uhd_async_message << "' at time " << md.time_spec.get_real_secs(); - } } auto time_now = std::chrono::steady_clock::now(); if (last_print_time + std::chrono::seconds(1) < time_now) { const double usrp_time = - myUsrp->get_time_now().get_real_secs(); + m_usrp->get_time_now().get_real_secs(); if ( (num_underflows > num_underflows_previous) or (num_late_packets > num_late_packets_previous)) { @@ -921,74 +651,7 @@ void OutputUHD::print_async_thread() } } -// ======================================= -// Remote Control for UHD -// ======================================= -void OutputUHD::set_parameter(const string& parameter, const string& value) -{ - stringstream ss(value); - ss.exceptions ( stringstream::failbit | stringstream::badbit ); - - if (parameter == "txgain") { - ss >> myConf.txgain; - myUsrp->set_tx_gain(myConf.txgain); - } - else if (parameter == "rxgain") { - ss >> myConf.rxgain; - myUsrp->set_rx_gain(myConf.rxgain); - } - else if (parameter == "freq") { - ss >> myConf.frequency; - tune_usrp_to(myUsrp, myConf.lo_offset, myConf.frequency); - myConf.frequency = myUsrp->get_tx_freq(); - } - else if (parameter == "muting") { - ss >> myConf.muting; - } - else if (parameter == "underruns" or - parameter == "latepackets" or - parameter == "frames") { - throw ParameterError("Parameter " + parameter + " is read-only."); - } - else { - stringstream ss; - ss << "Parameter '" << parameter - << "' is not exported by controllable " << get_rc_name(); - throw ParameterError(ss.str()); - } -} - -const string OutputUHD::get_parameter(const string& parameter) const -{ - stringstream ss; - if (parameter == "txgain") { - ss << myConf.txgain; - } - else if (parameter == "rxgain") { - ss << myConf.rxgain; - } - else if (parameter == "freq") { - ss << myConf.frequency; - } - else if (parameter == "muting") { - ss << myConf.muting; - } - else if (parameter == "underruns") { - ss << num_underflows; - } - else if (parameter == "latepackets") { - ss << num_late_packets; - } - else if (parameter == "frames") { - ss << num_frames_modulated; - } - else { - ss << "Parameter '" << parameter << - "' is not exported by controllable " << get_rc_name(); - throw ParameterError(ss.str()); - } - return ss.str(); -} +} // namespace Output #endif // HAVE_OUTPUT_UHD diff --git a/src/output/UHD.h b/src/output/UHD.h index e3f8174..220e4c8 100644 --- a/src/output/UHD.h +++ b/src/output/UHD.h @@ -40,8 +40,6 @@ DESCRIPTION: #include #include #include -#include -#include #include #include #include @@ -70,105 +68,39 @@ DESCRIPTION: namespace Output { -enum refclk_lock_loss_behaviour_t { CRASH, IGNORE }; - -/* This structure is used as initial configuration for OutputUHD. - * It must also contain all remote-controllable settings, otherwise - * they will get lost on a modulator restart. */ -struct OutputUHDConfig { - std::string device; - std::string usrpType; // e.g. b100, b200, usrp2 - - // The USRP1 can accept two daughterboards - std::string subDevice; // e.g. A:0 - - long masterClockRate = 32768000; - unsigned sampleRate = 2048000; - double frequency = 0.0; - double lo_offset = 0.0; - double txgain = 0.0; - double rxgain = 0.0; - bool enableSync = false; - - // When working with timestamps, mute the frames that - // do not have a timestamp - bool muteNoTimestamps = false; - unsigned dabMode = 0; - unsigned maxGPSHoldoverTime = 0; - - /* allowed values : auto, int, sma, mimo */ - std::string refclk_src; - - /* allowed values : int, sma, mimo */ - std::string pps_src; - - /* allowed values : pos, neg */ - std::string pps_polarity; - - /* What to do when the reference clock PLL loses lock */ - refclk_lock_loss_behaviour_t refclk_lock_loss_behaviour; - - // muting can only be changed using the remote control - bool muting = false; - - // TCP port on which to serve TX and RX samples for the - // digital pre distortion learning tool - uint16_t dpdFeedbackServerPort = 0; -}; - -class OutputUHD: public ModOutput, public RemoteControllable { +class UHD : public Output::SDRDevice +{ public: - OutputUHD(OutputUHDConfig& config); - OutputUHD(const OutputUHD& other) = delete; - OutputUHD operator=(const OutputUHD& other) = delete; - ~OutputUHD(); - - int process(Buffer* dataIn); - - const char* name() { return "OutputUHD"; } - - void setETISource(EtiSource *etiSource); + UHD(SDRDeviceConfig& config); + UHD(const UHD& other) = delete; + UHD& operator=(const UHD& other) = delete; + ~UHD(); + + virtual void tune(double lo_offset, double frequency) override; + virtual double get_tx_freq(void) override; + virtual void set_txgain(double txgain) override; + virtual double get_txgain(void) override; + virtual void transmit_frame(const struct FrameData& frame) override; + virtual RunStatistics get_run_statistics(void) override; + virtual double get_real_secs(void) override; + + // Return true if GPS and reference clock inputs are ok + virtual bool is_clk_source_ok(void) override; + virtual const char* device_name(void) override; - /*********** REMOTE CONTROL ***************/ - - /* Base function to set parameters. */ - virtual void set_parameter(const std::string& parameter, - const std::string& value); - - /* Getting a parameter always returns a string. */ - virtual const std::string get_parameter( - const std::string& parameter) const; + private: + SDRDeviceConfig& m_conf; + uhd::usrp::multi_usrp::sptr m_usrp; + uhd::tx_streamer::sptr m_tx_stream; - protected: - EtiSource *myEtiSource = nullptr; - OutputUHDConfig& myConf; - uhd::usrp::multi_usrp::sptr myUsrp; - std::shared_ptr mySyncBarrier; - bool first_run = true; - bool gps_fix_verified = false; - std::shared_ptr uhdFeedback; + size_t num_underflows = 0; + size_t num_overflows = 0; + size_t num_late_packets = 0; + size_t num_frames_modulated = 0; //TODO increment + size_t num_underflows_previous = 0; + size_t num_late_packets_previous = 0; - private: - // Each frame contains one OFDM frame, and its - // associated timestamp - struct UHDWorkerFrameData { - // Buffer holding frame data - std::vector buf; - - // A full timestamp contains a TIST according to standard - // and time information within MNSC with tx_second. - struct frame_timestamp ts; - }; - - // Resize the internal delay buffer according to the dabMode and - // the sample rate. - void SetDelayBuffer(unsigned int dabMode); - - // data - // The remote-controllable static delay is in the OutputUHDConfig - int myTFDurationMs; // TF duration in milliseconds - std::vector myDelayBuf; - size_t lastLen = 0; + uhd::tx_metadata_t md; // GPS Fix check variables int num_checks_without_gps_fix = 1; @@ -185,26 +117,9 @@ class OutputUHD: public ModOutput, public RemoteControllable { // Interval for checking the GPS at runtime static constexpr double gps_fix_check_interval = 10.0; // seconds - // Asynchronous message statistics - size_t num_underflows = 0; - size_t num_late_packets = 0; - size_t num_underflows_previous = 0; - size_t num_late_packets_previous = 0; - - size_t num_frames_modulated = 0; - - uhd::tx_metadata_t md; - bool last_tx_time_initialised = false; - uint32_t last_tx_second = 0; - uint32_t last_tx_pps = 0; - // Used to print statistics once a second std::chrono::steady_clock::time_point last_print_time; - bool sourceContainsTimestamp = false; - - ThreadsafeQueue frames; - // Returns true if we want to verify loss of refclk bool refclk_loss_needs_check(void) const; bool suppress_refclk_loss_check = false; @@ -216,19 +131,10 @@ class OutputUHD: public ModOutput, public RemoteControllable { // LEA-M8F board is used bool gpsdo_is_ettus(void) const; - std::atomic running; - boost::thread uhd_thread; - boost::thread async_rx_thread; - void stop_threads(void); - - uhd::tx_streamer::sptr myTxStream; - - // The worker thread decouples the modulator from UHD - void workerthread(); - void handle_frame(const struct UHDWorkerFrameData *frame); - void tx_frame(const struct UHDWorkerFrameData *frame, bool ts_update); - // Poll asynchronous metadata from UHD + std::atomic m_running; + boost::thread m_async_rx_thread; + void stop_threads(void); void print_async_thread(void); void check_gps(); -- cgit v1.2.3 From 4d8310ae0ffe1f78a2b8623d55f63ae504ff1aa8 Mon Sep 17 00:00:00 2001 From: "Matthias P. Braendli" Date: Sat, 4 Nov 2017 08:36:03 +0100 Subject: Make DPD Feedback server SDRDevice-agnostic --- Makefile.am | 17 +-- TODO | 21 +-- src/ConfigParser.cpp | 8 +- src/TimestampDecoder.h | 6 + src/output/Feedback.cpp | 356 ++++++++++++++++++++++++++++++++++++++++++++++++ src/output/Feedback.h | 116 ++++++++++++++++ src/output/SDR.cpp | 22 ++- src/output/SDR.h | 85 +----------- src/output/SDRDevice.h | 136 ++++++++++++++++++ src/output/Soapy.cpp | 57 ++++++-- src/output/Soapy.h | 11 +- src/output/UHD.cpp | 40 ++++++ src/output/UHD.h | 9 ++ 13 files changed, 771 insertions(+), 113 deletions(-) create mode 100644 src/output/Feedback.cpp create mode 100644 src/output/Feedback.h create mode 100644 src/output/SDRDevice.h (limited to 'src/output/UHD.h') diff --git a/Makefile.am b/Makefile.am index 8ce5f89..6e5c1a3 100644 --- a/Makefile.am +++ b/Makefile.am @@ -81,20 +81,21 @@ odr_dabmod_SOURCES = src/DabMod.cpp \ src/OutputZeroMQ.h \ src/TimestampDecoder.h \ src/TimestampDecoder.cpp \ - src/output/UHD.cpp \ - src/output/UHD.h \ - src/OutputUHDFeedback.cpp \ - src/OutputUHDFeedback.h \ + src/output/Feedback.cpp \ + src/output/Feedback.h \ + src/output/SDR.cpp \ + src/output/SDR.h \ + src/output/SDRDevice.h \ src/output/Soapy.cpp \ src/output/Soapy.h \ - src/output/SDR.h \ - src/output/SDR.cpp \ + src/output/UHD.cpp \ + src/output/UHD.h \ + src/InputFileReader.cpp \ src/InputMemory.cpp \ src/InputMemory.h \ - src/InputFileReader.cpp \ + src/InputReader.h \ src/InputTcpReader.cpp \ src/InputZeroMQReader.cpp \ - src/InputReader.h \ src/OutputFile.cpp \ src/OutputFile.h \ src/FrameMultiplexer.cpp \ diff --git a/TODO b/TODO index be42465..a566ff9 100644 --- a/TODO +++ b/TODO @@ -6,6 +6,13 @@ Unless written, no activity has been started on the topics. TODOs for ongoing SDR output refactoring ---------------------------------------- +Currently, all the frontend tuning and timestamping settings are UHD-specific. +To make it possible to run with synchronised=1 using Soapy, refactoring the +output to share the parts that are common. + +This would enable SFN support with LimeSDR devices. + + Clean up and separate GPS and refclk checks. @@ -13,10 +20,13 @@ Add antenna selection to config. Add refclk stuff and timestamps to Soapy. -Make an abstraction for the DPD feedback server, use it for Soapy and UHD. +*done* Make an abstraction for the DPD feedback server, use it for Soapy and UHD. Move staticdelay into a new process block +Double-check all #includes + +Move other non SDR outputs to the output folder. Tests, both with B200 and LimeSDR: - No timestamps @@ -41,15 +51,6 @@ However, when enabled, some receivers are not able to lock on the signal. Is the power of the TII too strong? Synchronisation wrong? -Rework Soapy and UHD outputs ----------------------------- -Currently, all the frontend tuning and timestamping settings are UHD-specific. -To make it possible to run with synchronised=1 using Soapy, refactoring the -output to share the parts that are common. - -This would enable SFN support with LimeSDR devices. - - Finalise EDI input ------------------ The EDI input, based on work started in http://git.mpb.li/git/odr-edilib/ diff --git a/src/ConfigParser.cpp b/src/ConfigParser.cpp index c532ab5..8facec3 100644 --- a/src/ConfigParser.cpp +++ b/src/ConfigParser.cpp @@ -29,15 +29,17 @@ # include "config.h" #endif +#include +#include +#include + #include "ConfigParser.h" #include "porting.h" #include "Utils.h" #include "Log.h" #include "DabModulator.h" +#include "output/SDR.h" -#include -#include -#include using namespace std; diff --git a/src/TimestampDecoder.h b/src/TimestampDecoder.h index 34d862f..1ef493e 100644 --- a/src/TimestampDecoder.h +++ b/src/TimestampDecoder.h @@ -94,6 +94,12 @@ struct frame_timestamp return t; } + long long int get_ns() const { + long long int ns = timestamp_sec * 1000000000ull; + ns += llrint((double)timestamp_pps / 0.016384); + return ns; + } + void print(const char* t) { fprintf(stderr, diff --git a/src/output/Feedback.cpp b/src/output/Feedback.cpp new file mode 100644 index 0000000..f0bbd98 --- /dev/null +++ b/src/output/Feedback.cpp @@ -0,0 +1,356 @@ +/* + Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Her Majesty the + Queen in Right of Canada (Communications Research Center Canada) + + Copyright (C) 2017 + Matthias P. Braendli, matthias.braendli@mpb.li + + http://opendigitalradio.org + +DESCRIPTION: + This presents a TCP socket to an external tool which calculates + a Digital Predistortion model from a short sequence of transmit + samples and corresponding receive samples. +*/ + +/* + This file is part of ODR-DabMod. + + ODR-DabMod is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as + published by the Free Software Foundation, either version 3 of the + License, or (at your option) any later version. + + ODR-DabMod is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with ODR-DabMod. If not, see . + */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include "output/Feedback.h" +#include "Utils.h" +#include "Socket.h" + +using namespace std; + +namespace Output { + +DPDFeedbackServer::DPDFeedbackServer( + std::shared_ptr device, + uint16_t port, + uint32_t sampleRate) : + m_port(port), + m_sampleRate(sampleRate), + m_device(device) +{ + if (m_port) { + m_running.store(true); + + rx_burst_thread = boost::thread( + &DPDFeedbackServer::ReceiveBurstThread, this); + + burst_tcp_thread = boost::thread( + &DPDFeedbackServer::ServeFeedbackThread, this); + } +} + +DPDFeedbackServer::~DPDFeedbackServer() +{ + m_running.store(false); + + rx_burst_thread.interrupt(); + if (rx_burst_thread.joinable()) { + rx_burst_thread.join(); + } + + burst_tcp_thread.interrupt(); + if (burst_tcp_thread.joinable()) { + burst_tcp_thread.join(); + } +} + +void DPDFeedbackServer::set_tx_frame( + const std::vector &buf, + const struct frame_timestamp &buf_ts) +{ + if (not m_running) { + throw runtime_error("DPDFeedbackServer not running"); + } + + boost::mutex::scoped_lock lock(burstRequest.mutex); + + if (buf.size() % sizeof(complexf) != 0) { + throw std::logic_error("Buffer for tx frame has incorrect size"); + } + + if (burstRequest.state == BurstRequestState::SaveTransmitFrame) { + const size_t n = std::min( + burstRequest.num_samples * sizeof(complexf), buf.size()); + + burstRequest.num_samples = n / sizeof(complexf); + + burstRequest.tx_samples.clear(); + burstRequest.tx_samples.resize(n); + // A frame will always begin with the NULL symbol, which contains + // no power. Instead of taking n samples at the beginning of the + // frame, we take them at the end and adapt the timestamp accordingly. + + const size_t start_ix = buf.size() - n; + copy(buf.begin() + start_ix, buf.end(), burstRequest.tx_samples.begin()); + + frame_timestamp ts = buf_ts; + ts += (1.0 * start_ix) / (sizeof(complexf) * m_sampleRate); + + burstRequest.tx_second = ts.timestamp_sec; + burstRequest.tx_pps = ts.timestamp_pps; + + // Prepare the next state + burstRequest.rx_second = ts.timestamp_sec; + burstRequest.rx_pps = ts.timestamp_pps; + burstRequest.state = BurstRequestState::SaveReceiveFrame; + + lock.unlock(); + burstRequest.mutex_notification.notify_one(); + } + else { + lock.unlock(); + } +} + +void DPDFeedbackServer::ReceiveBurstThread() +{ + try { + set_thread_name("dpdreceiveburst"); + + while (m_running) { + boost::mutex::scoped_lock lock(burstRequest.mutex); + while (burstRequest.state != BurstRequestState::SaveReceiveFrame) { + if (not m_running) break; + burstRequest.mutex_notification.wait(lock); + } + + if (not m_running) break; + + const size_t num_samps = burstRequest.num_samples; + + frame_timestamp ts; + ts.timestamp_sec = burstRequest.rx_second; + ts.timestamp_pps = burstRequest.rx_pps; + ts.timestamp_valid = true; + + // We need to free the mutex while we recv(), because otherwise we block the + // TX thread + lock.unlock(); + + const double device_time = m_device->get_real_secs(); + const double cmd_time = ts.get_real_secs(); + + std::vector buf(num_samps * sizeof(complexf)); + + const double timeout = 60; + size_t samples_read = m_device->receive_frame( + reinterpret_cast(buf.data()), + num_samps, ts, timeout); + + lock.lock(); + burstRequest.rx_samples = std::move(buf); + burstRequest.rx_samples.resize(samples_read * sizeof(complexf)); + + // The recv might have happened at another time than requested + burstRequest.rx_second = ts.timestamp_sec; + burstRequest.rx_pps = ts.timestamp_pps; + + etiLog.level(debug) << "DPD: acquired " << samples_read << + " RX feedback samples " << + "at time " << burstRequest.tx_second << " + " << + std::fixed << burstRequest.tx_pps / 16384000.0 << + " Delta=" << cmd_time - device_time; + + burstRequest.state = BurstRequestState::Acquired; + + lock.unlock(); + burstRequest.mutex_notification.notify_one(); + } + } + catch (const runtime_error &e) { + etiLog.level(error) << "DPD Feedback RX runtime error: " << e.what(); + } + catch (const std::exception &e) { + etiLog.level(error) << "DPD Feedback RX exception: " << e.what(); + } + catch (...) { + etiLog.level(error) << "DPD Feedback RX unknown exception!"; + } + + m_running.store(false); +} + +void DPDFeedbackServer::ServeFeedback() +{ + TCPSocket m_server_sock; + m_server_sock.listen(m_port); + + etiLog.level(info) << "DPD Feedback server listening on port " << m_port; + + while (m_running) { + struct sockaddr_in client; + TCPSocket client_sock = m_server_sock.accept_with_timeout(1000, &client); + + if (not client_sock.valid()) { + // No connection request received + continue; + } + + uint8_t request_version = 0; + ssize_t read = client_sock.recv(&request_version, 1, 0); + if (!read) break; // done reading + if (read < 0) { + etiLog.level(info) << + "DPD Feedback Server Client read request version failed: " << strerror(errno); + break; + } + + if (request_version != 1) { + etiLog.level(info) << "DPD Feedback Server wrong request version"; + break; + } + + uint32_t num_samples = 0; + read = client_sock.recv(&num_samples, 4, 0); + if (!read) break; // done reading + if (read < 0) { + etiLog.level(info) << + "DPD Feedback Server Client read num samples failed"; + break; + } + + // We are ready to issue the request now + { + boost::mutex::scoped_lock lock(burstRequest.mutex); + burstRequest.num_samples = num_samples; + burstRequest.state = BurstRequestState::SaveTransmitFrame; + + lock.unlock(); + } + + // Wait for the result to be ready + boost::mutex::scoped_lock lock(burstRequest.mutex); + while (burstRequest.state != BurstRequestState::Acquired) { + if (not m_running) break; + burstRequest.mutex_notification.wait(lock); + } + + burstRequest.state = BurstRequestState::None; + lock.unlock(); + + burstRequest.num_samples = std::min(burstRequest.num_samples, + std::min( + burstRequest.tx_samples.size() / sizeof(complexf), + burstRequest.rx_samples.size() / sizeof(complexf))); + + uint32_t num_samples_32 = burstRequest.num_samples; + if (client_sock.sendall(&num_samples_32, sizeof(num_samples_32)) < 0) { + etiLog.level(info) << + "DPD Feedback Server Client send num_samples failed"; + break; + } + + if (client_sock.sendall( + &burstRequest.tx_second, + sizeof(burstRequest.tx_second)) < 0) { + etiLog.level(info) << + "DPD Feedback Server Client send tx_second failed"; + break; + } + + if (client_sock.sendall( + &burstRequest.tx_pps, + sizeof(burstRequest.tx_pps)) < 0) { + etiLog.level(info) << + "DPD Feedback Server Client send tx_pps failed"; + break; + } + + const size_t frame_bytes = burstRequest.num_samples * sizeof(complexf); + + if (burstRequest.tx_samples.size() < frame_bytes) { + throw logic_error("DPD Feedback burstRequest invalid: not enough TX samples"); + } + + if (client_sock.sendall( + &burstRequest.tx_samples[0], + frame_bytes) < 0) { + etiLog.level(info) << + "DPD Feedback Server Client send tx_frame failed"; + break; + } + + if (client_sock.sendall( + &burstRequest.rx_second, + sizeof(burstRequest.rx_second)) < 0) { + etiLog.level(info) << + "DPD Feedback Server Client send rx_second failed"; + break; + } + + if (client_sock.sendall( + &burstRequest.rx_pps, + sizeof(burstRequest.rx_pps)) < 0) { + etiLog.level(info) << + "DPD Feedback Server Client send rx_pps failed"; + break; + } + + if (burstRequest.rx_samples.size() < frame_bytes) { + throw logic_error("DPD Feedback burstRequest invalid: not enough RX samples"); + } + + if (client_sock.sendall( + &burstRequest.rx_samples[0], + frame_bytes) < 0) { + etiLog.level(info) << + "DPD Feedback Server Client send rx_frame failed"; + break; + } + } +} + +void DPDFeedbackServer::ServeFeedbackThread() +{ + set_thread_name("dpdfeedbackserver"); + + while (m_running) { + try { + ServeFeedback(); + } + catch (const runtime_error &e) { + etiLog.level(error) << "DPD Feedback Server runtime error: " << e.what(); + } + catch (const std::exception &e) { + etiLog.level(error) << "DPD Feedback Server exception: " << e.what(); + } + catch (...) { + etiLog.level(error) << "DPD Feedback Server unknown exception!"; + } + + boost::this_thread::sleep(boost::posix_time::seconds(5)); + } + + m_running.store(false); +} + +} // namespace Output diff --git a/src/output/Feedback.h b/src/output/Feedback.h new file mode 100644 index 0000000..2cad508 --- /dev/null +++ b/src/output/Feedback.h @@ -0,0 +1,116 @@ +/* + Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Her Majesty the + Queen in Right of Canada (Communications Research Center Canada) + + Copyright (C) 2017 + Matthias P. Braendli, matthias.braendli@mpb.li + + http://opendigitalradio.org + +DESCRIPTION: + This presents a TCP socket to an external tool which calculates + a Digital Predistortion model from a short sequence of transmit + samples and corresponding receive samples. +*/ + +/* + This file is part of ODR-DabMod. + + ODR-DabMod is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as + published by the Free Software Foundation, either version 3 of the + License, or (at your option) any later version. + + ODR-DabMod is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with ODR-DabMod. If not, see . + */ + +#pragma once + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include +#include +#include +#include + +#include "Log.h" +#include "TimestampDecoder.h" +#include "output/SDRDevice.h" + +namespace Output { + +enum class BurstRequestState { + None, // To pending request + SaveTransmitFrame, // The TX thread has to save an outgoing frame + SaveReceiveFrame, // The RX thread has to save an incoming frame + Acquired, // Both TX and RX frames are ready +}; + +struct FeedbackBurstRequest { + // All fields in this struct are protected + mutable boost::mutex mutex; + boost::condition_variable mutex_notification; + + BurstRequestState state = BurstRequestState::None; + + // In the SaveTransmit states, num_samples complexf samples are saved into + // the vectors + size_t num_samples = 0; + + // The timestamp of the first sample of the TX buffers + uint32_t tx_second = 0; + uint32_t tx_pps = 0; // in units of 1/16384000s + + // Samples contain complexf, but since our internal representation is uint8_t + // we keep it like that + std::vector tx_samples; + + // The timestamp of the first sample of the RX buffers + uint32_t rx_second = 0; + uint32_t rx_pps = 0; + + std::vector rx_samples; // Also, actually complexf +}; + +// Serve TX samples and RX feedback samples over a TCP connection +class DPDFeedbackServer { + public: + DPDFeedbackServer( + std::shared_ptr device, + uint16_t port, // Set to 0 to disable the Feedbackserver + uint32_t sampleRate); + DPDFeedbackServer(const DPDFeedbackServer& other) = delete; + DPDFeedbackServer& operator=(const DPDFeedbackServer& other) = delete; + ~DPDFeedbackServer(); + + void set_tx_frame(const std::vector &buf, + const struct frame_timestamp& ts); + + private: + // Thread that reacts to burstRequests and receives from the SDR device + void ReceiveBurstThread(void); + + // Thread that listens for requests over TCP to get TX and RX feedback + void ServeFeedbackThread(void); + void ServeFeedback(void); + + boost::thread rx_burst_thread; + boost::thread burst_tcp_thread; + + FeedbackBurstRequest burstRequest; + + std::atomic_bool m_running; + uint16_t m_port = 0; + uint32_t m_sampleRate = 0; + std::shared_ptr m_device; +}; + +} // namespace Output diff --git a/src/output/SDR.cpp b/src/output/SDR.cpp index 2ad8e57..66e93e2 100644 --- a/src/output/SDR.cpp +++ b/src/output/SDR.cpp @@ -67,6 +67,13 @@ SDR::SDR(SDRDeviceConfig& config, std::shared_ptr device) : m_config.muting = true; m_device_thread = std::thread(&SDR::process_thread_entry, this); + + if (m_config.dpdFeedbackServerPort != 0) { + m_dpd_feedback_server = make_shared( + m_device, + m_config.dpdFeedbackServerPort, + m_config.sampleRate); + } } SDR::~SDR() @@ -108,10 +115,21 @@ int SDR::process(Buffer *dataIn) if (frame.ts.fct == -1) { etiLog.level(info) << - "OutputUHD: dropping one frame with invalid FCT"; + "SDR output: dropping one frame with invalid FCT"; } else { - // TODO setup Feedback and set tx_frame + try { + m_dpd_feedback_server->set_tx_frame(frame.buf, frame.ts); + } + catch (const runtime_error& e) { + etiLog.level(warn) << + "SDR output: Feedback server failed, restarting..."; + + m_dpd_feedback_server = std::make_shared( + m_device, + m_config.dpdFeedbackServerPort, + m_config.sampleRate); + } size_t num_frames = m_queue.push_wait_if_full(frame, FRAMES_MAX_SIZE); diff --git a/src/output/SDR.h b/src/output/SDR.h index 370bc38..5593ce3 100644 --- a/src/output/SDR.h +++ b/src/output/SDR.h @@ -36,92 +36,13 @@ DESCRIPTION: #include "ModPlugin.h" #include "EtiReader.h" +#include "output/SDRDevice.h" +#include "output/Feedback.h" namespace Output { using complexf = std::complex; -enum refclk_lock_loss_behaviour_t { CRASH, IGNORE }; - -/* This structure is used as initial configuration for all SDR devices. - * It must also contain all remote-controllable settings, otherwise - * they will get lost on a modulator restart. */ -struct SDRDeviceConfig { - std::string device; - std::string subDevice; // For UHD - - long masterClockRate = 32768000; - unsigned sampleRate = 2048000; - double frequency = 0.0; - double lo_offset = 0.0; - double txgain = 0.0; - double rxgain = 0.0; - bool enableSync = false; - - // When working with timestamps, mute the frames that - // do not have a timestamp - bool muteNoTimestamps = false; - unsigned dabMode = 0; - unsigned maxGPSHoldoverTime = 0; - - /* allowed values for UHD : auto, int, sma, mimo */ - std::string refclk_src; - - /* allowed values for UHD : int, sma, mimo */ - std::string pps_src; - - /* allowed values for UHD : pos, neg */ - std::string pps_polarity; - - /* What to do when the reference clock PLL loses lock */ - refclk_lock_loss_behaviour_t refclk_lock_loss_behaviour; - - // muting can only be changed using the remote control - bool muting = false; - - // TCP port on which to serve TX and RX samples for the - // digital pre distortion learning tool - uint16_t dpdFeedbackServerPort = 0; -}; - -// Each frame contains one OFDM frame, and its -// associated timestamp -struct FrameData { - // Buffer holding frame data - std::vector buf; - - // A full timestamp contains a TIST according to standard - // and time information within MNSC with tx_second. - struct frame_timestamp ts; -}; - - -// All SDR Devices must implement the SDRDevice interface -class SDRDevice { - public: - struct RunStatistics { - size_t num_underruns; - size_t num_late_packets; - size_t num_overruns; - size_t num_frames_modulated; //TODO increment - }; - - // TODO make some functions const - virtual void tune(double lo_offset, double frequency) = 0; - virtual double get_tx_freq(void) = 0; - virtual void set_txgain(double txgain) = 0; - virtual double get_txgain(void) = 0; - virtual void transmit_frame(const struct FrameData& frame) = 0; - virtual RunStatistics get_run_statistics(void) = 0; - virtual double get_real_secs(void) = 0; - - - // Return true if GPS and reference clock inputs are ok - virtual bool is_clk_source_ok(void) = 0; - - virtual const char* device_name(void) = 0; -}; - class SDR : public ModOutput, public RemoteControllable { public: SDR(SDRDeviceConfig& config, std::shared_ptr device); @@ -159,6 +80,8 @@ class SDR : public ModOutput, public RemoteControllable { std::shared_ptr m_device; std::string m_name; + std::shared_ptr m_dpd_feedback_server; + EtiSource *m_eti_source = nullptr; bool sourceContainsTimestamp = false; bool last_tx_time_initialised = false; diff --git a/src/output/SDRDevice.h b/src/output/SDRDevice.h new file mode 100644 index 0000000..856233b --- /dev/null +++ b/src/output/SDRDevice.h @@ -0,0 +1,136 @@ +/* + Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Her Majesty the + Queen in Right of Canada (Communications Research Center Canada) + + Copyright (C) 2017 + Matthias P. Braendli, matthias.braendli@mpb.li + + http://opendigitalradio.org + +DESCRIPTION: + Common interface for all SDR outputs +*/ + +/* + This file is part of ODR-DabMod. + + ODR-DabMod is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as + published by the Free Software Foundation, either version 3 of the + License, or (at your option) any later version. + + ODR-DabMod is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with ODR-DabMod. If not, see . + */ + +#pragma once + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include +#include +#include +#include + +#include "TimestampDecoder.h" + +namespace Output { + +enum refclk_lock_loss_behaviour_t { CRASH, IGNORE }; + +using complexf = std::complex; + +/* This structure is used as initial configuration for all SDR devices. + * It must also contain all remote-controllable settings, otherwise + * they will get lost on a modulator restart. */ +struct SDRDeviceConfig { + std::string device; + std::string subDevice; // For UHD + + long masterClockRate = 32768000; + unsigned sampleRate = 2048000; + double frequency = 0.0; + double lo_offset = 0.0; + double txgain = 0.0; + double rxgain = 0.0; + bool enableSync = false; + + // When working with timestamps, mute the frames that + // do not have a timestamp + bool muteNoTimestamps = false; + unsigned dabMode = 0; + unsigned maxGPSHoldoverTime = 0; + + /* allowed values for UHD : auto, int, sma, mimo */ + std::string refclk_src; + + /* allowed values for UHD : int, sma, mimo */ + std::string pps_src; + + /* allowed values for UHD : pos, neg */ + std::string pps_polarity; + + /* What to do when the reference clock PLL loses lock */ + refclk_lock_loss_behaviour_t refclk_lock_loss_behaviour; + + // muting can only be changed using the remote control + bool muting = false; + + // TCP port on which to serve TX and RX samples for the + // digital pre distortion learning tool + uint16_t dpdFeedbackServerPort = 0; +}; + +// Each frame contains one OFDM frame, and its +// associated timestamp +struct FrameData { + // Buffer holding frame data + std::vector buf; + + // A full timestamp contains a TIST according to standard + // and time information within MNSC with tx_second. + struct frame_timestamp ts; +}; + + +// All SDR Devices must implement the SDRDevice interface +class SDRDevice { + public: + struct RunStatistics { + size_t num_underruns; + size_t num_late_packets; + size_t num_overruns; + size_t num_frames_modulated; //TODO increment + }; + + // TODO make some functions const + virtual void tune(double lo_offset, double frequency) = 0; + virtual double get_tx_freq(void) = 0; + virtual void set_txgain(double txgain) = 0; + virtual double get_txgain(void) = 0; + virtual void transmit_frame(const struct FrameData& frame) = 0; + virtual RunStatistics get_run_statistics(void) = 0; + virtual double get_real_secs(void) = 0; + virtual void set_rxgain(double rxgain) = 0; + virtual double get_rxgain(void) = 0; + virtual size_t receive_frame( + complexf *buf, + size_t num_samples, + struct frame_timestamp& ts, + double timeout_secs) = 0; + + + // Return true if GPS and reference clock inputs are ok + virtual bool is_clk_source_ok(void) = 0; + + virtual const char* device_name(void) = 0; +}; + +} // namespace Output diff --git a/src/output/Soapy.cpp b/src/output/Soapy.cpp index d6b5953..e0a6f10 100644 --- a/src/output/Soapy.cpp +++ b/src/output/Soapy.cpp @@ -88,17 +88,18 @@ Soapy::Soapy(SDRDeviceConfig& config) : etiLog.level(info) << "SoapySDR:Actual tx gain: " << m_device->getGain(SOAPY_SDR_TX, 0); - std::vector channels; - channels.push_back(0); - m_stream = m_device->setupStream(SOAPY_SDR_TX, "CF32", channels); - m_device->activateStream(m_stream); + const std::vector channels({0}); + m_tx_stream = m_device->setupStream(SOAPY_SDR_TX, "CF32", channels); + m_device->activateStream(m_tx_stream); + + m_rx_stream = m_device->setupStream(SOAPY_SDR_RX, "CF32", channels); } Soapy::~Soapy() { if (m_device != nullptr) { - if (m_stream != nullptr) { - m_device->closeStream(m_stream); + if (m_tx_stream != nullptr) { + m_device->closeStream(m_tx_stream); } SoapySDR::Device::unmake(m_device); } @@ -156,6 +157,46 @@ double Soapy::get_real_secs(void) } } +void Soapy::set_rxgain(double rxgain) +{ + m_device->setGain(SOAPY_SDR_RX, 0, m_conf.rxgain); + m_conf.rxgain = m_device->getGain(SOAPY_SDR_RX, 0); +} + +double Soapy::get_rxgain(void) +{ + return m_device->getGain(SOAPY_SDR_RX, 0); +} + +size_t Soapy::receive_frame( + complexf *buf, + size_t num_samples, + struct frame_timestamp& ts, + double timeout_secs) +{ + int flags = 0; + long long timeNs = ts.get_ns(); + const size_t numElems = num_samples; + + void *buffs[1]; + buffs[0] = buf; + + m_device->activateStream(m_rx_stream, flags, timeNs, numElems); + + auto ret = m_device->readStream(m_tx_stream, buffs, num_samples, flags, timeNs); + + m_device->deactivateStream(m_rx_stream); + + // TODO update effective receive ts + + if (ret < 0) { + throw runtime_error("Soapy readStream error: " + to_string(ret)); + } + + return ret; +} + + bool Soapy::is_clk_source_ok() { // TODO @@ -181,7 +222,7 @@ void Soapy::transmit_frame(const struct FrameData& frame) } // Stream MTU is in samples, not bytes. - const size_t mtu = m_device->getStreamMTU(m_stream); + const size_t mtu = m_device->getStreamMTU(m_tx_stream); size_t num_acc_samps = 0; while (num_acc_samps < numSamples) { @@ -192,7 +233,7 @@ void Soapy::transmit_frame(const struct FrameData& frame) int flags = 0; - auto ret = m_device->writeStream(m_stream, buffs, samps_to_send, flags); + auto ret = m_device->writeStream(m_tx_stream, buffs, samps_to_send, flags); if (ret == SOAPY_SDR_TIMEOUT) { continue; diff --git a/src/output/Soapy.h b/src/output/Soapy.h index 97076b5..c603193 100644 --- a/src/output/Soapy.h +++ b/src/output/Soapy.h @@ -68,6 +68,14 @@ class Soapy : public Output::SDRDevice virtual RunStatistics get_run_statistics(void) override; virtual double get_real_secs(void) override; + virtual void set_rxgain(double rxgain) override; + virtual double get_rxgain(void) override; + virtual size_t receive_frame( + complexf *buf, + size_t num_samples, + struct frame_timestamp& ts, + double timeout_secs) override; + // Return true if GPS and reference clock inputs are ok virtual bool is_clk_source_ok(void) override; virtual const char* device_name(void) override; @@ -75,7 +83,8 @@ class Soapy : public Output::SDRDevice private: SDRDeviceConfig& m_conf; SoapySDR::Device *m_device = nullptr; - SoapySDR::Stream *m_stream = nullptr; + SoapySDR::Stream *m_tx_stream = nullptr; + SoapySDR::Stream *m_rx_stream = nullptr; size_t underflows = 0; size_t overflows = 0; diff --git a/src/output/UHD.cpp b/src/output/UHD.cpp index 13a0a57..3769c60 100644 --- a/src/output/UHD.cpp +++ b/src/output/UHD.cpp @@ -235,6 +235,10 @@ UHD::UHD( m_usrp, m_conf.dpdFeedbackServerPort, m_conf.sampleRate); */ + const uhd::stream_args_t stream_args("fc32"); //complex floats + m_rx_stream = m_usrp->get_rx_stream(stream_args); + m_tx_stream = m_usrp->get_tx_stream(stream_args); + MDEBUG("OutputUHD:UHD ready.\n"); } @@ -342,6 +346,42 @@ double UHD::get_real_secs(void) return m_usrp->get_time_now().get_real_secs(); } +void UHD::set_rxgain(double rxgain) +{ + m_usrp->set_rx_gain(m_conf.rxgain); + m_conf.rxgain = m_usrp->get_rx_gain(); +} + +double UHD::get_rxgain() +{ + return m_usrp->get_rx_gain(); +} + +size_t UHD::receive_frame( + complexf *buf, + size_t num_samples, + struct frame_timestamp& ts, + double timeout_secs) +{ + uhd::stream_cmd_t cmd( + uhd::stream_cmd_t::stream_mode_t::STREAM_MODE_NUM_SAMPS_AND_DONE); + cmd.num_samps = num_samples; + cmd.stream_now = false; + cmd.time_spec = uhd::time_spec_t(ts.timestamp_sec, ts.pps_offset()); + + m_rx_stream->issue_stream_cmd(cmd); + + uhd::rx_metadata_t md; + + constexpr double timeout = 60; + size_t samples_read = m_rx_stream->recv(buf, num_samples, md, timeout); + + // Update the ts with the effective receive TS + ts.timestamp_sec = md.time_spec.get_full_secs(); + ts.timestamp_pps = md.time_spec.get_frac_secs() * 16384000.0; + return samples_read; +} + // Return true if GPS and reference clock inputs are ok bool UHD::is_clk_source_ok(void) { diff --git a/src/output/UHD.h b/src/output/UHD.h index 220e4c8..3742924 100644 --- a/src/output/UHD.h +++ b/src/output/UHD.h @@ -84,6 +84,14 @@ class UHD : public Output::SDRDevice virtual RunStatistics get_run_statistics(void) override; virtual double get_real_secs(void) override; + virtual void set_rxgain(double rxgain) override; + virtual double get_rxgain(void) override; + virtual size_t receive_frame( + complexf *buf, + size_t num_samples, + struct frame_timestamp& ts, + double timeout_secs) override; + // Return true if GPS and reference clock inputs are ok virtual bool is_clk_source_ok(void) override; virtual const char* device_name(void) override; @@ -92,6 +100,7 @@ class UHD : public Output::SDRDevice SDRDeviceConfig& m_conf; uhd::usrp::multi_usrp::sptr m_usrp; uhd::tx_streamer::sptr m_tx_stream; + uhd::rx_streamer::sptr m_rx_stream; size_t num_underflows = 0; size_t num_overflows = 0; -- cgit v1.2.3 From 863c1eea672314d9a5834c740d151ef2b0130cf3 Mon Sep 17 00:00:00 2001 From: "Matthias P. Braendli" Date: Sat, 4 Nov 2017 08:37:48 +0100 Subject: Remove OutputUHD files --- src/OutputUHD.cpp | 1035 --------------------------------------------- src/OutputUHD.h | 250 ----------- src/OutputUHDFeedback.cpp | 362 ---------------- src/OutputUHDFeedback.h | 119 ------ src/output/UHD.h | 1 - 5 files changed, 1767 deletions(-) delete mode 100644 src/OutputUHD.cpp delete mode 100644 src/OutputUHD.h delete mode 100644 src/OutputUHDFeedback.cpp delete mode 100644 src/OutputUHDFeedback.h (limited to 'src/output/UHD.h') diff --git a/src/OutputUHD.cpp b/src/OutputUHD.cpp deleted file mode 100644 index e1fe9dd..0000000 --- a/src/OutputUHD.cpp +++ /dev/null @@ -1,1035 +0,0 @@ -/* - Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Her Majesty the - Queen in Right of Canada (Communications Research Center Canada) - - Copyright (C) 2017 - Matthias P. Braendli, matthias.braendli@mpb.li - - http://opendigitalradio.org - */ -/* - This file is part of ODR-DabMod. - - ODR-DabMod is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as - published by the Free Software Foundation, either version 3 of the - License, or (at your option) any later version. - - ODR-DabMod is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with ODR-DabMod. If not, see . - */ - -#include "OutputUHD.h" - -#ifdef HAVE_OUTPUT_UHD - -#include "PcDebug.h" -#include "Log.h" -#include "RemoteControl.h" -#include "Utils.h" - -#include - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace std; - -// Maximum number of frames that can wait in frames -static const size_t FRAMES_MAX_SIZE = 8; - -typedef std::complex complexf; - -std::string stringtrim(const std::string &s) -{ - auto wsfront = std::find_if_not(s.begin(), s.end(), - [](int c){ return std::isspace(c);} ); - return std::string(wsfront, - std::find_if_not(s.rbegin(), - std::string::const_reverse_iterator(wsfront), - [](int c){ return std::isspace(c);} ).base()); -} - -void uhd_msg_handler(uhd::msg::type_t type, const std::string &msg) -{ - if (type == uhd::msg::warning) { - etiLog.level(warn) << "UHD Warning: " << msg; - } - else if (type == uhd::msg::error) { - etiLog.level(error) << "UHD Error: " << msg; - } - else { - // do not print very short U messages and such - if (stringtrim(msg).size() != 1) { - etiLog.level(debug) << "UHD Message: " << msg; - } - } -} - -static void tune_usrp_to( - uhd::usrp::multi_usrp::sptr usrp, - double lo_offset, - double frequency) -{ - if (lo_offset != 0.0) { - etiLog.level(info) << std::fixed << std::setprecision(3) << - "OutputUHD:Setting freq to " << frequency << - " with LO offset " << lo_offset << "..."; - - const auto tr = uhd::tune_request_t(frequency, lo_offset); - uhd::tune_result_t result = usrp->set_tx_freq(tr); - - etiLog.level(debug) << "OutputUHD:" << - std::fixed << std::setprecision(0) << - " Target RF: " << result.target_rf_freq << - " Actual RF: " << result.actual_rf_freq << - " Target DSP: " << result.target_dsp_freq << - " Actual DSP: " << result.actual_dsp_freq; - } - else { - //set the centre frequency - etiLog.level(info) << std::fixed << std::setprecision(3) << - "OutputUHD:Setting freq to " << frequency << "..."; - usrp->set_tx_freq(frequency); - } - - usrp->set_rx_freq(frequency); -} - -// Check function for GPS TIMELOCK sensor from the ODR LEA-M8F board GPSDO -bool check_gps_timelock(uhd::usrp::multi_usrp::sptr usrp) -{ - try { - std::string sensor_value( - usrp->get_mboard_sensor("gps_timelock", 0).to_pp_string()); - - if (sensor_value.find("TIME LOCKED") == std::string::npos) { - etiLog.level(warn) << "OutputUHD: gps_timelock " << sensor_value; - return false; - } - - return true; - } - catch (uhd::lookup_error &e) { - etiLog.level(warn) << "OutputUHD: no gps_timelock sensor"; - return false; - } -} - -// Check function for GPS LOCKED sensor from the Ettus GPSDO -bool check_gps_locked(uhd::usrp::multi_usrp::sptr usrp) -{ - try { - uhd::sensor_value_t sensor_value( - usrp->get_mboard_sensor("gps_locked", 0)); - if (not sensor_value.to_bool()) { - etiLog.level(warn) << "OutputUHD: gps_locked " << - sensor_value.to_pp_string(); - return false; - } - - return true; - } - catch (uhd::lookup_error &e) { - etiLog.level(warn) << "OutputUHD: no gps_locked sensor"; - return false; - } -} - - -OutputUHD::OutputUHD( - OutputUHDConfig& config) : - ModOutput(), - RemoteControllable("uhd"), - myConf(config), - // Since we don't know the buffer size, we cannot initialise - // the buffers at object initialisation. - myDelayBuf(0), - running(false) -{ - myConf.muting = true; // is remote-controllable, and reset by the GPS fix check - myConf.staticDelayUs = 0; // is remote-controllable - - // Variables needed for GPS fix check - first_gps_fix_check.tv_sec = 0; - last_gps_fix_check.tv_sec = 0; - time_last_frame.tv_sec = 0; - - - std::stringstream device; - device << myConf.device; - - if (myConf.masterClockRate != 0) { - if (device.str() != "") { - device << ","; - } - device << "master_clock_rate=" << myConf.masterClockRate; - } - - if (myConf.usrpType != "") { - if (device.str() != "") { - device << ","; - } - device << "type=" << myConf.usrpType; - } - - MDEBUG("OutputUHD::OutputUHD(device: %s) @ %p\n", - device.str().c_str(), this); - - /* register the parameters that can be remote controlled */ - RC_ADD_PARAMETER(txgain, "UHD analog daughterboard TX gain"); - RC_ADD_PARAMETER(rxgain, "UHD analog daughterboard RX gain for DPD feedback"); - RC_ADD_PARAMETER(freq, "UHD transmission frequency"); - RC_ADD_PARAMETER(muting, "Mute the output by stopping the transmitter"); - RC_ADD_PARAMETER(staticdelay, "Set static delay (uS) between 0 and 96000"); - RC_ADD_PARAMETER(underruns, "Read-only counter of number of underruns"); - RC_ADD_PARAMETER(latepackets, "Read-only counter of number of late packets"); - RC_ADD_PARAMETER(frames, "Read-only counter of number of frames modulated"); - - uhd::msg::register_handler(uhd_msg_handler); - - uhd::set_thread_priority_safe(); - - etiLog.log(info, "OutputUHD:Creating the usrp device with: %s...", - device.str().c_str()); - - myUsrp = uhd::usrp::multi_usrp::make(device.str()); - - etiLog.log(info, "OutputUHD:Using device: %s...", - myUsrp->get_pp_string().c_str()); - - if (myConf.masterClockRate != 0.0) { - double master_clk_rate = myUsrp->get_master_clock_rate(); - etiLog.log(debug, "OutputUHD:Checking master clock rate: %f...", - master_clk_rate); - - if (fabs(master_clk_rate - myConf.masterClockRate) > - (myConf.masterClockRate * 1e-6)) { - throw std::runtime_error("Cannot set USRP master_clock_rate. Aborted."); - } - } - - MDEBUG("OutputUHD:Setting REFCLK and PPS input...\n"); - - if (myConf.refclk_src == "gpsdo-ettus") { - myUsrp->set_clock_source("gpsdo"); - } - else { - myUsrp->set_clock_source(myConf.refclk_src); - } - myUsrp->set_time_source(myConf.pps_src); - - if (myConf.subDevice != "") { - myUsrp->set_tx_subdev_spec(uhd::usrp::subdev_spec_t(myConf.subDevice), - uhd::usrp::multi_usrp::ALL_MBOARDS); - } - - etiLog.level(debug) << "UHD clock source is " << myUsrp->get_clock_source(0); - - etiLog.level(debug) << "UHD time source is " << myUsrp->get_time_source(0); - - myUsrp->set_tx_rate(myConf.sampleRate); - etiLog.log(debug, "OutputUHD:Set rate to %d. Actual TX Rate: %f sps...", - myConf.sampleRate, myUsrp->get_tx_rate()); - - if (fabs(myUsrp->get_tx_rate() / myConf.sampleRate) > - myConf.sampleRate * 1e-6) { - throw std::runtime_error("Cannot set USRP sample rate. Aborted."); - } - - tune_usrp_to(myUsrp, myConf.lo_offset, myConf.frequency); - - myConf.frequency = myUsrp->get_tx_freq(); - etiLog.level(info) << std::fixed << std::setprecision(3) << - "OutputUHD:Actual TX frequency: " << myConf.frequency; - - etiLog.level(info) << std::fixed << std::setprecision(3) << - "OutputUHD:Actual RX frequency: " << myUsrp->get_tx_freq(); - - myUsrp->set_tx_gain(myConf.txgain); - etiLog.log(debug, "OutputUHD:Actual TX Gain: %f", myUsrp->get_tx_gain()); - - etiLog.log(debug, "OutputUHD:Mute on missing timestamps: %s", - myConf.muteNoTimestamps ? "enabled" : "disabled"); - - // preparing output thread worker data - sourceContainsTimestamp = false; - - SetDelayBuffer(myConf.dabMode); - - myUsrp->set_rx_rate(myConf.sampleRate); - etiLog.log(debug, "OutputUHD:Actual RX Rate: %f sps.", myUsrp->get_rx_rate()); - - myUsrp->set_rx_antenna("RX2"); - etiLog.log(debug, "OutputUHD:Set RX Antenna: %s", - myUsrp->get_rx_antenna().c_str()); - - myUsrp->set_rx_gain(myConf.rxgain); - etiLog.log(debug, "OutputUHD:Actual RX Gain: %f", myUsrp->get_rx_gain()); - - uhdFeedback = std::make_shared( - myUsrp, myConf.dpdFeedbackServerPort, myConf.sampleRate); - - MDEBUG("OutputUHD:UHD ready.\n"); -} - -bool OutputUHD::refclk_loss_needs_check() const -{ - if (suppress_refclk_loss_check) { - return false; - } - return myConf.refclk_src != "internal"; -} - -bool OutputUHD::gpsfix_needs_check() const -{ - if (myConf.refclk_src == "internal") { - return false; - } - else if (myConf.refclk_src == "gpsdo") { - return (myConf.maxGPSHoldoverTime != 0); - } - else if (myConf.refclk_src == "gpsdo-ettus") { - return (myConf.maxGPSHoldoverTime != 0); - } - else { - return false; - } -} - -bool OutputUHD::gpsdo_is_ettus() const -{ - return (myConf.refclk_src == "gpsdo-ettus"); -} - -OutputUHD::~OutputUHD() -{ - stop_threads(); -} - -void OutputUHD::stop_threads() -{ - running.store(false); - uhd_thread.interrupt(); - uhd_thread.join(); - async_rx_thread.join(); -} - - -void OutputUHD::setETISource(EtiSource *etiSource) -{ - myEtiSource = etiSource; -} - -int transmission_frame_duration_ms(unsigned int dabMode) -{ - switch (dabMode) { - // could happen when called from constructor and we take the mode from ETI - case 0: return 0; - - case 1: return 96; - case 2: return 24; - case 3: return 24; - case 4: return 48; - default: - throw std::runtime_error("OutputUHD: invalid DAB mode"); - } -} - -void OutputUHD::SetDelayBuffer(unsigned int dabMode) -{ - // find out the duration of the transmission frame (Table 2 in ETSI 300 401) - myTFDurationMs = transmission_frame_duration_ms(dabMode); - - // The buffer size equals the number of samples per transmission frame so - // we calculate it by multiplying the duration of the transmission frame - // with the samplerate. - myDelayBuf.resize(myTFDurationMs * myConf.sampleRate / 1000); -} - -int OutputUHD::process(Buffer* dataIn) -{ - if (not gps_fix_verified) { - if (gpsfix_needs_check()) { - initial_gps_check(); - - if (num_checks_without_gps_fix == 0) { - set_usrp_time(); - gps_fix_verified = true; - myConf.muting = false; - } - } - else { - set_usrp_time(); - gps_fix_verified = true; - myConf.muting = false; - } - } - else { - if (first_run) { - etiLog.level(debug) << "OutputUHD: UHD initialising..."; - - // we only set the delay buffer from the dab mode signaled in ETI if the - // dab mode was not set in contructor - if (myTFDurationMs == 0) { - SetDelayBuffer(myEtiSource->getMode()); - } - - running.store(true); - uhd_thread = boost::thread(&OutputUHD::workerthread, this); - async_rx_thread = boost::thread( - &OutputUHD::print_async_thread, this); - - lastLen = dataIn->getLength(); - first_run = false; - etiLog.level(debug) << "OutputUHD: UHD initialising complete"; - } - - if (lastLen != dataIn->getLength()) { - // I expect that this never happens. - etiLog.level(emerg) << - "OutputUHD: Fatal error, input length changed from " << lastLen << - " to " << dataIn->getLength(); - throw std::runtime_error("Non-constant input length!"); - } - - sourceContainsTimestamp = myConf.enableSync and - myEtiSource->sourceContainsTimestamp(); - - if (gpsfix_needs_check()) { - try { - check_gps(); - } - catch (std::runtime_error& e) { - running.store(false); - etiLog.level(error) << e.what(); - } - } - - // Prepare the frame for the worker - UHDWorkerFrameData frame; - frame.buf.resize(dataIn->getLength()); - - // calculate delay and fill buffer - uint32_t noSampleDelay = (myConf.staticDelayUs * (myConf.sampleRate / 1000)) / 1000; - uint32_t noByteDelay = noSampleDelay * sizeof(complexf); - - const uint8_t* pInData = (uint8_t*)dataIn->getData(); - - uint8_t *pTmp = &frame.buf[0]; - if (noByteDelay) { - // copy remain from delaybuf - memcpy(pTmp, &myDelayBuf[0], noByteDelay); - // copy new data - memcpy(&pTmp[noByteDelay], pInData, dataIn->getLength() - noByteDelay); - // copy remaining data to delay buf - memcpy(&myDelayBuf[0], &pInData[dataIn->getLength() - noByteDelay], noByteDelay); - } - else { - std::copy(pInData, pInData + dataIn->getLength(), - frame.buf.begin()); - } - - myEtiSource->calculateTimestamp(frame.ts); - - if (not running.load()) { - uhd_thread.interrupt(); - uhd_thread.join(); - async_rx_thread.join(); - first_run = true; - - etiLog.level(error) << "OutputUHD UHD worker failed"; - throw std::runtime_error("UHD worker failed"); - } - - if (frame.ts.fct == -1) { - etiLog.level(info) << - "OutputUHD: dropping one frame with invalid FCT"; - } - else { - try { - uhdFeedback->set_tx_frame(frame.buf, frame.ts); - } - catch (const runtime_error& e) { - etiLog.level(warn) << - "OutputUHD: Feedback server failed, restarting..."; - - uhdFeedback = std::make_shared( - myUsrp, myConf.dpdFeedbackServerPort, myConf.sampleRate); - } - - size_t num_frames = frames.push_wait_if_full(frame, - FRAMES_MAX_SIZE); - etiLog.log(trace, "UHD,push %zu", num_frames); - } - } - - return dataIn->getLength(); -} - - -void OutputUHD::set_usrp_time() -{ - if (myConf.enableSync and (myConf.pps_src == "none")) { - etiLog.level(warn) << - "OutputUHD: WARNING:" - " you are using synchronous transmission without PPS input!"; - - struct timespec now; - if (clock_gettime(CLOCK_REALTIME, &now)) { - perror("OutputUHD:Error: could not get time: "); - etiLog.level(error) << "OutputUHD: could not get time"; - } - else { - myUsrp->set_time_now(uhd::time_spec_t(now.tv_sec)); - etiLog.level(info) << "OutputUHD: Setting USRP time to " << - std::fixed << - uhd::time_spec_t(now.tv_sec).get_real_secs(); - } - } - - if (myConf.pps_src != "none") { - /* handling time for synchronisation: wait until the next full - * second, and set the USRP time at next PPS */ - struct timespec now; - time_t seconds; - if (clock_gettime(CLOCK_REALTIME, &now)) { - etiLog.level(error) << "OutputUHD: could not get time :" << - strerror(errno); - throw std::runtime_error("OutputUHD: could not get time."); - } - else { - seconds = now.tv_sec; - - MDEBUG("OutputUHD:sec+1: %ld ; now: %ld ...\n", seconds+1, now.tv_sec); - while (seconds + 1 > now.tv_sec) { - usleep(1); - if (clock_gettime(CLOCK_REALTIME, &now)) { - etiLog.level(error) << "OutputUHD: could not get time :" << - strerror(errno); - throw std::runtime_error("OutputUHD: could not get time."); - } - } - MDEBUG("OutputUHD:sec+1: %ld ; now: %ld ...\n", seconds+1, now.tv_sec); - /* We are now shortly after the second change. */ - - usleep(200000); // 200ms, we want the PPS to be later - myUsrp->set_time_unknown_pps(uhd::time_spec_t(seconds + 2)); - etiLog.level(info) << "OutputUHD: Setting USRP time next pps to " << - std::fixed << - uhd::time_spec_t(seconds + 2).get_real_secs(); - } - - usleep(1e6); - etiLog.log(info, "OutputUHD: USRP time %f\n", - myUsrp->get_time_now().get_real_secs()); - } -} - -void OutputUHD::initial_gps_check() -{ - if (first_gps_fix_check.tv_sec == 0) { - etiLog.level(info) << "Waiting for GPS fix"; - - if (clock_gettime(CLOCK_MONOTONIC, &first_gps_fix_check) != 0) { - stringstream ss; - ss << "clock_gettime failure: " << strerror(errno); - throw std::runtime_error(ss.str()); - } - } - - check_gps(); - - if (last_gps_fix_check.tv_sec > - first_gps_fix_check.tv_sec + initial_gps_fix_wait) { - stringstream ss; - ss << "GPS did not show time lock in " << initial_gps_fix_wait << " seconds"; - throw std::runtime_error(ss.str()); - } - - if (time_last_frame.tv_sec == 0) { - if (clock_gettime(CLOCK_MONOTONIC, &time_last_frame) != 0) { - stringstream ss; - ss << "clock_gettime failure: " << strerror(errno); - throw std::runtime_error(ss.str()); - } - } - - struct timespec now; - if (clock_gettime(CLOCK_MONOTONIC, &now) != 0) { - stringstream ss; - ss << "clock_gettime failure: " << strerror(errno); - throw std::runtime_error(ss.str()); - } - - long delta_us = timespecdiff_us(time_last_frame, now); - long wait_time_us = transmission_frame_duration_ms(myConf.dabMode); - - if (wait_time_us - delta_us > 0) { - usleep(wait_time_us - delta_us); - } - - time_last_frame.tv_nsec += wait_time_us * 1000; - if (time_last_frame.tv_nsec >= 1000000000L) { - time_last_frame.tv_nsec -= 1000000000L; - time_last_frame.tv_sec++; - } -} - -void OutputUHD::check_gps() -{ - struct timespec time_now; - if (clock_gettime(CLOCK_MONOTONIC, &time_now) != 0) { - stringstream ss; - ss << "clock_gettime failure: " << strerror(errno); - throw std::runtime_error(ss.str()); - } - - // Divide interval by two because we alternate between - // launch and check - if (gpsfix_needs_check() and - last_gps_fix_check.tv_sec + gps_fix_check_interval/2.0 < - time_now.tv_sec) { - last_gps_fix_check = time_now; - - // Alternate between launching thread and checking the - // result. - if (gps_fix_task.joinable()) { - if (gps_fix_future.has_value()) { - - gps_fix_future.wait(); - - gps_fix_task.join(); - - if (not gps_fix_future.get()) { - if (num_checks_without_gps_fix == 0) { - etiLog.level(alert) << - "OutputUHD: GPS Time Lock lost"; - } - num_checks_without_gps_fix++; - } - else { - if (num_checks_without_gps_fix) { - etiLog.level(info) << - "OutputUHD: GPS Time Lock recovered"; - } - num_checks_without_gps_fix = 0; - } - - if (gps_fix_check_interval * num_checks_without_gps_fix > - myConf.maxGPSHoldoverTime) { - std::stringstream ss; - ss << "Lost GPS Time Lock for " << gps_fix_check_interval * - num_checks_without_gps_fix << " seconds"; - throw std::runtime_error(ss.str()); - } - } - } - else { - // Checking the sensor here takes too much - // time, it has to be done in a separate thread. - if (gpsdo_is_ettus()) { - gps_fix_pt = boost::packaged_task( - boost::bind(check_gps_locked, myUsrp) ); - } - else { - gps_fix_pt = boost::packaged_task( - boost::bind(check_gps_timelock, myUsrp) ); - } - gps_fix_future = gps_fix_pt.get_future(); - - gps_fix_task = boost::thread(boost::move(gps_fix_pt)); - } - } -} - -void OutputUHD::workerthread() -{ - // Set thread priority to realtime - if (int ret = set_realtime_prio(1)) { - etiLog.level(error) << "Could not set priority for UHD worker:" << ret; - } - - set_thread_name("uhdworker"); - - last_tx_time_initialised = false; - - uhd::stream_args_t stream_args("fc32"); //complex floats - myTxStream = myUsrp->get_tx_stream(stream_args); - - md.start_of_burst = false; - md.end_of_burst = false; - - num_underflows = 0; - num_late_packets = 0; - - size_t last_num_underflows = 0; - size_t pop_prebuffering = FRAMES_MAX_SIZE; - - while (running.load()) { - md.has_time_spec = false; - md.time_spec = uhd::time_spec_t(0.0); - - struct UHDWorkerFrameData frame; - etiLog.log(trace, "UHD,wait"); - frames.wait_and_pop(frame, pop_prebuffering); - etiLog.log(trace, "UHD,pop"); - - handle_frame(&frame); - num_frames_modulated++; - - /* Ensure we fill frames after every underrun and - * at startup to reduce underrun likelihood. */ - if (last_num_underflows < num_underflows) { - pop_prebuffering = FRAMES_MAX_SIZE; - } - else { - pop_prebuffering = 1; - } - last_num_underflows = num_underflows; - } - running.store(false); - etiLog.level(warn) << "UHD worker terminated"; -} - -void OutputUHD::handle_frame(const struct UHDWorkerFrameData *frame) -{ - // Transmit timeout - static const double tx_timeout = 20.0; - - // Check for ref_lock - if (refclk_loss_needs_check()) { - try { - if (not myUsrp->get_mboard_sensor("ref_locked", 0).to_bool()) { - etiLog.log(alert, - "OutputUHD: External reference clock lock lost !"); - if (myConf.refclk_lock_loss_behaviour == CRASH) { - throw std::runtime_error( - "OutputUHD: External reference clock lock lost."); - } - } - } - catch (uhd::lookup_error &e) { - suppress_refclk_loss_check = true; - etiLog.log(warn, "OutputUHD: This USRP does not have mboard " - "sensor for ext clock loss. Check disabled."); - } - } - - double usrp_time = myUsrp->get_time_now().get_real_secs(); - bool timestamp_discontinuity = false; - - if (sourceContainsTimestamp) { - // Tx time from MNSC and TIST - uint32_t tx_second = frame->ts.timestamp_sec; - uint32_t tx_pps = frame->ts.timestamp_pps; - - if (!frame->ts.timestamp_valid) { - /* We have not received a full timestamp through - * MNSC. We sleep through the frame. - */ - etiLog.level(info) << - "OutputUHD: Throwing sample " << frame->ts.fct << - " away: incomplete timestamp " << tx_second << - " / " << tx_pps; - usleep(20000); //TODO should this be TM-dependant ? - return; - } - - if (last_tx_time_initialised) { - const size_t sizeIn = frame->buf.size() / sizeof(complexf); - uint64_t increment = (uint64_t)sizeIn * 16384000ul / - (uint64_t)myConf.sampleRate; - // samps * ticks/s / (samps/s) - // (samps * ticks * s) / (s * samps) - // ticks - - uint32_t expected_sec = last_tx_second + increment / 16384000ul; - uint32_t expected_pps = last_tx_pps + increment % 16384000ul; - - while (expected_pps >= 16384000) { - expected_sec++; - expected_pps -= 16384000; - } - - if (expected_sec != tx_second or - expected_pps != tx_pps) { - etiLog.level(warn) << "OutputUHD: timestamp irregularity!" << - std::fixed << - " Expected " << - expected_sec << "+" << (double)expected_pps/16384000.0 << - "(" << expected_pps << ")" << - " Got " << - tx_second << "+" << (double)tx_pps/16384000.0 << - "(" << tx_pps << ")"; - - timestamp_discontinuity = true; - } - } - - last_tx_second = tx_second; - last_tx_pps = tx_pps; - last_tx_time_initialised = true; - - double pps_offset = tx_pps / 16384000.0; - - md.has_time_spec = true; - md.time_spec = uhd::time_spec_t(tx_second, pps_offset); - etiLog.log(trace, "UHD,tist %f", md.time_spec.get_real_secs()); - - // md is defined, let's do some checks - if (md.time_spec.get_real_secs() + tx_timeout < usrp_time) { - etiLog.level(warn) << - "OutputUHD: Timestamp in the past! offset: " << - std::fixed << - md.time_spec.get_real_secs() - usrp_time << - " (" << usrp_time << ")" - " frame " << frame->ts.fct << - ", tx_second " << tx_second << - ", pps " << pps_offset; - return; - } - - if (md.time_spec.get_real_secs() > usrp_time + TIMESTAMP_ABORT_FUTURE) { - etiLog.level(error) << - "OutputUHD: Timestamp way too far in the future! offset: " << - std::fixed << - md.time_spec.get_real_secs() - usrp_time; - throw std::runtime_error("Timestamp error. Aborted."); - } - } - else { // !sourceContainsTimestamp - if (myConf.muting or myConf.muteNoTimestamps) { - /* There was some error decoding the timestamp */ - if (myConf.muting) { - etiLog.log(info, - "OutputUHD: Muting sample %d requested\n", - frame->ts.fct); - } - else { - etiLog.log(info, - "OutputUHD: Muting sample %d : no timestamp\n", - frame->ts.fct); - } - usleep(20000); - return; - } - } - - tx_frame(frame, timestamp_discontinuity); -} - -void OutputUHD::tx_frame(const struct UHDWorkerFrameData *frame, bool ts_update) -{ - const double tx_timeout = 20.0; - const size_t sizeIn = frame->buf.size() / sizeof(complexf); - const complexf* in_data = reinterpret_cast(&frame->buf[0]); - - size_t usrp_max_num_samps = myTxStream->get_max_num_samps(); - size_t num_acc_samps = 0; //number of accumulated samples - while (running.load() and (not myConf.muting) and (num_acc_samps < sizeIn)) { - size_t samps_to_send = std::min(sizeIn - num_acc_samps, usrp_max_num_samps); - - uhd::tx_metadata_t md_tx = md; - - //ensure the the last packet has EOB set if the timestamps has been - //refreshed and need to be reconsidered. - md_tx.end_of_burst = ( - sourceContainsTimestamp and - (frame->ts.timestamp_refresh or ts_update) and - samps_to_send <= usrp_max_num_samps ); - - - //send a single packet - size_t num_tx_samps = myTxStream->send( - &in_data[num_acc_samps], - samps_to_send, md_tx, tx_timeout); - etiLog.log(trace, "UHD,sent %zu of %zu", num_tx_samps, samps_to_send); - - num_acc_samps += num_tx_samps; - - md_tx.time_spec = md.time_spec + - uhd::time_spec_t(0, num_tx_samps/myConf.sampleRate); - - if (num_tx_samps == 0) { - etiLog.log(warn, - "OutputUHD::workerthread() unable to write to device, skipping frame!\n"); - break; - } - } -} - -void OutputUHD::print_async_thread() -{ - while (running.load()) { - uhd::async_metadata_t async_md; - if (myUsrp->get_device()->recv_async_msg(async_md, 1)) { - const char* uhd_async_message = ""; - bool failure = false; - switch (async_md.event_code) { - case uhd::async_metadata_t::EVENT_CODE_BURST_ACK: - break; - case uhd::async_metadata_t::EVENT_CODE_UNDERFLOW: - uhd_async_message = "Underflow"; - num_underflows++; - break; - case uhd::async_metadata_t::EVENT_CODE_SEQ_ERROR: - uhd_async_message = "Packet loss between host and device."; - failure = true; - break; - case uhd::async_metadata_t::EVENT_CODE_TIME_ERROR: - uhd_async_message = "Packet had time that was late."; - num_late_packets++; - break; - case uhd::async_metadata_t::EVENT_CODE_UNDERFLOW_IN_PACKET: - uhd_async_message = "Underflow occurred inside a packet."; - failure = true; - break; - case uhd::async_metadata_t::EVENT_CODE_SEQ_ERROR_IN_BURST: - uhd_async_message = "Packet loss within a burst."; - failure = true; - break; - default: - uhd_async_message = "unknown event code"; - failure = true; - break; - } - - if (failure) { - etiLog.level(alert) << - "Received Async UHD Message '" << - uhd_async_message << "' at time " << - md.time_spec.get_real_secs(); - - } - } - - auto time_now = std::chrono::steady_clock::now(); - if (last_print_time + std::chrono::seconds(1) < time_now) { - const double usrp_time = - myUsrp->get_time_now().get_real_secs(); - - if ( (num_underflows > num_underflows_previous) or - (num_late_packets > num_late_packets_previous)) { - etiLog.log(info, - "OutputUHD status (usrp time: %f): " - "%d underruns and %d late packets since last status.\n", - usrp_time, - num_underflows, num_late_packets); - } - - num_underflows_previous = num_underflows; - num_late_packets_previous = num_late_packets; - - last_print_time = time_now; - } - } -} - -// ======================================= -// Remote Control for UHD -// ======================================= -void OutputUHD::set_parameter(const string& parameter, const string& value) -{ - stringstream ss(value); - ss.exceptions ( stringstream::failbit | stringstream::badbit ); - - if (parameter == "txgain") { - ss >> myConf.txgain; - myUsrp->set_tx_gain(myConf.txgain); - } - else if (parameter == "rxgain") { - ss >> myConf.rxgain; - myUsrp->set_rx_gain(myConf.rxgain); - } - else if (parameter == "freq") { - ss >> myConf.frequency; - tune_usrp_to(myUsrp, myConf.lo_offset, myConf.frequency); - myConf.frequency = myUsrp->get_tx_freq(); - } - else if (parameter == "muting") { - ss >> myConf.muting; - } - else if (parameter == "staticdelay") { - int64_t adjust; - ss >> adjust; - if (adjust > (myTFDurationMs * 1000)) - { // reset static delay for values outside range - myConf.staticDelayUs = 0; - } - else - { // the new adjust value is added to the existing delay and the result - // is wrapped around at TF duration - int newStaticDelayUs = myConf.staticDelayUs + adjust; - if (newStaticDelayUs > (myTFDurationMs * 1000)) - myConf.staticDelayUs = newStaticDelayUs - (myTFDurationMs * 1000); - else if (newStaticDelayUs < 0) - myConf.staticDelayUs = newStaticDelayUs + (myTFDurationMs * 1000); - else - myConf.staticDelayUs = newStaticDelayUs; - } - } - else if (parameter == "underruns" or - parameter == "latepackets" or - parameter == "frames") { - throw ParameterError("Parameter " + parameter + " is read-only."); - } - else { - stringstream ss; - ss << "Parameter '" << parameter - << "' is not exported by controllable " << get_rc_name(); - throw ParameterError(ss.str()); - } -} - -const string OutputUHD::get_parameter(const string& parameter) const -{ - stringstream ss; - if (parameter == "txgain") { - ss << myConf.txgain; - } - else if (parameter == "rxgain") { - ss << myConf.rxgain; - } - else if (parameter == "freq") { - ss << myConf.frequency; - } - else if (parameter == "muting") { - ss << myConf.muting; - } - else if (parameter == "staticdelay") { - ss << myConf.staticDelayUs; - } - else if (parameter == "underruns") { - ss << num_underflows; - } - else if (parameter == "latepackets") { - ss << num_late_packets; - } - else if (parameter == "frames") { - ss << num_frames_modulated; - } - else { - ss << "Parameter '" << parameter << - "' is not exported by controllable " << get_rc_name(); - throw ParameterError(ss.str()); - } - return ss.str(); -} - -#endif // HAVE_OUTPUT_UHD - diff --git a/src/OutputUHD.h b/src/OutputUHD.h deleted file mode 100644 index dfa471d..0000000 --- a/src/OutputUHD.h +++ /dev/null @@ -1,250 +0,0 @@ -/* - Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Her Majesty the - Queen in Right of Canada (Communications Research Center Canada) - - Copyright (C) 2017 - Matthias P. Braendli, matthias.braendli@mpb.li - - http://opendigitalradio.org - -DESCRIPTION: - It is an output driver for the USRP family of devices, and uses the UHD - library. This version is multi-threaded. A separate thread sends the data to - the device. - - Data between the modulator and the UHD thread are exchanged through a - threadsafe queue. -*/ - -/* - This file is part of ODR-DabMod. - - ODR-DabMod is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as - published by the Free Software Foundation, either version 3 of the - License, or (at your option) any later version. - - ODR-DabMod is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with ODR-DabMod. If not, see . - */ - -#pragma once - -#ifdef HAVE_CONFIG_H -# include -#endif - -#ifdef HAVE_OUTPUT_UHD - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "Log.h" -#include "ModPlugin.h" -#include "EtiReader.h" -#include "TimestampDecoder.h" -#include "RemoteControl.h" -#include "ThreadsafeQueue.h" -#include "OutputUHDFeedback.h" - -#include -#include - -//#define MDEBUG(fmt, args...) fprintf(LOG, fmt , ## args) -#define MDEBUG(fmt, args...) - -// If the timestamp is further in the future than -// 100 seconds, abort -#define TIMESTAMP_ABORT_FUTURE 100 - -// Add a delay to increase buffers when -// frames are too far in the future -#define TIMESTAMP_MARGIN_FUTURE 0.5 - -typedef std::complex complexf; - -// Each frame contains one OFDM frame, and its -// associated timestamp -struct UHDWorkerFrameData { - // Buffer holding frame data - std::vector buf; - - // A full timestamp contains a TIST according to standard - // and time information within MNSC with tx_second. - struct frame_timestamp ts; -}; - -enum refclk_lock_loss_behaviour_t { CRASH, IGNORE }; - -/* This structure is used as initial configuration for OutputUHD. - * It must also contain all remote-controllable settings, otherwise - * they will get lost on a modulator restart. */ -struct OutputUHDConfig { - std::string device; - std::string usrpType; // e.g. b100, b200, usrp2 - - // The USRP1 can accept two daughterboards - std::string subDevice; // e.g. A:0 - - long masterClockRate = 32768000; - unsigned sampleRate = 2048000; - double frequency = 0.0; - double lo_offset = 0.0; - double txgain = 0.0; - double rxgain = 0.0; - bool enableSync = false; - - // When working with timestamps, mute the frames that - // do not have a timestamp - bool muteNoTimestamps = false; - unsigned dabMode = 0; - unsigned maxGPSHoldoverTime = 0; - - /* allowed values : auto, int, sma, mimo */ - std::string refclk_src; - - /* allowed values : int, sma, mimo */ - std::string pps_src; - - /* allowed values : pos, neg */ - std::string pps_polarity; - - /* What to do when the reference clock PLL loses lock */ - refclk_lock_loss_behaviour_t refclk_lock_loss_behaviour; - - // muting can only be changed using the remote control - bool muting = false; - - // static delay in microseconds - int staticDelayUs = 0; - - // TCP port on which to serve TX and RX samples for the - // digital pre distortion learning tool - uint16_t dpdFeedbackServerPort = 0; -}; - -class OutputUHD: public ModOutput, public RemoteControllable { - public: - OutputUHD(OutputUHDConfig& config); - OutputUHD(const OutputUHD& other) = delete; - OutputUHD operator=(const OutputUHD& other) = delete; - ~OutputUHD(); - - int process(Buffer* dataIn); - - const char* name() { return "OutputUHD"; } - - void setETISource(EtiSource *etiSource); - - /*********** REMOTE CONTROL ***************/ - - /* Base function to set parameters. */ - virtual void set_parameter(const std::string& parameter, - const std::string& value); - - /* Getting a parameter always returns a string. */ - virtual const std::string get_parameter( - const std::string& parameter) const; - - protected: - EtiSource *myEtiSource = nullptr; - OutputUHDConfig& myConf; - uhd::usrp::multi_usrp::sptr myUsrp; - std::shared_ptr mySyncBarrier; - bool first_run = true; - bool gps_fix_verified = false; - std::shared_ptr uhdFeedback; - - private: - // Resize the internal delay buffer according to the dabMode and - // the sample rate. - void SetDelayBuffer(unsigned int dabMode); - - // data - // The remote-controllable static delay is in the OutputUHDConfig - int myTFDurationMs; // TF duration in milliseconds - std::vector myDelayBuf; - size_t lastLen = 0; - - // GPS Fix check variables - int num_checks_without_gps_fix = 1; - struct timespec first_gps_fix_check; - struct timespec last_gps_fix_check; - struct timespec time_last_frame; - boost::packaged_task gps_fix_pt; - boost::unique_future gps_fix_future; - boost::thread gps_fix_task; - - // Wait time in seconds to get fix - static const int initial_gps_fix_wait = 180; - - // Interval for checking the GPS at runtime - static constexpr double gps_fix_check_interval = 10.0; // seconds - - // Asynchronous message statistics - size_t num_underflows = 0; - size_t num_late_packets = 0; - size_t num_underflows_previous = 0; - size_t num_late_packets_previous = 0; - - size_t num_frames_modulated = 0; - - uhd::tx_metadata_t md; - bool last_tx_time_initialised = false; - uint32_t last_tx_second = 0; - uint32_t last_tx_pps = 0; - - // Used to print statistics once a second - std::chrono::steady_clock::time_point last_print_time; - - bool sourceContainsTimestamp = false; - - ThreadsafeQueue frames; - - // Returns true if we want to verify loss of refclk - bool refclk_loss_needs_check(void) const; - bool suppress_refclk_loss_check = false; - - // Returns true if we want to check for the gps_timelock sensor - bool gpsfix_needs_check(void) const; - - // Return true if the gpsdo is from ettus, false if it is the ODR - // LEA-M8F board is used - bool gpsdo_is_ettus(void) const; - - std::atomic running; - boost::thread uhd_thread; - boost::thread async_rx_thread; - void stop_threads(void); - - uhd::tx_streamer::sptr myTxStream; - - // The worker thread decouples the modulator from UHD - void workerthread(); - void handle_frame(const struct UHDWorkerFrameData *frame); - void tx_frame(const struct UHDWorkerFrameData *frame, bool ts_update); - - // Poll asynchronous metadata from UHD - void print_async_thread(void); - - void check_gps(); - - void set_usrp_time(); - - void initial_gps_check(); -}; - -#endif // HAVE_OUTPUT_UHD - diff --git a/src/OutputUHDFeedback.cpp b/src/OutputUHDFeedback.cpp deleted file mode 100644 index 68783f2..0000000 --- a/src/OutputUHDFeedback.cpp +++ /dev/null @@ -1,362 +0,0 @@ -/* - Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Her Majesty the - Queen in Right of Canada (Communications Research Center Canada) - - Copyright (C) 2017 - Matthias P. Braendli, matthias.braendli@mpb.li - - http://opendigitalradio.org - -DESCRIPTION: - This presents a TCP socket to an external tool which calculates - a Digital Predistortion model from a short sequence of transmit - samples and corresponding receive samples. -*/ - -/* - This file is part of ODR-DabMod. - - ODR-DabMod is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as - published by the Free Software Foundation, either version 3 of the - License, or (at your option) any later version. - - ODR-DabMod is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with ODR-DabMod. If not, see . - */ - -#ifdef HAVE_CONFIG_H -# include -#endif - -#ifdef HAVE_OUTPUT_UHD - -#include -#include -#include -#include -#include -#include -#include -#include -#include "OutputUHDFeedback.h" -#include "Utils.h" -#include "Socket.h" - -using namespace std; -typedef std::complex complexf; - -OutputUHDFeedback::OutputUHDFeedback( - uhd::usrp::multi_usrp::sptr usrp, - uint16_t port, - uint32_t sampleRate) -{ - m_port = port; - m_sampleRate = sampleRate; - m_usrp = usrp; - - if (m_port) { - m_running.store(true); - - rx_burst_thread = boost::thread(&OutputUHDFeedback::ReceiveBurstThread, this); - burst_tcp_thread = boost::thread(&OutputUHDFeedback::ServeFeedbackThread, this); - } -} - -OutputUHDFeedback::~OutputUHDFeedback() -{ - m_running.store(false); - - rx_burst_thread.interrupt(); - if (rx_burst_thread.joinable()) { - rx_burst_thread.join(); - } - - burst_tcp_thread.interrupt(); - if (burst_tcp_thread.joinable()) { - burst_tcp_thread.join(); - } -} - -void OutputUHDFeedback::set_tx_frame( - const std::vector &buf, - const struct frame_timestamp &buf_ts) -{ - if (not m_running) { - throw runtime_error("OutputUHDFeedback not running"); - } - - boost::mutex::scoped_lock lock(burstRequest.mutex); - - if (buf.size() % sizeof(complexf) != 0) { - throw std::logic_error("Buffer for tx frame has incorrect size"); - } - - if (burstRequest.state == BurstRequestState::SaveTransmitFrame) { - const size_t n = std::min( - burstRequest.num_samples * sizeof(complexf), buf.size()); - - burstRequest.num_samples = n / sizeof(complexf); - - burstRequest.tx_samples.clear(); - burstRequest.tx_samples.resize(n); - // A frame will always begin with the NULL symbol, which contains - // no power. Instead of taking n samples at the beginning of the - // frame, we take them at the end and adapt the timestamp accordingly. - - const size_t start_ix = buf.size() - n; - copy(buf.begin() + start_ix, buf.end(), burstRequest.tx_samples.begin()); - - frame_timestamp ts = buf_ts; - ts += (1.0 * start_ix) / (sizeof(complexf) * m_sampleRate); - - burstRequest.tx_second = ts.timestamp_sec; - burstRequest.tx_pps = ts.timestamp_pps; - - // Prepare the next state - burstRequest.rx_second = ts.timestamp_sec; - burstRequest.rx_pps = ts.timestamp_pps; - burstRequest.state = BurstRequestState::SaveReceiveFrame; - - lock.unlock(); - burstRequest.mutex_notification.notify_one(); - } - else { - lock.unlock(); - } -} - -void OutputUHDFeedback::ReceiveBurstThread() -{ - try { - set_thread_name("uhdreceiveburst"); - - uhd::stream_args_t stream_args("fc32"); //complex floats - auto rxStream = m_usrp->get_rx_stream(stream_args); - - while (m_running) { - boost::mutex::scoped_lock lock(burstRequest.mutex); - while (burstRequest.state != BurstRequestState::SaveReceiveFrame) { - if (not m_running) break; - burstRequest.mutex_notification.wait(lock); - } - - if (not m_running) break; - - uhd::stream_cmd_t cmd( - uhd::stream_cmd_t::stream_mode_t::STREAM_MODE_NUM_SAMPS_AND_DONE); - cmd.num_samps = burstRequest.num_samples; - cmd.stream_now = false; - - double pps = burstRequest.rx_pps / 16384000.0; - cmd.time_spec = uhd::time_spec_t(burstRequest.rx_second, pps); - - // We need to free the mutex while we recv(), because otherwise we block the - // TX thread - lock.unlock(); - - const double usrp_time = m_usrp->get_time_now().get_real_secs(); - const double cmd_time = cmd.time_spec.get_real_secs(); - - rxStream->issue_stream_cmd(cmd); - - uhd::rx_metadata_t md; - - std::vector buf(cmd.num_samps * sizeof(complexf)); - - const double timeout = 60; - size_t samples_read = rxStream->recv(&buf[0], cmd.num_samps, md, timeout); - - lock.lock(); - burstRequest.rx_samples = std::move(buf); - burstRequest.rx_samples.resize(samples_read * sizeof(complexf)); - - // The recv might have happened at another time than requested - burstRequest.rx_second = md.time_spec.get_full_secs(); - burstRequest.rx_pps = md.time_spec.get_frac_secs() * 16384000.0; - - etiLog.level(debug) << "DPD: acquired " << samples_read << - " RX feedback samples " << - "at time " << burstRequest.tx_second << " + " << - std::fixed << burstRequest.tx_pps / 16384000.0 << - " Delta=" << cmd_time - usrp_time; - - burstRequest.state = BurstRequestState::Acquired; - - lock.unlock(); - burstRequest.mutex_notification.notify_one(); - } - } - catch (const runtime_error &e) { - etiLog.level(error) << "DPD Feedback RX runtime error: " << e.what(); - } - catch (const std::exception &e) { - etiLog.level(error) << "DPD Feedback RX exception: " << e.what(); - } - catch (...) { - etiLog.level(error) << "DPD Feedback RX unknown exception!"; - } - - m_running.store(false); -} - -void OutputUHDFeedback::ServeFeedback() -{ - TCPSocket m_server_sock; - m_server_sock.listen(m_port); - - etiLog.level(info) << "DPD Feedback server listening on port " << m_port; - - while (m_running) { - struct sockaddr_in client; - TCPSocket client_sock = m_server_sock.accept_with_timeout(1000, &client); - - if (not client_sock.valid()) { - // No connection request received - continue; - } - - uint8_t request_version = 0; - ssize_t read = client_sock.recv(&request_version, 1, 0); - if (!read) break; // done reading - if (read < 0) { - etiLog.level(info) << - "DPD Feedback Server Client read request version failed: " << strerror(errno); - break; - } - - if (request_version != 1) { - etiLog.level(info) << "DPD Feedback Server wrong request version"; - break; - } - - uint32_t num_samples = 0; - read = client_sock.recv(&num_samples, 4, 0); - if (!read) break; // done reading - if (read < 0) { - etiLog.level(info) << - "DPD Feedback Server Client read num samples failed"; - break; - } - - // We are ready to issue the request now - { - boost::mutex::scoped_lock lock(burstRequest.mutex); - burstRequest.num_samples = num_samples; - burstRequest.state = BurstRequestState::SaveTransmitFrame; - - lock.unlock(); - } - - // Wait for the result to be ready - boost::mutex::scoped_lock lock(burstRequest.mutex); - while (burstRequest.state != BurstRequestState::Acquired) { - if (not m_running) break; - burstRequest.mutex_notification.wait(lock); - } - - burstRequest.state = BurstRequestState::None; - lock.unlock(); - - burstRequest.num_samples = std::min(burstRequest.num_samples, - std::min( - burstRequest.tx_samples.size() / sizeof(complexf), - burstRequest.rx_samples.size() / sizeof(complexf))); - - uint32_t num_samples_32 = burstRequest.num_samples; - if (client_sock.sendall(&num_samples_32, sizeof(num_samples_32)) < 0) { - etiLog.level(info) << - "DPD Feedback Server Client send num_samples failed"; - break; - } - - if (client_sock.sendall( - &burstRequest.tx_second, - sizeof(burstRequest.tx_second)) < 0) { - etiLog.level(info) << - "DPD Feedback Server Client send tx_second failed"; - break; - } - - if (client_sock.sendall( - &burstRequest.tx_pps, - sizeof(burstRequest.tx_pps)) < 0) { - etiLog.level(info) << - "DPD Feedback Server Client send tx_pps failed"; - break; - } - - const size_t frame_bytes = burstRequest.num_samples * sizeof(complexf); - - if (burstRequest.tx_samples.size() < frame_bytes) { - throw logic_error("DPD Feedback burstRequest invalid: not enough TX samples"); - } - - if (client_sock.sendall( - &burstRequest.tx_samples[0], - frame_bytes) < 0) { - etiLog.level(info) << - "DPD Feedback Server Client send tx_frame failed"; - break; - } - - if (client_sock.sendall( - &burstRequest.rx_second, - sizeof(burstRequest.rx_second)) < 0) { - etiLog.level(info) << - "DPD Feedback Server Client send rx_second failed"; - break; - } - - if (client_sock.sendall( - &burstRequest.rx_pps, - sizeof(burstRequest.rx_pps)) < 0) { - etiLog.level(info) << - "DPD Feedback Server Client send rx_pps failed"; - break; - } - - if (burstRequest.rx_samples.size() < frame_bytes) { - throw logic_error("DPD Feedback burstRequest invalid: not enough RX samples"); - } - - if (client_sock.sendall( - &burstRequest.rx_samples[0], - frame_bytes) < 0) { - etiLog.level(info) << - "DPD Feedback Server Client send rx_frame failed"; - break; - } - } -} - -void OutputUHDFeedback::ServeFeedbackThread() -{ - set_thread_name("uhdservefeedback"); - - while (m_running) { - try { - ServeFeedback(); - } - catch (const runtime_error &e) { - etiLog.level(error) << "DPD Feedback Server runtime error: " << e.what(); - } - catch (const std::exception &e) { - etiLog.level(error) << "DPD Feedback Server exception: " << e.what(); - } - catch (...) { - etiLog.level(error) << "DPD Feedback Server unknown exception!"; - } - - boost::this_thread::sleep(boost::posix_time::seconds(5)); - } - - m_running.store(false); -} - -#endif diff --git a/src/OutputUHDFeedback.h b/src/OutputUHDFeedback.h deleted file mode 100644 index 80d287f..0000000 --- a/src/OutputUHDFeedback.h +++ /dev/null @@ -1,119 +0,0 @@ -/* - Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Her Majesty the - Queen in Right of Canada (Communications Research Center Canada) - - Copyright (C) 2017 - Matthias P. Braendli, matthias.braendli@mpb.li - - http://opendigitalradio.org - -DESCRIPTION: - This presents a TCP socket to an external tool which calculates - a Digital Predistortion model from a short sequence of transmit - samples and corresponding receive samples. -*/ - -/* - This file is part of ODR-DabMod. - - ODR-DabMod is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as - published by the Free Software Foundation, either version 3 of the - License, or (at your option) any later version. - - ODR-DabMod is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with ODR-DabMod. If not, see . - */ - -#pragma once - -#ifdef HAVE_CONFIG_H -# include -#endif - -#ifdef HAVE_OUTPUT_UHD - -#include -#include -#include -#include -#include -#include -#include - -#include "Log.h" -#include "TimestampDecoder.h" - -enum class BurstRequestState { - None, // To pending request - SaveTransmitFrame, // The TX thread has to save an outgoing frame - SaveReceiveFrame, // The RX thread has to save an incoming frame - Acquired, // Both TX and RX frames are ready -}; - -struct UHDReceiveBurstRequest { - // All fields in this struct are protected - mutable boost::mutex mutex; - boost::condition_variable mutex_notification; - - BurstRequestState state = BurstRequestState::None; - - // In the SaveTransmit states, num_samples complexf samples are saved into - // the vectors - size_t num_samples = 0; - - // The timestamp of the first sample of the TX buffers - uint32_t tx_second = 0; - uint32_t tx_pps = 0; // in units of 1/16384000s - - // Samples contain complexf, but since our internal representation is uint8_t - // we keep it like that - std::vector tx_samples; - - // The timestamp of the first sample of the RX buffers - uint32_t rx_second = 0; - uint32_t rx_pps = 0; - - std::vector rx_samples; // Also, actually complexf -}; - -// Serve TX samples and RX feedback samples over a TCP connection -class OutputUHDFeedback { - public: - OutputUHDFeedback( - uhd::usrp::multi_usrp::sptr usrp, - uint16_t port, - uint32_t sampleRate); - OutputUHDFeedback(const OutputUHDFeedback& other) = delete; - OutputUHDFeedback& operator=(const OutputUHDFeedback& other) = delete; - ~OutputUHDFeedback(); - - void set_tx_frame(const std::vector &buf, - const struct frame_timestamp& ts); - - private: - // Thread that reacts to burstRequests and receives from the USRP - void ReceiveBurstThread(void); - - // Thread that listens for requests over TCP to get TX and RX feedback - void ServeFeedbackThread(void); - void ServeFeedback(void); - - boost::thread rx_burst_thread; - boost::thread burst_tcp_thread; - - UHDReceiveBurstRequest burstRequest; - - std::atomic_bool m_running; - uint16_t m_port = 0; - uint32_t m_sampleRate = 0; - uhd::usrp::multi_usrp::sptr m_usrp; -}; - - -#endif // HAVE_OUTPUT_UHD diff --git a/src/output/UHD.h b/src/output/UHD.h index 3742924..5ae477b 100644 --- a/src/output/UHD.h +++ b/src/output/UHD.h @@ -50,7 +50,6 @@ DESCRIPTION: #include "TimestampDecoder.h" #include "RemoteControl.h" #include "ThreadsafeQueue.h" -#include "OutputUHDFeedback.h" #include #include -- cgit v1.2.3 From 3502da409355db3e87ef9c4d6118b9cf50577521 Mon Sep 17 00:00:00 2001 From: "Matthias P. Braendli" Date: Sun, 26 Nov 2017 09:51:49 +0100 Subject: Move USRP GPSDO and time handling into separate class --- Makefile.am | 2 + TODO | 8 +- src/Utils.cpp | 14 +++ src/Utils.h | 4 + src/output/SDR.cpp | 42 ++++++- src/output/SDR.h | 4 + src/output/UHD.cpp | 292 ++++++------------------------------------------ src/output/UHD.h | 33 +----- src/output/USRPTime.cpp | 280 ++++++++++++++++++++++++++++++++++++++++++++++ src/output/USRPTime.h | 116 +++++++++++++++++++ 10 files changed, 496 insertions(+), 299 deletions(-) create mode 100644 src/output/USRPTime.cpp create mode 100644 src/output/USRPTime.h (limited to 'src/output/UHD.h') diff --git a/Makefile.am b/Makefile.am index 6e5c1a3..2d56842 100644 --- a/Makefile.am +++ b/Makefile.am @@ -90,6 +90,8 @@ odr_dabmod_SOURCES = src/DabMod.cpp \ src/output/Soapy.h \ src/output/UHD.cpp \ src/output/UHD.h \ + src/output/USRPTime.cpp \ + src/output/USRPTime.h \ src/InputFileReader.cpp \ src/InputMemory.cpp \ src/InputMemory.h \ diff --git a/TODO b/TODO index c175926..cf7f9df 100644 --- a/TODO +++ b/TODO @@ -15,12 +15,14 @@ This would enable SFN support with LimeSDR devices. Move dpd port from uhd section to somewhere else. -Clean up and separate GPS and refclk checks. Ensure muting is set properly at startup. +Clean up and separate GPS and refclk checks. + * *done* handle UHD GPSDO and time + * handle SoapySDR time + * Add refclk stuff and timestamps to Soapy. + * Ensure muting is set properly at startup. Add antenna selection to config. -Add refclk stuff and timestamps to Soapy. - *done* Make an abstraction for the DPD feedback server, use it for Soapy and UHD. Move staticdelay into a new process block diff --git a/src/Utils.cpp b/src/Utils.cpp index cd116c7..385253b 100644 --- a/src/Utils.cpp +++ b/src/Utils.cpp @@ -210,3 +210,17 @@ double parseChannel(const std::string& chan) } return freq; } + +int transmission_frame_duration_ms(unsigned int dabMode) +{ + switch (dabMode) { + case 1: return 96; + case 2: return 24; + case 3: return 24; + case 4: return 48; + default: + throw std::runtime_error("invalid DAB mode"); + } +} + + diff --git a/src/Utils.h b/src/Utils.h index 8da3a1b..2bc6cb3 100644 --- a/src/Utils.h +++ b/src/Utils.h @@ -67,3 +67,7 @@ void set_thread_name(const char *name); // Convert a channel like 10A to a frequency double parseChannel(const std::string& chan); +// dabMode is either 1, 2, 3, 4, corresponding to TM I, TM II, TM III and TM IV. +// throws a runtime_error if dabMode is not one of these values. +int transmission_frame_duration_ms(unsigned int dabMode); + diff --git a/src/output/SDR.cpp b/src/output/SDR.cpp index 356ae4f..ac25061 100644 --- a/src/output/SDR.cpp +++ b/src/output/SDR.cpp @@ -63,9 +63,12 @@ SDR::SDR(SDRDeviceConfig& config, std::shared_ptr device) : m_running(false), m_device(device) { - // muting is remote-controllable, and reset by the GPS fix check + // muting is remote-controllable m_config.muting = false; + time_last_frame.tv_sec = 0; + time_last_frame.tv_nsec = 0; + m_device_thread = std::thread(&SDR::process_thread_entry, this); m_dpd_feedback_server = make_shared( @@ -95,11 +98,6 @@ void SDR::stop() int SDR::process(Buffer *dataIn) { if (m_device) { - if (not m_device->is_clk_source_ok()) { - // Ignore frame - return dataIn->getLength(); - } - FrameData frame; frame.buf.resize(dataIn->getLength()); @@ -210,6 +208,37 @@ void SDR::setETISource(EtiSource *etiSource) m_eti_source = etiSource; } +void SDR::sleep_through_frame() +{ + struct timespec now; + if (clock_gettime(CLOCK_MONOTONIC, &now) != 0) { + stringstream ss; + ss << "clock_gettime failure: " << strerror(errno); + throw runtime_error(ss.str()); + } + + if (time_last_frame.tv_sec == 0) { + if (clock_gettime(CLOCK_MONOTONIC, &time_last_frame) != 0) { + stringstream ss; + ss << "clock_gettime failure: " << strerror(errno); + throw runtime_error(ss.str()); + } + } + + long delta_us = timespecdiff_us(time_last_frame, now); + long wait_time_us = transmission_frame_duration_ms(m_config.dabMode); + + if (wait_time_us - delta_us > 0) { + usleep(wait_time_us - delta_us); + } + + time_last_frame.tv_nsec += wait_time_us * 1000; + while (time_last_frame.tv_nsec >= 1000000000L) { + time_last_frame.tv_nsec -= 1000000000L; + time_last_frame.tv_sec++; + } +} + void SDR::handle_frame(struct FrameData& frame) { // Assumes m_device is valid @@ -217,6 +246,7 @@ void SDR::handle_frame(struct FrameData& frame) constexpr double tx_timeout = 20.0; if (not m_device->is_clk_source_ok()) { + sleep_through_frame(); return; } diff --git a/src/output/SDR.h b/src/output/SDR.h index 5593ce3..d3693da 100644 --- a/src/output/SDR.h +++ b/src/output/SDR.h @@ -70,6 +70,7 @@ class SDR : public ModOutput, public RemoteControllable { void stop(void); void process_thread_entry(void); void handle_frame(struct FrameData &frame); + void sleep_through_frame(void); SDRDeviceConfig& m_config; @@ -87,6 +88,9 @@ class SDR : public ModOutput, public RemoteControllable { bool last_tx_time_initialised = false; uint32_t last_tx_second = 0; uint32_t last_tx_pps = 0; + + struct timespec time_last_frame; + }; } diff --git a/src/output/UHD.cpp b/src/output/UHD.cpp index 54ff6c2..55beacc 100644 --- a/src/output/UHD.cpp +++ b/src/output/UHD.cpp @@ -28,6 +28,9 @@ #ifdef HAVE_OUTPUT_UHD +//#define MDEBUG(fmt, args...) fprintf(LOG, fmt , ## args) +#define MDEBUG(fmt, args...) + #include "PcDebug.h" #include "Log.h" #include "RemoteControl.h" @@ -81,46 +84,6 @@ static void uhd_msg_handler(uhd::msg::type_t type, const std::string &msg) } -// Check function for GPS TIMELOCK sensor from the ODR LEA-M8F board GPSDO -static bool check_gps_timelock(uhd::usrp::multi_usrp::sptr usrp) -{ - try { - std::string sensor_value( - usrp->get_mboard_sensor("gps_timelock", 0).to_pp_string()); - - if (sensor_value.find("TIME LOCKED") == std::string::npos) { - etiLog.level(warn) << "OutputUHD: gps_timelock " << sensor_value; - return false; - } - - return true; - } - catch (uhd::lookup_error &e) { - etiLog.level(warn) << "OutputUHD: no gps_timelock sensor"; - return false; - } -} - -// Check function for GPS LOCKED sensor from the Ettus GPSDO -static bool check_gps_locked(uhd::usrp::multi_usrp::sptr usrp) -{ - try { - uhd::sensor_value_t sensor_value( - usrp->get_mboard_sensor("gps_locked", 0)); - if (not sensor_value.to_bool()) { - etiLog.level(warn) << "OutputUHD: gps_locked " << - sensor_value.to_pp_string(); - return false; - } - - return true; - } - catch (uhd::lookup_error &e) { - etiLog.level(warn) << "OutputUHD: no gps_locked sensor"; - return false; - } -} - UHD::UHD( SDRDeviceConfig& config) : @@ -128,11 +91,6 @@ UHD::UHD( m_conf(config), m_running(false) { - // Variables needed for GPS fix check - first_gps_fix_check.tv_sec = 0; - last_gps_fix_check.tv_sec = 0; - time_last_frame.tv_sec = 0; - std::stringstream device; device << m_conf.device; @@ -183,6 +141,8 @@ UHD::UHD( } m_usrp->set_time_source(m_conf.pps_src); + m_device_time = std::make_shared(m_usrp, m_conf); + if (m_conf.subDevice != "") { m_usrp->set_tx_subdev_spec(uhd::usrp::subdev_spec_t(m_conf.subDevice), uhd::usrp::multi_usrp::ALL_MBOARDS); @@ -388,8 +348,34 @@ size_t UHD::receive_frame( // Return true if GPS and reference clock inputs are ok bool UHD::is_clk_source_ok(void) { - //TODO - return true; + bool ok = true; + + if (refclk_loss_needs_check()) { + try { + if (not m_usrp->get_mboard_sensor("ref_locked", 0).to_bool()) { + ok = false; + + etiLog.level(alert) << + "OutputUHD: External reference clock lock lost !"; + + if (m_conf.refclk_lock_loss_behaviour == CRASH) { + throw std::runtime_error( + "OutputUHD: External reference clock lock lost."); + } + } + } + catch (uhd::lookup_error &e) { + suppress_refclk_loss_check = true; + etiLog.log(warn, "OutputUHD: This USRP does not have mboard " + "sensor for ext clock loss. Check disabled."); + } + } + + if (m_device_time) { + ok |= m_device_time->verify_time(); + } + + return ok; } const char* UHD::device_name(void) @@ -406,27 +392,6 @@ bool UHD::refclk_loss_needs_check() const return m_conf.refclk_src != "internal"; } -bool UHD::gpsfix_needs_check() const -{ - if (m_conf.refclk_src == "internal") { - return false; - } - else if (m_conf.refclk_src == "gpsdo") { - return (m_conf.maxGPSHoldoverTime != 0); - } - else if (m_conf.refclk_src == "gpsdo-ettus") { - return (m_conf.maxGPSHoldoverTime != 0); - } - else { - return false; - } -} - -bool UHD::gpsdo_is_ettus() const -{ - return (m_conf.refclk_src == "gpsdo-ettus"); -} - void UHD::stop_threads() { m_running.store(false); @@ -436,197 +401,6 @@ void UHD::stop_threads() } -static int transmission_frame_duration_ms(unsigned int dabMode) -{ - switch (dabMode) { - // could happen when called from constructor and we take the mode from ETI - case 0: return 0; - - case 1: return 96; - case 2: return 24; - case 3: return 24; - case 4: return 48; - default: - throw std::runtime_error("OutputUHD: invalid DAB mode"); - } -} - - -void UHD::set_usrp_time() -{ - if (m_conf.enableSync and (m_conf.pps_src == "none")) { - etiLog.level(warn) << - "OutputUHD: WARNING:" - " you are using synchronous transmission without PPS input!"; - - struct timespec now; - if (clock_gettime(CLOCK_REALTIME, &now)) { - perror("OutputUHD:Error: could not get time: "); - etiLog.level(error) << "OutputUHD: could not get time"; - } - else { - m_usrp->set_time_now(uhd::time_spec_t(now.tv_sec)); - etiLog.level(info) << "OutputUHD: Setting USRP time to " << - std::fixed << - uhd::time_spec_t(now.tv_sec).get_real_secs(); - } - } - - if (m_conf.pps_src != "none") { - /* handling time for synchronisation: wait until the next full - * second, and set the USRP time at next PPS */ - struct timespec now; - time_t seconds; - if (clock_gettime(CLOCK_REALTIME, &now)) { - etiLog.level(error) << "OutputUHD: could not get time :" << - strerror(errno); - throw std::runtime_error("OutputUHD: could not get time."); - } - else { - seconds = now.tv_sec; - - MDEBUG("OutputUHD:sec+1: %ld ; now: %ld ...\n", seconds+1, now.tv_sec); - while (seconds + 1 > now.tv_sec) { - usleep(1); - if (clock_gettime(CLOCK_REALTIME, &now)) { - etiLog.level(error) << "OutputUHD: could not get time :" << - strerror(errno); - throw std::runtime_error("OutputUHD: could not get time."); - } - } - MDEBUG("OutputUHD:sec+1: %ld ; now: %ld ...\n", seconds+1, now.tv_sec); - /* We are now shortly after the second change. */ - - usleep(200000); // 200ms, we want the PPS to be later - m_usrp->set_time_unknown_pps(uhd::time_spec_t(seconds + 2)); - etiLog.level(info) << "OutputUHD: Setting USRP time next pps to " << - std::fixed << - uhd::time_spec_t(seconds + 2).get_real_secs(); - } - - usleep(1e6); - etiLog.log(info, "OutputUHD: USRP time %f\n", - m_usrp->get_time_now().get_real_secs()); - } -} - -void UHD::initial_gps_check() -{ - if (first_gps_fix_check.tv_sec == 0) { - etiLog.level(info) << "Waiting for GPS fix"; - - if (clock_gettime(CLOCK_MONOTONIC, &first_gps_fix_check) != 0) { - stringstream ss; - ss << "clock_gettime failure: " << strerror(errno); - throw std::runtime_error(ss.str()); - } - } - - check_gps(); - - if (last_gps_fix_check.tv_sec > - first_gps_fix_check.tv_sec + initial_gps_fix_wait) { - stringstream ss; - ss << "GPS did not show time lock in " << - initial_gps_fix_wait << " seconds"; - throw std::runtime_error(ss.str()); - } - - if (time_last_frame.tv_sec == 0) { - if (clock_gettime(CLOCK_MONOTONIC, &time_last_frame) != 0) { - stringstream ss; - ss << "clock_gettime failure: " << strerror(errno); - throw std::runtime_error(ss.str()); - } - } - - struct timespec now; - if (clock_gettime(CLOCK_MONOTONIC, &now) != 0) { - stringstream ss; - ss << "clock_gettime failure: " << strerror(errno); - throw std::runtime_error(ss.str()); - } - - long delta_us = timespecdiff_us(time_last_frame, now); - long wait_time_us = transmission_frame_duration_ms(m_conf.dabMode); - - if (wait_time_us - delta_us > 0) { - usleep(wait_time_us - delta_us); - } - - time_last_frame.tv_nsec += wait_time_us * 1000; - if (time_last_frame.tv_nsec >= 1000000000L) { - time_last_frame.tv_nsec -= 1000000000L; - time_last_frame.tv_sec++; - } -} - -void UHD::check_gps() -{ - struct timespec time_now; - if (clock_gettime(CLOCK_MONOTONIC, &time_now) != 0) { - stringstream ss; - ss << "clock_gettime failure: " << strerror(errno); - throw std::runtime_error(ss.str()); - } - - // Divide interval by two because we alternate between - // launch and check - if (gpsfix_needs_check() and - last_gps_fix_check.tv_sec + gps_fix_check_interval/2.0 < - time_now.tv_sec) { - last_gps_fix_check = time_now; - - // Alternate between launching thread and checking the - // result. - if (gps_fix_task.joinable()) { - if (gps_fix_future.has_value()) { - - gps_fix_future.wait(); - - gps_fix_task.join(); - - if (not gps_fix_future.get()) { - if (num_checks_without_gps_fix == 0) { - etiLog.level(alert) << - "OutputUHD: GPS Time Lock lost"; - } - num_checks_without_gps_fix++; - } - else { - if (num_checks_without_gps_fix) { - etiLog.level(info) << - "OutputUHD: GPS Time Lock recovered"; - } - num_checks_without_gps_fix = 0; - } - - if (gps_fix_check_interval * num_checks_without_gps_fix > - m_conf.maxGPSHoldoverTime) { - std::stringstream ss; - ss << "Lost GPS Time Lock for " << gps_fix_check_interval * - num_checks_without_gps_fix << " seconds"; - throw std::runtime_error(ss.str()); - } - } - } - else { - // Checking the sensor here takes too much - // time, it has to be done in a separate thread. - if (gpsdo_is_ettus()) { - gps_fix_pt = boost::packaged_task( - boost::bind(check_gps_locked, m_usrp) ); - } - else { - gps_fix_pt = boost::packaged_task( - boost::bind(check_gps_timelock, m_usrp) ); - } - gps_fix_future = gps_fix_pt.get_future(); - - gps_fix_task = boost::thread(boost::move(gps_fix_pt)); - } - } -} void UHD::print_async_thread() { diff --git a/src/output/UHD.h b/src/output/UHD.h index 5ae477b..4d3eecc 100644 --- a/src/output/UHD.h +++ b/src/output/UHD.h @@ -47,6 +47,7 @@ DESCRIPTION: #include "Log.h" #include "output/SDR.h" +#include "output/USRPTime.h" #include "TimestampDecoder.h" #include "RemoteControl.h" #include "ThreadsafeQueue.h" @@ -54,9 +55,6 @@ DESCRIPTION: #include #include -//#define MDEBUG(fmt, args...) fprintf(LOG, fmt , ## args) -#define MDEBUG(fmt, args...) - // If the timestamp is further in the future than // 100 seconds, abort #define TIMESTAMP_ABORT_FUTURE 100 @@ -100,6 +98,7 @@ class UHD : public Output::SDRDevice uhd::usrp::multi_usrp::sptr m_usrp; uhd::tx_streamer::sptr m_tx_stream; uhd::rx_streamer::sptr m_rx_stream; + std::shared_ptr m_device_time; size_t num_underflows = 0; size_t num_overflows = 0; @@ -110,21 +109,6 @@ class UHD : public Output::SDRDevice uhd::tx_metadata_t md; - // GPS Fix check variables - int num_checks_without_gps_fix = 1; - struct timespec first_gps_fix_check; - struct timespec last_gps_fix_check; - struct timespec time_last_frame; - boost::packaged_task gps_fix_pt; - boost::unique_future gps_fix_future; - boost::thread gps_fix_task; - - // Wait time in seconds to get fix - static const int initial_gps_fix_wait = 180; - - // Interval for checking the GPS at runtime - static constexpr double gps_fix_check_interval = 10.0; // seconds - // Used to print statistics once a second std::chrono::steady_clock::time_point last_print_time; @@ -132,24 +116,11 @@ class UHD : public Output::SDRDevice bool refclk_loss_needs_check(void) const; bool suppress_refclk_loss_check = false; - // Returns true if we want to check for the gps_timelock sensor - bool gpsfix_needs_check(void) const; - - // Return true if the gpsdo is from ettus, false if it is the ODR - // LEA-M8F board is used - bool gpsdo_is_ettus(void) const; - // Poll asynchronous metadata from UHD std::atomic m_running; boost::thread m_async_rx_thread; void stop_threads(void); void print_async_thread(void); - - void check_gps(); - - void set_usrp_time(); - - void initial_gps_check(); }; } // namespace Output diff --git a/src/output/USRPTime.cpp b/src/output/USRPTime.cpp new file mode 100644 index 0000000..dcedeab --- /dev/null +++ b/src/output/USRPTime.cpp @@ -0,0 +1,280 @@ +/* + Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Her Majesty the + Queen in Right of Canada (Communications Research Center Canada) + + Copyright (C) 2017 + Matthias P. Braendli, matthias.braendli@mpb.li + + http://opendigitalradio.org + +DESCRIPTION: + The part of the UHD output that takes care of the GPSDO. +*/ + +/* + This file is part of ODR-DabMod. + + ODR-DabMod is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as + published by the Free Software Foundation, either version 3 of the + License, or (at your option) any later version. + + ODR-DabMod is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with ODR-DabMod. If not, see . + */ + +#include "output/USRPTime.h" + +#ifdef HAVE_OUTPUT_UHD + +//#define MDEBUG(fmt, args...) fprintf(LOG, fmt , ## args) +#define MDEBUG(fmt, args...) + +namespace Output { + +using namespace std; + + +// Check function for GPS TIMELOCK sensor from the ODR LEA-M8F board GPSDO +static bool check_gps_timelock(uhd::usrp::multi_usrp::sptr& usrp) +{ + try { + const string sensor_value = + usrp->get_mboard_sensor("gps_timelock", 0).to_pp_string(); + + if (sensor_value.find("TIME LOCKED") == string::npos) { + etiLog.level(warn) << "OutputUHD: gps_timelock " << sensor_value; + return false; + } + + return true; + } + catch (const uhd::lookup_error &e) { + etiLog.level(warn) << "OutputUHD: no gps_timelock sensor"; + return false; + } +} + +// Check function for GPS LOCKED sensor from the Ettus GPSDO +static bool check_gps_locked(uhd::usrp::multi_usrp::sptr& usrp) +{ + try { + const uhd::sensor_value_t sensor_value( + usrp->get_mboard_sensor("gps_locked", 0)); + if (not sensor_value.to_bool()) { + etiLog.level(warn) << "OutputUHD: gps_locked " << + sensor_value.to_pp_string(); + return false; + } + + return true; + } + catch (const uhd::lookup_error &e) { + etiLog.level(warn) << "OutputUHD: no gps_locked sensor"; + return false; + } +} + + +USRPTime::USRPTime( + uhd::usrp::multi_usrp::sptr usrp, + SDRDeviceConfig& conf) : + m_usrp(usrp), + m_conf(conf), + time_last_check(timepoint_t::clock::now()) +{ + if (m_conf.enableSync and (m_conf.pps_src == "none")) { + set_usrp_time_from_localtime(); + } +} + +bool USRPTime::verify_time() +{ + if (not gpsfix_needs_check()) { + return true; + } + + /* During bootup, we say the gpsdo is not ok, and we poll the GPSDO until + * we reach lock. Then we sync time. If we do not reach lock in time, we + * crash. + * + * Once we are synced and we have lock, everything ok. If we lose lock for + * a number of seconds, we switch to the lost_fix state. + * + * In the lost fix state, we return false to get the TX muted, and we monitor. + * If the fix comes back, we unmute. If we reach the timeout, we crash. + */ + + check_gps(); + + const auto duration_without_fix = + gps_fix_check_interval * num_checks_without_gps_fix; + + switch (gps_state) { + case gps_state_e::bootup: + if (duration_without_fix > initial_gps_fix_wait) { + throw runtime_error("GPS did not fix in " + + to_string(initial_gps_fix_wait) + " seconds"); + } + + if (num_checks_without_gps_fix == 0) { + if (m_conf.pps_src != "none") { + set_usrp_time_from_pps(); + } + gps_state = gps_state_e::monitor_fix; + return true; + } + + return false; + + case gps_state_e::monitor_fix: + if (duration_without_fix > m_conf.maxGPSHoldoverTime) { + throw runtime_error("Lost GPS Fix for " + + to_string(duration_without_fix) + " seconds"); + } + + return true; + } + + throw logic_error("End of USRPTime::verify_time() reached"); +} + +void USRPTime::check_gps() +{ + timepoint_t time_now = timepoint_t::clock::now(); + + // Divide interval by two because we alternate between + // launch and check + const auto checkinterval = chrono::seconds(lrint(gps_fix_check_interval/2.0)); + + if (gpsfix_needs_check() and time_last_check + checkinterval < time_now) { + time_last_check = time_now; + + // Alternate between launching thread and checking the + // result. + if (gps_fix_task.joinable()) { + if (gps_fix_future.has_value()) { + + gps_fix_future.wait(); + + gps_fix_task.join(); + + if (not gps_fix_future.get()) { + if (num_checks_without_gps_fix == 0) { + etiLog.level(alert) << "OutputUHD: GPS Time Lock lost"; + } + num_checks_without_gps_fix++; + } + else { + if (num_checks_without_gps_fix) { + etiLog.level(info) << "OutputUHD: GPS Time Lock recovered"; + } + num_checks_without_gps_fix = 0; + } + } + } + else { + // Checking the sensor here takes too much + // time, it has to be done in a separate thread. + if (gpsdo_is_ettus()) { + gps_fix_pt = boost::packaged_task( + boost::bind(check_gps_locked, m_usrp) ); + } + else { + gps_fix_pt = boost::packaged_task( + boost::bind(check_gps_timelock, m_usrp) ); + } + gps_fix_future = gps_fix_pt.get_future(); + + gps_fix_task = boost::thread(boost::move(gps_fix_pt)); + } + } +} + +bool USRPTime::gpsfix_needs_check() const +{ + if (m_conf.refclk_src == "internal") { + return false; + } + else if (m_conf.refclk_src == "gpsdo") { + return (m_conf.maxGPSHoldoverTime != 0); + } + else if (m_conf.refclk_src == "gpsdo-ettus") { + return (m_conf.maxGPSHoldoverTime != 0); + } + else { + return false; + } +} + +bool USRPTime::gpsdo_is_ettus() const +{ + return (m_conf.refclk_src == "gpsdo-ettus"); +} + +void USRPTime::set_usrp_time_from_localtime() +{ + etiLog.level(warn) << + "OutputUHD: WARNING:" + " you are using synchronous transmission without PPS input!"; + + struct timespec now; + if (clock_gettime(CLOCK_REALTIME, &now)) { + etiLog.level(error) << "OutputUHD: could not get time :" << + strerror(errno); + } + else { + const uhd::time_spec_t t(now.tv_sec, (double)now.tv_nsec / 1e9); + m_usrp->set_time_now(t); + etiLog.level(info) << "OutputUHD: Setting USRP time to " << + std::fixed << t.get_real_secs(); + } +} + +void USRPTime::set_usrp_time_from_pps() +{ + /* handling time for synchronisation: wait until the next full + * second, and set the USRP time at next PPS */ + struct timespec now; + time_t seconds; + if (clock_gettime(CLOCK_REALTIME, &now)) { + etiLog.level(error) << "OutputUHD: could not get time :" << + strerror(errno); + throw std::runtime_error("OutputUHD: could not get time."); + } + else { + seconds = now.tv_sec; + + MDEBUG("OutputUHD:sec+1: %ld ; now: %ld ...\n", seconds+1, now.tv_sec); + while (seconds + 1 > now.tv_sec) { + usleep(1); + if (clock_gettime(CLOCK_REALTIME, &now)) { + etiLog.level(error) << "OutputUHD: could not get time :" << + strerror(errno); + throw std::runtime_error("OutputUHD: could not get time."); + } + } + MDEBUG("OutputUHD:sec+1: %ld ; now: %ld ...\n", seconds+1, now.tv_sec); + /* We are now shortly after the second change. */ + + usleep(200000); // 200ms, we want the PPS to be later + m_usrp->set_time_unknown_pps(uhd::time_spec_t(seconds + 2)); + etiLog.level(info) << "OutputUHD: Setting USRP time next pps to " << + std::fixed << + uhd::time_spec_t(seconds + 2).get_real_secs(); + } + + usleep(1e6); + etiLog.log(info, "OutputUHD: USRP time %f\n", + m_usrp->get_time_now().get_real_secs()); +} + + +} // namespace Output + +#endif // HAVE_OUTPUT_UHD diff --git a/src/output/USRPTime.h b/src/output/USRPTime.h new file mode 100644 index 0000000..7527f21 --- /dev/null +++ b/src/output/USRPTime.h @@ -0,0 +1,116 @@ +/* + Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Her Majesty the + Queen in Right of Canada (Communications Research Center Canada) + + Copyright (C) 2017 + Matthias P. Braendli, matthias.braendli@mpb.li + + http://opendigitalradio.org + +DESCRIPTION: + The part of the UHD output that takes care of the GPSDO and setting device + time. +*/ + +/* + This file is part of ODR-DabMod. + + ODR-DabMod is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as + published by the Free Software Foundation, either version 3 of the + License, or (at your option) any later version. + + ODR-DabMod is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with ODR-DabMod. If not, see . + */ + +#pragma once + +#ifdef HAVE_CONFIG_H +# include +#endif + +#ifdef HAVE_OUTPUT_UHD + +#include +#include +#include +#include +#include + +#include "Log.h" +#include "output/SDR.h" +#include "TimestampDecoder.h" +#include "RemoteControl.h" +#include "ThreadsafeQueue.h" + +#include +#include + +namespace Output { + +class USRPTime { + public: + USRPTime( uhd::usrp::multi_usrp::sptr usrp, + SDRDeviceConfig& conf); + + // Verifies the GPSDO state, that the device time is ok. + // Returns true if all ok. + // Should be called more often than the gps_fix_check_interval + bool verify_time(void); + + // Wait time in seconds to get fix + static const int initial_gps_fix_wait = 180; + + // Interval for checking the GPS at runtime + static constexpr double gps_fix_check_interval = 10.0; // seconds + + private: + enum class gps_state_e { + /* At startup, the LEA-M8F GPSDO gets issued a hotstart request to + * make sure we will not sync time on a PPS edge that is generated + * while the GPSDO is in holdover. In the bootup state, we wait for + * the first PPS after hotstart, and then sync time. + */ + bootup, + + /* Once the system is up, we check lock every now and then. If the + * fix is lost for too long, we crash. + */ + monitor_fix, + }; + + void check_gps(); + + uhd::usrp::multi_usrp::sptr m_usrp; + SDRDeviceConfig& m_conf; + + gps_state_e gps_state = gps_state_e::bootup; + int num_checks_without_gps_fix = 1; + + using timepoint_t = std::chrono::time_point; + timepoint_t time_last_check; + + boost::packaged_task gps_fix_pt; + boost::unique_future gps_fix_future; + boost::thread gps_fix_task; + + // Returns true if we want to check for the gps_timelock sensor + bool gpsfix_needs_check(void) const; + + // Return true if the gpsdo is from ettus, false if it is the ODR + // LEA-M8F board is used + bool gpsdo_is_ettus(void) const; + + void set_usrp_time_from_localtime(void); + void set_usrp_time_from_pps(void); +}; + +} // namespace Output + +#endif // HAVE_OUTPUT_UHD -- cgit v1.2.3 From 92bd6722d4dfc0526d5f2f12e99440b0e18d7479 Mon Sep 17 00:00:00 2001 From: "Matthias P. Braendli" Date: Mon, 25 Dec 2017 05:23:24 +0100 Subject: Avoid warning in SDR.cpp. Set UHD metadata time_spec. --- TODO | 1 + src/output/SDR.cpp | 6 +++--- src/output/UHD.cpp | 27 +++++++++++++++++++-------- src/output/UHD.h | 2 -- 4 files changed, 23 insertions(+), 13 deletions(-) (limited to 'src/output/UHD.h') diff --git a/TODO b/TODO index ce997d6..eeadd6d 100644 --- a/TODO +++ b/TODO @@ -23,6 +23,7 @@ Clean up and separate GPS and refclk checks. * handle SoapySDR time * Add refclk stuff and timestamps to Soapy. * Ensure muting is set properly at startup. + * Ensure synchronous is taken in account. Add antenna selection to config. diff --git a/src/output/SDR.cpp b/src/output/SDR.cpp index ac25061..ba296de 100644 --- a/src/output/SDR.cpp +++ b/src/output/SDR.cpp @@ -382,10 +382,10 @@ void SDR::set_parameter(const string& parameter, const string& value) throw ParameterError("Parameter " + parameter + " is read-only."); } else { - stringstream ss; - ss << "Parameter '" << parameter + stringstream ss_err; + ss_err << "Parameter '" << parameter << "' is not exported by controllable " << get_rc_name(); - throw ParameterError(ss.str()); + throw ParameterError(ss_err.str()); } } diff --git a/src/output/UHD.cpp b/src/output/UHD.cpp index 55beacc..cee35c7 100644 --- a/src/output/UHD.cpp +++ b/src/output/UHD.cpp @@ -260,13 +260,24 @@ void UHD::transmit_frame(const struct FrameData& frame) const size_t sizeIn = frame.buf.size() / sizeof(complexf); const complexf* in_data = reinterpret_cast(&frame.buf[0]); + uhd::tx_metadata_t md_tx; + + // TODO check for enableSync? + if (frame.ts.timestamp_valid) { + uhd::time_spec_t timespec(frame.ts.timestamp_sec, frame.ts.pps_offset()); + md_tx.time_spec = timespec; + md_tx.has_time_spec = true; + } + else { + md_tx.has_time_spec = false; + } + + size_t usrp_max_num_samps = m_tx_stream->get_max_num_samps(); size_t num_acc_samps = 0; //number of accumulated samples while (m_running.load() and (not m_conf.muting) and (num_acc_samps < sizeIn)) { size_t samps_to_send = std::min(sizeIn - num_acc_samps, usrp_max_num_samps); - uhd::tx_metadata_t md_tx = md; - // ensure the the last packet has EOB set if the timestamps has been // refreshed and need to be reconsidered. md_tx.end_of_burst = ( @@ -282,7 +293,7 @@ void UHD::transmit_frame(const struct FrameData& frame) num_acc_samps += num_tx_samps; - md_tx.time_spec = md.time_spec + + md_tx.time_spec = md_tx.time_spec + uhd::time_spec_t(0, num_tx_samps/m_conf.sampleRate); if (num_tx_samps == 0) { @@ -334,14 +345,14 @@ size_t UHD::receive_frame( m_rx_stream->issue_stream_cmd(cmd); - uhd::rx_metadata_t md; + uhd::rx_metadata_t md_rx; constexpr double timeout = 60; - size_t samples_read = m_rx_stream->recv(buf, num_samples, md, timeout); + size_t samples_read = m_rx_stream->recv(buf, num_samples, md_rx, timeout); // Update the ts with the effective receive TS - ts.timestamp_sec = md.time_spec.get_full_secs(); - ts.timestamp_pps = md.time_spec.get_frac_secs() * 16384000.0; + ts.timestamp_sec = md_rx.time_spec.get_full_secs(); + ts.timestamp_pps = md_rx.time_spec.get_frac_secs() * 16384000.0; return samples_read; } @@ -442,7 +453,7 @@ void UHD::print_async_thread() etiLog.level(alert) << "Received Async UHD Message '" << uhd_async_message << "' at time " << - md.time_spec.get_real_secs(); + async_md.time_spec.get_real_secs(); } } diff --git a/src/output/UHD.h b/src/output/UHD.h index 4d3eecc..448fb3f 100644 --- a/src/output/UHD.h +++ b/src/output/UHD.h @@ -107,8 +107,6 @@ class UHD : public Output::SDRDevice size_t num_underflows_previous = 0; size_t num_late_packets_previous = 0; - uhd::tx_metadata_t md; - // Used to print statistics once a second std::chrono::steady_clock::time_point last_print_time; -- cgit v1.2.3 From ac0b24dc76cae40cf513e97160da9305a7d5505c Mon Sep 17 00:00:00 2001 From: "Matthias P. Braendli" Date: Tue, 16 Jan 2018 12:11:27 +0100 Subject: Make some SDR functions const --- src/output/SDR.cpp | 1 - src/output/SDRDevice.h | 15 +++++++-------- src/output/Soapy.cpp | 16 ++++++++-------- src/output/Soapy.h | 16 ++++++++-------- src/output/UHD.cpp | 16 ++++++++-------- src/output/UHD.h | 18 +++++++++--------- 6 files changed, 40 insertions(+), 42 deletions(-) (limited to 'src/output/UHD.h') diff --git a/src/output/SDR.cpp b/src/output/SDR.cpp index a2fb42a..e478de5 100644 --- a/src/output/SDR.cpp +++ b/src/output/SDR.cpp @@ -201,7 +201,6 @@ void SDR::process_thread_entry() } m_running.store(false); - etiLog.level(warn) << "SDR Device thread terminated"; } const char* SDR::name() diff --git a/src/output/SDRDevice.h b/src/output/SDRDevice.h index 856233b..89a9856 100644 --- a/src/output/SDRDevice.h +++ b/src/output/SDRDevice.h @@ -110,16 +110,15 @@ class SDRDevice { size_t num_frames_modulated; //TODO increment }; - // TODO make some functions const virtual void tune(double lo_offset, double frequency) = 0; - virtual double get_tx_freq(void) = 0; + virtual double get_tx_freq(void) const = 0; virtual void set_txgain(double txgain) = 0; - virtual double get_txgain(void) = 0; + virtual double get_txgain(void) const = 0; virtual void transmit_frame(const struct FrameData& frame) = 0; - virtual RunStatistics get_run_statistics(void) = 0; - virtual double get_real_secs(void) = 0; + virtual RunStatistics get_run_statistics(void) const = 0; + virtual double get_real_secs(void) const = 0; virtual void set_rxgain(double rxgain) = 0; - virtual double get_rxgain(void) = 0; + virtual double get_rxgain(void) const = 0; virtual size_t receive_frame( complexf *buf, size_t num_samples, @@ -128,9 +127,9 @@ class SDRDevice { // Return true if GPS and reference clock inputs are ok - virtual bool is_clk_source_ok(void) = 0; + virtual bool is_clk_source_ok(void) const = 0; - virtual const char* device_name(void) = 0; + virtual const char* device_name(void) const = 0; }; } // namespace Output diff --git a/src/output/Soapy.cpp b/src/output/Soapy.cpp index e0a6f10..82c926b 100644 --- a/src/output/Soapy.cpp +++ b/src/output/Soapy.cpp @@ -2,7 +2,7 @@ Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) - Copyright (C) 2017 + Copyright (C) 2018 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org @@ -114,7 +114,7 @@ void Soapy::tune(double lo_offset, double frequency) m_device->setFrequency(SOAPY_SDR_TX, 0, m_conf.frequency, offset_arg); } -double Soapy::get_tx_freq(void) +double Soapy::get_tx_freq(void) const { if (not m_device) throw runtime_error("Soapy device not set up"); @@ -129,13 +129,13 @@ void Soapy::set_txgain(double txgain) m_device->setGain(SOAPY_SDR_TX, 0, m_conf.txgain); } -double Soapy::get_txgain(void) +double Soapy::get_txgain(void) const { if (not m_device) throw runtime_error("Soapy device not set up"); return m_device->getGain(SOAPY_SDR_TX, 0); } -SDRDevice::RunStatistics Soapy::get_run_statistics(void) +SDRDevice::RunStatistics Soapy::get_run_statistics(void) const { RunStatistics rs; rs.num_underruns = underflows; @@ -146,7 +146,7 @@ SDRDevice::RunStatistics Soapy::get_run_statistics(void) } -double Soapy::get_real_secs(void) +double Soapy::get_real_secs(void) const { if (m_device) { long long time_ns = m_device->getHardwareTime(); @@ -163,7 +163,7 @@ void Soapy::set_rxgain(double rxgain) m_conf.rxgain = m_device->getGain(SOAPY_SDR_RX, 0); } -double Soapy::get_rxgain(void) +double Soapy::get_rxgain(void) const { return m_device->getGain(SOAPY_SDR_RX, 0); } @@ -197,13 +197,13 @@ size_t Soapy::receive_frame( } -bool Soapy::is_clk_source_ok() +bool Soapy::is_clk_source_ok() const { // TODO return true; } -const char* Soapy::device_name(void) +const char* Soapy::device_name(void) const { return "Soapy"; } diff --git a/src/output/Soapy.h b/src/output/Soapy.h index c603193..7bf0239 100644 --- a/src/output/Soapy.h +++ b/src/output/Soapy.h @@ -2,7 +2,7 @@ Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) - Copyright (C) 2017 + Copyright (C) 2018 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org @@ -61,15 +61,15 @@ class Soapy : public Output::SDRDevice ~Soapy(); virtual void tune(double lo_offset, double frequency) override; - virtual double get_tx_freq(void) override; + virtual double get_tx_freq(void) const override; virtual void set_txgain(double txgain) override; - virtual double get_txgain(void) override; + virtual double get_txgain(void) const override; virtual void transmit_frame(const struct FrameData& frame) override; - virtual RunStatistics get_run_statistics(void) override; - virtual double get_real_secs(void) override; + virtual RunStatistics get_run_statistics(void) const override; + virtual double get_real_secs(void) const override; virtual void set_rxgain(double rxgain) override; - virtual double get_rxgain(void) override; + virtual double get_rxgain(void) const override; virtual size_t receive_frame( complexf *buf, size_t num_samples, @@ -77,8 +77,8 @@ class Soapy : public Output::SDRDevice double timeout_secs) override; // Return true if GPS and reference clock inputs are ok - virtual bool is_clk_source_ok(void) override; - virtual const char* device_name(void) override; + virtual bool is_clk_source_ok(void) const override; + virtual const char* device_name(void) const override; private: SDRDeviceConfig& m_conf; diff --git a/src/output/UHD.cpp b/src/output/UHD.cpp index 97cf5bb..dda008b 100644 --- a/src/output/UHD.cpp +++ b/src/output/UHD.cpp @@ -2,7 +2,7 @@ Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) - Copyright (C) 2017 + Copyright (C) 2018 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org @@ -230,7 +230,7 @@ void UHD::tune(double lo_offset, double frequency) m_usrp->set_rx_freq(frequency); } -double UHD::get_tx_freq(void) +double UHD::get_tx_freq(void) const { return m_usrp->get_tx_freq(); } @@ -241,7 +241,7 @@ void UHD::set_txgain(double txgain) m_conf.txgain = m_usrp->get_tx_gain(); } -double UHD::get_txgain(void) +double UHD::get_txgain(void) const { return m_usrp->get_tx_gain(); } @@ -306,7 +306,7 @@ void UHD::transmit_frame(const struct FrameData& frame) } -SDRDevice::RunStatistics UHD::get_run_statistics(void) +SDRDevice::RunStatistics UHD::get_run_statistics(void) const { RunStatistics rs; rs.num_underruns = num_underflows; @@ -316,7 +316,7 @@ SDRDevice::RunStatistics UHD::get_run_statistics(void) return rs; } -double UHD::get_real_secs(void) +double UHD::get_real_secs(void) const { return m_usrp->get_time_now().get_real_secs(); } @@ -327,7 +327,7 @@ void UHD::set_rxgain(double rxgain) m_conf.rxgain = m_usrp->get_rx_gain(); } -double UHD::get_rxgain() +double UHD::get_rxgain() const { return m_usrp->get_rx_gain(); } @@ -358,7 +358,7 @@ size_t UHD::receive_frame( } // Return true if GPS and reference clock inputs are ok -bool UHD::is_clk_source_ok(void) +bool UHD::is_clk_source_ok(void) const { bool ok = true; @@ -390,7 +390,7 @@ bool UHD::is_clk_source_ok(void) return ok; } -const char* UHD::device_name(void) +const char* UHD::device_name(void) const { return "UHD"; } diff --git a/src/output/UHD.h b/src/output/UHD.h index 448fb3f..ddcb33b 100644 --- a/src/output/UHD.h +++ b/src/output/UHD.h @@ -2,7 +2,7 @@ Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) - Copyright (C) 2017 + Copyright (C) 2018 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org @@ -74,15 +74,15 @@ class UHD : public Output::SDRDevice ~UHD(); virtual void tune(double lo_offset, double frequency) override; - virtual double get_tx_freq(void) override; + virtual double get_tx_freq(void) const override; virtual void set_txgain(double txgain) override; - virtual double get_txgain(void) override; + virtual double get_txgain(void) const override; virtual void transmit_frame(const struct FrameData& frame) override; - virtual RunStatistics get_run_statistics(void) override; - virtual double get_real_secs(void) override; + virtual RunStatistics get_run_statistics(void) const override; + virtual double get_real_secs(void) const override; virtual void set_rxgain(double rxgain) override; - virtual double get_rxgain(void) override; + virtual double get_rxgain(void) const override; virtual size_t receive_frame( complexf *buf, size_t num_samples, @@ -90,8 +90,8 @@ class UHD : public Output::SDRDevice double timeout_secs) override; // Return true if GPS and reference clock inputs are ok - virtual bool is_clk_source_ok(void) override; - virtual const char* device_name(void) override; + virtual bool is_clk_source_ok(void) const override; + virtual const char* device_name(void) const override; private: SDRDeviceConfig& m_conf; @@ -112,7 +112,7 @@ class UHD : public Output::SDRDevice // Returns true if we want to verify loss of refclk bool refclk_loss_needs_check(void) const; - bool suppress_refclk_loss_check = false; + mutable bool suppress_refclk_loss_check = false; // Poll asynchronous metadata from UHD std::atomic m_running; -- cgit v1.2.3 From d5db57bf0290133450b7cd10525532415076bf0e Mon Sep 17 00:00:00 2001 From: "Matthias P. Braendli" Date: Wed, 17 Jan 2018 10:13:20 +0100 Subject: Increment num_frames_modulated --- TODO | 2 ++ src/output/SDRDevice.h | 2 +- src/output/Soapy.cpp | 1 + src/output/Soapy.h | 2 +- src/output/UHD.cpp | 2 ++ src/output/UHD.h | 2 +- 6 files changed, 8 insertions(+), 3 deletions(-) (limited to 'src/output/UHD.h') diff --git a/TODO b/TODO index 53932ea..8f49571 100644 --- a/TODO +++ b/TODO @@ -15,6 +15,8 @@ that the LimeSDR gets support for a HW timestamp that can be set from a PPS signal. Discussion ongoing here https://discourse.myriadrf.org/t/synchronize-two-limesdr/1714 +DPD will be possible too. + Move dpd port from uhd section to somewhere else. diff --git a/src/output/SDRDevice.h b/src/output/SDRDevice.h index 89a9856..bcd5a39 100644 --- a/src/output/SDRDevice.h +++ b/src/output/SDRDevice.h @@ -107,7 +107,7 @@ class SDRDevice { size_t num_underruns; size_t num_late_packets; size_t num_overruns; - size_t num_frames_modulated; //TODO increment + size_t num_frames_modulated; }; virtual void tune(double lo_offset, double frequency) = 0; diff --git a/src/output/Soapy.cpp b/src/output/Soapy.cpp index 82c926b..44f8f58 100644 --- a/src/output/Soapy.cpp +++ b/src/output/Soapy.cpp @@ -255,6 +255,7 @@ void Soapy::transmit_frame(const struct FrameData& frame) num_acc_samps += ret; } + num_frames_modulated++; } } // namespace Output diff --git a/src/output/Soapy.h b/src/output/Soapy.h index 7bf0239..67b280d 100644 --- a/src/output/Soapy.h +++ b/src/output/Soapy.h @@ -89,7 +89,7 @@ class Soapy : public Output::SDRDevice size_t underflows = 0; size_t overflows = 0; size_t late_packets = 0; - size_t num_frames_modulated = 0; //TODO increment + size_t num_frames_modulated = 0; }; } // namespace Output diff --git a/src/output/UHD.cpp b/src/output/UHD.cpp index dda008b..c55939f 100644 --- a/src/output/UHD.cpp +++ b/src/output/UHD.cpp @@ -303,6 +303,8 @@ void UHD::transmit_frame(const struct FrameData& frame) break; } } + + num_frames_modulated++; } diff --git a/src/output/UHD.h b/src/output/UHD.h index ddcb33b..b34455c 100644 --- a/src/output/UHD.h +++ b/src/output/UHD.h @@ -103,7 +103,7 @@ class UHD : public Output::SDRDevice size_t num_underflows = 0; size_t num_overflows = 0; size_t num_late_packets = 0; - size_t num_frames_modulated = 0; //TODO increment + size_t num_frames_modulated = 0; size_t num_underflows_previous = 0; size_t num_late_packets_previous = 0; -- cgit v1.2.3