aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Makefile.am2
-rw-r--r--configure.ac14
-rw-r--r--doc/example.ini7
-rw-r--r--src/ConfigParser.cpp24
-rw-r--r--src/ConfigParser.h4
-rw-r--r--src/DabMod.cpp26
-rw-r--r--src/output/Dexter.cpp301
-rw-r--r--src/output/Dexter.h101
8 files changed, 469 insertions, 10 deletions
diff --git a/Makefile.am b/Makefile.am
index 39280fb..46f2c21 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -138,6 +138,8 @@ odr_dabmod_SOURCES += \
src/output/SDR.cpp \
src/output/SDR.h \
src/output/SDRDevice.h \
+ src/output/Dexter.cpp \
+ src/output/Dexter.h \
src/output/Soapy.cpp \
src/output/Soapy.h \
src/output/UHD.cpp \
diff --git a/configure.ac b/configure.ac
index 7590896..9b45a89 100644
--- a/configure.ac
+++ b/configure.ac
@@ -55,6 +55,9 @@ AC_ARG_ENABLE([native],
AC_ARG_ENABLE([easydabv3],
[AS_HELP_STRING([--enable-easydabv3], [Build for EasyDABv3 board])],
[], [enable_easydabv3=no])
+AC_ARG_ENABLE([dexter],
+ [AS_HELP_STRING([--enable-dexter], [Build for PrecisionWave Dexter board])],
+ [], [enable_dexter=no])
AC_ARG_ENABLE([limesdr],
[AS_HELP_STRING([--enable-limesdr], [Build for LimeSDR board])],
[], [enable_limesdr=no])
@@ -113,13 +116,17 @@ AS_IF([test "x$enable_limesdr" = "xyes"],
[AC_CHECK_LIB([LimeSuite], [LMS_Init], [LIMESDR_LIBS="-lLimeSuite"],
[AC_MSG_ERROR([LimeSDR LimeSuite is required])])])
+AS_IF([test "x$enable_dexter" = "xyes"],
+ [AC_CHECK_LIB([iio], [iio_create_scan_context], [IIO_LIBS="-liio"],
+ [AC_MSG_ERROR([libiio is required])])])
+
AS_IF([test "x$enable_bladerf" = "xyes"],
[AC_CHECK_LIB([bladeRF], [bladerf_open], [BLADERF_LIBS="-lbladeRF"],
[AC_MSG_ERROR([BladeRF library is required])])])
AC_SUBST([CFLAGS], ["$CFLAGS $EXTRA $FFTW_CFLAGS $SOAPYSDR_CFLAGS $PTHREAD_CFLAGS"])
AC_SUBST([CXXFLAGS], ["$CXXFLAGS $EXTRA $FFTW_CFLAGS $SOAPYSDR_CFLAGS $PTHREAD_CFLAGS"])
-AC_SUBST([LIBS], ["$FFTW_LIBS $SOAPYSDR_LIBS $PTHREAD_LIBS $ZMQ_LIBS $LIMESDR_LIBS $BLADERF_LIBS"])
+AC_SUBST([LIBS], ["$FFTW_LIBS $SOAPYSDR_LIBS $PTHREAD_LIBS $ZMQ_LIBS $LIMESDR_LIBS $IIO_LIBS $BLADERF_LIBS"])
AS_IF([test "x$enable_easydabv3" = "xyes" && test "x$enable_output_uhd" == "xyes"],
AC_MSG_ERROR([Cannot enable both EasyDABv3 and UHD output]))
@@ -144,6 +151,9 @@ AS_IF([test "x$enable_soapysdr" = "xyes"],
AS_IF([test "x$enable_limesdr" = "xyes"],
[AC_DEFINE(HAVE_LIMESDR, [1], [Define if LimeSDR output is enabled]) ])
+AS_IF([test "x$enable_dexter" = "xyes"],
+ [AC_DEFINE(HAVE_DEXTER, [1], [Define if Dexter output is enabled])])
+
AS_IF([test "x$enable_bladerf" = "xyes"],
[AC_DEFINE(HAVE_BLADERF, [1], [Define if BladeRF output is enabled]) ])
@@ -217,7 +227,7 @@ echo "***********************************************"
echo
enabled=""
disabled=""
-for feat in prof trace output_uhd zeromq soapysdr easydabv3 limesdr bladerf
+for feat in prof trace output_uhd zeromq soapysdr easydabv3 limesdr bladerf dexter
do
eval var=\$enable_$feat
AS_IF([test "x$var" = "xyes"],
diff --git a/doc/example.ini b/doc/example.ini
index aca7634..2105535 100644
--- a/doc/example.ini
+++ b/doc/example.ini
@@ -164,7 +164,7 @@ enabled=0
polycoeffile=polyCoefs
[output]
-; choose output: possible values: uhd, file, zmq, soapysdr, limesdr, bladerf
+; choose output: possible values: uhd, file, zmq, dexter, soapysdr, limesdr, bladerf
output=uhd
[fileoutput]
@@ -322,6 +322,11 @@ channel=13C
; Set to 0 to disable
;dpd_port=50055
+[dexteroutput]
+txgain=32768
+;frequency=234208000
+channel=13C
+
[limeoutput]
; Lime output directly runs against the LMS device driver. It does not support SFN nor predistortion.
device=
diff --git a/src/ConfigParser.cpp b/src/ConfigParser.cpp
index ee7acc3..44d52e6 100644
--- a/src/ConfigParser.cpp
+++ b/src/ConfigParser.cpp
@@ -312,6 +312,30 @@ static void parse_configfile(
mod_settings.useSoapyOutput = true;
}
#endif
+#if defined(HAVE_DEXTER)
+ else if (output_selected == "dexter") {
+ auto& outputdexter_conf = mod_settings.sdr_device_config;
+ outputdexter_conf.txgain = pt.GetReal("dexteroutput.txgain", 0.0);
+ outputdexter_conf.lo_offset = pt.GetReal("dexteroutput.lo_offset", 0.0);
+ outputdexter_conf.frequency = pt.GetReal("dexteroutput.frequency", 0);
+ std::string chan = pt.Get("dexteroutput.channel", "");
+ outputdexter_conf.dabMode = mod_settings.dabMode;
+
+ if (outputdexter_conf.frequency == 0 && chan == "") {
+ std::cerr << " dexter output enabled, but neither frequency nor channel defined.\n";
+ throw std::runtime_error("Configuration error");
+ }
+ else if (outputdexter_conf.frequency == 0) {
+ outputdexter_conf.frequency = parseChannel(chan);
+ }
+ else if (outputdexter_conf.frequency != 0 && chan != "") {
+ std::cerr << " dexter output: cannot define both frequency and channel.\n";
+ throw std::runtime_error("Configuration error");
+ }
+
+ mod_settings.useDexterOutput = true;
+ }
+#endif
#if defined(HAVE_LIMESDR)
else if (output_selected == "limesdr") {
auto& outputlime_conf = mod_settings.sdr_device_config;
diff --git a/src/ConfigParser.h b/src/ConfigParser.h
index 574caa2..8f2a1d2 100644
--- a/src/ConfigParser.h
+++ b/src/ConfigParser.h
@@ -51,9 +51,9 @@ struct mod_settings_t {
bool fileOutputShowMetadata = false;
bool useUHDOutput = false;
bool useSoapyOutput = false;
+ bool useDexterOutput = false;
bool useLimeOutput = false;
bool useBladeRFOutput = false;
- const std::string BladeRFOutputFormat = "s16"; // to transmit SC16 IQ
size_t outputRate = 2048000;
size_t clockRate = 0;
@@ -87,7 +87,7 @@ struct mod_settings_t {
// Settings for the OFDM windowing
size_t ofdmWindowOverlap = 0;
-#if defined(HAVE_OUTPUT_UHD) || defined(HAVE_SOAPYSDR) || defined(HAVE_LIMESDR) || defined(HAVE_BLADERF)
+#if defined(HAVE_OUTPUT_UHD) || defined(HAVE_SOAPYSDR) || defined(HAVE_LIMESDR) || defined(HAVE_BLADERF) || defined(HAVE_DEXTER)
Output::SDRDeviceConfig sdr_device_config;
#endif
diff --git a/src/DabMod.cpp b/src/DabMod.cpp
index f97c05d..15cdbaa 100644
--- a/src/DabMod.cpp
+++ b/src/DabMod.cpp
@@ -56,6 +56,7 @@
#include "output/SDR.h"
#include "output/UHD.h"
#include "output/Soapy.h"
+#include "output/Dexter.h"
#include "output/Lime.h"
#include "output/BladeRF.h"
#include "OutputZeroMQ.h"
@@ -151,6 +152,11 @@ static void printModSettings(const mod_settings_t& mod_settings)
mod_settings.sdr_device_config.masterClockRate << "\n";
}
#endif
+#if defined(HAVE_DEXTER)
+ else if (mod_settings.useDexterOutput) {
+ ss << " PrecisionWave DEXTER\n";
+ }
+#endif
#if defined(HAVE_LIMESDR)
else if (mod_settings.useLimeOutput) {
ss << " LimeSDR\n"
@@ -192,8 +198,7 @@ static void printModSettings(const mod_settings_t& mod_settings)
fprintf(stderr, "%s", ss.str().c_str());
}
-static shared_ptr<ModOutput> prepare_output(
- mod_settings_t& s)
+static shared_ptr<ModOutput> prepare_output(mod_settings_t& s)
{
shared_ptr<ModOutput> output;
@@ -249,6 +254,16 @@ static shared_ptr<ModOutput> prepare_output(
rcs.enrol((Output::SDR*)output.get());
}
#endif
+#if defined(HAVE_DEXTER)
+ else if (s.useDexterOutput) {
+ /* We normalise specifically range [-32768; 32767] */
+ s.normalise = 32767.0f / normalise_factor;
+ s.sdr_device_config.sampleRate = s.outputRate;
+ auto dexterdevice = make_shared<Output::Dexter>(s.sdr_device_config);
+ output = make_shared<Output::SDR>(s.sdr_device_config, dexterdevice);
+ rcs.enrol((Output::SDR*)output.get());
+ }
+#endif
#if defined(HAVE_LIMESDR)
else if (s.useLimeOutput) {
/* We normalise the same way as for the UHD output */
@@ -319,6 +334,7 @@ int launch_modulator(int argc, char* argv[])
mod_settings.useUHDOutput or
mod_settings.useZeroMQOutput or
mod_settings.useSoapyOutput or
+ mod_settings.useDexterOutput or
mod_settings.useLimeOutput or
mod_settings.useBladeRFOutput)) {
throw std::runtime_error("Configuration error: Output not specified");
@@ -333,9 +349,9 @@ int launch_modulator(int argc, char* argv[])
mod_settings.fileOutputFormat == "s16")) {
format_converter = make_shared<FormatConverter>(mod_settings.fileOutputFormat);
}
- else if (mod_settings.useBladeRFOutput) {
- format_converter = make_shared<FormatConverter>(mod_settings.BladeRFOutputFormat);
- }
+ else if (mod_settings.useBladeRFOutput or mod_settings.useDexterOutput) {
+ format_converter = make_shared<FormatConverter>("s16");
+ }
auto output = prepare_output(mod_settings);
diff --git a/src/output/Dexter.cpp b/src/output/Dexter.cpp
new file mode 100644
index 0000000..605c61a
--- /dev/null
+++ b/src/output/Dexter.cpp
@@ -0,0 +1,301 @@
+/*
+ Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Her Majesty the
+ Queen in Right of Canada (Communications Research Center Canada)
+
+ Copyright (C) 2022
+ 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 size_t TRANSMISSION_FRAME_LEN = (2656 + 76 * 2552) * 4;
+
+static string get_iio_error(int err)
+{
+ char dst[256];
+ iio_strerror(err, dst, sizeof(dst));
+ return string(dst);
+}
+
+Dexter::Dexter(SDRDeviceConfig& config) :
+ SDRDevice(),
+ m_conf(config)
+{
+ etiLog.level(info) << "Dexter:Creating the device";
+
+ m_ctx = iio_create_local_context();
+ if (!m_ctx) {
+ throw std::runtime_error("Dexter: Unable to create iio scan context");
+ }
+
+ m_dexter_dsp_tx = iio_context_find_device(m_ctx, "dexter_dsp_tx");
+ if (!m_dexter_dsp_tx) {
+ throw std::runtime_error("Dexter: Unable to find dexter_dsp_tx iio device");
+ }
+
+ m_ad9957_tx0 = iio_context_find_device(m_ctx, "ad9957_tx0");
+ if (!m_ad9957_tx0) {
+ throw std::runtime_error("Dexter: Unable to find ad9957_tx0 iio device");
+ }
+
+ int r;
+
+ // TODO make DC offset configurable and add to RC
+ if ((r = iio_device_attr_write_longlong(m_dexter_dsp_tx, "dc0", 0)) != 0) {
+ etiLog.level(warn) << "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) {
+ etiLog.level(warn) << "Failed to set dexter_dsp_tx.dc1 = false: " << get_iio_error(r);
+ }
+
+ 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);
+ }
+
+ if ((r = iio_device_attr_write_longlong(m_dexter_dsp_tx, "gain0", m_conf.txgain)) != 0) {
+ etiLog.level(warn) << "Failed to set dexter_dsp_tx.stream0_start_clks = 0: " << get_iio_error(r);
+ }
+
+ //iio_device_attr_read_longlong(const struct iio_device *dev, const char *attr, long long *val);
+
+ 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
+
+ // TODO: set H/W time
+
+ // Prepare streams
+ 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, TRANSMISSION_FRAME_LEN/sizeof(int16_t), 0);
+ if (!m_buffer) {
+ throw std::runtime_error("Dexter: Cannot create IIO buffer.");
+ }
+}
+
+Dexter::~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);
+ }
+
+ iio_context_destroy(m_ctx);
+ m_ctx = nullptr;
+ }
+}
+
+void Dexter::tune(double lo_offset, double frequency)
+{
+ // TODO lo_offset
+ long long freq = m_conf.frequency - 204800000;
+ int r = 0;
+
+ if ((r = iio_device_attr_write_longlong(m_dexter_dsp_tx, "frequency0", freq)) != 0) {
+ etiLog.level(warn) << "Failed to set dexter_dsp_tx.frequency0 = " << freq << " : " << get_iio_error(r);
+ }
+}
+
+double Dexter::get_tx_freq(void) const
+{
+ long long frequency = 0;
+ int r = 0;
+
+ if ((r = iio_device_attr_read_longlong(m_dexter_dsp_tx, "frequency0", &frequency)) != 0) {
+ etiLog.level(warn) << "Failed to read dexter_dsp_tx.frequency0 = " <<
+ frequency << " : " << get_iio_error(r);
+ return 0;
+ }
+ else {
+ return frequency + 204800000;
+ }
+}
+
+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.stream0_start_clks = 0: " << 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 set dexter_dsp_tx.stream0_start_clks = 0: " << 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 set dexter_dsp_tx.stream0_start_clks = 0: " << get_iio_error(r);
+ }
+ return txgain_readback;
+}
+
+void Dexter::set_bandwidth(double bandwidth)
+{
+ // TODO
+}
+
+double Dexter::get_bandwidth(void) const
+{
+ return 0;
+}
+
+SDRDevice::RunStatistics Dexter::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;
+ return rs;
+}
+
+
+double Dexter::get_real_secs(void) const
+{
+ // TODO
+ return 0;
+}
+
+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,
+ struct frame_timestamp& ts,
+ double timeout_secs)
+{
+ // TODO
+ return 0;
+}
+
+
+bool Dexter::is_clk_source_ok() const
+{
+ // TODO
+ return true;
+}
+
+const char* Dexter::device_name(void) const
+{
+ return "Dexter";
+}
+
+double Dexter::get_temperature(void) const
+{
+ // TODO
+ // XADC contains temperature, but value is weird
+ return std::numeric_limits<double>::quiet_NaN();
+}
+
+void Dexter::transmit_frame(const struct FrameData& frame)
+{
+ long long int timeNs = frame.ts.get_ns();
+ const bool has_time_spec = (m_conf.enableSync and frame.ts.timestamp_valid);
+
+ if (frame.buf.size() != TRANSMISSION_FRAME_LEN) {
+ etiLog.level(debug) << "Dexter::transmit_frame Expected " <<
+ TRANSMISSION_FRAME_LEN << " got " << frame.buf.size();
+ throw std::runtime_error("Dexter: invalid buffer size");
+ }
+
+ // 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());
+
+ memcpy(iio_buffer_start(m_buffer), frame.buf.data(), frame.buf.size());
+ ssize_t pushed = iio_buffer_push(m_buffer);
+ if (pushed < 0) {
+ etiLog.level(error) << "Dexter: failed to push buffer " << get_iio_error(pushed);
+ }
+
+ num_frames_modulated++;
+ // TODO overflows, late_packets
+
+ long long attr_value = 0;
+ int r = 0;
+
+ if ((r = iio_device_attr_read_longlong(m_dexter_dsp_tx, "buffer_underflows0", &attr_value)) == 0) {
+ fprintf(stderr, "buffer_underflows0 %lld\n", attr_value);
+ underflows = attr_value;
+ }
+}
+
+} // namespace Output
+
+#endif // HAVE_DEXTER
+
+
diff --git a/src/output/Dexter.h b/src/output/Dexter.h
new file mode 100644
index 0000000..7a7f6c1
--- /dev/null
+++ b/src/output/Dexter.h
@@ -0,0 +1,101 @@
+/*
+ Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Her Majesty the
+ Queen in Right of Canada (Communications Research Center Canada)
+
+ Copyright (C) 2022
+ 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 "output/SDR.h"
+#include "ModPlugin.h"
+#include "EtiReader.h"
+#include "RemoteControl.h"
+
+namespace Output {
+
+class Dexter : public Output::SDRDevice
+{
+ public:
+ Dexter(SDRDeviceConfig& config);
+ Dexter(const Dexter& other) = delete;
+ Dexter& operator=(const Dexter& other) = delete;
+ ~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(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 double get_real_secs(void) const override;
+
+ virtual void set_rxgain(double rxgain) override;
+ virtual double get_rxgain(void) const override;
+ virtual size_t receive_frame(
+ complexf *buf,
+ size_t num_samples,
+ struct frame_timestamp& ts,
+ double timeout_secs) override;
+
+ // Return true if GPS and reference clock inputs are ok
+ virtual bool is_clk_source_ok(void) const override;
+ virtual const char* device_name(void) const override;
+
+ virtual double get_temperature(void) const override;
+
+ private:
+ SDRDeviceConfig& m_conf;
+
+ struct iio_context* m_ctx = nullptr;
+ struct iio_device* m_dexter_dsp_tx = nullptr;
+
+ struct iio_device* m_ad9957_tx0 = nullptr;
+ struct iio_channel* m_tx_channel = nullptr;
+ struct iio_buffer *m_buffer = nullptr;
+
+ size_t underflows = 0;
+ size_t overflows = 0;
+ size_t late_packets = 0;
+ size_t num_frames_modulated = 0;
+};
+
+} // namespace Output
+
+#endif //HAVE_DEXTER
+