diff options
-rw-r--r-- | Makefile.am | 4 | ||||
-rw-r--r-- | configure.ac | 16 | ||||
-rw-r--r-- | src/DabMod.cpp | 1 | ||||
-rw-r--r-- | src/OfdmGenerator.cpp | 2 | ||||
-rw-r--r-- | src/output/Lime.cpp | 346 | ||||
-rw-r--r-- | src/output/Lime.h | 101 |
6 files changed, 466 insertions, 4 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/DabMod.cpp b/src/DabMod.cpp index c53b7c9..f804512 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" diff --git a/src/OfdmGenerator.cpp b/src/OfdmGenerator.cpp index 2642bff..00b53ad 100644 --- a/src/OfdmGenerator.cpp +++ b/src/OfdmGenerator.cpp @@ -229,7 +229,7 @@ int OfdmGenerator::process(Buffer* const dataIn, Buffer* dataOut) if (myCfr) { complexf *symbol = reinterpret_cast<complexf*>(myFftOut); - myPaprBeforeCFR.process_block(symbol, mySpacing); + myPaprBeforeCFR.process_block(symbol, mySpacing); if (myMERCalcIndex == i) { before_cfr.resize(mySpacing); diff --git a/src/output/Lime.cpp b/src/output/Lime.cpp new file mode 100644 index 0000000..82a42be --- /dev/null +++ b/src/output/Lime.cpp @@ -0,0 +1,346 @@ +/* + 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 <SoapySDR/Errors.hpp> +#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) +{ + etiLog.level(info) << + "Lime:Creating the device with: " << + m_conf.device; + /* + try { + m_device = SoapySDR::Device::make(m_conf.device); + stringstream ss; + ss << "SoapySDR driver=" << m_device->getDriverKey(); + ss << " hardware=" << m_device->getHardwareKey(); + for (const auto &it : m_device->getHardwareInfo()) { + ss << " " << it.first << "=" << it.second; + } + } + catch (const std::exception &ex) { + etiLog.level(error) << "Error making SoapySDR device: " << + ex.what(); + throw std::runtime_error("Cannot create SoapySDR output"); + } + + m_device->setMasterClockRate(m_conf.masterClockRate); + etiLog.level(info) << "SoapySDR master clock rate set to " << + std::fixed << std::setprecision(4) << + m_device->getMasterClockRate()/1000.0 << " kHz"; + + m_device->setSampleRate(SOAPY_SDR_TX, 0, m_conf.sampleRate); + etiLog.level(info) << "SoapySDR:Actual TX rate: " << + std::fixed << std::setprecision(4) << + m_device->getSampleRate(SOAPY_SDR_TX, 0) / 1000.0 << + " ksps."; + + tune(m_conf.lo_offset, m_conf.frequency); + m_conf.frequency = m_device->getFrequency(SOAPY_SDR_TX, 0); + etiLog.level(info) << "SoapySDR:Actual frequency: " << + std::fixed << std::setprecision(3) << + m_conf.frequency / 1000.0 << " kHz."; + + m_device->setGain(SOAPY_SDR_TX, 0, m_conf.txgain); + etiLog.level(info) << "SoapySDR:Actual tx gain: " << + std::fixed << std::setprecision(2) << + m_device->getGain(SOAPY_SDR_TX, 0); + + if (not m_conf.tx_antenna.empty()) { + m_device->setAntenna(SOAPY_SDR_TX, 0, m_conf.tx_antenna); + } + etiLog.level(info) << "SoapySDR:Actual tx antenna: " << + m_device->getAntenna(SOAPY_SDR_TX, 0); + + if (m_device->hasHardwareTime()) { + using namespace std::chrono; + auto n = system_clock::now(); + const long long ticks = duration_cast<nanoseconds>(n.time_since_epoch()).count(); + m_device->setHardwareTime(ticks); + } + + const std::vector<size_t> channels({0}); + m_tx_stream = m_device->setupStream(SOAPY_SDR_TX, "CF32", channels); + m_rx_stream = m_device->setupStream(SOAPY_SDR_RX, "CF32", channels); + */ +} + +Lime::~Lime() +{ + /* + if (m_device != nullptr) { + if (m_tx_stream != nullptr) { + m_device->closeStream(m_tx_stream); + } + + if (m_rx_stream != nullptr) { + m_device->closeStream(m_rx_stream); + } + SoapySDR::Device::unmake(m_device); + }*/ +} + +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"); +/* + long long int timeNs = frame.ts.get_ns(); + // muting and mutenotimestamp is handled by SDR + const bool has_time_spec = (m_conf.enableSync and frame.ts.timestamp_valid); + + if (not m_tx_stream_active) { + int flags = has_time_spec ? SOAPY_SDR_HAS_TIME : 0; + int ret = m_device->activateStream(m_tx_stream, flags, timeNs); + if (ret != 0) { + throw std::runtime_error(string("Soapy activate TX stream failed: ") + + SoapySDR::errToStr(ret)); + } + m_tx_stream_active = true; + } + + // 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("Soapy: invalid buffer size"); + } + + // Stream MTU is in samples, not bytes. + const size_t mtu = m_device->getStreamMTU(m_tx_stream); + + size_t num_acc_samps = 0; + while (num_acc_samps < numSamples) { + + const void *buffs[1]; + buffs[0] = buf + num_acc_samps; + + const size_t samps_to_send = std::min(numSamples - num_acc_samps, mtu); + + const bool eob_because_muting = m_conf.muting; + const bool end_of_burst = eob_because_muting or ( + frame.ts.timestamp_valid and + frame.ts.timestamp_refresh and + samps_to_send <= mtu ); + + int flags = 0; + + auto num_sent = m_device->writeStream( + m_tx_stream, buffs, samps_to_send, flags, timeNs); + + if (num_sent == SOAPY_SDR_TIMEOUT) { + continue; + } + else if (num_sent == SOAPY_SDR_OVERFLOW) { + overflows++; + continue; + } + else if (num_sent == SOAPY_SDR_UNDERFLOW) { + underflows++; + continue; + } + + if (num_sent < 0) { + etiLog.level(error) << "Unexpected stream error " << + SoapySDR::errToStr(num_sent); + throw std::runtime_error("Fault in Soapy"); + } + + timeNs += 1e9 * num_sent/m_conf.sampleRate; + + num_acc_samps += num_sent; + + if (end_of_burst) { + int ret_deact = m_device->deactivateStream(m_tx_stream); + if (ret_deact != 0) { + throw std::runtime_error( + string("Soapy activate TX stream failed: ") + + SoapySDR::errToStr(ret_deact)); + } + m_tx_stream_active = false; + } + + if (eob_because_muting) { + break; + } + } + 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..ad594c0 --- /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 + Matthias P. Braendli, matthias.braendli@mpb.li + + http://opendigitalradio.org + +DESCRIPTION: + It is an output driver using the SoapySDR library that can output to + many devices. +*/ + +/* + 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 <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; + /* + SoapySDR::Device *m_device = nullptr; + SoapySDR::Stream *m_tx_stream = nullptr; + bool m_tx_stream_active = false; + SoapySDR::Stream *m_rx_stream = nullptr; + */ + bool m_rx_stream_active = false; + + size_t underflows = 0; + size_t overflows = 0; + size_t late_packets = 0; + size_t num_frames_modulated = 0; +}; + +} // namespace Output + +#endif //HAVE_SOAPYSDR + |