diff options
author | Matthias P. Braendli <matthias.braendli@mpb.li> | 2023-04-26 18:08:54 +0200 |
---|---|---|
committer | Matthias P. Braendli <matthias.braendli@mpb.li> | 2023-04-26 18:08:54 +0200 |
commit | 830fb3ab0a8631055b2341b8dac50b937b6e99bb (patch) | |
tree | 5fcb354bdc464e9793ae5308fe34931016d66c10 | |
parent | b17001011204a47432a1deb970cd15466c16f0bf (diff) | |
download | dabmod-830fb3ab0a8631055b2341b8dac50b937b6e99bb.tar.gz dabmod-830fb3ab0a8631055b2341b8dac50b937b6e99bb.tar.bz2 dabmod-830fb3ab0a8631055b2341b8dac50b937b6e99bb.zip |
Dexter: Add three clock states and handling
-rw-r--r-- | src/ConfigParser.cpp | 14 | ||||
-rw-r--r-- | src/output/Dexter.cpp | 310 | ||||
-rw-r--r-- | src/output/Dexter.h | 18 |
3 files changed, 217 insertions, 125 deletions
diff --git a/src/ConfigParser.cpp b/src/ConfigParser.cpp index 3e223c3..cb4dc24 100644 --- a/src/ConfigParser.cpp +++ b/src/ConfigParser.cpp @@ -278,7 +278,8 @@ static void parse_configfile( mod_settings.sdr_device_config = sdr_device_config; mod_settings.useUHDOutput = true; } -#endif +#endif // defined(HAVE_OUTPUT_UHD) + #if defined(HAVE_SOAPYSDR) else if (output_selected == "soapysdr") { auto& outputsoapy_conf = mod_settings.sdr_device_config; @@ -309,7 +310,8 @@ static void parse_configfile( mod_settings.useSoapyOutput = true; } -#endif +#endif // defined(HAVE_SOAPYSDR) + #if defined(HAVE_DEXTER) else if (output_selected == "dexter") { auto& outputdexter_conf = mod_settings.sdr_device_config; @@ -318,6 +320,7 @@ static void parse_configfile( outputdexter_conf.frequency = pt.GetReal("dexteroutput.frequency", 0); std::string chan = pt.Get("dexteroutput.channel", ""); outputdexter_conf.dabMode = mod_settings.dabMode; + outputdexter_conf.maxGPSHoldoverTime = pt.GetInteger("dexteroutput.max_gps_holdover_time", 0); if (outputdexter_conf.frequency == 0 && chan == "") { std::cerr << " dexter output enabled, but neither frequency nor channel defined.\n"; @@ -333,7 +336,8 @@ static void parse_configfile( mod_settings.useDexterOutput = true; } -#endif +#endif // defined(HAVE_DEXTER) + #if defined(HAVE_LIMESDR) else if (output_selected == "limesdr") { auto& outputlime_conf = mod_settings.sdr_device_config; @@ -363,7 +367,7 @@ static void parse_configfile( mod_settings.useLimeOutput = true; } -#endif +#endif // defined(HAVE_LIMESDR) #if defined(HAVE_BLADERF) else if (output_selected == "bladerf") { @@ -392,7 +396,7 @@ static void parse_configfile( mod_settings.useBladeRFOutput = true; } -#endif +#endif // defined(HAVE_BLADERF) #if defined(HAVE_ZEROMQ) else if (output_selected == "zmq") { diff --git a/src/output/Dexter.cpp b/src/output/Dexter.cpp index 4cb9cd8..59baf7e 100644 --- a/src/output/Dexter.cpp +++ b/src/output/Dexter.cpp @@ -120,58 +120,6 @@ Dexter::Dexter(SDRDeviceConfig& config) : // skip: antenna - // get H/W time - /* Procedure: - * Wait 200ms after second change, fetch pps_clks attribute - * idem at the next second, and check that pps_clks incremented by DSP_CLOCK - * If ok, store the correspondence between current second change (measured in UTC clock time) - * and the counter value at pps rising edge. */ - - etiLog.level(info) << "Dexter: Waiting for second change..."; - - struct timespec time_at_startup; - fill_time(&time_at_startup); - time_at_startup.tv_nsec = 0; - - struct timespec time_now; - do { - fill_time(&time_now); - this_thread::sleep_for(chrono::milliseconds(1)); - } while (time_at_startup.tv_sec == time_now.tv_sec); - this_thread::sleep_for(chrono::milliseconds(200)); - - long long pps_clks = 0; - if ((r = iio_device_attr_read_longlong(m_dexter_dsp_tx, "pps_clks", &pps_clks)) != 0) { - etiLog.level(error) << "Failed to get dexter_dsp_tx.pps_clks: " << get_iio_error(r); - throw std::runtime_error("Dexter: Cannot read IIO attribute"); - } - - time_t tnow = time_now.tv_sec; - etiLog.level(info) << "Dexter: pps_clks " << pps_clks << " at UTC " << - put_time(std::gmtime(&tnow), "%Y-%m-%d %H:%M:%S"); - - time_at_startup.tv_sec = time_now.tv_sec; - do { - fill_time(&time_now); - this_thread::sleep_for(chrono::milliseconds(1)); - } while (time_at_startup.tv_sec == time_now.tv_sec); - this_thread::sleep_for(chrono::milliseconds(200)); - - long long pps_clks2 = 0; - if ((r = iio_device_attr_read_longlong(m_dexter_dsp_tx, "pps_clks", &pps_clks2)) != 0) { - etiLog.level(error) << "Failed to get dexter_dsp_tx.pps_clks: " << get_iio_error(r); - throw std::runtime_error("Dexter: Cannot read IIO attribute"); - } - tnow = time_now.tv_sec; - etiLog.level(info) << "Dexter: pps_clks increased by " << pps_clks2 - pps_clks << " at UTC " << - put_time(std::gmtime(&tnow), "%Y-%m-%d %H:%M:%S"); - - if ((uint64_t)pps_clks + DSP_CLOCK != (uint64_t)pps_clks2) { - throw std::runtime_error("Dexter: Wrong increase of pps_clks, expected " + to_string(DSP_CLOCK)); - } - m_utc_seconds_at_startup = time_now.tv_sec; - m_clock_count_at_startup = pps_clks2; - // The FIFO should not contain data, but setting gain=0 before setting start_clks to zero is an additional security if ((r = iio_device_attr_write_longlong(m_dexter_dsp_tx, "gain0", 0)) != 0) { throw std::runtime_error("Failed to set dexter_dsp_tx.gain0 = 0 : " + get_iio_error(r)); @@ -248,35 +196,118 @@ void Dexter::channel_down() etiLog.level(debug) << "DEXTER CHANNEL_DOWN"; } - -Dexter::~Dexter() +void Dexter::handle_hw_time() { - m_running = false; - if (m_underflow_read_thread.joinable()) { - m_underflow_read_thread.join(); - } - - if (m_ctx) { - if (m_dexter_dsp_tx) { - iio_device_attr_write_longlong(m_dexter_dsp_tx, "gain0", 0); - } - - if (m_buffer) { - iio_buffer_destroy(m_buffer); - m_buffer = nullptr; - } - - if (m_tx_channel) { - iio_channel_disable(m_tx_channel); - } - - iio_context_destroy(m_ctx); - m_ctx = nullptr; - } + /* + * On startup, wait until `gpsdo_locked==1` and `pps_loss_of_signal==0`, + * then do the clocks alignment and go to normal state. + * + * In normal state, if `pps_loss_of_signal==1`, go to holdover state. + * + * If we've been in holdover state for longer than the configured time, or + * if `pps_loss_of_signal==0` stop the mod and restart. + */ + int r; - if (m_underflow_ctx) { - iio_context_destroy(m_underflow_ctx); - m_underflow_ctx = nullptr; + switch (m_clock_state) { + case DexterClockState::Startup: + { + long long gpsdo_locked = 0; + if ((r = iio_device_attr_read_longlong(m_dexter_dsp_tx, "gpsdo_locked", &gpsdo_locked)) != 0) { + etiLog.level(error) << "Failed to get dexter_dsp_tx.gpsdo_locked: " << get_iio_error(r); + throw std::runtime_error("Dexter: Cannot read IIO attribute"); + } + + long long pps_loss_of_signal = 0; + if ((r = iio_device_attr_read_longlong(m_dexter_dsp_tx, "pps_loss_of_signal", &pps_loss_of_signal)) != 0) { + etiLog.level(error) << "Failed to get dexter_dsp_tx.pps_loss_of_signal: " << get_iio_error(r); + throw std::runtime_error("Dexter: Cannot read IIO attribute"); + } + + if (gpsdo_locked == 1 and pps_loss_of_signal == 0) { + /* Procedure: + * Wait 200ms after second change, fetch pps_clks attribute + * idem at the next second, and check that pps_clks incremented by DSP_CLOCK + * If ok, store the correspondence between current second change (measured in UTC clock time) + * and the counter value at pps rising edge. */ + + etiLog.level(info) << "Dexter: Waiting for second change..."; + + struct timespec time_at_startup; + fill_time(&time_at_startup); + time_at_startup.tv_nsec = 0; + + struct timespec time_now; + do { + fill_time(&time_now); + this_thread::sleep_for(chrono::milliseconds(1)); + } while (time_at_startup.tv_sec == time_now.tv_sec); + this_thread::sleep_for(chrono::milliseconds(200)); + + long long pps_clks = 0; + if ((r = iio_device_attr_read_longlong(m_dexter_dsp_tx, "pps_clks", &pps_clks)) != 0) { + etiLog.level(error) << "Failed to get dexter_dsp_tx.pps_clks: " << get_iio_error(r); + throw std::runtime_error("Dexter: Cannot read IIO attribute"); + } + + time_t tnow = time_now.tv_sec; + etiLog.level(info) << "Dexter: pps_clks " << pps_clks << " at UTC " << + put_time(std::gmtime(&tnow), "%Y-%m-%d %H:%M:%S"); + + time_at_startup.tv_sec = time_now.tv_sec; + do { + fill_time(&time_now); + this_thread::sleep_for(chrono::milliseconds(1)); + } while (time_at_startup.tv_sec == time_now.tv_sec); + this_thread::sleep_for(chrono::milliseconds(200)); + + long long pps_clks2 = 0; + if ((r = iio_device_attr_read_longlong(m_dexter_dsp_tx, "pps_clks", &pps_clks2)) != 0) { + etiLog.level(error) << "Failed to get dexter_dsp_tx.pps_clks: " << get_iio_error(r); + throw std::runtime_error("Dexter: Cannot read IIO attribute"); + } + tnow = time_now.tv_sec; + etiLog.level(info) << "Dexter: pps_clks increased by " << pps_clks2 - pps_clks << " at UTC " << + put_time(std::gmtime(&tnow), "%Y-%m-%d %H:%M:%S"); + + if ((uint64_t)pps_clks + DSP_CLOCK != (uint64_t)pps_clks2) { + throw std::runtime_error("Dexter: Wrong increase of pps_clks, expected " + to_string(DSP_CLOCK)); + } + + m_utc_seconds_at_startup = time_now.tv_sec; + m_clock_count_at_startup = pps_clks2; + m_holdover_since = chrono::steady_clock::time_point::min(); + m_clock_state = DexterClockState::Normal; + } + } + break; + case DexterClockState::Normal: + { + long long pps_loss_of_signal = 0; + if ((r = iio_device_attr_read_longlong(m_dexter_dsp_tx, "pps_loss_of_signal", &pps_loss_of_signal)) != 0) { + etiLog.level(error) << "Failed to get dexter_dsp_tx.pps_loss_of_signal: " << get_iio_error(r); + throw std::runtime_error("Dexter: Cannot read IIO attribute"); + } + + if (pps_loss_of_signal == 1) { + m_holdover_since = chrono::steady_clock::now(); + m_clock_state = DexterClockState::Holdover; + } + } + break; + case DexterClockState::Holdover: + { + using namespace chrono; + const duration<double> d = steady_clock::now() - m_holdover_since; + const auto max_holdover_duration = seconds(m_conf.maxGPSHoldoverTime); + if (d > max_holdover_duration) { + m_clock_state = DexterClockState::Startup; + m_utc_seconds_at_startup = 0; + m_clock_count_at_startup = 0; + m_holdover_since = chrono::steady_clock::time_point::min(); + } + } + break; } } @@ -526,7 +557,14 @@ double Dexter::get_real_secs(void) const throw std::runtime_error("Dexter: Cannot read IIO attribute"); } - return (double)m_utc_seconds_at_startup + (double)(clks - m_clock_count_at_startup) / (double)DSP_CLOCK; + switch (m_clock_state) { + case DexterClockState::Startup: + return 0; + case DexterClockState::Normal: + case DexterClockState::Holdover: + return (double)m_utc_seconds_at_startup + (double)(clks - m_clock_count_at_startup) / (double)DSP_CLOCK; + } + throw std::logic_error("Unhandled switch"); } void Dexter::set_rxgain(double rxgain) @@ -585,46 +623,53 @@ void Dexter::transmit_frame(struct FrameData&& frame) const bool require_timestamped_tx = (m_conf.enableSync and frame.ts.timestamp_valid); + handle_hw_time(); + if (not m_channel_is_up) { if (require_timestamped_tx) { - constexpr uint64_t TIMESTAMP_PPS_PER_DSP_CLOCKS = DSP_CLOCK / 16384000; - // TIMESTAMP_PPS_PER_DSP_CLOCKS=10 because timestamp_pps is represented in 16.384 MHz clocks - uint64_t frame_start_clocks = - // at second level - ((int64_t)frame.ts.timestamp_sec - (int64_t)m_utc_seconds_at_startup) * DSP_CLOCK + m_clock_count_at_startup + - // at subsecond level - (uint64_t)frame.ts.timestamp_pps * TIMESTAMP_PPS_PER_DSP_CLOCKS; - - const double margin_s = frame.ts.offset_to_system_time(); - - long long clks = 0; - int r = 0; - if ((r = iio_device_attr_read_longlong(m_dexter_dsp_tx, "clks", &clks)) != 0) { - etiLog.level(error) << "Failed to get dexter_dsp_tx.clks: " << get_iio_error(r); - throw std::runtime_error("Dexter: Cannot read IIO attribute"); + if (m_clock_state == DexterClockState::Startup) { + return; // not ready } - - const double margin_device_s = (double)(frame_start_clocks - clks) / DSP_CLOCK; - - etiLog.level(debug) << "DEXTER FCT " << frame.ts.fct << " TS CLK " << - ((int64_t)frame.ts.timestamp_sec - (int64_t)m_utc_seconds_at_startup) * DSP_CLOCK << " + " << - m_clock_count_at_startup << " + " << - (uint64_t)frame.ts.timestamp_pps * TIMESTAMP_PPS_PER_DSP_CLOCKS << " = " << - frame_start_clocks << " DELTA " << margin_s << " " << margin_device_s; - - // Ensure we hand the frame over to HW with a bit of margin - if (margin_s < 0.2) { - etiLog.level(warn) << "Skip frame short margin " << margin_s; - num_late++; - return; - } - - if ((r = iio_device_attr_write_longlong(m_dexter_dsp_tx, "stream0_start_clks", frame_start_clocks)) != 0) { - etiLog.level(warn) << "Skip frame, failed to set dexter_dsp_tx.stream0_start_clks = " << frame_start_clocks << " : " << get_iio_error(r); - num_late++; - return; + else { + constexpr uint64_t TIMESTAMP_PPS_PER_DSP_CLOCKS = DSP_CLOCK / 16384000; + // TIMESTAMP_PPS_PER_DSP_CLOCKS=10 because timestamp_pps is represented in 16.384 MHz clocks + uint64_t frame_start_clocks = + // at second level + ((int64_t)frame.ts.timestamp_sec - (int64_t)m_utc_seconds_at_startup) * DSP_CLOCK + m_clock_count_at_startup + + // at subsecond level + (uint64_t)frame.ts.timestamp_pps * TIMESTAMP_PPS_PER_DSP_CLOCKS; + + const double margin_s = frame.ts.offset_to_system_time(); + + long long clks = 0; + int r = 0; + if ((r = iio_device_attr_read_longlong(m_dexter_dsp_tx, "clks", &clks)) != 0) { + etiLog.level(error) << "Failed to get dexter_dsp_tx.clks: " << get_iio_error(r); + throw std::runtime_error("Dexter: Cannot read IIO attribute"); + } + + const double margin_device_s = (double)(frame_start_clocks - clks) / DSP_CLOCK; + + etiLog.level(debug) << "DEXTER FCT " << frame.ts.fct << " TS CLK " << + ((int64_t)frame.ts.timestamp_sec - (int64_t)m_utc_seconds_at_startup) * DSP_CLOCK << " + " << + m_clock_count_at_startup << " + " << + (uint64_t)frame.ts.timestamp_pps * TIMESTAMP_PPS_PER_DSP_CLOCKS << " = " << + frame_start_clocks << " DELTA " << margin_s << " " << margin_device_s; + + // Ensure we hand the frame over to HW with a bit of margin + if (margin_s < 0.2) { + etiLog.level(warn) << "Skip frame short margin " << margin_s; + num_late++; + return; + } + + if ((r = iio_device_attr_write_longlong(m_dexter_dsp_tx, "stream0_start_clks", frame_start_clocks)) != 0) { + etiLog.level(warn) << "Skip frame, failed to set dexter_dsp_tx.stream0_start_clks = " << frame_start_clocks << " : " << get_iio_error(r); + num_late++; + return; + } + m_require_timestamp_refresh = false; } - m_require_timestamp_refresh = false; } channel_up(); @@ -704,8 +749,37 @@ void Dexter::underflow_read_process() m_running = false; } -} // namespace Output +Dexter::~Dexter() +{ + m_running = false; + if (m_underflow_read_thread.joinable()) { + m_underflow_read_thread.join(); + } -#endif // HAVE_DEXTER + if (m_ctx) { + if (m_dexter_dsp_tx) { + iio_device_attr_write_longlong(m_dexter_dsp_tx, "gain0", 0); + } + if (m_buffer) { + iio_buffer_destroy(m_buffer); + m_buffer = nullptr; + } + if (m_tx_channel) { + iio_channel_disable(m_tx_channel); + } + + iio_context_destroy(m_ctx); + m_ctx = nullptr; + } + + if (m_underflow_ctx) { + iio_context_destroy(m_underflow_ctx); + m_underflow_ctx = nullptr; + } +} + +} // namespace Output + +#endif // HAVE_DEXTER diff --git a/src/output/Dexter.h b/src/output/Dexter.h index 3d47f87..f70bb14 100644 --- a/src/output/Dexter.h +++ b/src/output/Dexter.h @@ -42,6 +42,7 @@ DESCRIPTION: #include <ctime> #include <mutex> #include <thread> +#include <variant> #include "output/SDR.h" #include "ModPlugin.h" @@ -50,6 +51,12 @@ DESCRIPTION: namespace Output { +enum class DexterClockState { + Startup, + Normal, + Holdover +}; + class Dexter : public Output::SDRDevice { public: @@ -85,6 +92,7 @@ class Dexter : public Output::SDRDevice private: void channel_up(); void channel_down(); + void handle_hw_time(); bool m_channel_is_up = false; @@ -112,9 +120,15 @@ class Dexter : public Output::SDRDevice size_t num_buffers_pushed = 0; - uint64_t m_utc_seconds_at_startup; + DexterClockState m_clock_state = DexterClockState::Startup; + + // Only valid when m_clock_state is not Startup + uint64_t m_utc_seconds_at_startup = 0; uint64_t m_clock_count_at_startup = 0; - uint64_t m_clock_count_frame = 0; + + // Only valid when m_clock_state Holdover + std::chrono::steady_clock::time_point m_holdover_since = + std::chrono::steady_clock::time_point::min(); }; } // namespace Output |