aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Makefile.am4
-rw-r--r--configure.ac16
-rw-r--r--src/ConfigParser.cpp30
-rw-r--r--src/ConfigParser.h4
-rw-r--r--src/DabMod.cpp22
-rw-r--r--src/OfdmGenerator.cpp7
-rw-r--r--src/Utils.cpp3
-rw-r--r--src/output/Lime.cpp424
-rw-r--r--src/output/Lime.h101
-rw-r--r--src/output/SDRDevice.h2
10 files changed, 604 insertions, 9 deletions
diff --git a/Makefile.am b/Makefile.am
index 4787cff..382b578 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -621,6 +621,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 \
@@ -645,7 +647,7 @@ odr_dabmod_SOURCES += \
src/TII.h \
$(ASIO_FILES)
-odr_dabmod_LDADD += $(UHD_LIBS)
+odr_dabmod_LDADD += $(UHD_LIBS) $(LIMESDR_LIBS)
endif
if COMPILE_EDI
diff --git a/configure.ac b/configure.ac
index 753b407..e05be61 100644
--- a/configure.ac
+++ b/configure.ac
@@ -58,6 +58,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],
@@ -106,9 +109,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]))
@@ -127,6 +134,11 @@ AS_IF([test "x$enable_soapysdr" = "xyes"],
AS_IF([test "x$enable_edi" = "xyes"],
[AC_DEFINE(HAVE_EDI, [1], [Define if EDI input is enabled]) ])
+AS_IF([test "x$enable_limesdr" = "xyes"],
+ [AC_DEFINE(HAVE_LIMESDR, [1], [Define if LimeSDR input is enabled]) ])
+
+
+
AM_CONDITIONAL([COMPILE_EDI], [test "x$enable_edi" = "xyes"])
AS_IF([test "x$enable_easydabv3" = "xyes"],
@@ -200,7 +212,7 @@ echo "***********************************************"
echo
enabled=""
disabled=""
-for feat in edi prof trace output_uhd zeromq soapysdr easydabv3
+for feat in edi prof trace output_uhd zeromq soapysdr easydabv3 limesdr
do
eval var=\$enable_$feat
AS_IF([test "x$var" = "xyes"],
diff --git a/src/ConfigParser.cpp b/src/ConfigParser.cpp
index 69655b4..107ee45 100644
--- a/src/ConfigParser.cpp
+++ b/src/ConfigParser.cpp
@@ -307,6 +307,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 ee961fa..23b0528 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 57acd40..3806048 100644
--- a/src/DabMod.cpp
+++ b/src/DabMod.cpp
@@ -55,6 +55,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"
@@ -145,6 +146,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" <<
@@ -228,6 +237,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 */
@@ -277,7 +296,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 50af4fb..fd4f659 100644
--- a/src/Utils.cpp
+++ b/src/Utils.cpp
@@ -55,6 +55,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..219b97a
--- /dev/null
+++ b/src/output/Lime.cpp
@@ -0,0 +1,424 @@
+/*
+ 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
+
+
+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
+
+#include <chrono>
+#include <limits>
+#include <cstdio>
+#include <iomanip>
+
+#include "Log.h"
+#include "Utils.h"
+
+using namespace std;
+
+namespace Output
+{
+
+static constexpr size_t FRAMES_MAX_SIZE = 2;
+
+Lime::Lime(SDRDeviceConfig &config) : SDRDevice(),
+ m_conf(config)
+{
+ m_interpolate = m_conf.upsample;
+
+ etiLog.level(info) << "Lime:Creating the device with: " << m_conf.device;
+ int device_count = LMS_GetDeviceList(NULL);
+ if (device_count < 0)
+ {
+ etiLog.level(error) << "Error making LimeSDR device: " << LMS_GetLastErrorMessage();
+ throw std::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 std::runtime_error("Cannot find LimeSDR output device");
+ }
+ unsigned int device_i = 0; // If several cards, need to get device by configuration
+ if (LMS_Open(&m_device, device_list[device_i], NULL) < 0)
+ {
+ etiLog.level(error) << "Error making LimeSDR device: %s " << LMS_GetLastErrorMessage();
+ throw std::runtime_error("Cannot open LimeSDR output device");
+ }
+ if (LMS_Reset(m_device) < 0)
+ {
+ etiLog.level(error) << "Error making LimeSDR device: %s " << LMS_GetLastErrorMessage();
+ throw std::runtime_error("Cannot reset LimeSDR output device");
+ }
+
+ if (LMS_Init(m_device) < 0)
+ {
+ etiLog.level(error) << "Error making LimeSDR device: %s " << LMS_GetLastErrorMessage();
+ throw std::runtime_error("Cannot init LimeSDR output device");
+ }
+
+ if (LMS_EnableChannel(m_device, LMS_CH_TX, m_channel, true) < 0)
+ {
+ etiLog.level(error) << "Error making LimeSDR device: %s " << LMS_GetLastErrorMessage();
+ throw std::runtime_error("Cannot channel LimeSDR output device");
+ }
+
+ if (LMS_SetSampleRate(m_device, m_conf.masterClockRate * m_interpolate, 0) < 0)
+ {
+ etiLog.level(error) << "Error making LimeSDR device: %s " << LMS_GetLastErrorMessage();
+ throw std::runtime_error("Cannot channel 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 std::runtime_error("Cannot getsamplerate LimeSDR output device");
+ }
+ etiLog.level(info) << "LimeSDR master clock rate set to " << std::fixed << std::setprecision(4) << host_sample_rate / 1000.0 << " kHz";
+
+ if (LMS_SetLOFrequency(m_device, LMS_CH_TX, m_channel, m_conf.frequency) < 0)
+ {
+ etiLog.level(error) << "Error making LimeSDR device: %s " << LMS_GetLastErrorMessage();
+ throw std::runtime_error("Cannot Frequency LimeSDR output device");
+ }
+
+ 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 std::runtime_error("Cannot GetFrequency LimeSDR output device");
+ }
+ etiLog.level(info) << "LimeSDR:Actual frequency: " << std::fixed << std::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 std::runtime_error("Cannot Gain 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 std::runtime_error("Cannot Antenna 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 std::runtime_error("Cannot Gain LimeSDR output device");
+ }
+ switch (m_interpolate)
+ {
+ case 1:
+ {
+ static double coeff[] = {-0.0014080960536375642, 0.0010270054917782545, 0.0002103941806126386, -0.0023147952742874622, 0.004256128799170256, -0.0038850826676934958, -0.0006057845894247293, 0.008352266624569893, -0.014639420434832573, 0.01275692880153656, 0.0012119393795728683, -0.02339744009077549, 0.04088031128048897, -0.03649924695491791, -0.001745241112075746, 0.07178881019353867, -0.15494878590106964, 0.22244733572006226, 0.7530255913734436, 0.22244733572006226, -0.15494878590106964, 0.07178881019353867, -0.001745241112075746, -0.03649924695491791, 0.04088031128048897, -0.02339744009077549, 0.0012119393795728683, 0.01275692880153656, -0.014639420434832573, 0.008352266624569893, -0.0006057845894247293, -0.0038850826676934958, 0.004256128799170256, -0.0023147952742874622, 0.0002103941806126386, 0.0010270054917782545, -0.0014080960536375642};
+ LMS_SetGFIRCoeff(m_device, LMS_CH_TX, m_channel, LMS_GFIR3, coeff, 37);
+ LMS_SetGFIR(m_device, LMS_CH_TX, m_channel, LMS_GFIR3, true);
+ }
+ break;
+ case 2:
+ {
+ static double coeff[] = {0.0007009872933849692, 0.0006160094635561109, -0.0003868100175168365, -0.0010892765130847692, -0.0003017585549969226, 0.0013388358056545258, 0.0014964848523959517, -0.000810395460575819, -0.0028437587898224592, -0.001026041223667562, 0.0033166243229061365, 0.004008698742836714, -0.0016114861937239766, -0.006794447544962168, -0.0029077117796987295, 0.0070640090852975845, 0.009203733876347542, -0.002605677582323551, -0.014204192906618118, -0.007088471669703722, 0.013578214682638645, 0.019509244710206985, -0.0035577849484980106, -0.028872046619653702, -0.016949573531746864, 0.02703845500946045, 0.045044951140880585, -0.00423968443647027, -0.07416801154613495, -0.05744718387722969, 0.09617383778095245, 0.30029231309890747, 0.39504382014274597, 0.30029231309890747, 0.09617383778095245, -0.05744718387722969, -0.07416801154613495, -0.00423968443647027, 0.045044951140880585, 0.02703845500946045, -0.016949573531746864, -0.028872046619653702, -0.0035577849484980106, 0.019509244710206985, 0.013578214682638645, -0.007088471669703722, -0.014204192906618118, -0.002605677582323551, 0.009203733876347542, 0.0070640090852975845, -0.0029077117796987295, -0.006794447544962168, -0.0016114861937239766, 0.004008698742836714, 0.0033166243229061365, -0.001026041223667562, -0.0028437587898224592, -0.000810395460575819, 0.0014964848523959517, 0.0013388358056545258, -0.0003017585549969226, -0.0010892765130847692, -0.0003868100175168365, 0.0006160094635561109, 0.0007009872933849692};
+ LMS_SetGFIRCoeff(m_device, LMS_CH_TX, m_channel, LMS_GFIR3, coeff, 65);
+ LMS_SetGFIR(m_device, LMS_CH_TX, m_channel, LMS_GFIR3, true);
+ }
+ break;
+ }
+#define FRAME_LENGTH 196608
+ // FRAME DURATION is 96ms
+ unsigned int buffer_size = FRAME_LENGTH * m_interpolate * 10; // We take 10 Frame buffer size Fifo
+ interpolatebuf = new complexf[FRAME_LENGTH * m_interpolate];
+ // 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 = 1.0;
+ m_tx_stream.isTx = LMS_CH_TX;
+ m_tx_stream.dataFmt = lms_stream_t::LMS_FMT_F32;
+ if (LMS_SetupStream(m_device, &m_tx_stream) < 0)
+ {
+ etiLog.level(error) << "Error making LimeSDR device: %s " << LMS_GetLastErrorMessage();
+ throw std::runtime_error("Cannot Channel Activate 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)
+ {
+
+ //if (m_tx_stream != nullptr)
+ {
+ LMS_StopStream(&m_tx_stream);
+ LMS_DestroyStream(m_device, &m_tx_stream);
+ }
+ /*
+ if (m_rx_stream != nullptr) {
+ m_device->closeStream(m_rx_stream);
+ }
+ */
+ LMS_EnableChannel(m_device, LMS_CH_TX, m_channel, false);
+ LMS_Close(m_device);
+ }
+ if (interpolatebuf != nullptr)
+ delete (interpolatebuf);
+}
+
+void Lime::tune(double lo_offset, double frequency)
+{
+ /* if (not m_device) throw runtime_error("Soapy device not set up");
+
+ SoapySDR::Kwargs offset_arg;
+ offset_arg["OFFSET"] = to_string(lo_offset);
+ m_device->setFrequency(SOAPY_SDR_TX, 0, m_conf.frequency, offset_arg);
+ */
+}
+
+double Lime::get_tx_freq(void) const
+{
+ if (not m_device)
+ throw runtime_error("Lime device not set up");
+
+ // TODO lo offset
+ return 0; //m_device->getFrequency(SOAPY_SDR_TX, 0);
+}
+
+void Lime::set_txgain(double txgain)
+{
+ m_conf.txgain = txgain;
+ if (not m_device)
+ throw runtime_error("Lime device not set up");
+ //m_device->setGain(SOAPY_SDR_TX, 0, m_conf.txgain);
+}
+
+double Lime::get_txgain(void) const
+{
+ if (not m_device)
+ throw runtime_error("Lime device not set up");
+ return 0; //m_device->getGain(SOAPY_SDR_TX, 0);
+}
+
+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
+{
+ /*if (m_device) {
+ long long time_ns = m_device->getHardwareTime();
+ return time_ns / 1e9;
+ }
+ else {
+ return 0.0;
+ }*/
+ return 0.0;
+}
+
+void Lime::set_rxgain(double rxgain)
+{
+ /*m_device->setGain(SOAPY_SDR_RX, 0, m_conf.rxgain);
+ m_conf.rxgain = m_device->getGain(SOAPY_SDR_RX, 0);
+ */
+}
+
+double Lime::get_rxgain(void) const
+{
+ return 0.0; //m_device->getGain(SOAPY_SDR_RX, 0);
+}
+
+size_t Lime::receive_frame(
+ complexf *buf,
+ size_t num_samples,
+ struct frame_timestamp &ts,
+ double timeout_secs)
+{
+ /*int flags = 0;
+ long long timeNs = ts.get_ns();
+ const size_t numElems = num_samples;
+
+ void *buffs[1];
+ buffs[0] = buf;
+
+ int ret = m_device->activateStream(m_rx_stream, flags, timeNs, numElems);
+ if (ret != 0) {
+ throw std::runtime_error(string("Soapy activate RX stream failed: ") +
+ SoapySDR::errToStr(ret));
+ }
+ m_rx_stream_active = true;
+
+ int n_read = m_device->readStream(
+ m_rx_stream, buffs, num_samples, flags, timeNs);
+
+ ret = m_device->deactivateStream(m_rx_stream);
+ if (ret != 0) {
+ throw std::runtime_error(string("Soapy deactivate RX stream failed: ") +
+ SoapySDR::errToStr(ret));
+ }
+ m_rx_stream_active = false;
+
+ if (n_read < 0) {
+ throw std::runtime_error(string("Soapy failed to read from RX stream : ") +
+ SoapySDR::errToStr(ret));
+ }
+
+ ts.set_ns(timeNs);
+
+ return n_read;
+ */
+ 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
+{
+ // TODO Unimplemented
+ // LimeSDR exports 'lms7_temp'
+ return std::numeric_limits<double>::quiet_NaN();
+}
+
+void Lime::transmit_frame(const struct FrameData &frame)
+{
+
+ if (not m_device)
+ throw runtime_error("Lime device not set up");
+
+ /*if (not m_tx_stream_active)
+ {
+ unsigned int 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 = 0.8;
+ m_tx_stream.isTx = LMS_CH_TX;
+ m_tx_stream.dataFmt = lms_stream_t::LMS_FMT_F32;
+
+ if ( LMS_SetupStream(m_device, &m_tx_stream) < 0 )
+ {
+ etiLog.level(error) << "Error making LimeSDR device: %s " << LMS_GetLastErrorMessage();
+ throw std::runtime_error("Cannot Channel Activate LimeSDR output device");
+ }
+ //LMS_StartStream(&m_tx_stream);
+ LMS_SetGFIR(m_device, LMS_CH_TX, m_channel, LMS_GFIR3, true);
+ m_tx_stream_active = false;
+ }*/
+
+ // 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);
+ if ((frame.buf.size() % sizeof(complexf)) != 0)
+ {
+ throw std::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;
+
+ 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;
+
+ /* if(LimeStatus.fifoFilledCount>LimeStatus.fifoSize-2*FRAME_LENGTH*m_interpolate) // Drop if Fifo is just 2 frames before fullness
+ {
+ etiLog.level(info) << "Fifo overflow : drop";
+ return;
+ }*/
+
+ if (LimeStatus.fifoFilledCount < FRAME_LENGTH * 2 * m_interpolate) // Wait if Fifo is just 2 frames before fullness
+ {
+ etiLog.level(info) << "Fifo underflow : duplicate for filling garbage";
+ for (size_t i = 0; i < m_interpolate * 10; i++)
+ LMS_SendStream(&m_tx_stream, buf, numSamples, NULL, 1000);
+ }
+ /*
+ 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;
+ }
+
+ }
+*/
+
+ size_t num_sent = 0;
+ if (m_interpolate == 1)
+ num_sent = LMS_SendStream(&m_tx_stream, buf, numSamples, NULL, 1000);
+
+ if (m_interpolate > 1) // We upsample (1 0 0 0), low pass filter is done by FIR
+ {
+ for (size_t i = 0; i < numSamples; i++)
+ {
+ interpolatebuf[i * m_interpolate] = buf[i];
+ for (size_t j = 1; j < m_interpolate; j++)
+ interpolatebuf[i * m_interpolate + j] = complexf(0, 0);
+ }
+ num_sent = LMS_SendStream(&m_tx_stream, interpolatebuf, numSamples * m_interpolate, NULL, 1000);
+ }
+
+ if (num_sent <= 0)
+ {
+ etiLog.level(info) << "Underflow" << num_sent;
+ //throw std::runtime_error("Lime: Too Loonnnngg");
+ }
+ else
+ {
+ //etiLog.level(info) << "OK" << num_sent;
+ }
+
+ 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..ab0a388
--- /dev/null
+++ b/src/output/Lime.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) 2018
+ Evariste F5OEO, evaristec@gmail.com
+
+
+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
+//#define HAVE_LIMESDR
+#ifdef HAVE_LIMESDR
+
+#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 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;
+ lms_device_t *m_device = nullptr;
+ size_t m_channel = 0; // Should be set by config
+ /*
+ SoapySDR::Device *m_device = nullptr;
+ SoapySDR::Stream *m_tx_stream = nullptr;
+ */
+ lms_stream_t m_tx_stream;
+ bool m_tx_stream_active = false;
+ size_t m_interpolate = 1;
+ complexf *interpolatebuf = nullptr;
+
+ 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/SDRDevice.h b/src/output/SDRDevice.h
index 90a1123..9796479 100644
--- a/src/output/SDRDevice.h
+++ b/src/output/SDRDevice.h
@@ -63,7 +63,7 @@ struct SDRDeviceConfig {
double txgain = 0.0;
double rxgain = 0.0;
bool enableSync = false;
-
+ unsigned upsample = 1;
// When working with timestamps, mute the frames that
// do not have a timestamp
bool muteNoTimestamps = false;