aboutsummaryrefslogtreecommitdiffstats
path: root/src/output
diff options
context:
space:
mode:
authorMatthias P. Braendli <matthias.braendli@mpb.li>2023-11-21 22:12:14 +0100
committerMatthias P. Braendli <matthias.braendli@mpb.li>2023-11-21 22:12:14 +0100
commit5fe36a627405b8fc65bdb212a6d505b9a6c8e489 (patch)
tree8646d654e0467be8603c5ad37fb6cd89656dfd26 /src/output
parent477ac4639a7c7f74f07a6164096fc7de102528ff (diff)
parentf84065c3cc6fff0edb771f85190f7228f4d740b6 (diff)
downloaddabmod-5fe36a627405b8fc65bdb212a6d505b9a6c8e489.tar.gz
dabmod-5fe36a627405b8fc65bdb212a6d505b9a6c8e489.tar.bz2
dabmod-5fe36a627405b8fc65bdb212a6d505b9a6c8e489.zip
Merge branch 'dexter' into next
Diffstat (limited to 'src/output')
-rwxr-xr-xsrc/output/BladeRF.cpp28
-rwxr-xr-xsrc/output/BladeRF.h17
-rw-r--r--src/output/Dexter.cpp691
-rw-r--r--src/output/Dexter.h138
-rw-r--r--src/output/Feedback.cpp2
-rw-r--r--src/output/Feedback.h2
-rw-r--r--src/output/Lime.cpp37
-rw-r--r--src/output/Lime.h14
-rw-r--r--src/output/SDR.cpp281
-rw-r--r--src/output/SDR.h13
-rw-r--r--src/output/SDRDevice.h30
-rw-r--r--src/output/Soapy.cpp24
-rw-r--r--src/output/Soapy.h12
-rw-r--r--src/output/UHD.cpp33
-rw-r--r--src/output/UHD.h18
-rw-r--r--src/output/USRPTime.cpp9
16 files changed, 1106 insertions, 243 deletions
diff --git a/src/output/BladeRF.cpp b/src/output/BladeRF.cpp
index a6ad0cc..c16b64d 100755
--- a/src/output/BladeRF.cpp
+++ b/src/output/BladeRF.cpp
@@ -239,13 +239,10 @@ double BladeRF::get_bandwidth(void) const
return (double)bw;
}
-SDRDevice::RunStatistics BladeRF::get_run_statistics(void) const
+SDRDevice::run_statistics_t BladeRF::get_run_statistics(void) const
{
- RunStatistics rs;
- rs.num_underruns = underflows;
- rs.num_overruns = overflows;
- rs.num_late_packets = late_packets;
- rs.num_frames_modulated = num_frames_modulated;
+ run_statistics_t rs;
+ rs["frames"] = num_frames_modulated;
return rs;
}
@@ -269,14 +266,14 @@ double BladeRF::get_rxgain(void) const
size_t BladeRF::receive_frame(
complexf *buf,
size_t num_samples,
- struct frame_timestamp &ts,
+ frame_timestamp &ts,
double timeout_secs)
{
// TODO
return 0;
}
-bool BladeRF::is_clk_source_ok() const
+bool BladeRF::is_clk_source_ok()
{
// TODO
return true;
@@ -287,24 +284,23 @@ const char *BladeRF::device_name(void) const
return "BladeRF";
}
-double BladeRF::get_temperature(void) const
+std::optional<double> BladeRF::get_temperature(void) const
{
if (not m_device)
throw runtime_error("BladeRF device not set up");
float temp = 0.0;
-
int status = bladerf_get_rfic_temperature(m_device, &temp);
- if (status < 0)
- {
+ if (status >= 0) {
+ return (double)temp;
+ }
+ else {
etiLog.level(error) << "Error getting BladeRF temperature: %s " << bladerf_strerror(status);
+ return std::nullopt;
}
-
- return (double)temp;
}
-
-void BladeRF::transmit_frame(const struct FrameData &frame) // SC16 frames
+void BladeRF::transmit_frame(struct FrameData&& frame) // SC16 frames
{
const size_t num_samples = frame.buf.size() / (2*sizeof(int16_t));
diff --git a/src/output/BladeRF.h b/src/output/BladeRF.h
index bc6db38..fa3419e 100755
--- a/src/output/BladeRF.h
+++ b/src/output/BladeRF.h
@@ -74,8 +74,8 @@ class BladeRF : public Output::SDRDevice
virtual double get_txgain(void) const override;
virtual void set_bandwidth(double bandwidth) override;
virtual double get_bandwidth(void) const override;
- virtual void transmit_frame(const struct FrameData& frame) override;
- virtual RunStatistics get_run_statistics(void) const override;
+ virtual void transmit_frame(struct FrameData&& frame) override;
+ virtual run_statistics_t get_run_statistics(void) const override;
virtual double get_real_secs(void) const override;
virtual void set_rxgain(double rxgain) override;
@@ -83,14 +83,14 @@ class BladeRF : public Output::SDRDevice
virtual size_t receive_frame(
complexf *buf,
size_t num_samples,
- struct frame_timestamp& ts,
+ frame_timestamp& ts,
double timeout_secs) override;
// Return true if GPS and reference clock inputs are ok
- virtual bool is_clk_source_ok(void) const override;
+ virtual bool is_clk_source_ok(void) override;
virtual const char* device_name(void) const override;
- virtual double get_temperature(void) const override;
+ virtual std::optional<double> get_temperature(void) const override;
private:
@@ -99,14 +99,9 @@ class BladeRF : public Output::SDRDevice
bladerf_channel m_channel = BLADERF_CHANNEL_TX(0); // channel TX0
//struct bladerf_stream* m_stream; /* used for asynchronous api */
- size_t underflows = 0;
- size_t overflows = 0;
- size_t late_packets = 0;
size_t num_frames_modulated = 0;
- //size_t num_underflows_previous = 0;
- //size_t num_late_packets_previous = 0;
};
} // namespace Output
-#endif // HAVE_BLADERF \ No newline at end of file
+#endif // HAVE_BLADERF
diff --git a/src/output/Dexter.cpp b/src/output/Dexter.cpp
new file mode 100644
index 0000000..26472e8
--- /dev/null
+++ b/src/output/Dexter.cpp
@@ -0,0 +1,691 @@
+/*
+ Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Her Majesty the
+ Queen in Right of Canada (Communications Research Center Canada)
+
+ Copyright (C) 2023
+ Matthias P. Braendli, matthias.braendli@mpb.li
+
+ http://opendigitalradio.org
+
+DESCRIPTION:
+ It is an output driver using libiio targeting the PrecisionWave DEXTER board.
+*/
+
+/*
+ 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include "output/Dexter.h"
+
+#ifdef HAVE_DEXTER
+
+#include <chrono>
+#include <limits>
+#include <cstdio>
+#include <iomanip>
+
+#include "Log.h"
+#include "Utils.h"
+
+using namespace std;
+
+namespace Output {
+
+static constexpr uint64_t DSP_CLOCK = 2048000uLL * 80;
+
+static constexpr uint64_t IIO_TIMEOUT_MS = 1000;
+
+static constexpr size_t TRANSMISSION_FRAME_LEN_SAMPS = (2656 + 76 * 2552) * /* I+Q */ 2;
+static constexpr size_t IIO_BUFFERS = 2;
+static constexpr size_t IIO_BUFFER_LEN_SAMPS = TRANSMISSION_FRAME_LEN_SAMPS / IIO_BUFFERS;
+
+static string get_iio_error(int err)
+{
+ char dst[256];
+ iio_strerror(-err, dst, sizeof(dst));
+ return string(dst);
+}
+
+static void fill_time(struct timespec *t)
+{
+ if (clock_gettime(CLOCK_REALTIME, t) != 0) {
+ throw std::runtime_error(string("Failed to retrieve CLOCK_REALTIME") + strerror(errno));
+ }
+}
+
+Dexter::Dexter(SDRDeviceConfig& config) :
+ SDRDevice(),
+ m_conf(config)
+{
+ etiLog.level(info) << "Dexter:Creating the device";
+
+ m_ctx = iio_create_local_context();
+ if (not m_ctx) {
+ throw std::runtime_error("Dexter: Unable to create iio context");
+ }
+
+ int r;
+ if ((r = iio_context_set_timeout(m_ctx, IIO_TIMEOUT_MS)) != 0) {
+ etiLog.level(error) << "Failed to set IIO timeout " << get_iio_error(r);
+ }
+
+ m_dexter_dsp_tx = iio_context_find_device(m_ctx, "dexter_dsp_tx");
+ if (not m_dexter_dsp_tx) {
+ throw std::runtime_error("Dexter: Unable to find dexter_dsp_tx iio device");
+ }
+
+ m_ad9957 = iio_context_find_device(m_ctx, "ad9957");
+ if (not m_ad9957) {
+ throw std::runtime_error("Dexter: Unable to find ad9957 iio device");
+ }
+
+ m_ad9957_tx0 = iio_context_find_device(m_ctx, "ad9957_tx0");
+ if (not m_ad9957_tx0) {
+ throw std::runtime_error("Dexter: Unable to find ad9957_tx0 iio device");
+ }
+
+ // TODO make DC offset configurable and add to RC
+ if ((r = iio_device_attr_write_longlong(m_dexter_dsp_tx, "dc0", 0)) != 0) {
+ throw std::runtime_error("Failed to set dexter_dsp_tx.dc0 = false: " + get_iio_error(r));
+ }
+
+ if ((r = iio_device_attr_write_longlong(m_dexter_dsp_tx, "dc1", 0)) != 0) {
+ throw std::runtime_error("Failed to set dexter_dsp_tx.dc1 = false: " + get_iio_error(r));
+ }
+
+ if (m_conf.sampleRate != 2048000) {
+ throw std::runtime_error("Dexter: Only 2048000 samplerate supported");
+ }
+
+ tune(m_conf.lo_offset, m_conf.frequency);
+ // TODO m_conf.frequency = m_dexter_dsp_tx->getFrequency(SOAPY_SDR_TX, 0);
+ etiLog.level(info) << "Dexter:Actual frequency: " <<
+ std::fixed << std::setprecision(3) <<
+ m_conf.frequency / 1000.0 << " kHz.";
+
+ // skip: Set bandwidth
+
+ // skip: antenna
+
+ // 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));
+ }
+
+ if ((r = iio_device_attr_write_longlong(m_dexter_dsp_tx, "stream0_flush_fifo_trigger", 1)) != 0) {
+ throw std::runtime_error("Failed to set dexter_dsp_tx.stream0_flush_fifo_trigger = 1 : " + get_iio_error(r));
+ }
+
+ if ((r = iio_device_attr_write_longlong(m_dexter_dsp_tx, "stream0_start_clks", 0)) != 0) {
+ throw std::runtime_error("Failed to set dexter_dsp_tx.stream0_start_clks = 0 : " + get_iio_error(r));
+ }
+
+ constexpr int CHANNEL_INDEX = 0;
+ m_tx_channel = iio_device_get_channel(m_ad9957_tx0, CHANNEL_INDEX);
+ if (m_tx_channel == nullptr) {
+ throw std::runtime_error("Dexter: Cannot create IIO channel.");
+ }
+
+ iio_channel_enable(m_tx_channel);
+
+ m_buffer = iio_device_create_buffer(m_ad9957_tx0, IIO_BUFFER_LEN_SAMPS, 0);
+ if (not m_buffer) {
+ throw std::runtime_error("Dexter: Cannot create IIO buffer.");
+ }
+
+ // Flush the FPGA FIFO
+ {
+ constexpr size_t buflen_samps = TRANSMISSION_FRAME_LEN_SAMPS / IIO_BUFFERS;
+ constexpr size_t buflen = buflen_samps * sizeof(int16_t);
+
+ memset(iio_buffer_start(m_buffer), 0, buflen);
+ ssize_t pushed = iio_buffer_push(m_buffer);
+ if (pushed < 0) {
+ etiLog.level(error) << "Dexter: init push buffer " << get_iio_error(pushed);
+ }
+ this_thread::sleep_for(chrono::milliseconds(200));
+ }
+
+ if ((r = iio_device_attr_write_longlong(m_dexter_dsp_tx, "gain0", m_conf.txgain)) != 0) {
+ etiLog.level(error) << "Failed to set dexter_dsp_tx.gain0 = " << m_conf.txgain <<
+ " : " << get_iio_error(r);
+ }
+
+ m_running = true;
+ m_underflow_read_thread = std::thread(&Dexter::underflow_read_process, this);
+}
+
+void Dexter::channel_up()
+{
+ int r;
+ if ((r = iio_device_attr_write_longlong(m_dexter_dsp_tx, "gain0", m_conf.txgain)) != 0) {
+ etiLog.level(error) << "Failed to set dexter_dsp_tx.gain0 = " << m_conf.txgain <<
+ " : " << get_iio_error(r);
+ }
+
+ m_channel_is_up = true;
+ etiLog.level(debug) << "DEXTER CHANNEL_UP";
+}
+
+void Dexter::channel_down()
+{
+ int r;
+ if ((r = iio_device_attr_write_longlong(m_dexter_dsp_tx, "gain0", 0)) != 0) {
+ etiLog.level(error) << "Failed to set dexter_dsp_tx.gain0 = 0: " << get_iio_error(r);
+ }
+
+ // Setting stream0_start_clocks to 0 will flush out the FIFO, but we need to wait a bit before
+ // we "up" the channel again
+ if ((r = iio_device_attr_write_longlong(m_dexter_dsp_tx, "stream0_start_clks", 0)) != 0) {
+ etiLog.level(warn) << "Failed to set dexter_dsp_tx.stream0_start_clks = 0 : " << get_iio_error(r);
+ }
+
+ long long underflows_old = 0;
+
+ if ((r = iio_device_attr_read_longlong(m_dexter_dsp_tx, "buffer_underflows0", &underflows_old)) != 0) {
+ etiLog.level(warn) << "Failed to read dexter_dsp_tx.buffer_underflows0 : " << get_iio_error(r);
+ }
+
+ long long underflows = underflows_old;
+
+ // Limiting to 10*96ms is just a safety to avoid running into an infinite loop
+ for (size_t i = 0; underflows == underflows_old && i < 10; i++) {
+ if ((r = iio_device_attr_read_longlong(m_dexter_dsp_tx, "buffer_underflows0", &underflows)) != 0) {
+ etiLog.level(warn) << "Failed to read dexter_dsp_tx.buffer_underflows0 : " << get_iio_error(r);
+ }
+
+ this_thread::sleep_for(chrono::milliseconds(96));
+ }
+
+ if (underflows == underflows_old) {
+ etiLog.level(warn) << "DEXTER CHANNEL_DOWN, no underflow detected! " << underflows;
+ }
+
+ m_channel_is_up = false;
+ etiLog.level(debug) << "DEXTER CHANNEL_DOWN";
+}
+
+void Dexter::handle_hw_time()
+{
+ /*
+ * 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;
+
+ 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_holdover_since_t = 0;
+ m_clock_state = DexterClockState::Normal;
+ etiLog.level(debug) << "Dexter: switch clock state Startup -> 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_holdover_since_t = chrono::system_clock::to_time_t(chrono::system_clock::now());
+ m_clock_state = DexterClockState::Holdover;
+ etiLog.level(debug) << "Dexter: switch clock state Normal -> Holdover";
+ }
+ }
+ break;
+ case DexterClockState::Holdover:
+ {
+ using namespace chrono;
+
+ 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");
+ }
+
+ const duration<double> d = steady_clock::now() - m_holdover_since;
+ const auto max_holdover_duration = seconds(m_conf.maxGPSHoldoverTime);
+ if (d > max_holdover_duration or pps_loss_of_signal == 0) {
+ 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();
+ m_holdover_since_t = 0;
+ etiLog.level(debug) << "Dexter: switch clock state Holdover -> Startup";
+ }
+ }
+ break;
+ }
+}
+
+void Dexter::tune(double lo_offset, double frequency)
+{
+ // lo_offset is applied to the DSP, and frequency is given to the ad9957
+
+ long long freq = frequency;
+ int r = 0;
+ if ((r = iio_device_attr_write_longlong(m_ad9957, "center_frequency", freq)) != 0) {
+ etiLog.level(warn) << "Failed to set ad9957.center_frequency = " << freq << " : " << get_iio_error(r);
+ }
+
+ long long lo_offs = lo_offset;
+
+ if ((r = iio_device_attr_write_longlong(m_dexter_dsp_tx, "frequency0", lo_offs)) != 0) {
+ etiLog.level(warn) << "Failed to set dexter_dsp_tx.frequency0 = " << lo_offs << " : " << get_iio_error(r);
+ }
+}
+
+double Dexter::get_tx_freq(void) const
+{
+ long long lo_offset = 0;
+ int r = 0;
+
+ if ((r = iio_device_attr_read_longlong(m_dexter_dsp_tx, "frequency0", &lo_offset)) != 0) {
+ etiLog.level(warn) << "Failed to read dexter_dsp_tx.frequency0: " << get_iio_error(r);
+ return 0;
+ }
+
+ long long frequency = 0;
+ if ((r = iio_device_attr_read_longlong(m_ad9957, "center_frequency", &frequency)) != 0) {
+ etiLog.level(warn) << "Failed to read ad9957.center_frequency: " << get_iio_error(r);
+ return 0;
+ }
+
+ return frequency + lo_offset;
+}
+
+void Dexter::set_txgain(double txgain)
+{
+ int r = 0;
+ if ((r = iio_device_attr_write_longlong(m_dexter_dsp_tx, "gain0", txgain)) != 0) {
+ etiLog.level(warn) << "Failed to set dexter_dsp_tx.gain0 = " << txgain << ": " << get_iio_error(r);
+ }
+
+ long long txgain_readback = 0;
+ if ((r = iio_device_attr_read_longlong(m_dexter_dsp_tx, "gain0", &txgain_readback)) != 0) {
+ etiLog.level(warn) << "Failed to read dexter_dsp_tx.gain0: " << get_iio_error(r);
+ }
+ else {
+ m_conf.txgain = txgain_readback;
+ }
+}
+
+double Dexter::get_txgain(void) const
+{
+ long long txgain_readback = 0;
+ int r = 0;
+ if ((r = iio_device_attr_read_longlong(m_dexter_dsp_tx, "gain0", &txgain_readback)) != 0) {
+ etiLog.level(warn) << "Failed to read dexter_dsp_tx.gain0: " << get_iio_error(r);
+ }
+ return txgain_readback;
+}
+
+void Dexter::set_bandwidth(double bandwidth)
+{
+ return;
+}
+
+double Dexter::get_bandwidth(void) const
+{
+ return 0;
+}
+
+SDRDevice::run_statistics_t Dexter::get_run_statistics(void) const
+{
+ run_statistics_t rs;
+ {
+ std::unique_lock<std::mutex> lock(m_attr_thread_mutex);
+ rs["underruns"].v = underflows;
+ }
+ rs["latepackets"].v = num_late;
+ rs["frames"].v = num_frames_modulated;
+
+ rs["in_holdover_since"].v = 0;
+ rs["remaining_holdover_s"].v = nullopt;
+ switch (m_clock_state) {
+ case DexterClockState::Startup:
+ rs["clock_state"].v = "startup"; break;
+ case DexterClockState::Normal:
+ rs["clock_state"].v = "normal"; break;
+ case DexterClockState::Holdover:
+ rs["clock_state"].v = "holdover";
+ rs["in_holdover_since"].v = m_holdover_since_t;
+ {
+ using namespace std::chrono;
+ const auto max_holdover_duration = seconds(m_conf.maxGPSHoldoverTime);
+ const duration<double> remaining = max_holdover_duration - (steady_clock::now() - m_holdover_since);
+ rs["remaining_holdover_s"].v = (ssize_t)duration_cast<seconds>(remaining).count();
+ }
+ break;
+ }
+
+ return rs;
+}
+
+double Dexter::get_real_secs(void) const
+{
+ 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");
+ }
+
+ 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)
+{
+ // TODO
+}
+
+double Dexter::get_rxgain(void) const
+{
+ // TODO
+ return 0;
+}
+
+size_t Dexter::receive_frame(
+ complexf *buf,
+ size_t num_samples,
+ frame_timestamp& ts,
+ double timeout_secs)
+{
+ // TODO
+ return 0;
+}
+
+
+bool Dexter::is_clk_source_ok()
+{
+ if (m_conf.enableSync) {
+ handle_hw_time();
+ return m_clock_state != DexterClockState::Startup;
+ }
+ else {
+ return true;
+ }
+}
+
+const char* Dexter::device_name(void) const
+{
+ return "Dexter";
+}
+
+std::optional<double> Dexter::get_temperature(void) const
+{
+ std::ifstream in("/sys/bus/i2c/devices/1-002f/hwmon/hwmon0/temp1_input", std::ios::in | std::ios::binary);
+ if (in) {
+ double tbaseboard;
+ in >> tbaseboard;
+ return tbaseboard / 1000.0;
+ }
+ else {
+ return {};
+ }
+}
+
+void Dexter::transmit_frame(struct FrameData&& frame)
+{
+ constexpr size_t frame_len_bytes = TRANSMISSION_FRAME_LEN_SAMPS * sizeof(int16_t);
+ if (frame.buf.size() != frame_len_bytes) {
+ etiLog.level(debug) << "Dexter::transmit_frame Expected " <<
+ frame_len_bytes << " got " << frame.buf.size();
+ throw std::runtime_error("Dexter: invalid buffer size");
+ }
+
+ const bool require_timestamped_tx = (m_conf.enableSync and frame.ts.timestamp_valid);
+
+ if (not m_channel_is_up) {
+ if (require_timestamped_tx) {
+ if (m_clock_state == DexterClockState::Startup) {
+ return; // not ready
+ }
+ 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;
+ }
+ }
+
+ channel_up();
+ }
+
+ if (m_require_timestamp_refresh) {
+ etiLog.level(debug) << "DEXTER REQUIRE REFRESH";
+ channel_down();
+ m_require_timestamp_refresh = false;
+ }
+
+ // DabMod::launch_modulator ensures we get int16_t IQ here
+ //const size_t num_samples = frame.buf.size() / (2*sizeof(int16_t));
+ //const int16_t *buf = reinterpret_cast<const int16_t*>(frame.buf.data());
+
+ if (m_channel_is_up) {
+ for (size_t i = 0; i < IIO_BUFFERS; i++) {
+ constexpr size_t buflen_samps = TRANSMISSION_FRAME_LEN_SAMPS / IIO_BUFFERS;
+ constexpr size_t buflen = buflen_samps * sizeof(int16_t);
+
+ memcpy(iio_buffer_start(m_buffer), frame.buf.data() + (i * buflen), buflen);
+ ssize_t pushed = iio_buffer_push(m_buffer);
+ if (pushed < 0) {
+ etiLog.level(error) << "Dexter: failed to push buffer " << get_iio_error(pushed) <<
+ " after " << num_buffers_pushed << " bufs";
+ num_buffers_pushed = 0;
+ channel_down();
+ break;
+ }
+ num_buffers_pushed++;
+ }
+ num_frames_modulated++;
+ }
+
+ {
+ std::unique_lock<std::mutex> lock(m_attr_thread_mutex);
+ size_t u = underflows;
+ lock.unlock();
+
+ if (u != 0 and u != prev_underflows) {
+ etiLog.level(warn) << "Dexter: underflow! " << prev_underflows << " -> " << u;
+ }
+
+ prev_underflows = u;
+ }
+}
+
+void Dexter::underflow_read_process()
+{
+ m_underflow_ctx = iio_create_local_context();
+ if (not m_underflow_ctx) {
+ throw std::runtime_error("Dexter: Unable to create iio context for underflow");
+ }
+
+ auto dexter_dsp_tx = iio_context_find_device(m_ctx, "dexter_dsp_tx");
+ if (not dexter_dsp_tx) {
+ throw std::runtime_error("Dexter: Unable to find dexter_dsp_tx iio device");
+ }
+
+ set_thread_name("dexter_underflow");
+
+ while (m_running) {
+ this_thread::sleep_for(chrono::seconds(1));
+ long long underflows_attr = 0;
+
+ int r = iio_device_attr_read_longlong(m_dexter_dsp_tx, "buffer_underflows0", &underflows_attr);
+
+ if (r == 0) {
+ size_t underflows_new = underflows_attr;
+
+ std::unique_lock<std::mutex> lock(m_attr_thread_mutex);
+ if (underflows_new != underflows and underflows_attr != 0) {
+ underflows = underflows_new;
+ }
+ }
+ }
+ m_running = false;
+}
+
+Dexter::~Dexter()
+{
+ 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;
+ }
+
+ 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
new file mode 100644
index 0000000..d4f425f
--- /dev/null
+++ b/src/output/Dexter.h
@@ -0,0 +1,138 @@
+/*
+ Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Her Majesty the
+ Queen in Right of Canada (Communications Research Center Canada)
+
+ Copyright (C) 2023
+ Matthias P. Braendli, matthias.braendli@mpb.li
+
+ http://opendigitalradio.org
+
+DESCRIPTION:
+ It is an output driver using libiio targeting the PrecisionWave DEXTER board.
+*/
+
+/*
+ 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 <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#ifdef HAVE_DEXTER
+#include "iio.h"
+
+#include <string>
+#include <memory>
+#include <ctime>
+#include <mutex>
+#include <thread>
+#include <variant>
+
+#include "output/SDR.h"
+#include "ModPlugin.h"
+#include "EtiReader.h"
+#include "RemoteControl.h"
+
+namespace Output {
+
+enum class DexterClockState {
+ Startup,
+ Normal,
+ Holdover
+};
+
+class Dexter : public Output::SDRDevice
+{
+ public:
+ Dexter(SDRDeviceConfig& config);
+ Dexter(const Dexter& other) = delete;
+ Dexter& operator=(const Dexter& other) = delete;
+ virtual ~Dexter();
+
+ virtual void tune(double lo_offset, double frequency) override;
+ virtual double get_tx_freq(void) const override;
+ virtual void set_txgain(double txgain) override;
+ virtual double get_txgain() const override;
+ virtual void set_bandwidth(double bandwidth) override;
+ virtual double get_bandwidth() const override;
+ virtual void transmit_frame(struct FrameData&& frame) override;
+ virtual run_statistics_t get_run_statistics() const override;
+ virtual double get_real_secs() const override;
+
+ virtual void set_rxgain(double rxgain) override;
+ virtual double get_rxgain() const override;
+ virtual size_t receive_frame(
+ complexf *buf,
+ size_t num_samples,
+ frame_timestamp& ts,
+ double timeout_secs) override;
+
+ // Return true if GPS and reference clock inputs are ok
+ virtual bool is_clk_source_ok() override;
+ virtual const char* device_name() const override;
+
+ virtual std::optional<double> get_temperature() const override;
+
+ private:
+ void channel_up();
+ void channel_down();
+ void handle_hw_time();
+
+ bool m_channel_is_up = false;
+
+ SDRDeviceConfig& m_conf;
+
+ struct iio_context* m_ctx = nullptr;
+ struct iio_device* m_dexter_dsp_tx = nullptr;
+
+ struct iio_device* m_ad9957 = nullptr;
+ struct iio_device* m_ad9957_tx0 = nullptr;
+ struct iio_channel* m_tx_channel = nullptr;
+ struct iio_buffer *m_buffer = nullptr;
+
+ /* Underflows are counted in a separate thread */
+ struct iio_context* m_underflow_ctx = nullptr;
+ std::atomic<bool> m_running = ATOMIC_VAR_INIT(false);
+ std::thread m_underflow_read_thread;
+ void underflow_read_process();
+ mutable std::mutex m_attr_thread_mutex;
+ size_t underflows = 0;
+
+ size_t prev_underflows = 0;
+ size_t num_late = 0;
+ size_t num_frames_modulated = 0;
+
+ size_t num_buffers_pushed = 0;
+
+ 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;
+
+ // Only valid when m_clock_state Holdover
+ std::chrono::steady_clock::time_point m_holdover_since =
+ std::chrono::steady_clock::time_point::min();
+ std::time_t m_holdover_since_t = 0;
+};
+
+} // namespace Output
+
+#endif //HAVE_DEXTER
+
diff --git a/src/output/Feedback.cpp b/src/output/Feedback.cpp
index 88d8319..d112b5a 100644
--- a/src/output/Feedback.cpp
+++ b/src/output/Feedback.cpp
@@ -84,7 +84,7 @@ DPDFeedbackServer::~DPDFeedbackServer()
void DPDFeedbackServer::set_tx_frame(
const std::vector<uint8_t> &buf,
- const struct frame_timestamp &buf_ts)
+ const frame_timestamp &buf_ts)
{
if (not m_running) {
throw runtime_error("DPDFeedbackServer not running");
diff --git a/src/output/Feedback.h b/src/output/Feedback.h
index aef86b0..b31347f 100644
--- a/src/output/Feedback.h
+++ b/src/output/Feedback.h
@@ -94,7 +94,7 @@ class DPDFeedbackServer {
~DPDFeedbackServer();
void set_tx_frame(const std::vector<uint8_t> &buf,
- const struct frame_timestamp& ts);
+ const frame_timestamp& ts);
private:
// Thread that reacts to burstRequests and receives from the SDR device
diff --git a/src/output/Lime.cpp b/src/output/Lime.cpp
index 593cddb..47045e7 100644
--- a/src/output/Lime.cpp
+++ b/src/output/Lime.cpp
@@ -318,13 +318,14 @@ double Lime::get_bandwidth(void) const
return bw;
}
-SDRDevice::RunStatistics Lime::get_run_statistics(void) const
+SDRDevice::run_statistics_t Lime::get_run_statistics(void) const
{
- RunStatistics rs;
- rs.num_underruns = underflows;
- rs.num_overruns = overflows;
- rs.num_late_packets = late_packets;
- rs.num_frames_modulated = num_frames_modulated;
+ run_statistics_t rs;
+ rs["underruns"] = underflows;
+ rs["overruns"] = overflows;
+ rs["dropped_packets"] = dropped_packets;
+ rs["frames"] = num_frames_modulated;
+ rs["fifo_fill"] = m_last_fifo_fill_percent * 100;
return rs;
}
@@ -348,14 +349,14 @@ double Lime::get_rxgain(void) const
size_t Lime::receive_frame(
complexf *buf,
size_t num_samples,
- struct frame_timestamp &ts,
+ frame_timestamp &ts,
double timeout_secs)
{
// TODO
return 0;
}
-bool Lime::is_clk_source_ok() const
+bool Lime::is_clk_source_ok()
{
// TODO
return true;
@@ -366,25 +367,23 @@ const char *Lime::device_name(void) const
return "Lime";
}
-double Lime::get_temperature(void) const
+std::optional<double> Lime::get_temperature(void) const
{
if (not m_device)
throw runtime_error("Lime device not set up");
- float_type temp = numeric_limits<float_type>::quiet_NaN();
- if (LMS_GetChipTemperature(m_device, 0, &temp) < 0)
- {
+ float_type temp = 0;
+ if (LMS_GetChipTemperature(m_device, 0, &temp) >= 0) {
+ return temp;
+ }
+ else {
etiLog.level(error) << "Error getting LimeSDR temperature: %s " << LMS_GetLastErrorMessage();
+ return std::nullopt;
}
- return temp;
}
-float Lime::get_fifo_fill_percent(void) const
-{
- return m_last_fifo_fill_percent * 100;
-}
-void Lime::transmit_frame(const struct FrameData &frame)
+void Lime::transmit_frame(struct FrameData&& frame)
{
if (not m_device)
throw runtime_error("Lime device not set up");
@@ -406,7 +405,7 @@ void Lime::transmit_frame(const struct FrameData &frame)
LMS_GetStreamStatus(&m_tx_stream, &LimeStatus);
overflows += LimeStatus.overrun;
underflows += LimeStatus.underrun;
- late_packets += LimeStatus.droppedPackets;
+ dropped_packets += LimeStatus.droppedPackets;
#ifdef LIMEDEBUG
etiLog.level(info) << LimeStatus.fifoFilledCount << "/" << LimeStatus.fifoSize << ":" << numSamples << "Rate" << LimeStatus.linkRate / (2 * 2.0);
diff --git a/src/output/Lime.h b/src/output/Lime.h
index 72a018e..4510bf2 100644
--- a/src/output/Lime.h
+++ b/src/output/Lime.h
@@ -66,8 +66,8 @@ class Lime : public Output::SDRDevice
virtual double get_txgain(void) const override;
virtual void set_bandwidth(double bandwidth) override;
virtual double get_bandwidth(void) const override;
- virtual void transmit_frame(const struct FrameData &frame) override;
- virtual RunStatistics get_run_statistics(void) const override;
+ virtual void transmit_frame(struct FrameData&& frame) override;
+ virtual run_statistics_t get_run_statistics(void) const override;
virtual double get_real_secs(void) const override;
virtual void set_rxgain(double rxgain) override;
@@ -75,15 +75,14 @@ class Lime : public Output::SDRDevice
virtual size_t receive_frame(
complexf *buf,
size_t num_samples,
- struct frame_timestamp &ts,
+ frame_timestamp &ts,
double timeout_secs) override;
// Return true if GPS and reference clock inputs are ok
- virtual bool is_clk_source_ok(void) const override;
+ virtual bool is_clk_source_ok(void) override;
virtual const char *device_name(void) const override;
- virtual double get_temperature(void) const override;
- virtual float get_fifo_fill_percent(void) const;
+ virtual std::optional<double> get_temperature(void) const override;
private:
SDRDeviceConfig &m_conf;
@@ -95,11 +94,10 @@ class Lime : public Output::SDRDevice
std::vector<complexf> interpolatebuf;
std::vector<short> m_i16samples;
std::atomic<float> m_last_fifo_fill_percent = ATOMIC_VAR_INIT(0);
-
size_t underflows = 0;
size_t overflows = 0;
- size_t late_packets = 0;
+ size_t dropped_packets = 0;
size_t num_frames_modulated = 0;
};
diff --git a/src/output/SDR.cpp b/src/output/SDR.cpp
index b0c09b6..75a7ee3 100644
--- a/src/output/SDR.cpp
+++ b/src/output/SDR.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) 2022
+ Copyright (C) 2023
Matthias P. Braendli, matthias.braendli@mpb.li
http://opendigitalradio.org
@@ -25,7 +25,9 @@
*/
#include "output/SDR.h"
+#include "output/UHD.h"
#include "output/Lime.h"
+#include "output/Dexter.h"
#include "PcDebug.h"
#include "Log.h"
@@ -46,17 +48,16 @@ using namespace std;
namespace Output {
-// Maximum number of frames that can wait in frames
-static constexpr size_t FRAMES_MAX_SIZE = 8;
+// Maximum number of frames that can wait in frames.
+// Keep it low when not using synchronised transmission, in order to reduce delay.
+// When using synchronised transmission, use a 6s buffer to give us enough margin.
+static constexpr size_t FRAMES_MAX_SIZE_UNSYNC = 8;
+static constexpr size_t FRAMES_MAX_SIZE_SYNC = 250;
// If the timestamp is further in the future than
// 100 seconds, abort
static constexpr double TIMESTAMP_ABORT_FUTURE = 100;
-// Add a delay to increase buffers when
-// frames are too far in the future
-static constexpr double TIMESTAMP_MARGIN_FUTURE = 0.5;
-
SDR::SDR(SDRDeviceConfig& config, std::shared_ptr<SDRDevice> device) :
ModOutput(), ModMetadata(), RemoteControllable("sdr"),
m_config(config),
@@ -77,20 +78,40 @@ SDR::SDR(SDRDeviceConfig& config, std::shared_ptr<SDRDevice> device) :
RC_ADD_PARAMETER(txgain, "TX gain");
RC_ADD_PARAMETER(rxgain, "RX gain for DPD feedback");
RC_ADD_PARAMETER(bandwidth, "Analog front-end bandwidth");
- RC_ADD_PARAMETER(freq, "Transmission frequency");
+ RC_ADD_PARAMETER(freq, "Transmission frequency in Hz");
+ RC_ADD_PARAMETER(channel, "Transmission frequency as channel");
RC_ADD_PARAMETER(muting, "Mute the output by stopping the transmitter");
RC_ADD_PARAMETER(temp, "Temperature in degrees C of the device");
RC_ADD_PARAMETER(underruns, "Counter of number of underruns");
RC_ADD_PARAMETER(latepackets, "Counter of number of late packets");
RC_ADD_PARAMETER(frames, "Counter of number of frames modulated");
- RC_ADD_PARAMETER(gpsdo_num_sv, "Number of Satellite Vehicles tracked by GPSDO");
- RC_ADD_PARAMETER(gpsdo_holdover, "1 if the GPSDO is in holdover, 0 if it is using gnss");
+ RC_ADD_PARAMETER(synchronous, "1 if configured for synchronous transmission");
+ RC_ADD_PARAMETER(max_gps_holdover_time, "Max holdover duration in seconds");
+
+#ifdef HAVE_OUTPUT_UHD
+ if (std::dynamic_pointer_cast<UHD>(device)) {
+ RC_ADD_PARAMETER(gpsdo_num_sv, "Number of Satellite Vehicles tracked by GPSDO");
+ RC_ADD_PARAMETER(gpsdo_holdover, "1 if the GPSDO is in holdover, 0 if it is using gnss");
+ }
+#endif // HAVE_OUTPUT_UHD
+
+ RC_ADD_PARAMETER(queued_frames_ms, "Number of frames queued, represented in milliseconds");
#ifdef HAVE_LIMESDR
if (std::dynamic_pointer_cast<Lime>(device)) {
RC_ADD_PARAMETER(fifo_fill, "A value representing the Lime FIFO fullness [percent]");
}
#endif // HAVE_LIMESDR
+
+#ifdef HAVE_DEXTER
+ if (std::dynamic_pointer_cast<Dexter>(device)) {
+ RC_ADD_PARAMETER(in_holdover_since, "DEXTER timestamp when holdover began");
+ RC_ADD_PARAMETER(remaining_holdover_s, "DEXTER remaining number of seconds in holdover");
+ RC_ADD_PARAMETER(clock_state, "DEXTER clock state: startup/normal/holdover");
+ }
+#endif // HAVE_DEXTER
+
+
}
SDR::~SDR()
@@ -104,6 +125,11 @@ SDR::~SDR()
}
}
+void SDR::set_sample_size(size_t size)
+{
+ m_size = size;
+}
+
int SDR::process(Buffer *dataIn)
{
if (not m_running) {
@@ -125,6 +151,7 @@ meta_vec_t SDR::process_metadata(const meta_vec_t& metadataIn)
if (m_device and m_running) {
FrameData frame;
frame.buf = std::move(m_frame);
+ frame.sampleSize = m_size;
if (metadataIn.empty()) {
etiLog.level(info) <<
@@ -138,7 +165,7 @@ meta_vec_t SDR::process_metadata(const meta_vec_t& metadataIn)
* This behaviour is different to earlier versions of ODR-DabMod,
* which took the timestamp from the latest ETI frame.
*/
- frame.ts = *(metadataIn[0].ts);
+ frame.ts = metadataIn[0].ts;
// TODO check device running
@@ -157,9 +184,12 @@ meta_vec_t SDR::process_metadata(const meta_vec_t& metadataIn)
m_config.sampleRate);
}
- size_t num_frames = m_queue.push_wait_if_full(frame,
- FRAMES_MAX_SIZE);
- etiLog.log(trace, "SDR,push %zu", num_frames);
+
+ const auto max_size = m_config.enableSync ? FRAMES_MAX_SIZE_SYNC : FRAMES_MAX_SIZE_UNSYNC;
+ auto r = m_queue.push_overflow(std::move(frame), max_size);
+ etiLog.log(trace, "SDR,push %d %zu", r.overflowed, r.new_size);
+
+ num_queue_overflows += r.overflowed ? 1 : 0;
}
}
else {
@@ -180,16 +210,13 @@ void SDR::process_thread_entry()
last_tx_time_initialised = false;
- size_t last_num_underflows = 0;
- size_t pop_prebuffering = FRAMES_MAX_SIZE;
-
m_running.store(true);
try {
while (m_running.load()) {
struct FrameData frame;
etiLog.log(trace, "SDR,wait");
- m_queue.wait_and_pop(frame, pop_prebuffering);
+ m_queue.wait_and_pop(frame);
etiLog.log(trace, "SDR,pop");
if (m_running.load() == false) {
@@ -197,20 +224,7 @@ void SDR::process_thread_entry()
}
if (m_device) {
- handle_frame(frame);
-
- const auto rs = m_device->get_run_statistics();
-
- /* Ensure we fill frames after every underrun and
- * at startup to reduce underrun likelihood. */
- if (last_num_underflows < rs.num_underruns) {
- pop_prebuffering = FRAMES_MAX_SIZE;
- }
- else {
- pop_prebuffering = 1;
- }
-
- last_num_underflows = rs.num_underruns;
+ handle_frame(std::move(frame));
}
}
}
@@ -236,46 +250,19 @@ const char* SDR::name()
return m_name.c_str();
}
-void SDR::sleep_through_frame()
-{
- using namespace std::chrono;
-
- const auto now = steady_clock::now();
- if (not t_last_frame_initialised) {
- t_last_frame = now;
- t_last_frame_initialised = true;
- }
-
- const auto delta = now - t_last_frame;
- const auto wait_time = transmission_frame_duration(m_config.dabMode);
-
- if (wait_time > delta) {
- this_thread::sleep_for(wait_time - delta);
- }
-
- t_last_frame += wait_time;
-}
-
-void SDR::handle_frame(struct FrameData& frame)
+void SDR::handle_frame(struct FrameData&& frame)
{
// Assumes m_device is valid
- constexpr double tx_timeout = 20.0;
-
if (not m_device->is_clk_source_ok()) {
- sleep_through_frame();
return;
}
const auto& time_spec = frame.ts;
- if (m_config.enableSync and m_config.muteNoTimestamps and
- not time_spec.timestamp_valid) {
- sleep_through_frame();
- etiLog.log(info,
- "OutputSDR: Muting sample %d : no timestamp\n",
- frame.ts.fct);
+ if (m_config.enableSync and m_config.muteNoTimestamps and not time_spec.timestamp_valid) {
+ etiLog.log(info, "OutputSDR: Muting sample %d : no timestamp\n", frame.ts.fct);
return;
}
@@ -298,11 +285,12 @@ void SDR::handle_frame(struct FrameData& frame)
}
if (frame.ts.offset_changed) {
+ etiLog.level(debug) << "TS offset changed";
m_device->require_timestamp_refresh();
}
if (last_tx_time_initialised) {
- const size_t sizeIn = frame.buf.size() / sizeof(complexf);
+ const size_t sizeIn = frame.buf.size() / frame.sampleSize;
// Checking units for the increment calculation:
// samps * ticks/s / (samps/s)
@@ -341,7 +329,7 @@ void SDR::handle_frame(struct FrameData& frame)
etiLog.log(trace, "SDR,tist %f", time_spec.get_real_secs());
- if (time_spec.get_real_secs() + tx_timeout < device_time) {
+ if (time_spec.get_real_secs() < device_time) {
etiLog.level(warn) <<
"OutputSDR: Timestamp in the past at FCT=" << frame.ts.fct << " offset: " <<
std::fixed <<
@@ -350,6 +338,7 @@ void SDR::handle_frame(struct FrameData& frame)
" frame " << frame.ts.fct <<
", tx_second " << tx_second <<
", pps " << pps_offset;
+ m_device->require_timestamp_refresh();
return;
}
@@ -363,13 +352,12 @@ void SDR::handle_frame(struct FrameData& frame)
}
if (m_config.muting) {
- etiLog.log(info,
- "OutputSDR: Muting FCT=%d requested",
- frame.ts.fct);
+ etiLog.log(info, "OutputSDR: Muting FCT=%d requested", frame.ts.fct);
+ m_device->require_timestamp_refresh();
return;
}
- m_device->transmit_frame(frame);
+ m_device->transmit_frame(std::move(frame));
}
// =======================================
@@ -397,21 +385,33 @@ void SDR::set_parameter(const string& parameter, const string& value)
m_device->tune(m_config.lo_offset, m_config.frequency);
m_config.frequency = m_device->get_tx_freq();
}
+ else if (parameter == "channel") {
+ try {
+ const double frequency = parse_channel(value);
+
+ m_config.frequency = frequency;
+ m_device->tune(m_config.lo_offset, m_config.frequency);
+ m_config.frequency = m_device->get_tx_freq();
+ }
+ catch (const std::out_of_range& e) {
+ throw ParameterError("Cannot parse channel");
+ }
+ }
else if (parameter == "muting") {
ss >> m_config.muting;
}
- else if (parameter == "underruns" or
- parameter == "latepackets" or
- parameter == "frames" or
- parameter == "gpsdo_num_sv" or
- parameter == "gpsdo_holdover" or
- parameter == "fifo_fill") {
- throw ParameterError("Parameter " + parameter + " is read-only.");
+ else if (parameter == "synchronous") {
+ uint32_t enableSync = 0;
+ ss >> enableSync;
+ m_config.enableSync = enableSync > 0;
+ }
+ else if (parameter == "max_gps_holdover_time") {
+ ss >> m_config.maxGPSHoldoverTime;
}
else {
stringstream ss_err;
ss_err << "Parameter '" << parameter
- << "' is not exported by controllable " << get_rc_name();
+ << "' is read-only or not exported by controllable " << get_rc_name();
throw ParameterError(ss_err.str());
}
}
@@ -432,6 +432,16 @@ const string SDR::get_parameter(const string& parameter) const
else if (parameter == "freq") {
ss << m_config.frequency;
}
+ else if (parameter == "channel") {
+ const auto maybe_freq = convert_frequency_to_channel(m_config.frequency);
+
+ if (maybe_freq.has_value()) {
+ ss << *maybe_freq;
+ }
+ else {
+ throw ParameterError("Frequency is outside list of channels");
+ }
+ }
else if (parameter == "muting") {
ss << m_config.muting;
}
@@ -439,55 +449,57 @@ const string SDR::get_parameter(const string& parameter) const
if (not m_device) {
throw ParameterError("OutputSDR has no device");
}
- const double temp = m_device->get_temperature();
- if (std::isnan(temp)) {
- throw ParameterError("Temperature not available");
+ const std::optional<double> temp = m_device->get_temperature();
+ if (temp) {
+ ss << *temp;
}
else {
- ss << temp;
- }
- }
- else if (parameter == "underruns" or
- parameter == "latepackets" or
- parameter == "frames" ) {
- if (not m_device) {
- throw ParameterError("OutputSDR has no device");
- }
- const auto stat = m_device->get_run_statistics();
-
- if (parameter == "underruns") {
- ss << stat.num_underruns;
- }
- else if (parameter == "latepackets") {
- ss << stat.num_late_packets;
- }
- else if (parameter == "frames") {
- ss << stat.num_frames_modulated;
+ throw ParameterError("Temperature not available");
}
}
- else if (parameter == "gpsdo_num_sv") {
- const auto stat = m_device->get_run_statistics();
- ss << stat.gpsdo_num_sv;
+ else if (parameter == "queued_frames_ms") {
+ ss << m_queue.size() *
+ chrono::duration_cast<chrono::milliseconds>(transmission_frame_duration(m_config.dabMode))
+ .count();
}
- else if (parameter == "gpsdo_holdover") {
- const auto stat = m_device->get_run_statistics();
- ss << (stat.gpsdo_holdover ? 1 : 0);
+ else if (parameter == "synchronous") {
+ ss << m_config.enableSync;
}
-#ifdef HAVE_LIMESDR
- else if (parameter == "fifo_fill") {
- const auto dev = std::dynamic_pointer_cast<Lime>(m_device);
-
- if (dev) {
- ss << dev->get_fifo_fill_percent();
- }
- else {
- ss << "Parameter '" << parameter <<
- "' is not exported by controllable " << get_rc_name();
- throw ParameterError(ss.str());
- }
+ else if (parameter == "max_gps_holdover_time") {
+ ss << m_config.maxGPSHoldoverTime;
}
-#endif // HAVE_LIMESDR
else {
+ if (m_device) {
+ const auto stat = m_device->get_run_statistics();
+ try {
+ const auto& value = stat.at(parameter).v;
+ if (std::holds_alternative<string>(value)) {
+ ss << std::get<string>(value);
+ }
+ else if (std::holds_alternative<double>(value)) {
+ ss << std::get<double>(value);
+ }
+ else if (std::holds_alternative<ssize_t>(value)) {
+ ss << std::get<ssize_t>(value);
+ }
+ else if (std::holds_alternative<size_t>(value)) {
+ ss << std::get<size_t>(value);
+ }
+ else if (std::holds_alternative<bool>(value)) {
+ ss << (std::get<bool>(value) ? 1 : 0);
+ }
+ else if (std::holds_alternative<std::nullopt_t>(value)) {
+ ss << "";
+ }
+ else {
+ throw std::logic_error("variant alternative not handled");
+ }
+ return ss.str();
+ }
+ catch (const std::out_of_range&) {
+ }
+ }
+
ss << "Parameter '" << parameter <<
"' is not exported by controllable " << get_rc_name();
throw ParameterError(ss.str());
@@ -495,4 +507,39 @@ const string SDR::get_parameter(const string& parameter) const
return ss.str();
}
+const json::map_t SDR::get_all_values() const
+{
+ json::map_t stat = m_device->get_run_statistics();
+
+ stat["txgain"].v = m_config.txgain;
+ stat["rxgain"].v = m_config.rxgain;
+ stat["freq"].v = m_config.frequency;
+ stat["muting"].v = m_config.muting;
+ stat["temp"].v = std::nullopt;
+
+ const auto maybe_freq = convert_frequency_to_channel(m_config.frequency);
+
+ if (maybe_freq.has_value()) {
+ stat["channel"].v = *maybe_freq;
+ }
+ else {
+ stat["channel"].v = std::nullopt;
+ }
+
+ if (m_device) {
+ const std::optional<double> temp = m_device->get_temperature();
+ if (temp) {
+ stat["temp"].v = *temp;
+ }
+ }
+ stat["queued_frames_ms"].v = m_queue.size() *
+ (size_t)chrono::duration_cast<chrono::milliseconds>(transmission_frame_duration(m_config.dabMode))
+ .count();
+
+ stat["synchronous"].v = m_config.enableSync;
+ stat["max_gps_holdover_time"].v = (size_t)m_config.maxGPSHoldoverTime;
+
+ return stat;
+}
+
} // namespace Output
diff --git a/src/output/SDR.h b/src/output/SDR.h
index 33477bf..960de0c 100644
--- a/src/output/SDR.h
+++ b/src/output/SDR.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) 2022
+ Copyright (C) 2023
Matthias P. Braendli, matthias.braendli@mpb.li
http://opendigitalradio.org
@@ -51,6 +51,7 @@ class SDR : public ModOutput, public ModMetadata, public RemoteControllable {
SDR operator=(const SDR& other) = delete;
virtual ~SDR();
+ virtual void set_sample_size(size_t size);
virtual int process(Buffer *dataIn) override;
virtual meta_vec_t process_metadata(const meta_vec_t& metadataIn) override;
@@ -66,15 +67,17 @@ class SDR : public ModOutput, public ModMetadata, public RemoteControllable {
virtual const std::string get_parameter(
const std::string& parameter) const override;
+ virtual const json::map_t get_all_values() const override;
+
private:
void process_thread_entry(void);
- void handle_frame(struct FrameData &frame);
- void sleep_through_frame(void);
+ void handle_frame(struct FrameData&& frame);
SDRDeviceConfig& m_config;
std::atomic<bool> m_running = ATOMIC_VAR_INIT(false);
std::thread m_device_thread;
+ size_t m_size = sizeof(complexf);
std::vector<uint8_t> m_frame;
ThreadsafeQueue<FrameData> m_queue;
@@ -86,9 +89,7 @@ class SDR : public ModOutput, public ModMetadata, public RemoteControllable {
bool last_tx_time_initialised = false;
uint32_t last_tx_second = 0;
uint32_t last_tx_pps = 0;
-
- bool t_last_frame_initialised = false;
- std::chrono::steady_clock::time_point t_last_frame;
+ size_t num_queue_overflows = 0;
};
}
diff --git a/src/output/SDRDevice.h b/src/output/SDRDevice.h
index b599f5a..378829c 100644
--- a/src/output/SDRDevice.h
+++ b/src/output/SDRDevice.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) 2022
+ Copyright (C) 2023
Matthias P. Braendli, matthias.braendli@mpb.li
http://opendigitalradio.org
@@ -38,6 +38,9 @@ DESCRIPTION:
#include <string>
#include <vector>
#include <complex>
+#include <variant>
+#include <optional>
+#include <unordered_map>
#include "TimestampDecoder.h"
@@ -98,32 +101,25 @@ struct SDRDeviceConfig {
struct FrameData {
// Buffer holding frame data
std::vector<uint8_t> buf;
+ size_t sampleSize = sizeof(complexf);
// A full timestamp contains a TIST according to standard
// and time information within MNSC with tx_second.
- struct frame_timestamp ts;
+ frame_timestamp ts;
};
// All SDR Devices must implement the SDRDevice interface
class SDRDevice {
public:
- struct RunStatistics {
- size_t num_underruns = 0;
- size_t num_late_packets = 0;
- size_t num_overruns = 0;
- size_t num_frames_modulated = 0;
-
- int gpsdo_num_sv = 0;
- bool gpsdo_holdover = false;
- };
+ using run_statistics_t = json::map_t;
virtual void tune(double lo_offset, double frequency) = 0;
virtual double get_tx_freq(void) const = 0;
virtual void set_txgain(double txgain) = 0;
virtual double get_txgain(void) const = 0;
- virtual void transmit_frame(const struct FrameData& frame) = 0;
- virtual RunStatistics get_run_statistics(void) const = 0;
+ virtual void transmit_frame(struct FrameData&& frame) = 0;
+ virtual run_statistics_t 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) const = 0;
@@ -132,14 +128,14 @@ class SDRDevice {
virtual size_t receive_frame(
complexf *buf,
size_t num_samples,
- struct frame_timestamp& ts,
+ frame_timestamp& ts,
double timeout_secs) = 0;
- // Returns device temperature in degrees C or NaN if not available
- virtual double get_temperature(void) const = 0;
+ // Returns device temperature in degrees C
+ virtual std::optional<double> get_temperature(void) const = 0;
// Return true if GPS and reference clock inputs are ok
- virtual bool is_clk_source_ok(void) const = 0;
+ virtual bool is_clk_source_ok(void) = 0;
virtual const char* device_name(void) const = 0;
diff --git a/src/output/Soapy.cpp b/src/output/Soapy.cpp
index 684a9a4..7931860 100644
--- a/src/output/Soapy.cpp
+++ b/src/output/Soapy.cpp
@@ -180,13 +180,13 @@ double Soapy::get_bandwidth(void) const
return m_device->getBandwidth(SOAPY_SDR_TX, 0);
}
-SDRDevice::RunStatistics Soapy::get_run_statistics(void) const
+SDRDevice::run_statistics_t Soapy::get_run_statistics(void) const
{
- RunStatistics rs;
- rs.num_underruns = underflows;
- rs.num_overruns = overflows;
- rs.num_late_packets = late_packets;
- rs.num_frames_modulated = num_frames_modulated;
+ run_statistics_t rs;
+ rs["underruns"].v = underflows;
+ rs["overruns"].v = overflows;
+ rs["timeouts"].v = timeouts;
+ rs["frames"].v = num_frames_modulated;
return rs;
}
@@ -216,7 +216,7 @@ double Soapy::get_rxgain(void) const
size_t Soapy::receive_frame(
complexf *buf,
size_t num_samples,
- struct frame_timestamp& ts,
+ frame_timestamp& ts,
double timeout_secs)
{
int flags = 0;
@@ -254,7 +254,7 @@ size_t Soapy::receive_frame(
}
-bool Soapy::is_clk_source_ok() const
+bool Soapy::is_clk_source_ok()
{
// TODO
return true;
@@ -265,14 +265,14 @@ const char* Soapy::device_name(void) const
return "Soapy";
}
-double Soapy::get_temperature(void) const
+std::optional<double> Soapy::get_temperature(void) const
{
// TODO Unimplemented
// LimeSDR exports 'lms7_temp'
- return std::numeric_limits<double>::quiet_NaN();
+ return std::nullopt;
}
-void Soapy::transmit_frame(const struct FrameData& frame)
+void Soapy::transmit_frame(struct FrameData&& frame)
{
if (not m_device) throw runtime_error("Soapy device not set up");
@@ -320,6 +320,7 @@ void Soapy::transmit_frame(const struct FrameData& frame)
m_tx_stream, buffs, samps_to_send, flags, timeNs);
if (num_sent == SOAPY_SDR_TIMEOUT) {
+ timeouts++;
continue;
}
else if (num_sent == SOAPY_SDR_OVERFLOW) {
@@ -349,6 +350,7 @@ void Soapy::transmit_frame(const struct FrameData& frame)
SoapySDR::errToStr(ret_deact));
}
m_tx_stream_active = false;
+ m_require_timestamp_refresh = false;
}
if (eob_because_muting) {
diff --git a/src/output/Soapy.h b/src/output/Soapy.h
index 4ee53ca..4fce11a 100644
--- a/src/output/Soapy.h
+++ b/src/output/Soapy.h
@@ -65,8 +65,8 @@ class Soapy : public Output::SDRDevice
virtual double get_txgain(void) const override;
virtual void set_bandwidth(double bandwidth) override;
virtual double get_bandwidth(void) const override;
- virtual void transmit_frame(const struct FrameData& frame) override;
- virtual RunStatistics get_run_statistics(void) const override;
+ virtual void transmit_frame(struct FrameData&& frame) override;
+ virtual run_statistics_t get_run_statistics(void) const override;
virtual double get_real_secs(void) const override;
virtual void set_rxgain(double rxgain) override;
@@ -74,14 +74,14 @@ class Soapy : public Output::SDRDevice
virtual size_t receive_frame(
complexf *buf,
size_t num_samples,
- struct frame_timestamp& ts,
+ frame_timestamp& ts,
double timeout_secs) override;
// Return true if GPS and reference clock inputs are ok
- virtual bool is_clk_source_ok(void) const override;
+ virtual bool is_clk_source_ok(void) override;
virtual const char* device_name(void) const override;
- virtual double get_temperature(void) const override;
+ virtual std::optional<double> get_temperature(void) const override;
private:
SDRDeviceConfig& m_conf;
@@ -91,9 +91,9 @@ class Soapy : public Output::SDRDevice
SoapySDR::Stream *m_rx_stream = nullptr;
bool m_rx_stream_active = false;
+ size_t timeouts = 0;
size_t underflows = 0;
size_t overflows = 0;
- size_t late_packets = 0;
size_t num_frames_modulated = 0;
};
diff --git a/src/output/UHD.cpp b/src/output/UHD.cpp
index ac34ce4..094e021 100644
--- a/src/output/UHD.cpp
+++ b/src/output/UHD.cpp
@@ -315,7 +315,7 @@ double UHD::get_bandwidth(void) const
return m_usrp->get_tx_bandwidth();
}
-void UHD::transmit_frame(const struct FrameData& frame)
+void UHD::transmit_frame(struct FrameData&& frame)
{
const double tx_timeout = 20.0;
const size_t sizeIn = frame.buf.size() / sizeof(complexf);
@@ -350,6 +350,7 @@ void UHD::transmit_frame(const struct FrameData& frame)
frame.ts.timestamp_valid and
m_require_timestamp_refresh and
samps_to_send <= usrp_max_num_samps );
+ m_require_timestamp_refresh = false;
//send a single packet
size_t num_tx_samps = m_tx_stream->send(
@@ -359,7 +360,7 @@ void UHD::transmit_frame(const struct FrameData& frame)
num_acc_samps += num_tx_samps;
- md_tx.time_spec += uhd::time_spec_t(0, num_tx_samps/m_conf.sampleRate);
+ md_tx.time_spec += uhd::time_spec_t::from_ticks(num_tx_samps, (double)m_conf.sampleRate);
if (num_tx_samps == 0) {
etiLog.log(warn,
@@ -376,18 +377,22 @@ void UHD::transmit_frame(const struct FrameData& frame)
}
-SDRDevice::RunStatistics UHD::get_run_statistics(void) const
+SDRDevice::run_statistics_t UHD::get_run_statistics(void) const
{
- 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;
+ run_statistics_t rs;
+ rs["underruns"].v = num_underflows;
+ rs["overruns"].v = num_overflows;
+ rs["late_packets"].v = num_late_packets;
+ rs["frames"].v = num_frames_modulated;
if (m_device_time) {
const auto gpsdo_stat = m_device_time->get_gnss_stats();
- rs.gpsdo_holdover = gpsdo_stat.holdover;
- rs.gpsdo_num_sv = gpsdo_stat.num_sv;
+ rs["gpsdo_holdover"].v = gpsdo_stat.holdover;
+ rs["gpsdo_num_sv"].v = gpsdo_stat.num_sv;
+ }
+ else {
+ rs["gpsdo_holdover"].v = true;
+ rs["gpsdo_num_sv"].v = 0;
}
return rs;
}
@@ -411,7 +416,7 @@ double UHD::get_rxgain() const
size_t UHD::receive_frame(
complexf *buf,
size_t num_samples,
- struct frame_timestamp& ts,
+ frame_timestamp& ts,
double timeout_secs)
{
uhd::stream_cmd_t cmd(
@@ -434,7 +439,7 @@ size_t UHD::receive_frame(
}
// Return true if GPS and reference clock inputs are ok
-bool UHD::is_clk_source_ok(void) const
+bool UHD::is_clk_source_ok(void)
{
bool ok = true;
@@ -471,13 +476,13 @@ const char* UHD::device_name(void) const
return "UHD";
}
-double UHD::get_temperature(void) const
+std::optional<double> UHD::get_temperature(void) const
{
try {
return std::round(m_usrp->get_tx_sensor("temp", 0).to_real());
}
catch (const uhd::lookup_error &e) {
- return std::numeric_limits<double>::quiet_NaN();
+ return std::nullopt;
}
}
diff --git a/src/output/UHD.h b/src/output/UHD.h
index 29867fb..9891c7a 100644
--- a/src/output/UHD.h
+++ b/src/output/UHD.h
@@ -55,14 +55,6 @@ DESCRIPTION:
#include <stdio.h>
#include <sys/types.h>
-// 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 {
class UHD : public Output::SDRDevice
@@ -79,8 +71,8 @@ class UHD : public Output::SDRDevice
virtual double get_txgain(void) const override;
virtual void set_bandwidth(double bandwidth) override;
virtual double get_bandwidth(void) const override;
- virtual void transmit_frame(const struct FrameData& frame) override;
- virtual RunStatistics get_run_statistics(void) const override;
+ virtual void transmit_frame(struct FrameData&& frame) override;
+ virtual run_statistics_t get_run_statistics(void) const override;
virtual double get_real_secs(void) const override;
virtual void set_rxgain(double rxgain) override;
@@ -88,14 +80,14 @@ class UHD : public Output::SDRDevice
virtual size_t receive_frame(
complexf *buf,
size_t num_samples,
- struct frame_timestamp& ts,
+ frame_timestamp& ts,
double timeout_secs) override;
// Return true if GPS and reference clock inputs are ok
- virtual bool is_clk_source_ok(void) const override;
+ virtual bool is_clk_source_ok(void) override;
virtual const char* device_name(void) const override;
- virtual double get_temperature(void) const override;
+ virtual std::optional<double> get_temperature(void) const override;
private:
SDRDeviceConfig& m_conf;
diff --git a/src/output/USRPTime.cpp b/src/output/USRPTime.cpp
index d1197ec..5a11851 100644
--- a/src/output/USRPTime.cpp
+++ b/src/output/USRPTime.cpp
@@ -46,11 +46,14 @@ USRPTime::USRPTime(
m_conf(conf),
time_last_check(timepoint_t::clock::now())
{
- if (m_conf.pps_src == "none") {
+ if (m_conf.refclk_src == "internal" and m_conf.pps_src != "none") {
+ etiLog.level(warn) << "OutputUHD: Unusal refclk and pps source settings. Setting time once, no monitoring.";
+ set_usrp_time_from_pps();
+ }
+ else if (m_conf.pps_src == "none") {
if (m_conf.enableSync) {
etiLog.level(warn) <<
- "OutputUHD: WARNING:"
- " you are using synchronous transmission without PPS input!";
+ "OutputUHD: you are using synchronous transmission without PPS input!";
}
set_usrp_time_from_localtime();