diff --git a/plugins/samplesink/limesdroutput/CMakeLists.txt b/plugins/samplesink/limesdroutput/CMakeLists.txt
new file mode 100644
index 000000000..4fd61a12b
--- /dev/null
+++ b/plugins/samplesink/limesdroutput/CMakeLists.txt
@@ -0,0 +1,55 @@
+project(limesdroutput)
+
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
+
+set(limesdroutput_SOURCES
+ limesdroutputgui.cpp
+ limesdroutput.cpp
+ limesdroutputplugin.cpp
+ limesdroutputsettings.cpp
+ limesdroutputthread.cpp
+)
+
+set(limesdroutput_HEADERS
+ limesdroutputgui.h
+ limesdroutput.h
+ limesdroutputplugin.h
+ limesdroutputsettings.h
+ limesdroutputthread.h
+)
+
+set(limesdroutput_FORMS
+ limesdroutputgui.ui
+)
+
+include_directories(
+ .
+ ${CMAKE_CURRENT_BINARY_DIR}
+ ${CMAKE_SOURCE_DIR}/devices
+ ${LIMESUITE_INCLUDE_DIR}
+)
+
+#include(${QT_USE_FILE})
+add_definitions(${QT_DEFINITIONS})
+add_definitions(-DQT_PLUGIN)
+add_definitions(-DQT_SHARED)
+
+#qt4_wrap_cpp(limesdroutput_HEADERS_MOC ${limesdroutput_HEADERS})
+qt5_wrap_ui(limesdroutput_FORMS_HEADERS ${limesdroutput_FORMS})
+
+add_library(outputlimesdr SHARED
+ ${limesdroutput_SOURCES}
+ ${limesdroutput_HEADERS_MOC}
+ ${limesdroutput_FORMS_HEADERS}
+)
+
+target_link_libraries(outputlimesdr
+ ${QT_LIBRARIES}
+ ${LIMESUITE_LIBRARY}
+ sdrbase
+ limesdrdevice
+)
+
+qt5_use_modules(inputlimesdr Core Widgets)
+
+install(TARGETS inputlimesdr DESTINATION lib/plugins/samplesource)
diff --git a/plugins/samplesink/limesdroutput/limesdroutput.cpp b/plugins/samplesink/limesdroutput/limesdroutput.cpp
new file mode 100644
index 000000000..e046dbf45
--- /dev/null
+++ b/plugins/samplesink/limesdroutput/limesdroutput.cpp
@@ -0,0 +1,845 @@
+///////////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 2017 Edouard Griffiths, F4EXB //
+// //
+// This program 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 as version 3 of the License, or //
+// //
+// This program 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 V3 for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see . //
+///////////////////////////////////////////////////////////////////////////////////
+
+#include
+#include
+#include
+#include
+#include "lime/LimeSuite.h"
+
+#include "device/devicesourceapi.h"
+#include "device/devicesinkapi.h"
+#include "dsp/dspcommands.h"
+#include "limesdrinputthread.h"
+#include "limesdr/devicelimesdrparam.h"
+#include "limesdr/devicelimesdr.h"
+#include "limesdroutput.h"
+
+MESSAGE_CLASS_DEFINITION(LimeSDROutput::MsgConfigureLimeSDR, Message)
+MESSAGE_CLASS_DEFINITION(LimeSDROutput::MsgGetStreamInfo, Message)
+MESSAGE_CLASS_DEFINITION(LimeSDROutput::MsgSetReferenceConfig, Message)
+MESSAGE_CLASS_DEFINITION(LimeSDROutput::MsgReportLimeSDRToGUI, Message)
+MESSAGE_CLASS_DEFINITION(LimeSDROutput::MsgReportStreamInfo, Message)
+
+
+LimeSDROutput::LimeSDROutput(DeviceSinkAPI *deviceAPI) :
+ m_deviceAPI(deviceAPI),
+ m_settings(),
+ m_limeSDROutputThread(0),
+ m_deviceDescription(),
+ m_running(false),
+ m_firstConfig(true)
+{
+ m_streamId.handle = 0;
+ openDevice();
+}
+
+LimeSDROutput::~LimeSDROutput()
+{
+ if (m_running) stop();
+ closeDevice();
+}
+
+bool LimeSDROutput::openDevice()
+{
+ // look for Tx buddies and get reference to common parameters
+ // if there is a channel left take the first available
+ if (m_deviceAPI->getSinkBuddies().size() > 0) // look sink sibling first
+ {
+ qDebug("LimeSDROutput::openDevice: look in Ix buddies");
+
+ DeviceSinkAPI *sinkBuddy = m_deviceAPI->getSinkBuddies()[0];
+ m_deviceShared = *((DeviceLimeSDRShared *) sinkBuddy->getBuddySharedPtr()); // copy shared data
+ DeviceLimeSDRParams *deviceParams = m_deviceShared.m_deviceParams; // get device parameters
+
+ if (deviceParams == 0)
+ {
+ qCritical("LimeSDROutput::openDevice: cannot get device parameters from Tx buddy");
+ return false; // the device params should have been created by the buddy
+ }
+ else
+ {
+ qDebug("LimeSDROutput::openDevice: getting device parameters from Tx buddy");
+ }
+
+ if (m_deviceAPI->getSinkBuddies().size() == deviceParams->m_nbTxChannels)
+ {
+ qCritical("LimeSDROutput::openDevice: no more Tx channels available in device");
+ return false; // no more Tx channels available in device
+ }
+ else
+ {
+ qDebug("LimeSDROutput::openDevice: at least one more Tx channel is available in device");
+ }
+
+ // look for unused channel number
+ char *busyChannels = new char[deviceParams->m_nbTxChannels];
+ memset(busyChannels, 0, deviceParams->m_nbTxChannels);
+
+ for (int i = 0; i < m_deviceAPI->getSinkBuddies().size(); i++)
+ {
+ DeviceSinkAPI *buddy = m_deviceAPI->getSinkBuddies()[i];
+ DeviceLimeSDRShared *buddyShared = (DeviceLimeSDRShared *) buddy->getBuddySharedPtr();
+ busyChannels[buddyShared->m_channel] = 1;
+
+ if (buddyShared->m_thread) { // suspend Tx buddy's thread for proper stream allocation later
+ ((LimeSDROutputThread *) buddyShared->m_thread)->stopWork();
+ }
+ }
+
+ std::size_t ch = 0;
+
+ for (;ch < deviceParams->m_nbTxChannels; ch++)
+ {
+ if (busyChannels[ch] == 0) {
+ break; // first available is the good one
+ }
+ }
+
+ m_deviceShared.m_channel = ch;
+ delete[] busyChannels;
+ }
+ // look for Rx buddies and get reference to common parameters
+ // take the first Rx channel
+ else if (m_deviceAPI->getSourceBuddies().size() > 0) // then source
+ {
+ qDebug("LimeSDROutput::openDevice: look in Rx buddies");
+
+ DeviceSourceAPI *sourceBuddy = m_deviceAPI->getSourceBuddies()[0];
+ m_deviceShared = *((DeviceLimeSDRShared *) sourceBuddy->getBuddySharedPtr()); // copy parameters
+
+ if (m_deviceShared.m_deviceParams == 0)
+ {
+ qCritical("LimeSDROutput::openDevice: cannot get device parameters from Rx buddy");
+ return false; // the device params should have been created by the buddy
+ }
+ else
+ {
+ qDebug("LimeSDROutput::openDevice: getting device parameters from Rx buddy");
+ }
+
+ m_deviceShared.m_channel = 0; // take first channel
+ }
+ // There are no buddies then create the first LimeSDR common parameters
+ // open the device this will also populate common fields
+ // take the first Tx channel
+ else
+ {
+ qDebug("LimeSDROutput::openDevice: open device here");
+
+ m_deviceShared.m_deviceParams = new DeviceLimeSDRParams();
+ char serial[256];
+ strcpy(serial, qPrintable(m_deviceAPI->getSampleSinkSerial()));
+ m_deviceShared.m_deviceParams->open(serial);
+ m_deviceShared.m_channel = 0; // take first channel
+ }
+
+ m_deviceAPI->setBuddySharedPtr(&m_deviceShared); // propagate common parameters to API
+
+ // acquire the channel
+
+ if (LMS_EnableChannel(m_deviceShared.m_deviceParams->getDevice(), LMS_CH_TX, m_deviceShared.m_channel, true) != 0)
+ {
+ qCritical("LimeSDROutput::openDevice: cannot enable Tx channel %lu", m_deviceShared.m_channel);
+ return false;
+ }
+ else
+ {
+ qDebug("LimeSDROutput::openDevice: Tx channel %lu enabled", m_deviceShared.m_channel);
+ }
+
+ // set up the stream
+
+ m_streamId.channel = m_deviceShared.m_channel; // channel number
+ m_streamId.fifoSize = 1024 * 128; // fifo size in samples
+ m_streamId.throughputVsLatency = 1.0; // optimize for max throughput
+ m_streamId.isTx = true; // TX channel
+ m_streamId.dataFmt = lms_stream_t::LMS_FMT_I12; // 12-bit integers
+
+ if (LMS_SetupStream(m_deviceShared.m_deviceParams->getDevice(), &m_streamId) != 0)
+ {
+ qCritical("LimeSDROutput::start: cannot setup the stream on Tx channel %lu", m_deviceShared.m_channel);
+ return false;
+ }
+ else
+ {
+ qDebug("LimeSDROutput::start: stream set up on Tx channel %lu", m_deviceShared.m_channel);
+ }
+
+ // resume Tx buddy's threads
+
+ for (int i = 0; i < m_deviceAPI->getSinkBuddies().size(); i++)
+ {
+ DeviceSinkAPI *buddy = m_deviceAPI->getSinkBuddies()[i];
+ DeviceLimeSDRShared *buddyShared = (DeviceLimeSDRShared *) buddy->getBuddySharedPtr();
+
+ if (buddyShared->m_thread) {
+ ((LimeSDROutputThread *) buddyShared->m_thread)->startWork();
+ }
+ }
+
+ return true;
+}
+
+void LimeSDROutput::closeDevice()
+{
+ if (m_deviceShared.m_deviceParams->getDevice() == 0) { // was never open
+ return;
+ }
+
+ // suspend Tx buddy's threads
+
+ for (int i = 0; i < m_deviceAPI->getSinkBuddies().size(); i++)
+ {
+ DeviceSinkAPI *buddy = m_deviceAPI->getSinkBuddies()[i];
+ DeviceLimeSDRShared *buddyShared = (DeviceLimeSDRShared *) buddy->getBuddySharedPtr();
+
+ if (buddyShared->m_thread) {
+ ((LimeSDROutputThread *) buddyShared->m_thread)->stopWork();
+ }
+ }
+
+ // destroy the stream
+ LMS_DestroyStream(m_deviceShared.m_deviceParams->getDevice(), &m_streamId);
+ m_streamId.handle = 0;
+
+ // release the channel
+
+ if (LMS_EnableChannel(m_deviceShared.m_deviceParams->getDevice(), LMS_CH_TX, m_deviceShared.m_channel, false) != 0)
+ {
+ qWarning("LimeSDROutput::closeDevice: cannot disable Tx channel %lu", m_deviceShared.m_channel);
+ }
+
+ m_deviceShared.m_channel = -1;
+
+ // resume Tx buddy's threads
+
+ for (int i = 0; i < m_deviceAPI->getSinkBuddies().size(); i++)
+ {
+ DeviceSourceAPI *buddy = m_deviceAPI->getSinkBuddies()[i];
+ DeviceLimeSDRShared *buddyShared = (DeviceLimeSDRShared *) buddy->getBuddySharedPtr();
+
+ if (buddyShared->m_thread) {
+ ((LimeSDROutputThread *) buddyShared->m_thread)->startWork();
+ }
+ }
+
+ // No buddies so effectively close the device
+
+ if ((m_deviceAPI->getSourceBuddies().size() == 0) && (m_deviceAPI->getSinkBuddies().size() == 0))
+ {
+ m_deviceShared.m_deviceParams->close();
+ delete m_deviceShared.m_deviceParams;
+ m_deviceShared.m_deviceParams = 0;
+ }
+}
+
+bool LimeSDROutput::start()
+{
+ if (!m_deviceShared.m_deviceParams->getDevice()) {
+ return false;
+ }
+
+ if (m_running) stop();
+
+ // start / stop streaming is done in the thread.
+
+ if ((m_limeSDROutputThread = new LimeSDROutputThread(&m_streamId, &m_sampleSourceFifo)) == 0)
+ {
+ qFatal("LimeSDROutput::start: cannot create thread");
+ stop();
+ return false;
+ }
+ else
+ {
+ qDebug("LimeSDROutput::start: thread created");
+ }
+
+ m_limeSDROutputThread->setLog2Interpolation(m_settings.m_log2SoftInterp);
+
+ m_limeSDROutputThread->startWork();
+
+ m_deviceShared.m_thread = (void *) m_limeSDROutputThread;
+ m_running = true;
+
+ return true;
+}
+
+void LimeSDROutput::stop()
+{
+ if (m_limeSDROutputThread != 0)
+ {
+ m_limeSDROutputThread->stopWork();
+ delete m_limeSDROutputThread;
+ m_limeSDROutputThread = 0;
+ }
+
+ m_deviceShared.m_thread = 0;
+ m_running = false;
+}
+
+const QString& LimeSDROutput::getDeviceDescription() const
+{
+ return m_deviceDescription;
+}
+
+int LimeSDROutput::getSampleRate() const
+{
+ int rate = m_settings.m_devSampleRate;
+ return (rate / (1<m_loRangeTx;
+ minF = range.min;
+ maxF = range.max;
+ stepF = range.step;
+ qDebug("LimeSDROutput::getLORange: min: %f max: %f step: %f", range.min, range.max, range.step);
+}
+
+void LimeSDROutput::getSRRange(float& minF, float& maxF, float& stepF) const
+{
+ lms_range_t range = m_deviceShared.m_deviceParams->m_srRangeTx;
+ minF = range.min;
+ maxF = range.max;
+ stepF = range.step;
+ qDebug("LimeSDROutput::getSRRange: min: %f max: %f step: %f", range.min, range.max, range.step);
+}
+
+void LimeSDROutput::getLPRange(float& minF, float& maxF, float& stepF) const
+{
+ lms_range_t range = m_deviceShared.m_deviceParams->m_lpfRangeTx;
+ minF = range.min;
+ maxF = range.max;
+ stepF = range.step;
+ qDebug("LimeSDROutput::getLPRange: min: %f max: %f step: %f", range.min, range.max, range.step);
+}
+
+uint32_t LimeSDROutput::getHWLog2Interp() const
+{
+ return m_deviceShared.m_deviceParams->m_log2OvSRTx;
+}
+
+bool LimeSDROutput::handleMessage(const Message& message)
+{
+ if (MsgConfigureLimeSDR::match(message))
+ {
+ MsgConfigureLimeSDR& conf = (MsgConfigureLimeSDR&) message;
+ qDebug() << "LimeSDROutput::handleMessage: MsgConfigureLimeSDR";
+
+ if (!applySettings(conf.getSettings(), m_firstConfig))
+ {
+ qDebug("LimeSDROutput::handleMessage config error");
+ }
+ else
+ {
+ m_firstConfig = false;
+ }
+
+ return true;
+ }
+ else if (MsgSetReferenceConfig::match(message))
+ {
+ MsgSetReferenceConfig& conf = (MsgSetReferenceConfig&) message;
+ qDebug() << "LimeSDROutput::handleMessage: MsgSetReferenceConfig";
+ m_settings = conf.getSettings();
+ m_deviceShared.m_ncoFrequency = m_settings.m_ncoEnable ? m_settings.m_ncoFrequency : 0; // for buddies
+ m_deviceShared.m_centerFrequency = m_settings.m_centerFrequency; // for buddies
+ return true;
+ }
+ else if (MsgGetStreamInfo::match(message))
+ {
+// qDebug() << "LimeSDROutput::handleMessage: MsgGetStreamInfo";
+ lms_stream_status_t status;
+
+ if (m_streamId.handle && (LMS_GetStreamStatus(&m_streamId, &status) == 0))
+ {
+ MsgReportStreamInfo *report = MsgReportStreamInfo::create(
+ true, // Success
+ status.active,
+ status.fifoFilledCount,
+ status.fifoSize,
+ status.underrun,
+ status.overrun,
+ status.droppedPackets,
+ status.sampleRate,
+ status.linkRate,
+ status.timestamp);
+ m_deviceAPI->getDeviceOutputMessageQueue()->push(report);
+ }
+ else
+ {
+ MsgReportStreamInfo *report = MsgReportStreamInfo::create(
+ false, // Success
+ false, // status.active,
+ 0, // status.fifoFilledCount,
+ 16384, // status.fifoSize,
+ 0, // status.underrun,
+ 0, // status.overrun,
+ 0, // status.droppedPackets,
+ 0, // status.sampleRate,
+ 0, // status.linkRate,
+ 0); // status.timestamp);
+ m_deviceAPI->getDeviceOutputMessageQueue()->push(report);
+ }
+
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+}
+
+bool LimeSDROutput::applySettings(const LimeSDROutputSettings& settings, bool force)
+{
+ bool forwardChangeOwnDSP = false;
+ bool forwardChangeTxDSP = false;
+ bool forwardChangeAllDSP = false;
+ bool suspendOwnThread = false;
+ bool suspendTxThread = false;
+ bool suspendAllThread = false;
+ bool doCalibration = false;
+// QMutexLocker mutexLocker(&m_mutex);
+
+ // determine if buddies threads or own thread need to be suspended
+
+ if ((m_settings.m_devSampleRate != settings.m_devSampleRate) || force)
+ {
+ suspendAllThread = true;
+ }
+
+ if ((m_settings.m_log2HardInterp != settings.m_log2HardInterp) ||
+ (m_settings.m_centerFrequency != settings.m_centerFrequency) || force)
+ {
+ suspendTxThread = true;
+ }
+
+ if ((m_settings.m_gain != settings.m_gain) ||
+ (m_settings.m_lpfBW != settings.m_lpfBW) ||
+ (m_settings.m_lpfFIRBW != settings.m_lpfFIRBW) ||
+ (m_settings.m_lpfFIREnable != settings.m_lpfFIREnable) ||
+ (m_settings.m_ncoEnable != settings.m_ncoEnable) ||
+ (m_settings.m_ncoFrequency != settings.m_ncoFrequency) || force)
+ {
+ suspendOwnThread = true;
+ }
+
+ // suspend buddies threads or own thread
+
+ if (suspendAllThread)
+ {
+ const std::vector& sourceBuddies = m_deviceAPI->getSourceBuddies();
+ std::vector::const_iterator itSource = sourceBuddies.begin();
+
+ for (; itSource != sourceBuddies.end(); ++itSource)
+ {
+ DeviceLimeSDRShared *buddySharedPtr = (DeviceLimeSDRShared *) (*itSource)->getBuddySharedPtr();
+ if (buddySharedPtr->m_thread)
+ {
+ ((LimeSDRInputThread *) buddySharedPtr->m_thread)->stopWork();
+ }
+ }
+
+ const std::vector& sinkBuddies = m_deviceAPI->getSinkBuddies();
+ std::vector::const_iterator itSink = sinkBuddies.begin();
+
+ for (; itSink != sinkBuddies.end(); ++itSink)
+ {
+ DeviceLimeSDRShared *buddySharedPtr = (DeviceLimeSDRShared *) (*itSink)->getBuddySharedPtr();
+ if (buddySharedPtr->m_thread)
+ {
+ ((LimeSDROutputThread *) buddySharedPtr->m_thread)->stopWork();
+ }
+ }
+
+ if (m_limeSDROutputThread) {
+ m_limeSDROutputThread->stopWork();
+ }
+ }
+ else if (suspendTxThread)
+ {
+ const std::vector& sinkBuddies = m_deviceAPI->getSinkBuddies();
+ std::vector::const_iterator itSink = sinkBuddies.begin();
+
+ for (; itSink != sinkBuddies.end(); ++itSink)
+ {
+ DeviceLimeSDRShared *buddySharedPtr = (DeviceLimeSDRShared *) (*itSink)->getBuddySharedPtr();
+ if (buddySharedPtr->m_thread)
+ {
+ ((LimeSDROutputThread *) buddySharedPtr->m_thread)->stopWork();
+ }
+ }
+
+ if (m_limeSDROutputThread) {
+ m_limeSDROutputThread->stopWork();
+ }
+ }
+ else if (suspendOwnThread)
+ {
+ if (m_limeSDROutputThread) {
+ m_limeSDROutputThread->stopWork();
+ }
+ }
+
+ // apply settings
+
+ if ((m_settings.m_gain != settings.m_gain) || force)
+ {
+ m_settings.m_gain = settings.m_gain;
+
+ if (m_deviceShared.m_deviceParams->getDevice() != 0)
+ {
+ if (LMS_SetGaindB(m_deviceShared.m_deviceParams->getDevice(),
+ LMS_CH_TX,
+ m_deviceShared.m_channel,
+ m_settings.m_gain) < 0)
+ {
+ qDebug("LimeSDROutput::applySettings: LMS_SetGaindB() failed");
+ }
+ else
+ {
+ doCalibration = true;
+ qDebug() << "LimeSDROutput::applySettings: Gain set to " << m_settings.m_gain;
+ }
+ }
+ }
+
+ if ((m_settings.m_devSampleRate != settings.m_devSampleRate)
+ || (m_settings.m_log2HardInterp != settings.m_log2HardInterp) || force)
+ {
+ forwardChangeTxDSP = m_settings.m_log2HardInterp != settings.m_log2HardInterp;
+ forwardChangeAllDSP = m_settings.m_devSampleRate != settings.m_devSampleRate;
+
+ m_settings.m_devSampleRate = settings.m_devSampleRate;
+ m_settings.m_log2HardInterp = settings.m_log2HardInterp;
+
+ if (m_deviceShared.m_deviceParams->getDevice() != 0)
+ {
+ if (LMS_SetSampleRateDir(m_deviceShared.m_deviceParams->getDevice(),
+ LMS_CH_TX,
+ m_settings.m_devSampleRate,
+ 1<m_log2OvSRTx = m_settings.m_log2HardInterp;
+ m_deviceShared.m_deviceParams->m_sampleRate = m_settings.m_devSampleRate;
+ doCalibration = true;
+ qDebug("LimeSDROutput::applySettings: set sample rate set to %d with oversampling of %d",
+ m_settings.m_devSampleRate,
+ 1<getDevice() != 0)
+ {
+ if (LMS_SetLPFBW(m_deviceShared.m_deviceParams->getDevice(),
+ LMS_CH_TX,
+ m_deviceShared.m_channel,
+ m_settings.m_lpfBW) < 0)
+ {
+ qCritical("LimeSDROutput::applySettings: could not set LPF to %f Hz", m_settings.m_lpfBW);
+ }
+ else
+ {
+ doCalibration = true;
+ qDebug("LimeSDROutput::applySettings: LPF set to %f Hz", m_settings.m_lpfBW);
+ }
+ }
+ }
+
+ if ((m_settings.m_lpfFIRBW != settings.m_lpfFIRBW) ||
+ (m_settings.m_lpfFIREnable != settings.m_lpfFIREnable) || force)
+ {
+ m_settings.m_lpfFIRBW = settings.m_lpfFIRBW;
+ m_settings.m_lpfFIREnable = settings.m_lpfFIREnable;
+
+ if (m_deviceShared.m_deviceParams->getDevice() != 0)
+ {
+ if (LMS_SetGFIRLPF(m_deviceShared.m_deviceParams->getDevice(),
+ LMS_CH_TX,
+ m_deviceShared.m_channel,
+ m_settings.m_lpfFIREnable,
+ m_settings.m_lpfFIRBW) < 0)
+ {
+ qCritical("LimeSDROutput::applySettings: could %s and set LPF FIR to %f Hz",
+ m_settings.m_lpfFIREnable ? "enable" : "disable",
+ m_settings.m_lpfFIRBW);
+ }
+ else
+ {
+ doCalibration = true;
+ qDebug("LimeSDROutput::applySettings: %sd and set LPF FIR to %f Hz",
+ m_settings.m_lpfFIREnable ? "enable" : "disable",
+ m_settings.m_lpfFIRBW);
+ }
+ }
+ }
+
+ if ((m_settings.m_ncoFrequency != settings.m_ncoFrequency) ||
+ (m_settings.m_ncoEnable != settings.m_ncoEnable) || force)
+ {
+ m_settings.m_ncoFrequency = settings.m_ncoFrequency;
+ m_settings.m_ncoEnable = settings.m_ncoEnable;
+
+ if (m_deviceShared.m_deviceParams->getDevice() != 0)
+ {
+ if (DeviceLimeSDR::setNCOFrequency(m_deviceShared.m_deviceParams->getDevice(),
+ LMS_CH_TX,
+ m_deviceShared.m_channel,
+ m_settings.m_ncoEnable,
+ m_settings.m_ncoFrequency))
+ {
+ doCalibration = true;
+ forwardChangeOwnDSP = true;
+ m_deviceShared.m_ncoFrequency = m_settings.m_ncoEnable ? m_settings.m_ncoFrequency : 0; // for buddies
+ qDebug("LimeSDROutput::applySettings: %sd and set NCO to %d Hz",
+ m_settings.m_ncoEnable ? "enable" : "disable",
+ m_settings.m_ncoFrequency);
+ }
+ else
+ {
+ qCritical("LimeSDROutput::applySettings: could not %s and set NCO to %d Hz",
+ m_settings.m_ncoEnable ? "enable" : "disable",
+ m_settings.m_ncoFrequency);
+ }
+ }
+ }
+
+ if ((m_settings.m_log2SoftInterp != settings.m_log2SoftInterp) || force)
+ {
+ m_settings.m_log2SoftInterp = settings.m_log2SoftInterp;
+ forwardChangeOwnDSP = true;
+
+ if (m_limeSDROutputThread != 0)
+ {
+ m_limeSDROutputThread->setLog2Interpolation(m_settings.m_log2SoftInterp);
+ qDebug() << "LimeSDROutput::applySettings: set soft decimation to " << (1<getDevice() != 0)
+ {
+ if (LMS_SetLOFrequency(m_deviceShared.m_deviceParams->getDevice(),
+ LMS_CH_TX,
+ m_deviceShared.m_channel, // same for both channels anyway but switches antenna port automatically
+ m_settings.m_centerFrequency) < 0)
+ {
+ qCritical("LimeSDROutput::applySettings: could not set frequency to %lu", m_settings.m_centerFrequency);
+ }
+ else
+ {
+ doCalibration = true;
+ m_deviceShared.m_centerFrequency = m_settings.m_centerFrequency; // for buddies
+ qDebug("LimeSDROutput::applySettings: frequency set to %lu", m_settings.m_centerFrequency);
+ }
+ }
+ }
+
+
+ if (doCalibration)
+ {
+ if (LMS_Calibrate(m_deviceShared.m_deviceParams->getDevice(),
+ LMS_CH_TX,
+ m_deviceShared.m_channel,
+ m_settings.m_lpfBW,
+ 0) < 0)
+ {
+ qCritical("LimeSDROutput::applySettings: calibration failed on Rx channel %lu", m_deviceShared.m_channel);
+ }
+ else
+ {
+ qDebug("LimeSDROutput::applySettings: calibration successful on Rx channel %lu", m_deviceShared.m_channel);
+ }
+ }
+
+ // resume buddies threads or own thread
+
+ if (suspendAllThread)
+ {
+ const std::vector& sourceBuddies = m_deviceAPI->getSourceBuddies();
+ std::vector::const_iterator itSource = sourceBuddies.begin();
+
+ for (; itSource != sourceBuddies.end(); ++itSource)
+ {
+ DeviceLimeSDRShared *buddySharedPtr = (DeviceLimeSDRShared *) (*itSource)->getBuddySharedPtr();
+ if (buddySharedPtr->m_thread)
+ {
+ ((LimeSDRInputThread *) buddySharedPtr->m_thread)->startWork();
+ }
+ }
+
+ const std::vector& sinkBuddies = m_deviceAPI->getSinkBuddies();
+ std::vector::const_iterator itSink = sinkBuddies.begin();
+
+ for (; itSink != sinkBuddies.end(); ++itSink)
+ {
+ DeviceLimeSDRShared *buddySharedPtr = (DeviceLimeSDRShared *) (*itSink)->getBuddySharedPtr();
+ if (buddySharedPtr->m_thread)
+ {
+ ((LimeSDROutputThread *) buddySharedPtr->m_thread)->startWork();
+ }
+ }
+
+ if (m_limeSDROutputThread) {
+ m_limeSDROutputThread->startWork();
+ }
+ }
+ else if (suspendTxThread)
+ {
+ const std::vector& sinkBuddies = m_deviceAPI->getSinkBuddies();
+ std::vector::const_iterator itSink = sinkBuddies.begin();
+
+ for (; itSink != sinkBuddies.end(); ++itSink)
+ {
+ DeviceLimeSDRShared *buddySharedPtr = (DeviceLimeSDRShared *) (*itSink)->getBuddySharedPtr();
+ if (buddySharedPtr->m_thread)
+ {
+ ((LimeSDROutputThread *) buddySharedPtr->m_thread)->startWork();
+ }
+ }
+
+ if (m_limeSDROutputThread) {
+ m_limeSDROutputThread->startWork();
+ }
+ }
+ else if (suspendOwnThread)
+ {
+ if (m_limeSDROutputThread) {
+ m_limeSDROutputThread->startWork();
+ }
+ }
+
+ // forward changes to buddies or oneself
+
+ if (forwardChangeAllDSP)
+ {
+ qDebug("LimeSDROutput::applySettings: forward change to all buddies");
+
+ int sampleRate = m_settings.m_devSampleRate/(1<getDeviceInputMessageQueue()->push(notif);
+
+ // send to sink buddies
+ const std::vector& sinkBuddies = m_deviceAPI->getSinkBuddies();
+ std::vector::const_iterator itSink = sinkBuddies.begin();
+
+ for (; itSink != sinkBuddies.end(); ++itSink)
+ {
+ DeviceLimeSDRShared *buddySharedPtr = (DeviceLimeSDRShared *) (*itSink)->getBuddySharedPtr();
+ uint64_t buddyCenterFreq = buddySharedPtr->m_centerFrequency;
+ int buddyNCOFreq = buddySharedPtr->m_ncoFrequency;
+ DSPSignalNotification *notif = new DSPSignalNotification(sampleRate, buddyCenterFreq + buddyNCOFreq); // do not change center frequency
+ (*itSink)->getDeviceInputMessageQueue()->push(notif);
+ MsgReportLimeSDRToGUI *report = MsgReportLimeSDRToGUI::create(
+ m_settings.m_centerFrequency,
+ m_settings.m_devSampleRate,
+ m_settings.m_log2HardInterp);
+ (*itSink)->getDeviceOutputMessageQueue()->push(report);
+ }
+
+ // send to source buddies
+ const std::vector& sourceBuddies = m_deviceAPI->getSourceBuddies();
+ std::vector::const_iterator itSource = sourceBuddies.begin();
+
+ for (; itSource != sourceBuddies.end(); ++itSource)
+ {
+ DeviceLimeSDRShared *buddySharedPtr = (DeviceLimeSDRShared *) (*itSource)->getBuddySharedPtr();
+ int buddyNCOFreq = buddySharedPtr->m_ncoFrequency;
+ DSPSignalNotification *notif = new DSPSignalNotification(sampleRate, m_settings.m_centerFrequency + buddyNCOFreq);
+ (*itSource)->getDeviceInputMessageQueue()->push(notif);
+ MsgReportLimeSDRToGUI *report = MsgReportLimeSDRToGUI::create(
+ m_settings.m_centerFrequency,
+ m_settings.m_devSampleRate,
+ m_settings.m_log2HardInterp);
+ (*itSource)->getDeviceOutputMessageQueue()->push(report);
+ }
+ }
+ else if (forwardChangeTxDSP)
+ {
+ qDebug("LimeSDROutput::applySettings: forward change to Tx buddies");
+
+ int sampleRate = m_settings.m_devSampleRate/(1<getDeviceInputMessageQueue()->push(notif);
+
+ // send to sink buddies
+ const std::vector& sinkBuddies = m_deviceAPI->getSinkBuddies();
+ std::vector::const_iterator itSink = sinkBuddies.begin();
+
+ for (; itSink != sinkBuddies.end(); ++itSink)
+ {
+ DeviceLimeSDRShared *buddySharedPtr = (DeviceLimeSDRShared *) (*itSink)->getBuddySharedPtr();
+ uint64_t buddyCenterFreq = buddySharedPtr->m_centerFrequency;
+ int buddyNCOFreq = buddySharedPtr->m_ncoFrequency;
+ DSPSignalNotification *notif = new DSPSignalNotification(sampleRate, buddyCenterFreq + buddyNCOFreq); // do not change center frequency
+ (*itSink)->getDeviceInputMessageQueue()->push(notif);
+ MsgReportLimeSDRToGUI *report = MsgReportLimeSDRToGUI::create(
+ m_settings.m_centerFrequency,
+ m_settings.m_devSampleRate,
+ m_settings.m_log2HardInterp);
+ (*itSink)->getDeviceOutputMessageQueue()->push(report);
+ }
+ }
+ else if (forwardChangeOwnDSP)
+ {
+ qDebug("LimeSDROutput::applySettings: forward change to self only");
+
+ int sampleRate = m_settings.m_devSampleRate/(1<getDeviceInputMessageQueue()->push(notif);
+ }
+
+ qDebug() << "LimeSDROutput::applySettings: center freq: " << m_settings.m_centerFrequency << " Hz"
+ << " device stream sample rate: " << m_settings.m_devSampleRate << "S/s"
+ << " sample rate with soft decimation: " << m_settings.m_devSampleRate/(1<. //
+///////////////////////////////////////////////////////////////////////////////////
+
+#ifndef PLUGINS_SAMPLESOURCE_LIMESDROUTPUT_LIMESDROUTPUT_H_
+#define PLUGINS_SAMPLESOURCE_LIMESDROUTPUT_LIMESDROUTPUT_H_
+
+#include
+#include
+
+#include "dsp/devicesamplesink.h"
+#include "limesdr/devicelimesdrshared.h"
+#include "limesdroutputsettings.h"
+
+class DeviceSinkAPI;
+class LimeSDROutputThread;
+struct DeviceLimeSDRParams;
+
+class LimeSDROutput : public DeviceSampleSink
+{
+public:
+ class MsgConfigureLimeSDR : public Message {
+ MESSAGE_CLASS_DECLARATION
+
+ public:
+ const LimeSDROutputSettings& getSettings() const { return m_settings; }
+
+ static MsgConfigureLimeSDR* create(const LimeSDROutputSettings& settings)
+ {
+ return new MsgConfigureLimeSDR(settings);
+ }
+
+ private:
+ LimeSDROutputSettings m_settings;
+
+ MsgConfigureLimeSDR(const LimeSDROutputSettings& settings) :
+ Message(),
+ m_settings(settings)
+ { }
+ };
+
+ class MsgSetReferenceConfig : public Message {
+ MESSAGE_CLASS_DECLARATION
+
+ public:
+ const LimeSDROutputSettings& getSettings() const { return m_settings; }
+
+ static MsgSetReferenceConfig* create(const LimeSDROutputSettings& settings)
+ {
+ return new MsgSetReferenceConfig(settings);
+ }
+
+ private:
+ LimeSDROutputSettings m_settings;
+
+ MsgSetReferenceConfig(const LimeSDROutputSettings& settings) :
+ Message(),
+ m_settings(settings)
+ { }
+ };
+
+ class MsgGetStreamInfo : public Message {
+ MESSAGE_CLASS_DECLARATION
+
+ public:
+ static MsgGetStreamInfo* create()
+ {
+ return new MsgGetStreamInfo();
+ }
+
+ private:
+ MsgGetStreamInfo() :
+ Message()
+ { }
+ };
+
+ class MsgReportLimeSDRToGUI : public Message {
+ MESSAGE_CLASS_DECLARATION
+
+ public:
+ float getCenterFrequency() const { return m_centerFrequency; }
+ int getSampleRate() const { return m_sampleRate; }
+ uint32_t getLog2HardInterp() const { return m_log2HardInterp; }
+
+ static MsgReportLimeSDRToGUI* create(float centerFrequency, int sampleRate, uint32_t log2HardInterp)
+ {
+ return new MsgReportLimeSDRToGUI(centerFrequency, sampleRate, log2HardInterp);
+ }
+
+ private:
+ float m_centerFrequency;
+ int m_sampleRate;
+ uint32_t m_log2HardInterp;
+
+ MsgReportLimeSDRToGUI(float centerFrequency, int sampleRate, uint32_t log2HardInterp) :
+ Message(),
+ m_centerFrequency(centerFrequency),
+ m_sampleRate(sampleRate),
+ m_log2HardInterp(log2HardInterp)
+ { }
+ };
+
+ class MsgReportStreamInfo : public Message {
+ MESSAGE_CLASS_DECLARATION
+
+ public:
+ bool getSuccess() const { return m_success; }
+ bool getActive() const { return m_active; }
+ uint32_t getFifoFilledCount() const { return m_fifoFilledCount; }
+ uint32_t getFifoSize() const { return m_fifoSize; }
+ uint32_t getUnderrun() const { return m_underrun; }
+ uint32_t getOverrun() const { return m_overrun; }
+ uint32_t getDroppedPackets() const { return m_droppedPackets; }
+ float getSampleRate() const { return m_sampleRate; }
+ float getLinkRate() const { return m_linkRate; }
+ uint64_t getTimestamp() const { return m_timestamp; }
+
+ static MsgReportStreamInfo* create(
+ bool success,
+ bool active,
+ uint32_t fifoFilledCount,
+ uint32_t fifoSize,
+ uint32_t underrun,
+ uint32_t overrun,
+ uint32_t droppedPackets,
+ float sampleRate,
+ float linkRate,
+ uint64_t timestamp
+ )
+ {
+ return new MsgReportStreamInfo(
+ success,
+ active,
+ fifoFilledCount,
+ fifoSize,
+ underrun,
+ overrun,
+ droppedPackets,
+ sampleRate,
+ linkRate,
+ timestamp
+ );
+ }
+
+ private:
+ bool m_success;
+ // everything from lms_stream_status_t
+ bool m_active; //!< Indicates whether the stream is currently active
+ uint32_t m_fifoFilledCount; //!< Number of samples in FIFO buffer
+ uint32_t m_fifoSize; //!< Size of FIFO buffer
+ uint32_t m_underrun; //!< FIFO underrun count
+ uint32_t m_overrun; //!< FIFO overrun count
+ uint32_t m_droppedPackets; //!< Number of dropped packets by HW
+ float m_sampleRate; //!< Sampling rate of the stream
+ float m_linkRate; //!< Combined data rate of all stream of the same direction (TX or RX)
+ uint64_t m_timestamp; //!< Current HW timestamp
+
+ MsgReportStreamInfo(
+ bool success,
+ bool active,
+ uint32_t fifoFilledCount,
+ uint32_t fifoSize,
+ uint32_t underrun,
+ uint32_t overrun,
+ uint32_t droppedPackets,
+ float sampleRate,
+ float linkRate,
+ uint64_t timestamp
+ ) :
+ Message(),
+ m_success(success),
+ m_active(active),
+ m_fifoFilledCount(fifoFilledCount),
+ m_fifoSize(fifoSize),
+ m_underrun(underrun),
+ m_overrun(overrun),
+ m_droppedPackets(droppedPackets),
+ m_sampleRate(sampleRate),
+ m_linkRate(linkRate),
+ m_timestamp(timestamp)
+ { }
+ };
+
+ LimeSDROutput(DeviceSinkAPI *deviceAPI);
+ virtual ~LimeSDROutput();
+
+ virtual bool start();
+ virtual void stop();
+
+ virtual const QString& getDeviceDescription() const;
+ virtual int getSampleRate() const;
+ virtual quint64 getCenterFrequency() const;
+
+ virtual bool handleMessage(const Message& message);
+
+ std::size_t getChannelIndex();
+ void getLORange(float& minF, float& maxF, float& stepF) const;
+ void getSRRange(float& minF, float& maxF, float& stepF) const;
+ void getLPRange(float& minF, float& maxF, float& stepF) const;
+ uint32_t getHWLog2Interp() const;
+
+private:
+ DeviceSinkAPI *m_deviceAPI;
+ QMutex m_mutex;
+ LimeSDROutputSettings m_settings;
+ LimeSDROutputThread* m_limeSDROutputThread;
+ QString m_deviceDescription;
+ bool m_running;
+ DeviceLimeSDRShared m_deviceShared;
+ bool m_firstConfig;
+
+ lms_stream_t m_streamId;
+
+ bool openDevice();
+ void closeDevice();
+ bool applySettings(const LimeSDROutputSettings& settings, bool force);
+};
+
+#endif /* PLUGINS_SAMPLESOURCE_LIMESDROUTPUT_LIMESDROUTPUT_H_ */
diff --git a/plugins/samplesink/limesdroutput/limesdroutputgui.ui b/plugins/samplesink/limesdroutput/limesdroutputgui.ui
new file mode 100644
index 000000000..e4c11cb53
--- /dev/null
+++ b/plugins/samplesink/limesdroutput/limesdroutputgui.ui
@@ -0,0 +1,825 @@
+
+
+ LimeSDROutputGUI
+
+
+
+ 0
+ 0
+ 350
+ 290
+
+
+
+
+ 0
+ 0
+
+
+
+
+ 350
+ 290
+
+
+
+
+ Sans Serif
+ 9
+
+
+
+ LimeSDR Input
+
+
+
+ 3
+
+
+ 2
+
+
+ 2
+
+
+ 2
+
+
+ 2
+
+ -
+
+
+ 2
+
+
-
+
+
-
+
+
-
+
+
+ start/stop acquisition
+
+
+
+
+
+
+ :/play.png
+ :/stop.png:/play.png
+
+
+
+
+
+ -
+
+
-
+
+
+ I/Q sample rate kS/s
+
+
+ 00000k
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 0
+ 0
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 32
+ 16
+
+
+
+
+ Monospace
+ 20
+
+
+
+ SizeVerCursor
+
+
+ Qt::StrongFocus
+
+
+ Main center frequency in kHz
+
+
+
+ -
+
+
+ kHz
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 0
+ 0
+
+
+
+
+ -
+
+
+
+ 20
+ 0
+
+
+
+ Channel number
+
+
+ #0
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+
+
+ -
+
+
+ 2
+
+
+ 2
+
+
-
+
+
+ Enable the TSP NCO
+
+
+ NCO
+
+
+
+ -
+
+
+
+ 22
+ 22
+
+
+
+ Reset the NCO to zero frequency
+
+
+ R
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 32
+ 16
+
+
+
+
+ Monospace
+ 12
+
+
+
+ Center frequency with NCO engaged (kHz)
+
+
+
+ -
+
+
+ kHz
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ -
+
+
+ 2
+
+
+ 2
+
+
-
+
+
+ Hw
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+ -
+
+
+
+ 50
+ 16777215
+
+
+
+ TSP hardware interpolation factor
+
+
+ 2
+
+
-
+
+ 1
+
+
+ -
+
+ 2
+
+
+ -
+
+ 4
+
+
+ -
+
+ 8
+
+
+ -
+
+ 16
+
+
+ -
+
+ 32
+
+
+
+
+ -
+
+
+ Sw
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+ -
+
+
+
+ 50
+ 16777215
+
+
+
+ Software interpolation factor
+
+
+ 0
+
+
-
+
+ 1
+
+
+ -
+
+ 2
+
+
+ -
+
+ 4
+
+
+ -
+
+ 8
+
+
+ -
+
+ 16
+
+
+ -
+
+ 32
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ SR
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 32
+ 16
+
+
+
+
+ Monospace
+ 12
+
+
+
+ Device to host sample rate
+
+
+
+ -
+
+
+ S/s
+
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ -
+
+
+ 2
+
+
+ 2
+
+
-
+
+
+ LP
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 32
+ 16
+
+
+
+
+ Monospace
+ 12
+
+
+
+ Analog lowpass filer bandwidth (kHz)
+
+
+
+ -
+
+
+ kHz
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
+
+ Enable or disable TSP digital FIR lowpass filters
+
+
+ FIR
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 32
+ 16
+
+
+
+
+ Monospace
+ 12
+
+
+
+ Digital FIR lowpass filers bandwidth (kHz)
+
+
+
+ -
+
+
+ kHz
+
+
+
+
+
+ -
+
+
+ 2
+
+
+ 2
+
+
-
+
+
+ Gain
+
+
+
+ -
+
+
+ Global gain setting (dB)
+
+
+ 0
+
+
+ 70
+
+
+ 1
+
+
+ 20
+
+
+ Qt::Horizontal
+
+
+
+ -
+
+
+
+ 40
+ 0
+
+
+
+ Global gain (dB)
+
+
+ 20dB
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ -
+
+
+ 2
+
+
+ 2
+
+
-
+
+
+
+ 24
+ 24
+
+
+
+
+ 24
+ 24
+
+
+
+ Green when stream is reporting data
+
+
+
+
+
+ :/stream.png
+
+
+
+ -
+
+
+
+ 12
+ 0
+
+
+
+ Red if underruns
+
+
+ background:rgb(79,79,79);
+
+
+ U
+
+
+ Qt::AlignCenter
+
+
+
+ -
+
+
+
+ 12
+ 0
+
+
+
+ Red if overruns
+
+
+ background:rgb(79,79,79);
+
+
+ O
+
+
+ Qt::AlignCenter
+
+
+
+ -
+
+
+
+ 12
+ 0
+
+
+
+ Red if dropped packets
+
+
+ background:rgb(79,79,79);
+
+
+ D
+
+
+ Qt::AlignCenter
+
+
+
+ -
+
+
+
+ 90
+ 0
+
+
+
+ Stream link rate (MB/s)
+
+
+ 000.000 MB/s
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+ -
+
+
+
+ 16777215
+ 16
+
+
+
+
+ 8
+
+
+
+ FIFO fill status
+
+
+ QProgressBar{border: 2px solid rgb(79, 79, 79); text-align: center;}
+QToolTip{background-color: white; color: black;}
+
+
+ 0
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ -
+
+
-
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
+
+
+
+
+
+
+ ValueDial
+ QWidget
+
+ 1
+
+
+ ButtonSwitch
+ QToolButton
+
+
+
+
+
+
+
+
diff --git a/plugins/samplesink/limesdroutput/limesdroutputplugin.cpp b/plugins/samplesink/limesdroutput/limesdroutputplugin.cpp
new file mode 100644
index 000000000..a8cf546d6
--- /dev/null
+++ b/plugins/samplesink/limesdroutput/limesdroutputplugin.cpp
@@ -0,0 +1,130 @@
+///////////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 2015 Edouard Griffiths, F4EXB //
+// //
+// This program 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 as version 3 of the License, or //
+// //
+// This program 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 V3 for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see . //
+///////////////////////////////////////////////////////////////////////////////////
+
+#include
+#include
+
+#include
+#include
+
+#include "lime/LimeSuite.h"
+#include "plugin/pluginapi.h"
+#include "util/simpleserializer.h"
+#include "device/devicesinkapi.h"
+
+#include "limesdroutputgui.h"
+#include "limesdroutputplugin.h"
+
+const PluginDescriptor LimeSDROutputPlugin::m_pluginDescriptor = {
+ QString("LimeSDR Output"),
+ QString("3.4.0"),
+ QString("(c) Edouard Griffiths, F4EXB"),
+ QString("https://github.com/f4exb/sdrangel"),
+ true,
+ QString("https://github.com/f4exb/sdrangel")
+};
+
+const QString LimeSDROutputPlugin::m_hardwareID = "LimeSDR";
+const QString LimeSDROutputPlugin::m_deviceTypeID = LIMESDROUTPUT_DEVICE_TYPE_ID;
+
+LimeSDROutputPlugin::LimeSDROutputPlugin(QObject* parent) :
+ QObject(parent)
+{
+}
+
+const PluginDescriptor& LimeSDROutputPlugin::getPluginDescriptor() const
+{
+ return m_pluginDescriptor;
+}
+
+void LimeSDROutputPlugin::initPlugin(PluginAPI* pluginAPI)
+{
+ pluginAPI->registerSampleSource(m_deviceTypeID, this);
+}
+
+PluginInterface::SamplingDevices LimeSDROutputPlugin::enumSampleSinks()
+{
+ lms_info_str_t* deviceList;
+ int nbDevices;
+ SamplingDevices result;
+
+ if ((nbDevices = LMS_GetDeviceList(0)) <= 0)
+ {
+ qDebug("LimeSDROutputPlugin::enumSampleSources: Could not find any LimeSDR device");
+ return result; // empty result
+ }
+
+ deviceList = new lms_info_str_t[nbDevices];
+
+ if (LMS_GetDeviceList(deviceList) < 0)
+ {
+ qDebug("LimeSDROutputPlugin::enumSampleSources: Could not obtain LimeSDR devices information");
+ delete[] deviceList;
+ return result; // empty result
+ }
+ else
+ {
+ for (int i = 0; i < nbDevices; i++)
+ {
+ std::string serial("N/D");
+ findSerial((const char *) deviceList[i], serial);
+
+ qDebug("LimeSDROutputPlugin::enumSampleSources: device #%d: %s", i, (char *) deviceList[i]);
+ QString displayedName(QString("LimeSDR[%1] %2").arg(i).arg(serial.c_str()));
+ result.append(SamplingDevice(displayedName,
+ m_hardwareID,
+ m_deviceTypeID,
+ QString(deviceList[i]),
+ i));
+ }
+ }
+
+ delete[] deviceList;
+ return result;
+}
+
+PluginGUI* LimeSDROutputPlugin::createSampleSinkPluginGUI(const QString& sinkId,QWidget **widget, DeviceSinkAPI *deviceAPI)
+{
+ if(sinkId == m_deviceTypeID)
+ {
+ LimeSDROutputGUI* gui = new LimeSDROutputGUI(deviceAPI);
+ *widget = gui;
+ return gui;
+ }
+ else
+ {
+ return 0;
+ }
+}
+
+bool LimeSDROutputPlugin::findSerial(const char *lmsInfoStr, std::string& serial)
+{
+ std::regex serial_reg("serial=([0-9,A-F]+)");
+ std::string input(lmsInfoStr);
+ std::smatch result;
+ std::regex_search(input, result, serial_reg);
+
+ if (result[1].str().length()>0)
+ {
+ serial = result[1].str();
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+}
+
diff --git a/plugins/samplesink/limesdroutput/limesdroutputplugin.h b/plugins/samplesink/limesdroutput/limesdroutputplugin.h
new file mode 100644
index 000000000..3e631598e
--- /dev/null
+++ b/plugins/samplesink/limesdroutput/limesdroutputplugin.h
@@ -0,0 +1,50 @@
+///////////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 2017 Edouard Griffiths, F4EXB //
+// //
+// This program 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 as version 3 of the License, or //
+// //
+// This program 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 V3 for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see . //
+///////////////////////////////////////////////////////////////////////////////////
+
+#ifndef PLUGINS_SAMPLESOURCE_LIMESDROUTPUT_LIMESDROUTPUTPLUGIN_H_
+#define PLUGINS_SAMPLESOURCE_LIMESDROUTPUT_LIMESDROUTPUTPLUGIN_H_
+
+#include
+#include "plugin/plugininterface.h"
+
+class PluginAPI;
+
+#define LIMESDROUTPUT_DEVICE_TYPE_ID "sdrangel.samplesink.limesdr"
+
+class LimeSDROutputPlugin : public QObject, public PluginInterface {
+ Q_OBJECT
+ Q_INTERFACES(PluginInterface)
+ Q_PLUGIN_METADATA(IID LIMESDROUTPUT_DEVICE_TYPE_ID)
+
+public:
+ explicit LimeSDROutputPlugin(QObject* parent = 0);
+
+ const PluginDescriptor& getPluginDescriptor() const;
+ void initPlugin(PluginAPI* pluginAPI);
+
+ virtual SamplingDevices enumSampleSinks();
+ virtual PluginGUI* createSampleSinkPluginGUI(const QString& sinkId, QWidget **widget, DeviceSinkAPI *deviceAPI);
+
+ static const QString m_hardwareID;
+ static const QString m_deviceTypeID;
+
+private:
+ static const PluginDescriptor m_pluginDescriptor;
+ static bool findSerial(const char *lmsInfoStr, std::string& serial);
+};
+
+
+#endif /* PLUGINS_SAMPLESOURCE_LIMESDROUTPUT_LIMESDROUTPUTPLUGIN_H_ */
diff --git a/plugins/samplesink/limesdroutput/limesdroutputsettings.cpp b/plugins/samplesink/limesdroutput/limesdroutputsettings.cpp
new file mode 100644
index 000000000..41dd96ec6
--- /dev/null
+++ b/plugins/samplesink/limesdroutput/limesdroutputsettings.cpp
@@ -0,0 +1,91 @@
+///////////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 2017 Edouard Griffiths, F4EXB //
+// //
+// This program 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 as version 3 of the License, or //
+// //
+// This program 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 V3 for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see . //
+///////////////////////////////////////////////////////////////////////////////////
+
+#include "util/simpleserializer.h"
+
+#include "limesdroutputsettings.h"
+
+LimeSDROutputSettings::LimeSDROutputSettings()
+{
+ resetToDefaults();
+}
+
+void LimeSDROutputSettings::resetToDefaults()
+{
+ m_centerFrequency = 435000*1000;
+ m_devSampleRate = 5000000;
+ m_log2HardInterp = 3;
+ m_log2SoftInterp = 0;
+ m_lpfBW = 4.5e6f;
+ m_lpfFIREnable = false;
+ m_lpfFIRBW = 2.5e6f;
+ m_gain = 30;
+ m_ncoEnable = false;
+ m_ncoFrequency = 0;
+}
+
+QByteArray LimeSDROutputSettings::serialize() const
+{
+ SimpleSerializer s(1);
+
+ s.writeS32(1, m_devSampleRate);
+ s.writeU32(2, m_log2HardInterp);
+ s.writeU32(5, m_log2SoftInterp);
+ s.writeFloat(7, m_lpfBW);
+ s.writeBool(8, m_lpfFIREnable);
+ s.writeFloat(9, m_lpfFIRBW);
+ s.writeU32(10, m_gain);
+ s.writeBool(11, m_ncoEnable);
+ s.writeS32(12, m_ncoFrequency);
+
+ return s.final();
+}
+
+bool LimeSDROutputSettings::deserialize(const QByteArray& data)
+{
+ SimpleDeserializer d(data);
+
+ if (!d.isValid())
+ {
+ resetToDefaults();
+ return false;
+ }
+
+ if (d.getVersion() == 1)
+ {
+ int intval;
+
+ d.readS32(1, &m_devSampleRate, 5000000);
+ d.readU32(2, &m_log2HardInterp, 2);
+ d.readU32(5, &m_log2SoftInterp, 0);
+ d.readFloat(7, &m_lpfBW, 1.5e6);
+ d.readBool(8, &m_lpfFIREnable, false);
+ d.readFloat(9, &m_lpfFIRBW, 1.5e6);
+ d.readU32(10, &m_gain, 0);
+ d.readBool(11, &m_ncoEnable, false);
+ d.readS32(12, &m_ncoFrequency, 0);
+
+ return true;
+ }
+ else
+ {
+ resetToDefaults();
+ return false;
+ }
+
+}
+
+
diff --git a/plugins/samplesink/limesdroutput/limesdroutputsettings.h b/plugins/samplesink/limesdroutput/limesdroutputsettings.h
new file mode 100644
index 000000000..7793e3d1d
--- /dev/null
+++ b/plugins/samplesink/limesdroutput/limesdroutputsettings.h
@@ -0,0 +1,54 @@
+///////////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 2017 Edouard Griffiths, F4EXB //
+// //
+// This program 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 as version 3 of the License, or //
+// //
+// This program 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 V3 for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see . //
+///////////////////////////////////////////////////////////////////////////////////
+
+#ifndef PLUGINS_SAMPLESOURCE_LIMESDROUTPUT_LIMESDROUTPUTSETTINGS_H_
+#define PLUGINS_SAMPLESOURCE_LIMESDROUTPUT_LIMESDROUTPUTSETTINGS_H_
+
+#include
+#include
+
+/**
+ * These are the settings individual to each hardware channel or software Tx chain
+ * Plus the settings to be saved in the presets
+ */
+struct LimeSDROutputSettings
+{
+ typedef enum {
+ FC_POS_INFRA = 0,
+ FC_POS_SUPRA,
+ FC_POS_CENTER
+ } fcPos_t;
+
+ // global settings to be saved
+ uint64_t m_centerFrequency;
+ int m_devSampleRate;
+ uint32_t m_log2HardInterp;
+ // channel settings
+ uint32_t m_log2SoftInterp;
+ float m_lpfBW; //!< LMS amalog lowpass filter bandwidth (Hz)
+ bool m_lpfFIREnable; //!< Enable LMS digital lowpass FIR filters
+ float m_lpfFIRBW; //!< LMS digital lowpass FIR filters bandwidth (Hz)
+ uint32_t m_gain; //!< Optimally distributed gain (dB)
+ bool m_ncoEnable; //!< Enable TSP NCO and mixing
+ int m_ncoFrequency; //!< Actual NCO frequency (the resulting frequency with mixing is displayed)
+
+ LimeSDROutputSettings();
+ void resetToDefaults();
+ QByteArray serialize() const;
+ bool deserialize(const QByteArray& data);
+};
+
+#endif /* PLUGINS_SAMPLESOURCE_LIMESDRINPUT_LIMESDRINPUTSETTINGS_H_ */
diff --git a/plugins/samplesink/limesdroutput/limesdroutputthread.cpp b/plugins/samplesink/limesdroutput/limesdroutputthread.cpp
new file mode 100644
index 000000000..1f123f2b4
--- /dev/null
+++ b/plugins/samplesink/limesdroutput/limesdroutputthread.cpp
@@ -0,0 +1,142 @@
+///////////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 2017 Edouard Griffiths, F4EXB //
+// //
+// This program 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 as version 3 of the License, or //
+// //
+// This program 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 V3 for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see . //
+///////////////////////////////////////////////////////////////////////////////////
+
+#include
+
+#include "limesdroutputthread.h"
+#include "limesdroutputsettings.h"
+
+LimeSDROutputThread::LimeSDROutputThread(lms_stream_t* stream, SampleSourceFifo* sampleFifo, QObject* parent) :
+ QThread(parent),
+ m_running(false),
+ m_stream(stream),
+ m_sampleFifo(sampleFifo),
+ m_log2Interp(0),
+ m_fcPos(LimeSDROutputSettings::FC_POS_CENTER)
+{
+ m_sampleFifo->resize(16*LIMESDROUTPUT_BLOCKSIZE);
+}
+
+LimeSDROutputThread::~LimeSDROutputThread()
+{
+ stopWork();
+}
+
+void LimeSDROutputThread::startWork()
+{
+ if (m_running) return; // return if running already
+
+ m_startWaitMutex.lock();
+ start();
+ while(!m_running)
+ m_startWaiter.wait(&m_startWaitMutex, 100);
+ m_startWaitMutex.unlock();
+}
+
+void LimeSDROutputThread::stopWork()
+{
+ if (!m_running) return; // return if not running
+
+ m_running = false;
+ wait();
+}
+
+void LimeSDROutputThread::setLog2Interpolation(unsigned int log2_interp)
+{
+ m_log2Interp = log2_interp;
+}
+
+void LimeSDROutputThread::setFcPos(int fcPos)
+{
+ m_fcPos = fcPos;
+}
+
+void LimeSDROutputThread::run()
+{
+ int res;
+
+ lms_stream_meta_t metadata; //Use metadata for additional control over sample receive function behaviour
+ metadata.flushPartialPacket = false; //Do not discard data remainder when read size differs from packet size
+ metadata.waitForTimestamp = false; //Do not wait for specific timestamps
+
+ m_running = true;
+ m_startWaiter.wakeAll();
+
+ if (LMS_StartStream(m_stream) < 0) {
+ qCritical("LimeSDROutputThread::run: could not start stream");
+ } else {
+ qDebug("LimeSDROutputThread::run: stream started");
+ }
+
+ while (m_running)
+ {
+ callback(m_buf, 2 * res);
+
+ if ((res = LMS_SendStream(m_stream, (void *) m_buf, LIMESDR_BLOCKSIZE, &metadata, 1000)) < 0)
+ {
+ qCritical("LimeSDROutputThread::run write error: %s", strerror(errno));
+ break;
+ }
+ }
+
+ if (LMS_StopStream(m_stream) < 0) {
+ qCritical("LimeSDROutputThread::run: could not stop stream");
+ } else {
+ qDebug("LimeSDROutputThread::run: stream stopped");
+ }
+
+ m_running = false;
+}
+
+// Interpolate according to specified log2 (ex: log2=4 => decim=16)
+void LimeSDROutputThread::callback(const qint16* buf, qint32 len)
+{
+ SampleVector::iterator beginRead;
+ m_sampleFifo->readAdvance(beginRead, len/(1<. //
+///////////////////////////////////////////////////////////////////////////////////
+
+#ifndef PLUGINS_SAMPLESOURCE_LIMESDROUTPUT_LIMESDROUTPUTTHREAD_H_
+#define PLUGINS_SAMPLESOURCE_LIMESDROUTPUT_LIMESDROUTPUTTHREAD_H_
+
+#include
+#include
+#include
+
+#include "lime/LimeSuite.h"
+
+#include "dsp/samplesourcefifo.h"
+#include "dsp/interpolators.h"
+
+#define LIMESDROUTPUT_BLOCKSIZE (1<<14) //complex samples per buffer ~10k (16k)
+
+class LimeSDROutputThread : public QThread
+{
+ Q_OBJECT
+
+public:
+ LimeSDROutputThread(lms_stream_t* stream, SampleSourceFifo* sampleFifo, QObject* parent = 0);
+ ~LimeSDROutputThread();
+
+ void startWork();
+ void stopWork();
+ void setLog2Interpolation(unsigned int log2_ioterp);
+ void setFcPos(int fcPos);
+
+private:
+ QMutex m_startWaitMutex;
+ QWaitCondition m_startWaiter;
+ bool m_running;
+
+ lms_stream_t* m_stream;
+ qint16 m_buf[2*LIMESDROUTPUT_BLOCKSIZE]; //must hold I+Q values of each sample hence 2xcomplex size
+ SampleSourceFifo* m_sampleFifo;
+
+ unsigned int m_log2Interp; // soft decimation
+ int m_fcPos;
+
+ Interpolators m_interpolators;
+
+ void run();
+ void callback(const qint16* buf, qint32 len);
+};
+
+
+
+#endif /* PLUGINS_SAMPLESOURCE_LIMESDROUTPUT_LIMESDROUTPUTTHREAD_H_ */