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 --- src/output/USRPTime.cpp | 280 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 280 insertions(+) create mode 100644 src/output/USRPTime.cpp (limited to 'src/output/USRPTime.cpp') 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 -- cgit v1.2.3 From 7ca7edbc5a80b3869fb0772946c00c76c264da8d Mon Sep 17 00:00:00 2001 From: "Matthias P. Braendli" Date: Wed, 17 Jan 2018 11:30:17 +0100 Subject: Don't use CLOCK_GETTIME in SDR --- TODO | 4 ++-- src/Utils.cpp | 16 ++++++++-------- src/Utils.h | 22 +++------------------- src/output/SDR.cpp | 43 ++++++++++++------------------------------- src/output/SDR.h | 8 ++++---- src/output/USRPTime.cpp | 2 +- 6 files changed, 30 insertions(+), 65 deletions(-) (limited to 'src/output/USRPTime.cpp') diff --git a/TODO b/TODO index 8f49571..ac0a877 100644 --- a/TODO +++ b/TODO @@ -18,7 +18,7 @@ https://discourse.myriadrf.org/t/synchronize-two-limesdr/1714 DPD will be possible too. -Move dpd port from uhd section to somewhere else. +Test sleep_through_frame implementation. Clean up and separate GPS and refclk checks. * *done* handle UHD GPSDO and time @@ -31,7 +31,7 @@ Add antenna selection to config. Test RC entries. -Portability: replace clock_gettime with std::chrono +*done* Portability: replace clock_gettime with std::chrono *done* Make an abstraction for the DPD feedback server, use it for Soapy and UHD. diff --git a/src/Utils.cpp b/src/Utils.cpp index f113be3..b4816d3 100644 --- a/src/Utils.cpp +++ b/src/Utils.cpp @@ -3,7 +3,7 @@ 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 @@ -215,16 +215,16 @@ double parseChannel(const std::string& chan) return freq; } -int transmission_frame_duration_ms(unsigned int dabMode) +std::chrono::milliseconds transmission_frame_duration(unsigned int dabmode) { - switch (dabMode) { - case 1: return 96; - case 2: return 24; - case 3: return 24; - case 4: return 48; + using namespace std::chrono; + switch (dabmode) { + case 1: return milliseconds(96); + case 2: return milliseconds(24); + case 3: return milliseconds(24); + case 4: return milliseconds(48); default: throw std::runtime_error("invalid DAB mode"); } } - diff --git a/src/Utils.h b/src/Utils.h index 5d5831b..9e88488 100644 --- a/src/Utils.h +++ b/src/Utils.h @@ -3,7 +3,7 @@ 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 @@ -36,6 +36,7 @@ #include #include #include +#include void printUsage(const char* progName); @@ -43,22 +44,6 @@ void printVersion(void); void printStartupInfo(void); -inline long timespecdiff_us(struct timespec& oldTime, struct timespec& time) -{ - long tv_sec; - long tv_nsec; - if (time.tv_nsec < oldTime.tv_nsec) { - tv_sec = time.tv_sec - 1 - oldTime.tv_sec; - tv_nsec = 1000000000L + time.tv_nsec - oldTime.tv_nsec; - } - else { - tv_sec = time.tv_sec - oldTime.tv_sec; - tv_nsec = time.tv_nsec - oldTime.tv_nsec; - } - - return tv_sec * 1000 + tv_nsec / 1000; -} - // Set SCHED_RR with priority prio (0=lowest) int set_realtime_prio(int prio); @@ -70,5 +55,4 @@ 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); - +std::chrono::milliseconds transmission_frame_duration(unsigned int dabmode); diff --git a/src/output/SDR.cpp b/src/output/SDR.cpp index e478de5..8906ef6 100644 --- a/src/output/SDR.cpp +++ b/src/output/SDR.cpp @@ -59,15 +59,11 @@ static constexpr double TIMESTAMP_MARGIN_FUTURE = 0.5; SDR::SDR(SDRDeviceConfig& config, std::shared_ptr device) : ModOutput(), ModMetadata(), RemoteControllable("sdr"), m_config(config), - m_running(false), m_device(device) { // 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( @@ -77,16 +73,11 @@ SDR::SDR(SDRDeviceConfig& config, std::shared_ptr device) : } SDR::~SDR() -{ - stop(); -} - -void SDR::stop() { m_running.store(false); FrameData end_marker; - end_marker.buf.resize(0); + end_marker.buf.clear(); m_queue.push(end_marker); if (m_device_thread.joinable()) { @@ -218,33 +209,23 @@ const char* SDR::name() 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()); - } + using namespace std::chrono; - 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()); - } + const auto now = steady_clock::now(); + + if (not t_last_frame_initialised) { + t_last_frame = now; + t_last_frame_initialised = true; } - long delta_us = timespecdiff_us(time_last_frame, now); - long wait_time_us = transmission_frame_duration_ms(m_config.dabMode); + const auto delta = now - t_last_frame; + const auto wait_time = transmission_frame_duration(m_config.dabMode); - if (wait_time_us - delta_us > 0) { - usleep(wait_time_us - delta_us); + if (wait_time > delta) { + this_thread::sleep_for(wait_time - delta); } - 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++; - } + t_last_frame += wait_time; } void SDR::handle_frame(struct FrameData& frame) diff --git a/src/output/SDR.h b/src/output/SDR.h index 4c7de5a..a55f7c0 100644 --- a/src/output/SDR.h +++ b/src/output/SDR.h @@ -34,6 +34,7 @@ DESCRIPTION: # include #endif +#include #include "ModPlugin.h" #include "EtiReader.h" #include "output/SDRDevice.h" @@ -66,14 +67,13 @@ class SDR : public ModOutput, public ModMetadata, public RemoteControllable { const std::string& parameter) const override; private: - void stop(void); void process_thread_entry(void); void handle_frame(struct FrameData &frame); void sleep_through_frame(void); SDRDeviceConfig& m_config; - std::atomic m_running; + std::atomic m_running = ATOMIC_VAR_INIT(false); std::thread m_device_thread; std::vector m_frame; ThreadsafeQueue m_queue; @@ -87,8 +87,8 @@ class SDR : public ModOutput, public ModMetadata, public RemoteControllable { uint32_t last_tx_second = 0; uint32_t last_tx_pps = 0; - struct timespec time_last_frame; - + bool t_last_frame_initialised = false; + std::chrono::steady_clock::time_point t_last_frame; }; } diff --git a/src/output/USRPTime.cpp b/src/output/USRPTime.cpp index dcedeab..9ed398e 100644 --- a/src/output/USRPTime.cpp +++ b/src/output/USRPTime.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 -- cgit v1.2.3 From 5bc2069c9636566b0f3944fabc936fd9f2133355 Mon Sep 17 00:00:00 2001 From: "Matthias P. Braendli" Date: Wed, 17 Jan 2018 12:32:29 +0100 Subject: Replace clock_gettime entirely in USRPTime --- src/output/USRPTime.cpp | 80 +++++++++++++++++++++++-------------------------- 1 file changed, 37 insertions(+), 43 deletions(-) (limited to 'src/output/USRPTime.cpp') diff --git a/src/output/USRPTime.cpp b/src/output/USRPTime.cpp index 9ed398e..59fa6b5 100644 --- a/src/output/USRPTime.cpp +++ b/src/output/USRPTime.cpp @@ -217,64 +217,58 @@ bool USRPTime::gpsdo_is_ettus() const return (m_conf.refclk_src == "gpsdo-ettus"); } +/* Return a uhd:time_spec representing current system time + * with 1ms granularity. */ +static uhd::time_spec_t uhd_timespec_now(void) +{ + using namespace std::chrono; + auto n = system_clock::now(); + const long long ticks = duration_cast(n.time_since_epoch()).count(); + return uhd::time_spec_t::from_ticks(ticks, 1000); +} + 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(); - } + const auto t = uhd_timespec_now(); + 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() { + using namespace std::chrono; + /* 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(); - } + auto now = uhd_timespec_now(); + const time_t secs_since_epoch = now.get_full_secs(); - usleep(1e6); - etiLog.log(info, "OutputUHD: USRP time %f\n", - m_usrp->get_time_now().get_real_secs()); + while (secs_since_epoch + 1 > now.get_full_secs()) { + this_thread::sleep_for(milliseconds(1)); + now = uhd_timespec_now(); + } + /* We are now shortly after the second change. + * Wait 200ms to ensure the PPS comes later. */ + this_thread::sleep_for(milliseconds(200)); + + const auto time_set = uhd::time_spec_t(secs_since_epoch + 2); + etiLog.level(info) << "OutputUHD: Setting USRP time next pps to " << + std::fixed << time_set.get_real_secs(); + m_usrp->set_time_next_pps(time_set); + + // The UHD doc says we need to give the USRP one second to update + // all the internal registers. + this_thread::sleep_for(seconds(1)); + etiLog.level(info) << "OutputUHD: USRP time " << + std::fixed << m_usrp->get_time_now().get_real_secs(); } - } // namespace Output #endif // HAVE_OUTPUT_UHD -- cgit v1.2.3 From 0d1c88f1168506b082984445c9d1be2d1f4ac4f3 Mon Sep 17 00:00:00 2001 From: "Matthias P. Braendli" Date: Thu, 18 Jan 2018 08:27:47 +0100 Subject: Initialise USRP time also with pps --- src/output/USRPTime.cpp | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) (limited to 'src/output/USRPTime.cpp') diff --git a/src/output/USRPTime.cpp b/src/output/USRPTime.cpp index 59fa6b5..935d56b 100644 --- a/src/output/USRPTime.cpp +++ b/src/output/USRPTime.cpp @@ -88,9 +88,22 @@ USRPTime::USRPTime( m_conf(conf), time_last_check(timepoint_t::clock::now()) { - if (m_conf.enableSync and (m_conf.pps_src == "none")) { + if (m_conf.pps_src == "none") { + if (m_conf.enableSync) { + etiLog.level(warn) << + "OutputUHD: WARNING:" + " you are using synchronous transmission without PPS input!"; + } + set_usrp_time_from_localtime(); } + else if (m_conf.pps_src == "pps" or m_conf.pps_src == "gpsdo") { + set_usrp_time_from_pps(); + } + else { + throw std::runtime_error("USRPTime not implemented yet: " + + m_conf.pps_src); + } } bool USRPTime::verify_time() @@ -229,10 +242,6 @@ static uhd::time_spec_t uhd_timespec_now(void) void USRPTime::set_usrp_time_from_localtime() { - etiLog.level(warn) << - "OutputUHD: WARNING:" - " you are using synchronous transmission without PPS input!"; - const auto t = uhd_timespec_now(); m_usrp->set_time_now(t); -- cgit v1.2.3