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.cpp27
-rw-r--r--src/ConfigParser.h4
-rw-r--r--src/DabMod.cpp71
-rw-r--r--src/EtiReader.cpp34
-rw-r--r--src/EtiReader.h17
-rw-r--r--src/FicSource.cpp69
-rw-r--r--src/FicSource.h21
-rw-r--r--src/FormatConverter.cpp27
-rw-r--r--src/FormatConverter.h5
-rw-r--r--src/ModPlugin.h4
-rw-r--r--src/OutputFile.cpp31
-rw-r--r--src/TimestampDecoder.cpp32
-rw-r--r--src/TimestampDecoder.h4
-rwxr-xr-xsrc/output/BladeRF.cpp2
-rwxr-xr-xsrc/output/BladeRF.h4
-rw-r--r--src/output/Dexter.cpp427
-rw-r--r--src/output/Dexter.h105
-rw-r--r--src/output/Feedback.cpp2
-rw-r--r--src/output/Feedback.h2
-rw-r--r--src/output/Lime.cpp2
-rw-r--r--src/output/Lime.h2
-rw-r--r--src/output/SDR.cpp15
-rw-r--r--src/output/SDR.h2
-rw-r--r--src/output/SDRDevice.h5
-rw-r--r--src/output/Soapy.cpp2
-rw-r--r--src/output/Soapy.h2
-rw-r--r--src/output/UHD.cpp2
-rw-r--r--src/output/UHD.h2
-rw-r--r--src/output/USRPTime.cpp9
32 files changed, 798 insertions, 156 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..9190c60 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;
@@ -385,7 +409,7 @@ static void parse_configfile(
}
-#if defined(HAVE_OUTPUT_UHD)
+#if defined(HAVE_OUTPUT_UHD) || defined(HAVE_DEXTER)
mod_settings.sdr_device_config.enableSync = (pt.GetInteger("delaymanagement.synchronous", 0) == 1);
mod_settings.sdr_device_config.muteNoTimestamps = (pt.GetInteger("delaymanagement.mutenotimestamps", 0) == 1);
if (mod_settings.sdr_device_config.enableSync) {
@@ -406,7 +430,6 @@ static void parse_configfile(
throw std::runtime_error("Configuration error");
}
}
-
#endif
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..278f8ce 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"
@@ -114,7 +115,7 @@ enum class run_modulator_state_t {
reconfigure // Some sort of change of configuration we cannot handle happened
};
-static run_modulator_state_t run_modulator(modulator_data& m);
+static run_modulator_state_t run_modulator(const mod_settings_t& mod_settings, modulator_data& m);
static void printModSettings(const mod_settings_t& mod_settings)
{
@@ -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);
@@ -410,6 +426,10 @@ int launch_modulator(int argc, char* argv[])
if (format_converter) {
flowgraph.connect(modulator, format_converter);
flowgraph.connect(format_converter, output);
+
+ if (auto o = dynamic_pointer_cast<Output::SDR>(output)) {
+ o->set_sample_size(format_converter->get_format_size());
+ }
}
else {
flowgraph.connect(modulator, output);
@@ -419,7 +439,7 @@ int launch_modulator(int argc, char* argv[])
etiLog.level(info) << inputReader->GetPrintableInfo();
}
- run_modulator_state_t st = run_modulator(m);
+ run_modulator_state_t st = run_modulator(mod_settings, m);
etiLog.log(trace, "DABMOD,run_modulator() = %d", st);
switch (st) {
@@ -489,12 +509,13 @@ struct zmq_input_timeout : public std::exception
}
};
-static run_modulator_state_t run_modulator(modulator_data& m)
+static run_modulator_state_t run_modulator(const mod_settings_t& mod_settings, modulator_data& m)
{
auto ret = run_modulator_state_t::failure;
try {
int last_eti_fct = -1;
auto last_frame_received = chrono::steady_clock::now();
+ frame_timestamp ts;
Buffer data;
if (m.inputReader) {
data.setLength(6144);
@@ -568,6 +589,7 @@ static run_modulator_state_t run_modulator(modulator_data& m)
fct = m.etiReader->getFct();
fp = m.etiReader->getFp();
+ ts = m.ediInput->ediReader.getTimestamp();
}
else if (m.ediInput) {
while (running and not m.ediInput->ediReader.isFrameReady()) {
@@ -596,9 +618,10 @@ static run_modulator_state_t run_modulator(modulator_data& m)
fct = m.ediInput->ediReader.getFct();
fp = m.ediInput->ediReader.getFp();
+ ts = m.ediInput->ediReader.getTimestamp();
}
- const unsigned expected_fct = (last_eti_fct + 1) % 250;
+ bool fct_good = false;
if (last_eti_fct == -1) {
if (fp != 0) {
// Do not start the flowgraph before we get to FP 0
@@ -609,19 +632,37 @@ static run_modulator_state_t run_modulator(modulator_data& m)
continue;
}
else {
- last_eti_fct = fct;
- m.framecount++;
- m.flowgraph->run();
+ fct_good = true;
}
}
- else if (fct == expected_fct) {
+ else {
+ const unsigned expected_fct = (last_eti_fct + 1) % 250;
+ if (fct == expected_fct) {
+ fct_good = true;
+ }
+ else {
+ etiLog.level(info) << "ETI FCT discontinuity, expected " <<
+ expected_fct << " received " << fct;
+ if (m.ediInput) {
+ m.ediInput->ediReader.clearFrame();
+ }
+ return run_modulator_state_t::again;
+ }
+ }
+
+ // timestamp is good if we run unsynchronised, or if it's in the future
+ bool ts_good = not mod_settings.sdr_device_config.enableSync or
+ (ts.timestamp_valid and ts.offset_to_system_time() > 0);
+
+ if (fct_good and ts_good) {
last_eti_fct = fct;
m.framecount++;
m.flowgraph->run();
}
else {
- etiLog.level(info) << "ETI FCT discontinuity, expected " <<
- expected_fct << " received " << fct;
+ etiLog.level(warn) << "Skipping frame " << fct << " FCT " <<
+ (fct_good ? "good" : "bad") << " TS " <<
+ (ts_good ? "good" : "bad");
if (m.ediInput) {
m.ediInput->ediReader.clearFrame();
}
diff --git a/src/EtiReader.cpp b/src/EtiReader.cpp
index d1c7622..e992e62 100644
--- a/src/EtiReader.cpp
+++ b/src/EtiReader.cpp
@@ -78,6 +78,11 @@ unsigned EtiReader::getFct()
return eti_fc.FCT;
}
+frame_timestamp EtiReader::getTimestamp()
+{
+ return myTimestampDecoder.getTimestamp();
+}
+
const std::vector<std::shared_ptr<SubchannelSource> > EtiReader::getSubchannels() const
{
@@ -278,22 +283,15 @@ int EtiReader::loadEtiData(const Buffer& dataIn)
return dataIn.getLength() - input_size;
}
-bool EtiReader::sourceContainsTimestamp()
-{
- return (ntohl(eti_tist.TIST) & 0xFFFFFF) != 0xFFFFFF;
- /* See ETS 300 799, Annex C.2.2 */
-}
-
uint32_t EtiReader::getPPSOffset()
{
- if (!sourceContainsTimestamp()) {
- //fprintf(stderr, "****** SOURCE NO TS\n");
+ const uint32_t timestamp = ntohl(eti_tist.TIST) & 0xFFFFFF;
+
+ /* See ETS 300 799, Annex C.2.2 */
+ if (timestamp == 0xFFFFFF) {
return 0.0;
}
- uint32_t timestamp = ntohl(eti_tist.TIST) & 0xFFFFFF;
- //fprintf(stderr, "****** TIST 0x%x\n", timestamp);
-
return timestamp;
}
@@ -329,6 +327,11 @@ unsigned EdiReader::getFct()
return m_fc.fct();
}
+frame_timestamp EdiReader::getTimestamp()
+{
+ return m_timestamp_decoder.getTimestamp();
+}
+
const std::vector<std::shared_ptr<SubchannelSource> > EdiReader::getSubchannels() const
{
std::vector<std::shared_ptr<SubchannelSource> > sources;
@@ -346,15 +349,6 @@ const std::vector<std::shared_ptr<SubchannelSource> > EdiReader::getSubchannels(
return sources;
}
-bool EdiReader::sourceContainsTimestamp()
-{
- if (not (m_frameReady and m_fc_valid)) {
- throw std::runtime_error("Trying to get timestamp before it is ready");
- }
-
- return m_fc.tsta != 0xFFFFFF;
-}
-
bool EdiReader::isFrameReady()
{
return m_frameReady;
diff --git a/src/EtiReader.h b/src/EtiReader.h
index d97acf6..fb2c84c 100644
--- a/src/EtiReader.h
+++ b/src/EtiReader.h
@@ -59,8 +59,8 @@ public:
/* Get the current Frame Count */
virtual unsigned getFct() = 0;
- /* Returns true if we have valid time stamps in the ETI*/
- virtual bool sourceContainsTimestamp() = 0;
+ /* Returns current Timestamp */
+ virtual frame_timestamp getTimestamp() = 0;
/* Return the FIC source to be used for modulation */
virtual std::shared_ptr<FicSource>& getFic(void);
@@ -97,18 +97,17 @@ class EtiReader : public EtiSource
public:
EtiReader(double& tist_offset_s);
- virtual unsigned getMode();
- virtual unsigned getFp();
- virtual unsigned getFct();
+ virtual unsigned getMode() override;
+ virtual unsigned getFp() override;
+ virtual unsigned getFct() override;
+ virtual frame_timestamp getTimestamp() override;
/* Read ETI data from dataIn. Returns the number of bytes
* read from the buffer.
*/
int loadEtiData(const Buffer& dataIn);
- virtual bool sourceContainsTimestamp();
-
- virtual const std::vector<std::shared_ptr<SubchannelSource> > getSubchannels() const;
+ virtual const std::vector<std::shared_ptr<SubchannelSource> > getSubchannels() const override;
private:
/* Transform the ETI TIST to a PPS offset in units of 1/16384000 s */
@@ -141,7 +140,7 @@ public:
virtual unsigned getMode() override;
virtual unsigned getFp() override;
virtual unsigned getFct() override;
- virtual bool sourceContainsTimestamp() override;
+ virtual frame_timestamp getTimestamp() override;
virtual const std::vector<std::shared_ptr<SubchannelSource> > getSubchannels() const override;
virtual bool isFrameReady(void);
diff --git a/src/FicSource.cpp b/src/FicSource.cpp
index 2b95085..d824058 100644
--- a/src/FicSource.cpp
+++ b/src/FicSource.cpp
@@ -2,7 +2,7 @@
Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011 Her Majesty
the Queen in Right of Canada (Communications Research Center Canada)
- Copyright (C) 2018
+ Copyright (C) 2022
Matthias P. Braendli, matthias.braendli@mpb.li
http://opendigitalradio.org
@@ -27,7 +27,6 @@
#include "FicSource.h"
#include "PcDebug.h"
#include "Log.h"
-#include "TimestampDecoder.h"
#include <stdexcept>
#include <string>
@@ -36,46 +35,45 @@
#include <string.h>
-const std::vector<PuncturingRule>& FicSource::get_rules()
-{
- return d_puncturing_rules;
-}
-
-
FicSource::FicSource(unsigned ficf, unsigned mid) :
ModInput()
{
// PDEBUG("FicSource::FicSource(...)\n");
// PDEBUG(" Start address: %i\n", d_start_address);
-// PDEBUG(" Framesize: %i\n", d_framesize);
+// PDEBUG(" Framesize: %i\n", m_framesize);
// PDEBUG(" Protection: %i\n", d_protection);
if (ficf == 0) {
- d_framesize = 0;
- d_buffer.setLength(0);
+ m_buffer.setLength(0);
return;
}
if (mid == 3) {
- d_framesize = 32 * 4;
- d_puncturing_rules.emplace_back(29 * 16, 0xeeeeeeee);
- d_puncturing_rules.emplace_back(3 * 16, 0xeeeeeeec);
+ m_framesize = 32 * 4;
+ m_puncturing_rules.emplace_back(29 * 16, 0xeeeeeeee);
+ m_puncturing_rules.emplace_back(3 * 16, 0xeeeeeeec);
} else {
- d_framesize = 24 * 4;
- d_puncturing_rules.emplace_back(21 * 16, 0xeeeeeeee);
- d_puncturing_rules.emplace_back(3 * 16, 0xeeeeeeec);
+ m_framesize = 24 * 4;
+ m_puncturing_rules.emplace_back(21 * 16, 0xeeeeeeee);
+ m_puncturing_rules.emplace_back(3 * 16, 0xeeeeeeec);
}
- d_buffer.setLength(d_framesize);
+ m_buffer.setLength(m_framesize);
+}
+
+size_t FicSource::getFramesize() const
+{
+ return m_framesize;
}
-size_t FicSource::getFramesize()
+const std::vector<PuncturingRule>& FicSource::get_rules() const
{
- return d_framesize;
+ return m_puncturing_rules;
}
+
void FicSource::loadFicData(const Buffer& fic)
{
- d_buffer = fic;
+ m_buffer = fic;
}
int FicSource::process(Buffer* outputData)
@@ -83,34 +81,31 @@ int FicSource::process(Buffer* outputData)
PDEBUG("FicSource::process (outputData: %p, outputSize: %zu)\n",
outputData, outputData->getLength());
- if (d_buffer.getLength() != d_framesize) {
+ if (m_buffer.getLength() != m_framesize) {
throw std::runtime_error(
- "ERROR: FicSource::process.outputSize != d_framesize: " +
- std::to_string(d_buffer.getLength()) + " != " +
- std::to_string(d_framesize));
+ "ERROR: FicSource::process.outputSize != m_framesize: " +
+ std::to_string(m_buffer.getLength()) + " != " +
+ std::to_string(m_framesize));
}
- *outputData = d_buffer;
+ *outputData = m_buffer;
return outputData->getLength();
}
-void FicSource::loadTimestamp(const std::shared_ptr<struct frame_timestamp>& ts)
+void FicSource::loadTimestamp(const frame_timestamp& ts)
{
- d_ts = ts;
+ m_ts_valid = true;
+ m_ts = ts;
}
-
meta_vec_t FicSource::process_metadata(const meta_vec_t& metadataIn)
{
- if (not d_ts) {
- return {};
- }
-
- using namespace std;
meta_vec_t md_vec;
- flowgraph_metadata meta;
- meta.ts = d_ts;
- md_vec.push_back(meta);
+ if (m_ts_valid) {
+ flowgraph_metadata meta;
+ meta.ts = m_ts;
+ md_vec.push_back(meta);
+ }
return md_vec;
}
diff --git a/src/FicSource.h b/src/FicSource.h
index 93c1a7f..01dba2d 100644
--- a/src/FicSource.h
+++ b/src/FicSource.h
@@ -2,7 +2,7 @@
Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011 Her Majesty
the Queen in Right of Canada (Communications Research Center Canada)
- Copyright (C) 2016
+ Copyright (C) 2022
Matthias P. Braendli, matthias.braendli@mpb.li
http://opendigitalradio.org
@@ -33,6 +33,7 @@
#include "PuncturingRule.h"
#include "Eti.h"
#include "ModPlugin.h"
+#include "TimestampDecoder.h"
#include <vector>
#include <sys/types.h>
@@ -41,21 +42,21 @@ class FicSource : public ModInput, public ModMetadata
public:
FicSource(unsigned ficf, unsigned mid);
- size_t getFramesize();
- const std::vector<PuncturingRule>& get_rules();
+ size_t getFramesize() const;
+ const std::vector<PuncturingRule>& get_rules() const;
void loadFicData(const Buffer& fic);
int process(Buffer* outputData) override;
const char* name() override { return "FicSource"; }
- void loadTimestamp(const std::shared_ptr<struct frame_timestamp>& ts);
- virtual meta_vec_t process_metadata(
- const meta_vec_t& metadataIn) override;
+ void loadTimestamp(const frame_timestamp& ts);
+ virtual meta_vec_t process_metadata(const meta_vec_t& metadataIn) override;
private:
- size_t d_framesize;
- Buffer d_buffer;
- std::shared_ptr<struct frame_timestamp> d_ts;
- std::vector<PuncturingRule> d_puncturing_rules;
+ size_t m_framesize = 0;
+ Buffer m_buffer;
+ frame_timestamp m_ts;
+ bool m_ts_valid = false;
+ std::vector<PuncturingRule> m_puncturing_rules;
};
diff --git a/src/FormatConverter.cpp b/src/FormatConverter.cpp
index 0f86d42..cda8a4d 100644
--- a/src/FormatConverter.cpp
+++ b/src/FormatConverter.cpp
@@ -2,7 +2,7 @@
Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011 Her Majesty
the Queen in Right of Canada (Communications Research Center Canada)
- Copyright (C) 2017
+ Copyright (C) 2022
Matthias P. Braendli, matthias.braendli@mpb.li
http://opendigitalradio.org
@@ -34,10 +34,6 @@
#include <stdexcept>
#include <assert.h>
-#ifdef __SSE__
-# include <xmmintrin.h>
-#endif
-
FormatConverter::FormatConverter(const std::string& format) :
ModCodec(),
m_format(format)
@@ -68,7 +64,7 @@ int FormatConverter::process(Buffer* const dataIn, Buffer* dataOut)
out[i] = in[i] + 128;
}
}
- else {
+ else if (m_format == "s8") {
dataOut->setLength(sizeIn * sizeof(int8_t));
int8_t* out = reinterpret_cast<int8_t*>(dataOut->getData());
@@ -76,6 +72,9 @@ int FormatConverter::process(Buffer* const dataIn, Buffer* dataOut)
out[i] = in[i];
}
}
+ else {
+ throw std::runtime_error("FormatConverter: Invalid format " + m_format);
+ }
return dataOut->getLength();
}
@@ -85,3 +84,19 @@ const char* FormatConverter::name()
return "FormatConverter";
}
+size_t FormatConverter::get_format_size() const
+{
+ // Returns 2*sizeof(SAMPLE_TYPE) because we have I + Q
+ if (m_format == "s16") {
+ return 4;
+ }
+ else if (m_format == "u8") {
+ return 2;
+ }
+ else if (m_format == "s8") {
+ return 2;
+ }
+ else {
+ throw std::runtime_error("FormatConverter: Invalid format " + m_format);
+ }
+}
diff --git a/src/FormatConverter.h b/src/FormatConverter.h
index cc8a606..ceb2e17 100644
--- a/src/FormatConverter.h
+++ b/src/FormatConverter.h
@@ -2,7 +2,7 @@
Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011 Her Majesty
the Queen in Right of Canada (Communications Research Center Canada)
- Copyright (C) 2017
+ Copyright (C) 2022
Matthias P. Braendli, matthias.braendli@mpb.li
http://opendigitalradio.org
@@ -40,11 +40,14 @@
class FormatConverter : public ModCodec
{
public:
+ // Allowed formats: s8, u8 and s16
FormatConverter(const std::string& format);
int process(Buffer* const dataIn, Buffer* dataOut);
const char* name();
+ size_t get_format_size() const;
+
private:
std::string m_format;
};
diff --git a/src/ModPlugin.h b/src/ModPlugin.h
index 7f03618..470508f 100644
--- a/src/ModPlugin.h
+++ b/src/ModPlugin.h
@@ -32,6 +32,7 @@
#include "Buffer.h"
#include "ThreadsafeQueue.h"
+#include "TimestampDecoder.h"
#include <cstddef>
#include <vector>
#include <memory>
@@ -41,9 +42,8 @@
// All flowgraph elements derive from ModPlugin, or a variant of it.
// Some ModPlugins also support handling metadata.
-struct frame_timestamp;
struct flowgraph_metadata {
- std::shared_ptr<struct frame_timestamp> ts;
+ frame_timestamp ts;
};
using meta_vec_t = std::vector<flowgraph_metadata>;
diff --git a/src/OutputFile.cpp b/src/OutputFile.cpp
index acaebad..2ee838c 100644
--- a/src/OutputFile.cpp
+++ b/src/OutputFile.cpp
@@ -74,28 +74,23 @@ meta_vec_t OutputFile::process_metadata(const meta_vec_t& metadataIn)
frame_timestamp first_ts;
for (const auto& md : metadataIn) {
- if (md.ts) {
- // The following code assumes TM I, where we get called every 96ms.
- // Support for other transmission modes skipped because this is mostly
- // debugging code.
+ // The following code assumes TM I, where we get called every 96ms.
+ // Support for other transmission modes skipped because this is mostly
+ // debugging code.
- if (md.ts->fp == 0 or md.ts->fp == 4) {
- first_ts = *md.ts;
- }
+ if (md.ts.fp == 0 or md.ts.fp == 4) {
+ first_ts = md.ts;
+ }
- ss << " FCT=" << md.ts->fct <<
- " FP=" << (int)md.ts->fp;
- if (md.ts->timestamp_valid) {
- ss << " TS=" << md.ts->timestamp_sec << " + " <<
- std::fixed
- << (double)md.ts->timestamp_pps / 163840000.0 << ";";
- }
- else {
- ss << " TS invalid;";
- }
+ ss << " FCT=" << md.ts.fct <<
+ " FP=" << (int)md.ts.fp;
+ if (md.ts.timestamp_valid) {
+ ss << " TS=" << md.ts.timestamp_sec << " + " <<
+ std::fixed
+ << (double)md.ts.timestamp_pps / 163840000.0 << ";";
}
else {
- ss << " void, ";
+ ss << " TS invalid;";
}
}
diff --git a/src/TimestampDecoder.cpp b/src/TimestampDecoder.cpp
index 3cfa0cc..54a5817 100644
--- a/src/TimestampDecoder.cpp
+++ b/src/TimestampDecoder.cpp
@@ -36,6 +36,20 @@
//#define MDEBUG(fmt, args...) fprintf (LOG, "*****" fmt , ## args)
#define MDEBUG(fmt, args...) PDEBUG(fmt, ## args)
+double frame_timestamp::offset_to_system_time() const
+{
+ if (not timestamp_valid) {
+ throw new std::runtime_error("Cannot calculate offset for invalid timestamp");
+ }
+
+ struct timespec t;
+ if (clock_gettime(CLOCK_REALTIME, &t) != 0) {
+ throw std::runtime_error(std::string("Failed to retrieve CLOCK_REALTIME") + strerror(errno));
+ }
+
+ return get_real_secs() - (double)t.tv_sec - (t.tv_nsec / 1000000000.0);
+}
+
frame_timestamp& frame_timestamp::operator+=(const double& diff)
{
double offset_pps, offset_secs;
@@ -75,20 +89,20 @@ TimestampDecoder::TimestampDecoder(double& offset_s) :
timestamp_offset << " offset";
}
-std::shared_ptr<frame_timestamp> TimestampDecoder::getTimestamp()
+frame_timestamp TimestampDecoder::getTimestamp()
{
- auto ts = std::make_shared<frame_timestamp>();
+ frame_timestamp ts;
- ts->timestamp_valid = full_timestamp_received;
- ts->timestamp_sec = time_secs;
- ts->timestamp_pps = time_pps;
- ts->fct = latestFCT;
- ts->fp = latestFP;
+ ts.timestamp_valid = full_timestamp_received;
+ ts.timestamp_sec = time_secs;
+ ts.timestamp_pps = time_pps;
+ ts.fct = latestFCT;
+ ts.fp = latestFP;
- ts->timestamp_refresh = offset_changed;
+ ts.timestamp_refresh = offset_changed;
offset_changed = false;
- *ts += timestamp_offset;
+ ts += timestamp_offset;
return ts;
}
diff --git a/src/TimestampDecoder.h b/src/TimestampDecoder.h
index d083061..3616bab 100644
--- a/src/TimestampDecoder.h
+++ b/src/TimestampDecoder.h
@@ -56,6 +56,8 @@ struct frame_timestamp
return timestamp_pps / 16384000.0;
}
+ double offset_to_system_time() const;
+
double get_real_secs() const {
double t = timestamp_sec;
t += pps_offset();
@@ -93,7 +95,7 @@ class TimestampDecoder : public RemoteControllable
*/
TimestampDecoder(double& offset_s);
- std::shared_ptr<frame_timestamp> getTimestamp(void);
+ frame_timestamp getTimestamp(void);
/* Update timestamp data from ETI */
void updateTimestampEti(
diff --git a/src/output/BladeRF.cpp b/src/output/BladeRF.cpp
index a6ad0cc..dd48736 100755
--- a/src/output/BladeRF.cpp
+++ b/src/output/BladeRF.cpp
@@ -269,7 +269,7 @@ 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
diff --git a/src/output/BladeRF.h b/src/output/BladeRF.h
index bc6db38..e048daa 100755
--- a/src/output/BladeRF.h
+++ b/src/output/BladeRF.h
@@ -83,7 +83,7 @@ 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
@@ -109,4 +109,4 @@ class BladeRF : public Output::SDRDevice
} // 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..e4f672b
--- /dev/null
+++ b/src/output/Dexter.cpp
@@ -0,0 +1,427 @@
+/*
+ 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 uint64_t DSP_CLOCK = 2048000uLL * 80;
+
+static constexpr size_t TRANSMISSION_FRAME_LEN = (2656 + 76 * 2552) * 4;
+static constexpr size_t IIO_BUFFERS = 4;
+static constexpr size_t IIO_BUFFER_LEN = TRANSMISSION_FRAME_LEN / 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 (!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);
+ }
+
+ 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
+
+ // 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;
+
+ // Reset start_clks
+ 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);
+ }
+
+ // 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, IIO_BUFFER_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 = 0;
+ rs.num_late_packets = num_late;
+ rs.num_frames_modulated = num_frames_modulated;
+ return rs;
+}
+
+
+double Dexter::get_real_secs(void) const
+{
+ struct timespec time_now;
+ fill_time(&time_now);
+ return (double)time_now.tv_sec + time_now.tv_nsec / 1000000000.0;
+
+ /* We don't use actual device time, because we only have clock counter on pps edge available, not
+ * current clock counter. */
+#if 0
+ long long pps_clks = 0;
+ int r = 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");
+ }
+
+ return (double)m_utc_seconds_at_startup + (double)(pps_clks - m_clock_count_at_startup) / (double)DSP_CLOCK;
+#endif
+}
+
+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() 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)
+{
+ 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");
+ }
+
+ const bool has_time_spec = (m_conf.enableSync and frame.ts.timestamp_valid);
+
+ if (has_time_spec) {
+ /*
+ uint64_t timeS = frame.ts.timestamp_sec;
+ etiLog.level(debug) << "Dexter: TS S " << timeS << " - " << m_utc_seconds_at_startup << " = " <<
+ timeS - m_utc_seconds_at_startup;
+ */
+
+ // 10 because timestamp_pps is represented in 16.384 MHz clocks
+ constexpr uint64_t TIMESTAMP_PPS_PER_DSP_CLOCKS = DSP_CLOCK / 16384000;
+ uint64_t frame_ts_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;
+
+ long long pps_clks = 0;
+ int r;
+ 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);
+ }
+
+ etiLog.level(debug) << "Dexter: 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_ts_clocks << " DELTA " <<
+ frame_ts_clocks << " - " << pps_clks << " = " <<
+ (double)((int64_t)frame_ts_clocks - pps_clks) / DSP_CLOCK;
+
+ // Ensure we hand the frame over to HW at least 0.1s before timestamp
+ if (((int64_t)frame_ts_clocks - pps_clks) < (int64_t)DSP_CLOCK / 10) {
+ etiLog.level(warn) << "Skip frame short margin";
+ num_late++;
+ return;
+ }
+
+
+ if ((r = iio_device_attr_write_longlong(m_dexter_dsp_tx, "stream0_start_clks", frame_ts_clocks)) != 0) {
+ etiLog.level(warn) << "Skip frame, failed to set dexter_dsp_tx.stream0_start_clks = " << frame_ts_clocks << " : " << get_iio_error(r);
+ num_late++;
+ return;
+ }
+ }
+
+ // 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());
+
+ for (size_t i = 0; i < IIO_BUFFERS; i++) {
+ constexpr size_t buflen = TRANSMISSION_FRAME_LEN / IIO_BUFFERS;
+
+ 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);
+ }
+ }
+
+ num_frames_modulated++;
+
+ long long attr_value = 0;
+ int r = 0;
+
+ if ((r = iio_device_attr_read_longlong(m_dexter_dsp_tx, "buffer_underflows0", &attr_value)) == 0) {
+ if ((size_t)attr_value != underflows and underflows != 0) {
+ etiLog.level(warn) << "Dexter: underflow! " << underflows << " -> " << 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..5418b73
--- /dev/null
+++ b/src/output/Dexter.h
@@ -0,0 +1,105 @@
+/*
+ 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 <ctime>
+
+#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,
+ 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 num_late = 0;
+ size_t num_frames_modulated = 0;
+
+ uint64_t m_utc_seconds_at_startup;
+ uint64_t m_clock_count_at_startup = 0;
+ uint64_t m_clock_count_frame = 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 6f7eed5..d3e4640 100644
--- a/src/output/Lime.cpp
+++ b/src/output/Lime.cpp
@@ -353,7 +353,7 @@ 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
diff --git a/src/output/Lime.h b/src/output/Lime.h
index 72a018e..a4603c0 100644
--- a/src/output/Lime.h
+++ b/src/output/Lime.h
@@ -75,7 +75,7 @@ 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
diff --git a/src/output/SDR.cpp b/src/output/SDR.cpp
index 6078fc7..f1ed2b0 100644
--- a/src/output/SDR.cpp
+++ b/src/output/SDR.cpp
@@ -104,6 +104,12 @@ SDR::~SDR()
}
}
+void SDR::set_sample_size(size_t size)
+{
+ etiLog.level(debug) << "Setting sample size to " << size;
+ m_size = size;
+}
+
int SDR::process(Buffer *dataIn)
{
if (not m_running) {
@@ -125,6 +131,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 +145,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
@@ -261,8 +268,6 @@ 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;
@@ -298,7 +303,7 @@ void SDR::handle_frame(struct FrameData& frame)
}
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)
@@ -337,7 +342,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 <<
diff --git a/src/output/SDR.h b/src/output/SDR.h
index ee89243..4dfde73 100644
--- a/src/output/SDR.h
+++ b/src/output/SDR.h
@@ -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;
@@ -75,6 +76,7 @@ class SDR : public ModOutput, public ModMetadata, public RemoteControllable {
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;
diff --git a/src/output/SDRDevice.h b/src/output/SDRDevice.h
index bb63f60..0bba74a 100644
--- a/src/output/SDRDevice.h
+++ b/src/output/SDRDevice.h
@@ -98,10 +98,11 @@ 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;
};
@@ -132,7 +133,7 @@ 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
diff --git a/src/output/Soapy.cpp b/src/output/Soapy.cpp
index f138e9a..50f91a4 100644
--- a/src/output/Soapy.cpp
+++ b/src/output/Soapy.cpp
@@ -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;
diff --git a/src/output/Soapy.h b/src/output/Soapy.h
index 4ee53ca..ca2618b 100644
--- a/src/output/Soapy.h
+++ b/src/output/Soapy.h
@@ -74,7 +74,7 @@ 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
diff --git a/src/output/UHD.cpp b/src/output/UHD.cpp
index 3cf5aef..9b22dde 100644
--- a/src/output/UHD.cpp
+++ b/src/output/UHD.cpp
@@ -411,7 +411,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(
diff --git a/src/output/UHD.h b/src/output/UHD.h
index 29867fb..4c1a4f0 100644
--- a/src/output/UHD.h
+++ b/src/output/UHD.h
@@ -88,7 +88,7 @@ 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
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();