diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/ConfigParser.cpp | 27 | ||||
| -rw-r--r-- | src/ConfigParser.h | 4 | ||||
| -rw-r--r-- | src/DabMod.cpp | 71 | ||||
| -rw-r--r-- | src/EtiReader.cpp | 34 | ||||
| -rw-r--r-- | src/EtiReader.h | 17 | ||||
| -rw-r--r-- | src/FicSource.cpp | 69 | ||||
| -rw-r--r-- | src/FicSource.h | 21 | ||||
| -rw-r--r-- | src/FormatConverter.cpp | 27 | ||||
| -rw-r--r-- | src/FormatConverter.h | 5 | ||||
| -rw-r--r-- | src/ModPlugin.h | 4 | ||||
| -rw-r--r-- | src/OutputFile.cpp | 31 | ||||
| -rw-r--r-- | src/TimestampDecoder.cpp | 32 | ||||
| -rw-r--r-- | src/TimestampDecoder.h | 4 | ||||
| -rwxr-xr-x | src/output/BladeRF.cpp | 2 | ||||
| -rwxr-xr-x | src/output/BladeRF.h | 4 | ||||
| -rw-r--r-- | src/output/Dexter.cpp | 427 | ||||
| -rw-r--r-- | src/output/Dexter.h | 105 | ||||
| -rw-r--r-- | src/output/Feedback.cpp | 2 | ||||
| -rw-r--r-- | src/output/Feedback.h | 2 | ||||
| -rw-r--r-- | src/output/Lime.cpp | 2 | ||||
| -rw-r--r-- | src/output/Lime.h | 2 | ||||
| -rw-r--r-- | src/output/SDR.cpp | 15 | ||||
| -rw-r--r-- | src/output/SDR.h | 2 | ||||
| -rw-r--r-- | src/output/SDRDevice.h | 5 | ||||
| -rw-r--r-- | src/output/Soapy.cpp | 2 | ||||
| -rw-r--r-- | src/output/Soapy.h | 2 | ||||
| -rw-r--r-- | src/output/UHD.cpp | 2 | ||||
| -rw-r--r-- | src/output/UHD.h | 2 | ||||
| -rw-r--r-- | src/output/USRPTime.cpp | 9 | 
29 files changed, 778 insertions, 153 deletions
| 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(); | 
