diff options
| -rw-r--r-- | AUTHORS | 3 | ||||
| -rw-r--r-- | Makefile.am | 4 | ||||
| -rw-r--r-- | configure.ac | 14 | ||||
| -rw-r--r-- | doc/example.ini | 19 | ||||
| -rw-r--r-- | src/ConfigParser.cpp | 30 | ||||
| -rw-r--r-- | src/ConfigParser.h | 4 | ||||
| -rw-r--r-- | src/DabMod.cpp | 22 | ||||
| -rw-r--r-- | src/OfdmGenerator.cpp | 7 | ||||
| -rw-r--r-- | src/Utils.cpp | 3 | ||||
| -rw-r--r-- | src/output/Lime.cpp | 461 | ||||
| -rw-r--r-- | src/output/Lime.h | 108 | ||||
| -rw-r--r-- | src/output/SDR.cpp | 20 | ||||
| -rw-r--r-- | src/output/SDRDevice.h | 3 | 
13 files changed, 687 insertions, 11 deletions
@@ -27,3 +27,6 @@ Sergiy  Andreas Steger      - Digital Predistortion Computation Engine + +Evariste F5OEO +    - LimeSDR integration diff --git a/Makefile.am b/Makefile.am index 9644c16..c9d632d 100644 --- a/Makefile.am +++ b/Makefile.am @@ -144,6 +144,8 @@ odr_dabmod_SOURCES += \  					  src/output/UHD.h \  					  src/output/USRPTime.cpp \  					  src/output/USRPTime.h \ +					  src/output/Lime.cpp \ +					  src/output/Lime.h \  					  src/PhaseReference.cpp \  					  src/PhaseReference.h \  					  src/QpskSymbolMapper.cpp \ @@ -167,6 +169,6 @@ odr_dabmod_SOURCES += \  					  src/TII.cpp \  					  src/TII.h -odr_dabmod_LDADD   += $(UHD_LIBS) +odr_dabmod_LDADD   += $(UHD_LIBS) $(LIMESDR_LIBS)  endif diff --git a/configure.ac b/configure.ac index 08cc214..d21ec29 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([limesdr], +        [AS_HELP_STRING([--enable-limesdr], [Build for LimeSDR board])], +        [], [enable_limesdr=no])          # UHD support control  AC_ARG_ENABLE([output_uhd], @@ -103,9 +106,13 @@ AX_PTHREAD([], AC_MSG_ERROR([requires pthread]))  AS_IF([test "x$enable_easydabv3" = "xno"],      [PKG_CHECK_MODULES([SOAPYSDR], [SoapySDR], enable_soapysdr=yes, enable_soapysdr=no)]) +AS_IF([test "x$enable_limesdr" = "xyes"], +         [AC_CHECK_LIB([LimeSuite], [LMS_Init], [LIMESDR_LIBS="-lLimeSuite"], +                       [AC_MSG_ERROR([LimeSDR LimeSuite 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"]) +AC_SUBST([LIBS], ["$FFTW_LIBS $SOAPYSDR_LIBS $PTHREAD_LIBS $ZMQ_LIBS $LIMESDR_LIBS"])  AS_IF([test "x$enable_easydabv3" = "xyes" && test "x$enable_output_uhd" == "xyes"],        AC_MSG_ERROR([Cannot enable both EasyDABv3 and UHD output])) @@ -121,6 +128,9 @@ AS_IF([test "x$enable_output_uhd" = "xyes"],  AS_IF([test "x$enable_soapysdr" = "xyes"],        [AC_DEFINE(HAVE_SOAPYSDR, [1], [Define if SoapySDR output is enabled])]) +AS_IF([test "x$enable_limesdr" = "xyes"], +      [AC_DEFINE(HAVE_LIMESDR, [1], [Define if LimeSDR input is enabled]) ]) +  AS_IF([test "x$enable_easydabv3" = "xyes"],        AC_DEFINE(BUILD_FOR_EASYDABV3, [1], [Define if we are building for EasyDABv3])) @@ -192,7 +202,7 @@ echo "***********************************************"  echo  enabled=""  disabled="" -for feat in prof trace output_uhd zeromq soapysdr easydabv3 +for feat in prof trace output_uhd zeromq soapysdr easydabv3 limesdr  do      eval var=\$enable_$feat      AS_IF([test "x$var" = "xyes"], diff --git a/doc/example.ini b/doc/example.ini index d7a261b..da9ac08 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 +; choose output: possible values: uhd, file, zmq, soapysdr, limesdr  output=uhd  [fileoutput] @@ -322,6 +322,23 @@ channel=13C  ; Set to 0 to disable  ;dpd_port=50055 +[limeoutput] +; Lime output directly runs against the LMS device driver. It does not support SFN nor predistortion. +device= +;master_clock_rate= + +; txgain range: 0 .. 100 +txgain=20 +tx_antenna=BAND1 +;lo_offset=2048000 +;frequency=234208000 +channel=13C + +; The LimeSDR contains a FIR filter in FPGA that can be used to filter the IQ signal. +; This is useful because it allows us to upsample in a very cheap way in software instead +; of using the FFT-based resampler. +upsample=1 +  ; Used for running single-frequency networks  [delaymanagement]  ; Enable handling of timestamps for SFN diff --git a/src/ConfigParser.cpp b/src/ConfigParser.cpp index d5d1995..73e51dd 100644 --- a/src/ConfigParser.cpp +++ b/src/ConfigParser.cpp @@ -312,6 +312,36 @@ static void parse_configfile(          mod_settings.useSoapyOutput = true;      }  #endif +#if defined(HAVE_LIMESDR) +    else if (output_selected == "limesdr") { +        auto& outputlime_conf = mod_settings.sdr_device_config; +        outputlime_conf.device = pt.Get("limeoutput.device", ""); +        outputlime_conf.masterClockRate = pt.GetInteger("limeoutput.master_clock_rate", 0); +        outputlime_conf.txgain = pt.GetReal("limeoutput.txgain", 0.0); +        outputlime_conf.tx_antenna = pt.Get("limeoutput.tx_antenna", ""); +        outputlime_conf.lo_offset = pt.GetReal("limeoutput.lo_offset", 0.0); +        outputlime_conf.frequency = pt.GetReal("limeoutput.frequency", 0); +        std::string chan = pt.Get("limeoutput.channel", ""); +        outputlime_conf.dabMode = mod_settings.dabMode; +        outputlime_conf.upsample = pt.GetInteger("limeoutput.upsample", 1); + +        if (outputlime_conf.frequency == 0 && chan == "") { +            std::cerr << "       Lime output enabled, but neither frequency nor channel defined.\n"; +            throw std::runtime_error("Configuration error"); +        } +        else if (outputlime_conf.frequency == 0) { +            outputlime_conf.frequency =  parseChannel(chan); +        } +        else if (outputlime_conf.frequency != 0 && chan != "") { +            std::cerr << "       Lime output: cannot define both frequency and channel.\n"; +            throw std::runtime_error("Configuration error"); +        } + +        outputlime_conf.dpdFeedbackServerPort = pt.GetInteger("limeoutput.dpd_port", 0); + +        mod_settings.useLimeOutput = true; +    } +#endif  #if defined(HAVE_ZEROMQ)      else if (output_selected == "zmq") {          mod_settings.outputName = pt.Get("zmqoutput.listen", ""); diff --git a/src/ConfigParser.h b/src/ConfigParser.h index 7e706c0..33d7824 100644 --- a/src/ConfigParser.h +++ b/src/ConfigParser.h @@ -37,6 +37,7 @@  #include "output/SDR.h"  #include "output/UHD.h"  #include "output/Soapy.h" +#include "output/Lime.h"  #define ZMQ_INPUT_MAX_FRAME_QUEUE 500 @@ -49,6 +50,7 @@ struct mod_settings_t {      bool fileOutputShowMetadata = false;      bool useUHDOutput = false;      bool useSoapyOutput = false; +    bool useLimeOutput = false;      size_t outputRate = 2048000;      size_t clockRate = 0; @@ -82,7 +84,7 @@ struct mod_settings_t {      // Settings for the OFDM windowing      size_t ofdmWindowOverlap = 0; -#if defined(HAVE_OUTPUT_UHD) || defined(HAVE_SOAPYSDR) +#if defined(HAVE_OUTPUT_UHD) || defined(HAVE_SOAPYSDR) || defined(HAVE_LIMESDR)      Output::SDRDeviceConfig sdr_device_config;  #endif diff --git a/src/DabMod.cpp b/src/DabMod.cpp index 922f9e4..d624a12 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/Lime.h"  #include "OutputZeroMQ.h"  #include "InputReader.h"  #include "PcDebug.h" @@ -149,6 +150,14 @@ static void printModSettings(const mod_settings_t& mod_settings)                  mod_settings.sdr_device_config.masterClockRate << "\n";      }  #endif +#if defined(HAVE_LIMESDR) +    else if (mod_settings.useLimeOutput) { +        ss << " LimeSDR\n" +            "  Device: " << mod_settings.sdr_device_config.device << "\n" << +            "  master_clock_rate: " << +                mod_settings.sdr_device_config.masterClockRate << "\n"; +    } +#endif      else if (mod_settings.useZeroMQOutput) {          ss << " ZeroMQ\n" <<              "  Listening on: " << mod_settings.outputName << "\n" << @@ -232,6 +241,16 @@ static shared_ptr<ModOutput> prepare_output(          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 */ +        s.normalise = 1.0f / normalise_factor; +        s.sdr_device_config.sampleRate = s.outputRate; +        auto limedevice = make_shared<Output::Lime>(s.sdr_device_config); +        output = make_shared<Output::SDR>(s.sdr_device_config, limedevice); +        rcs.enrol((Output::SDR*)output.get()); +    } +#endif  #if defined(HAVE_ZEROMQ)      else if (s.useZeroMQOutput) {          /* We normalise the same way as for the UHD output */ @@ -281,7 +300,8 @@ int launch_modulator(int argc, char* argv[])      if (not (mod_settings.useFileOutput or               mod_settings.useUHDOutput or               mod_settings.useZeroMQOutput or -             mod_settings.useSoapyOutput)) { +             mod_settings.useSoapyOutput or +             mod_settings.useLimeOutput)) {          throw std::runtime_error("Configuration error: Output not specified");      } diff --git a/src/OfdmGenerator.cpp b/src/OfdmGenerator.cpp index 1331e63..00b53ad 100644 --- a/src/OfdmGenerator.cpp +++ b/src/OfdmGenerator.cpp @@ -226,10 +226,11 @@ int OfdmGenerator::process(Buffer* const dataIn, Buffer* dataOut)          fftwf_execute(myFftPlan); // IFFT from myFftIn to myFftOut -        complexf *symbol = reinterpret_cast<complexf*>(myFftOut); -        myPaprBeforeCFR.process_block(symbol, mySpacing); - +                  if (myCfr) { +            complexf *symbol = reinterpret_cast<complexf*>(myFftOut); +            myPaprBeforeCFR.process_block(symbol, mySpacing); +              if (myMERCalcIndex == i) {                  before_cfr.resize(mySpacing);                  memcpy(reinterpret_cast<fftwf_complex*>(before_cfr.data()), diff --git a/src/Utils.cpp b/src/Utils.cpp index 6f4f3a3..f39c4c9 100644 --- a/src/Utils.cpp +++ b/src/Utils.cpp @@ -52,6 +52,9 @@ static void printHeader()  #if defined(HAVE_SOAPYSDR)          "output_soapysdr " <<  #endif +#if defined(HAVE_LIMESDR) +        "output_limesdr " << +#endif  #if defined(__FAST_MATH__)          "fast-math " <<  #endif diff --git a/src/output/Lime.cpp b/src/output/Lime.cpp new file mode 100644 index 0000000..6f7eed5 --- /dev/null +++ b/src/output/Lime.cpp @@ -0,0 +1,461 @@ +/* +   Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Her Majesty the +   Queen in Right of Canada (Communications Research Center Canada) + +   Copyright (C) 2018 +   Evariste F5OEO, evaristec@gmail.com + +   Copyright (C) 2019 +   Matthias P. Braendli, matthias.braendli@mpb.li + +    http://opendigitalradio.org + +DESCRIPTION: +   It is an output driver using the LimeSDR library. +*/ + +/* +   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/Lime.h" + +#ifdef HAVE_LIMESDR + +//#define LIMEDEBUG +#include <chrono> +#include <limits> +#include <cstdio> +#include <iomanip> + +#include "Log.h" +#include "Utils.h" +#ifdef __ARM_NEON__ +#include <arm_neon.h> +#endif +using namespace std; + +namespace Output +{ + +static constexpr size_t FRAMES_MAX_SIZE = 2; +static constexpr size_t FRAME_LENGTH = 196608; // at native sample rate! + +#ifdef __ARM_NEON__ +void conv_s16_from_float(unsigned n, const float *a, short *b) +{ +    unsigned i; + +    const float32x4_t plusone4 = vdupq_n_f32(1.0f); +    const float32x4_t minusone4 = vdupq_n_f32(-1.0f); +    const float32x4_t half4 = vdupq_n_f32(0.5f); +    const float32x4_t scale4 = vdupq_n_f32(32767.0f); +    const uint32x4_t mask4 = vdupq_n_u32(0x80000000); + +    for (i = 0; i < n / 4; i++) +    { +        float32x4_t v4 = ((float32x4_t *)a)[i]; +        v4 = vmulq_f32(vmaxq_f32(vminq_f32(v4, plusone4), minusone4), scale4); + +        const float32x4_t w4 = vreinterpretq_f32_u32(vorrq_u32(vandq_u32( +                                                                   vreinterpretq_u32_f32(v4), mask4), +                                                               vreinterpretq_u32_f32(half4))); + +        ((int16x4_t *)b)[i] = vmovn_s32(vcvtq_s32_f32(vaddq_f32(v4, w4))); +    } +} +#else +void conv_s16_from_float(unsigned n, const float *a, short *b) +{ +    unsigned i; + +    for (i = 0; i < n; i++) +    { +        b[i] = (short)(a[i] * 32767.0f); +    } +} +#endif + +Lime::Lime(SDRDeviceConfig &config) : SDRDevice(), m_conf(config) +{ +    m_interpolate = m_conf.upsample; + +    etiLog.level(info) << "Lime:Creating the device with: " << m_conf.device; + +    const int device_count = LMS_GetDeviceList(nullptr); +    if (device_count < 0) +    { +        etiLog.level(error) << "Error making LimeSDR device: " << LMS_GetLastErrorMessage(); +        throw runtime_error("Cannot find LimeSDR output device"); +    } + +    lms_info_str_t device_list[device_count]; +    if (LMS_GetDeviceList(device_list) < 0) +    { +        etiLog.level(error) << "Error making LimeSDR device: %s " << LMS_GetLastErrorMessage(); +        throw runtime_error("Cannot find LimeSDR output device"); +    } + +    size_t device_i = 0; // If several cards, need to get device by configuration +    if (LMS_Open(&m_device, device_list[device_i], nullptr) < 0) +    { +        etiLog.level(error) << "Error making LimeSDR device: %s " << LMS_GetLastErrorMessage(); +        throw runtime_error("Cannot open LimeSDR output device"); +    } + +    if (LMS_Reset(m_device) < 0) +    { +        etiLog.level(error) << "Error making LimeSDR device: %s " << LMS_GetLastErrorMessage(); +        throw runtime_error("Cannot reset LimeSDR output device"); +    } + +    if (LMS_Init(m_device) < 0) +    { +        etiLog.level(error) << "Error making LimeSDR device: %s " << LMS_GetLastErrorMessage(); +        throw runtime_error("Cannot init LimeSDR output device"); +    } + +    if (m_conf.masterClockRate != 0) +    { +        if (LMS_SetClockFreq(m_device, LMS_CLOCK_CGEN, m_conf.masterClockRate) < 0) +        { +            etiLog.level(error) << "Error making LimeSDR device: %s " << LMS_GetLastErrorMessage(); +            throw runtime_error("Cannot set master clock rate (CGEN) for LimeSDR output device"); +        } + +        float_type masterClockRate = 0; +        if (LMS_GetClockFreq(m_device, LMS_CLOCK_CGEN, &masterClockRate) < 0) +        { +            etiLog.level(error) << "Error reading CGEN clock LimeSDR device: %s " << LMS_GetLastErrorMessage(); +        } +        else +        { +            etiLog.level(info) << "LimeSDR master clock rate set to " << fixed << setprecision(4) << masterClockRate; +        } +    } + +    if (LMS_EnableChannel(m_device, LMS_CH_TX, m_channel, true) < 0) +    { +        etiLog.level(error) << "Error making LimeSDR device: %s " << LMS_GetLastErrorMessage(); +        throw runtime_error("Cannot enable channel for LimeSDR output device"); +    } + +    if (LMS_SetSampleRate(m_device, m_conf.sampleRate * m_interpolate, 0) < 0) +    { +        etiLog.level(error) << "Error making LimeSDR device: %s " << LMS_GetLastErrorMessage(); +        throw runtime_error("Cannot set sample rate for LimeSDR output device"); +    } +    float_type host_sample_rate = 0.0; + +    if (LMS_GetSampleRate(m_device, LMS_CH_TX, m_channel, &host_sample_rate, NULL) < 0) +    { +        etiLog.level(error) << "Error making LimeSDR device: %s " << LMS_GetLastErrorMessage(); +        throw runtime_error("Cannot get samplerate for LimeSDR output device"); +    } +    etiLog.level(info) << "LimeSDR sample rate set to " << fixed << setprecision(4) << host_sample_rate / 1000.0 << " kHz"; + +    tune(m_conf.lo_offset, m_conf.frequency); + +    float_type cur_frequency = 0.0; + +    if (LMS_GetLOFrequency(m_device, LMS_CH_TX, m_channel, &cur_frequency) < 0) +    { +        etiLog.level(error) << "Error making LimeSDR device: %s " << LMS_GetLastErrorMessage(); +        throw runtime_error("Cannot get frequency for LimeSDR output device"); +    } +    etiLog.level(info) << "LimeSDR:Actual frequency: " << fixed << setprecision(3) << cur_frequency / 1000.0 << " kHz."; + +    if (LMS_SetNormalizedGain(m_device, LMS_CH_TX, m_channel, m_conf.txgain / 100.0) < 0) +    { +        //value 0..100 -> Normalize +        etiLog.level(error) << "Error making LimeSDR device: %s " << LMS_GetLastErrorMessage(); +        throw runtime_error("Cannot set TX gain for LimeSDR output device"); +    } + +    if (LMS_SetAntenna(m_device, LMS_CH_TX, m_channel, LMS_PATH_TX2) < 0) +    { +        etiLog.level(error) << "Error making LimeSDR device: %s " << LMS_GetLastErrorMessage(); +        throw runtime_error("Cannot set antenna for LimeSDR output device"); +    } + +    double bandwidth_calibrating = 2.5e6; // Minimal bandwidth +    if (LMS_Calibrate(m_device, LMS_CH_TX, m_channel, bandwidth_calibrating, 0) < 0) +    { +        etiLog.level(error) << "Error making LimeSDR device: %s " << LMS_GetLastErrorMessage(); +        throw runtime_error("Cannot calibrate LimeSDR output device"); +    } + +    switch (m_interpolate) +    { +    case 1: +    { +        //design matlab +        static double coeff[61] = { +            -0.0008126748726, -0.0003874975955, 0.0007290032809, -0.0009636150789, 0.0007643355639, +            3.123887291e-05, -0.001263667713, 0.002418729011, -0.002785810735, 0.001787990681, +            0.0006407162873, -0.003821208142, 0.006409643684, -0.006850919221, 0.004091503099, +            0.00172403187, -0.008917749859, 0.01456955727, -0.01547530293, 0.009518089704, +            0.00304264226, -0.01893160492, 0.0322769247, -0.03613986075, 0.02477015182, +            0.0041426518, -0.04805115238, 0.09958232939, -0.1481673121, 0.1828524768, +            0.8045722842, 0.1828524768, -0.1481673121, 0.09958232939, -0.04805115238, +            0.0041426518, 0.02477015182, -0.03613986075, 0.0322769247, -0.01893160492, +            0.00304264226, 0.009518089704, -0.01547530293, 0.01456955727, -0.008917749859, +            0.00172403187, 0.004091503099, -0.006850919221, 0.006409643684, -0.003821208142, +            0.0006407162873, 0.001787990681, -0.002785810735, 0.002418729011, -0.001263667713, +            3.123887291e-05, 0.0007643355639, -0.0009636150789, 0.0007290032809, -0.0003874975955, +            -0.0008126748726}; + +        LMS_SetGFIRCoeff(m_device, LMS_CH_TX, m_channel, LMS_GFIR3, coeff, 61); +    } +    break; +     +    default: +        throw runtime_error("Unsupported interpolate: " + to_string(m_interpolate)); +    } + +    if (m_conf.sampleRate != 2048000) +    { +        throw runtime_error("Lime output only supports native samplerate = 2048000"); +        /* The buffer_size calculation below does not take into account resampling */ +    } + +    // Frame duration is 96ms +    size_t buffer_size = FRAME_LENGTH * m_interpolate * 10; // We take 10 Frame buffer size Fifo +    // Fifo seems to be round to multiple of SampleRate +    m_tx_stream.channel = m_channel; +    m_tx_stream.fifoSize = buffer_size; +    m_tx_stream.throughputVsLatency = 2.0; // Should be {0..1} but could be extended  +    m_tx_stream.isTx = LMS_CH_TX; +    m_tx_stream.dataFmt = lms_stream_t::LMS_FMT_I16; +    if (LMS_SetupStream(m_device, &m_tx_stream) < 0) +    { +        etiLog.level(error) << "Error making LimeSDR device: %s " << LMS_GetLastErrorMessage(); +        throw runtime_error("Cannot setup TX stream for LimeSDR output device"); +    } +    LMS_StartStream(&m_tx_stream); +    LMS_SetGFIR(m_device, LMS_CH_TX, m_channel, LMS_GFIR3, true); +} + +Lime::~Lime() +{ +    if (m_device != nullptr) +    { +        LMS_StopStream(&m_tx_stream); +        LMS_DestroyStream(m_device, &m_tx_stream); +        LMS_EnableChannel(m_device, LMS_CH_TX, m_channel, false); +        LMS_Close(m_device); +    } +} + +void Lime::tune(double lo_offset, double frequency) +{ +    if (not m_device) +        throw runtime_error("Lime device not set up"); + +    if (LMS_SetLOFrequency(m_device, LMS_CH_TX, m_channel, m_conf.frequency) < 0) +    { +        etiLog.level(error) << "Error setting LimeSDR TX frequency: %s " << LMS_GetLastErrorMessage(); +    } +} + +double Lime::get_tx_freq(void) const +{ +    if (not m_device) +        throw runtime_error("Lime device not set up"); + +    float_type cur_frequency = 0.0; + +    if (LMS_GetLOFrequency(m_device, LMS_CH_TX, m_channel, &cur_frequency) < 0) +    { +        etiLog.level(error) << "Error getting LimeSDR TX frequency: %s " << LMS_GetLastErrorMessage(); +    } + +    return cur_frequency; +} + +void Lime::set_txgain(double txgain) +{ +    m_conf.txgain = txgain; +    if (not m_device) +        throw runtime_error("Lime device not set up"); + +    if (LMS_SetNormalizedGain(m_device, LMS_CH_TX, m_channel, m_conf.txgain / 100.0) < 0) +    { +        etiLog.level(error) << "Error setting LimeSDR TX gain: %s " << LMS_GetLastErrorMessage(); +    } +} + +double Lime::get_txgain(void) const +{ +    if (not m_device) +        throw runtime_error("Lime device not set up"); + +    float_type txgain = 0; +    if (LMS_GetNormalizedGain(m_device, LMS_CH_TX, m_channel, &txgain) < 0) +    { +        etiLog.level(error) << "Error getting LimeSDR TX gain: %s " << LMS_GetLastErrorMessage(); +    } +    return txgain; +} + +void Lime::set_bandwidth(double bandwidth) +{ +    LMS_SetLPFBW(m_device, LMS_CH_TX, m_channel, bandwidth); +} + +double Lime::get_bandwidth(void) const +{ +    double bw; +    LMS_GetLPFBW(m_device, LMS_CH_TX, m_channel, &bw); +    return bw; +} + +SDRDevice::RunStatistics Lime::get_run_statistics(void) const +{ +    RunStatistics rs; +    rs.num_underruns = underflows; +    rs.num_overruns = overflows; +    rs.num_late_packets = late_packets; +    rs.num_frames_modulated = num_frames_modulated; +    return rs; +} + +double Lime::get_real_secs(void) const +{ +    // TODO +    return 0.0; +} + +void Lime::set_rxgain(double rxgain) +{ +    // TODO +} + +double Lime::get_rxgain(void) const +{ +    // TODO +    return 0.0; +} + +size_t Lime::receive_frame( +    complexf *buf, +    size_t num_samples, +    struct frame_timestamp &ts, +    double timeout_secs) +{ +    // TODO +    return 0; +} + +bool Lime::is_clk_source_ok() const +{ +    // TODO +    return true; +} + +const char *Lime::device_name(void) const +{ +    return "Lime"; +} + +double Lime::get_temperature(void) const +{ +    if (not m_device) +        throw runtime_error("Lime device not set up"); + +    float_type temp = numeric_limits<float_type>::quiet_NaN(); +    if (LMS_GetChipTemperature(m_device, 0, &temp) < 0) +    { +        etiLog.level(error) << "Error getting LimeSDR temperature: %s " << LMS_GetLastErrorMessage(); +    } +    return temp; +} + +float Lime::get_fifo_fill_percent(void) const +{ +    return m_last_fifo_fill_percent * 100; +} + +void Lime::transmit_frame(const struct FrameData &frame) +{ +    if (not m_device) +        throw runtime_error("Lime device not set up"); + +    // The frame buffer contains bytes representing FC32 samples +    const complexf *buf = reinterpret_cast<const complexf *>(frame.buf.data()); +    const size_t numSamples = frame.buf.size() / sizeof(complexf); + +    m_i16samples.resize(numSamples * 2); +    short *buffi16 = &m_i16samples[0]; +     +    conv_s16_from_float(numSamples * 2, (const float *)buf, buffi16); +    if ((frame.buf.size() % sizeof(complexf)) != 0) +    { +        throw runtime_error("Lime: invalid buffer size"); +    } + +    lms_stream_status_t LimeStatus; +    LMS_GetStreamStatus(&m_tx_stream, &LimeStatus); +    overflows += LimeStatus.overrun; +    underflows += LimeStatus.underrun; +    late_packets += LimeStatus.droppedPackets; + +#ifdef LIMEDEBUG +    etiLog.level(info) << LimeStatus.fifoFilledCount << "/" << LimeStatus.fifoSize << ":" << numSamples << "Rate" << LimeStatus.linkRate / (2 * 2.0); +    etiLog.level(info) << "overrun" << LimeStatus.overrun << "underun" << LimeStatus.underrun << "drop" << LimeStatus.droppedPackets; +#endif + +    m_last_fifo_fill_percent.store((float)LimeStatus.fifoFilledCount / (float)LimeStatus.fifoSize); + +    /* +    if(LimeStatus.fifoFilledCount>=5*FRAME_LENGTH*m_interpolate) // Start if FIFO is half full { +        if(not m_tx_stream_active) { +            etiLog.level(info) << "Fifo OK : Normal running"; +            LMS_StartStream(&m_tx_stream); +            m_tx_stream_active = true; +        } +    } +*/ + +    ssize_t num_sent = 0; + +    lms_stream_meta_t meta; +    meta.flushPartialPacket = true; +    meta.timestamp = 0; +    meta.waitForTimestamp = false; + +    if (m_interpolate == 1) +    { +        num_sent = LMS_SendStream(&m_tx_stream, buffi16, numSamples, &meta, 1000); +    } + +     + +    if (num_sent == 0) +    { +        etiLog.level(info) << "Lime: zero samples sent" << num_sent; +    } +    else if (num_sent == -1) +    { +        etiLog.level(error) << "Error sending LimeSDR stream: %s " << LMS_GetLastErrorMessage(); +    } + +    num_frames_modulated++; +} + +} // namespace Output + +#endif // HAVE_LIMESDR diff --git a/src/output/Lime.h b/src/output/Lime.h new file mode 100644 index 0000000..72a018e --- /dev/null +++ b/src/output/Lime.h @@ -0,0 +1,108 @@ +/* +   Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Her Majesty the +   Queen in Right of Canada (Communications Research Center Canada) + +   Copyright (C) 2018 +   Evariste F5OEO, evaristec@gmail.com + +   Copyright (C) 2019 +   Matthias P. Braendli, matthias.braendli@mpb.li + +    http://opendigitalradio.org + +DESCRIPTION: +  It is an output driver using the LimeSDR library. +*/ + +/* +   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_LIMESDR + +#include <atomic> +#include <string> +#include <memory> + +#include "output/SDR.h" +#include "ModPlugin.h" +#include "EtiReader.h" +#include "RemoteControl.h" +#include <lime/LimeSuite.h> + +namespace Output +{ + +class Lime : public Output::SDRDevice +{ +  public: +    Lime(SDRDeviceConfig &config); +    Lime(const Lime &other) = delete; +    Lime &operator=(const Lime &other) = delete; +    ~Lime(); + +    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; +    virtual float get_fifo_fill_percent(void) const; + +  private: +    SDRDeviceConfig &m_conf; +    lms_device_t *m_device = nullptr; +    size_t m_channel = 0; // Should be set by config +    lms_stream_t m_tx_stream; +    bool m_tx_stream_active = false; +    size_t m_interpolate = 1; +    std::vector<complexf> interpolatebuf; +    std::vector<short> m_i16samples;  +    std::atomic<float> m_last_fifo_fill_percent = ATOMIC_VAR_INIT(0); +     + +    size_t underflows = 0; +    size_t overflows = 0; +    size_t late_packets = 0; +    size_t num_frames_modulated = 0; +}; + +} // namespace Output + +#endif //HAVE_SOAPYSDR diff --git a/src/output/SDR.cpp b/src/output/SDR.cpp index fd8b4ba..ad65c1c 100644 --- a/src/output/SDR.cpp +++ b/src/output/SDR.cpp @@ -25,6 +25,7 @@   */  #include "output/SDR.h" +#include "output/Lime.h"  #include "PcDebug.h"  #include "Log.h" @@ -84,6 +85,10 @@ SDR::SDR(SDRDeviceConfig& config, std::shared_ptr<SDRDevice> device) :      RC_ADD_PARAMETER(frames, "Counter of number of frames modulated");      RC_ADD_PARAMETER(gpsdo_num_sv, "Number of Satellite Vehicles tracked by GPSDO");      RC_ADD_PARAMETER(gpsdo_holdover, "1 if the GPSDO is in holdover, 0 if it is using gnss"); + +    if (std::dynamic_pointer_cast<Lime>(device)) { +        RC_ADD_PARAMETER(fifo_fill, "A value representing the Lime FIFO fullness [percent]"); +    }  }  SDR::~SDR() @@ -393,7 +398,8 @@ void SDR::set_parameter(const string& parameter, const string& value)               parameter == "latepackets" or               parameter == "frames" or               parameter == "gpsdo_num_sv" or -             parameter == "gpsdo_holdover") { +             parameter == "gpsdo_holdover" or +             parameter == "fifo_fill") {          throw ParameterError("Parameter " + parameter + " is read-only.");      }      else { @@ -461,6 +467,18 @@ const string SDR::get_parameter(const string& parameter) const          const auto stat = m_device->get_run_statistics();          ss << (stat.gpsdo_holdover ? 1 : 0);      } +    else if (parameter == "fifo_fill") { +        const auto dev = std::dynamic_pointer_cast<Lime>(m_device); + +        if (dev) { +            ss << dev->get_fifo_fill_percent(); +        } +        else { +            ss << "Parameter '" << parameter << +                "' is not exported by controllable " << get_rc_name(); +            throw ParameterError(ss.str()); +        } +    }      else {          ss << "Parameter '" << parameter <<              "' is not exported by controllable " << get_rc_name(); diff --git a/src/output/SDRDevice.h b/src/output/SDRDevice.h index a1a488f..d84ebf9 100644 --- a/src/output/SDRDevice.h +++ b/src/output/SDRDevice.h @@ -2,7 +2,7 @@     Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Her Majesty the     Queen in Right of Canada (Communications Research Center Canada) -   Copyright (C) 2017 +   Copyright (C) 2019     Matthias P. Braendli, matthias.braendli@mpb.li      http://opendigitalradio.org @@ -64,6 +64,7 @@ struct SDRDeviceConfig {      double rxgain = 0.0;      bool enableSync = false;      double bandwidth = 0.0; +    unsigned upsample = 1;      // When working with timestamps, mute the frames that      // do not have a timestamp  | 
