From 6087160593e74aff9147153c69ea23849fc8b921 Mon Sep 17 00:00:00 2001 From: "Matthias P. Braendli" Date: Wed, 27 Jul 2022 12:05:05 +0200 Subject: Add PrecisionWave DEXTER support --- Makefile.am | 2 + configure.ac | 14 ++- doc/example.ini | 7 +- src/ConfigParser.cpp | 24 ++++ src/ConfigParser.h | 4 +- src/DabMod.cpp | 26 ++++- src/output/Dexter.cpp | 301 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/output/Dexter.h | 101 +++++++++++++++++ 8 files changed, 469 insertions(+), 10 deletions(-) create mode 100644 src/output/Dexter.cpp create mode 100644 src/output/Dexter.h 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 prepare_output( - mod_settings_t& s) +static shared_ptr prepare_output(mod_settings_t& s) { shared_ptr output; @@ -249,6 +254,16 @@ static shared_ptr 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(s.sdr_device_config); + output = make_shared(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(mod_settings.fileOutputFormat); } - else if (mod_settings.useBladeRFOutput) { - format_converter = make_shared(mod_settings.BladeRFOutputFormat); - } + else if (mod_settings.useBladeRFOutput or mod_settings.useDexterOutput) { + format_converter = make_shared("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 . + */ + +#include "output/Dexter.h" + +#ifdef HAVE_DEXTER + +#include +#include +#include +#include + +#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::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(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 . + */ + +#pragma once + +#ifdef HAVE_CONFIG_H +# include +#endif + +#ifdef HAVE_DEXTER +#include "iio.h" + +#include +#include + +#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 + -- cgit v1.2.3