From 826941ba80c9f492ee50232abfb384538f1a0af7 Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 20 Apr 2017 18:21:01 +0200 Subject: [PATCH] LimeSDR input: NCO support (draft #1) --- devices/limesdr/CMakeLists.txt | 2 + devices/limesdr/devicelimesdr.cpp | 167 +++++++++++++++++ devices/limesdr/devicelimesdr.h | 33 ++++ .../limesdrinput/limesdrinput.cpp | 34 +++- .../limesdrinput/limesdrinputgui.cpp | 32 +++- .../limesdrinput/limesdrinputgui.h | 3 + .../limesdrinput/limesdrinputgui.ui | 169 +++++++++++++----- .../limesdrinput/limesdrinputsettings.cpp | 2 + .../limesdrinput/limesdrinputsettings.h | 2 + 9 files changed, 396 insertions(+), 48 deletions(-) create mode 100644 devices/limesdr/devicelimesdr.cpp create mode 100644 devices/limesdr/devicelimesdr.h diff --git a/devices/limesdr/CMakeLists.txt b/devices/limesdr/CMakeLists.txt index 6856b1e72..0313da1f3 100644 --- a/devices/limesdr/CMakeLists.txt +++ b/devices/limesdr/CMakeLists.txt @@ -1,10 +1,12 @@ project(limesdrdevice) set(limesdrdevice_SOURCES + devicelimesdr.cpp devicelimesdrparam.cpp ) set(limesdrdevice_HEADERS + devicelimesdr.h devicelimesdrparam.h ) diff --git a/devices/limesdr/devicelimesdr.cpp b/devices/limesdr/devicelimesdr.cpp new file mode 100644 index 000000000..90931fcad --- /dev/null +++ b/devices/limesdr/devicelimesdr.cpp @@ -0,0 +1,167 @@ +/////////////////////////////////////////////////////////////////////////////////// +// 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 "devicelimesdr.h" + +bool DeviceLimeSDR::enableNCO(lms_device_t *device, bool dir_tx, std::size_t chan, float frequency, bool enable) +{ + if (LMS_WriteParam(device, LMS7param(MAC), chan+1) < 0) + { + fprintf(stderr, "DeviceLimeSDR::enableNCO: cannot address channel #%lu\n", chan); + return false; + } + + if (dir_tx) + { + if (LMS_WriteParam(device, LMS7param(CMIX_BYP_RXTSP), enable ? 0 : 1) < 0) + { + fprintf(stderr, "DeviceLimeSDR::enableNCO: cannot %s Rx NCO\n", enable ? "enable" : "disable"); + return false; + } + else + { + return true; + } + } + else + { + if (LMS_WriteParam(device, LMS7param(CMIX_BYP_TXTSP), enable ? 0 : 1) < 0) + { + fprintf(stderr, "DeviceLimeSDR::enableNCO: cannot %s Tx NCO\n", enable ? "enable" : "disable"); + return false; + } + else + { + return true; + } + } +} + +bool DeviceLimeSDR::setNCOFrequency(lms_device_t *device, bool dir_tx, std::size_t chan, float frequency) +{ + bool positive; + float freqs[LMS_NCO_VAL_COUNT]; + float phos[LMS_NCO_VAL_COUNT]; + + if (LMS_GetNCOFrequency(device, dir_tx, chan, freqs, phos) < 0) + { + fprintf(stderr, "DeviceLimeSDR::setNCOFrequency: cannot get NCO frequencies and phases\n"); + } + + if (frequency < 0) + { + positive = false; + frequency = -frequency; + } + else + { + positive = true; + } + + freqs[0] = frequency; + + if (LMS_SetNCOFrequency(device, dir_tx, chan, freqs, phos) < 0) + { + fprintf(stderr, "DeviceLimeSDR::setNCOFrequency: cannot set frequency to %f\n", frequency); + return false; + } + + if (LMS_SetNCOIndex(device, dir_tx, chan, 0, positive) < 0) // TODO: verify positive = downconvert ? + { + fprintf(stderr, "DeviceLimeSDR::setNCOFrequency: cannot set conversion direction %s\n", positive ? "down" : "up"); + return false; + } + + return true; +} + +bool DeviceLimeSDR::setNCOFrequency(lms_device_t *device, bool dir_tx, std::size_t chan, bool enable, float frequency) +{ + if (enable) + { + bool positive; + float freqs[LMS_NCO_VAL_COUNT]; + float phos[LMS_NCO_VAL_COUNT]; + + if (LMS_GetNCOFrequency(device, dir_tx, chan, freqs, phos) < 0) + { + fprintf(stderr, "DeviceLimeSDR::setNCOFrequency: cannot get NCO frequencies and phases\n"); + } + + if (frequency < 0) + { + positive = false; + frequency = -frequency; + } + else + { + positive = true; + } + + freqs[0] = frequency; + + if (LMS_SetNCOFrequency(device, dir_tx, chan, freqs, phos) < 0) + { + fprintf(stderr, "DeviceLimeSDR::setNCOFrequency: cannot set frequency to %f\n", frequency); + return false; + } + + if (LMS_SetNCOIndex(device, dir_tx, chan, 0, positive) < 0) // TODO: verify positive = downconvert ? + { + fprintf(stderr, "DeviceLimeSDR::setNCOFrequency: cannot set conversion direction %s\n", positive ? "down" : "up"); + return false; + } + + return true; + } + else + { + if (LMS_WriteParam(device, LMS7param(MAC), chan+1) < 0) + { + fprintf(stderr, "DeviceLimeSDR::setNCOFrequency: cannot address channel #%lu\n", chan); + return false; + } + + if (dir_tx) + { + if (LMS_WriteParam(device, LMS7param(CMIX_BYP_RXTSP), 1) < 0) + { + fprintf(stderr, "DeviceLimeSDR::enableNCO: cannot disable Rx NCO on channel %lu\n", chan); + return false; + } + else + { + return true; + } + } + else + { + if (LMS_WriteParam(device, LMS7param(CMIX_BYP_TXTSP), 1) < 0) + { + fprintf(stderr, "DeviceLimeSDR::enableNCO: cannot disable Tx NCO on channel %lu\n", chan); + return false; + } + else + { + return true; + } + } + } +} + + diff --git a/devices/limesdr/devicelimesdr.h b/devices/limesdr/devicelimesdr.h new file mode 100644 index 000000000..7b7d2202f --- /dev/null +++ b/devices/limesdr/devicelimesdr.h @@ -0,0 +1,33 @@ +/////////////////////////////////////////////////////////////////////////////////// +// 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 DEVICES_LIMESDR_DEVICELIMESDR_H_ +#define DEVICES_LIMESDR_DEVICELIMESDR_H_ + +#include "lime/LimeSuite.h" + +class DeviceLimeSDR +{ +public: + /** enable or disable NCO. If re-enabled frequency should have been set once */ + static bool enableNCO(lms_device_t *device, bool dir_tx, std::size_t chan, bool enable); + /** set NCO frequency with positive or negative frequency (deals with up/down convert). Enables NCO */ + static bool setNCOFrequency(lms_device_t *device, bool dir_tx, std::size_t chan, float frequency); + /** combination of the two like LMS_SetGFIRLPF */ + static bool setNCOFrequency(lms_device_t *device, bool dir_tx, std::size_t chan, bool enable, float frequency); +}; + +#endif /* DEVICES_LIMESDR_DEVICELIMESDR_H_ */ diff --git a/plugins/samplesource/limesdrinput/limesdrinput.cpp b/plugins/samplesource/limesdrinput/limesdrinput.cpp index 3eb448dd2..e3f6def3d 100644 --- a/plugins/samplesource/limesdrinput/limesdrinput.cpp +++ b/plugins/samplesource/limesdrinput/limesdrinput.cpp @@ -26,6 +26,7 @@ #include "limesdrinput.h" #include "limesdrinputthread.h" #include "limesdr/devicelimesdrparam.h" +#include "limesdr/devicelimesdr.h" MESSAGE_CLASS_DEFINITION(LimeSDRInput::MsgConfigureLimeSDR, Message) MESSAGE_CLASS_DEFINITION(LimeSDRInput::MsgGetStreamInfo, Message) @@ -457,7 +458,9 @@ bool LimeSDRInput::applySettings(const LimeSDRInputSettings& settings, bool forc 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) || force) + (m_settings.m_lpfFIREnable != settings.m_lpfFIREnable) || + (m_settings.m_ncoEnable != settings.m_ncoEnable) || + (m_settings.m_ncoFrequency != settings.m_ncoFrequency) || force) { suspendOwnThread = true; } @@ -648,6 +651,34 @@ bool LimeSDRInput::applySettings(const LimeSDRInputSettings& settings, bool forc } } + 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_RX, + m_deviceShared.m_channel, + m_settings.m_ncoEnable, + m_settings.m_ncoFrequency)) + { + doCalibration = true; + qDebug("LimeSDRInput::applySettings: %sd and set NCO to %f Hz", + m_settings.m_ncoEnable ? "enable" : "disable", + m_settings.m_ncoFrequency); + } + else + { + qCritical("LimeSDRInput::applySettings: could %s and set LPF FIR to %f Hz", + m_settings.m_ncoEnable ? "enable" : "disable", + m_settings.m_ncoFrequency); + } + } + } + if ((m_settings.m_log2SoftDecim != settings.m_log2SoftDecim) || force) { m_settings.m_log2SoftDecim = settings.m_log2SoftDecim; @@ -682,6 +713,7 @@ bool LimeSDRInput::applySettings(const LimeSDRInputSettings& settings, bool forc } } + if (doCalibration) { if (LMS_Calibrate(m_deviceShared.m_deviceParams->getDevice(), diff --git a/plugins/samplesource/limesdrinput/limesdrinputgui.cpp b/plugins/samplesource/limesdrinput/limesdrinputgui.cpp index aab39ff83..87a366af2 100644 --- a/plugins/samplesource/limesdrinput/limesdrinputgui.cpp +++ b/plugins/samplesource/limesdrinput/limesdrinputgui.cpp @@ -61,6 +61,8 @@ LimeSDRInputGUI::LimeSDRInputGUI(DeviceSourceAPI *deviceAPI, QWidget* parent) : ui->lpFIR->setColorMapper(ColorMapper(ColorMapper::ReverseGold)); ui->lpFIR->setValueRange(5, 1U, 56000U); + ui->ncoFrequency->setColorMapper(ColorMapper(ColorMapper::ReverseGold)); + ui->channelNumberText->setText(tr("#%1").arg(m_limeSDRInput->getChannelIndex())); connect(&m_updateTimer, SIGNAL(timeout()), this, SLOT(updateHardware())); @@ -244,6 +246,12 @@ void LimeSDRInputGUI::displaySettings() ui->gain->setValue(m_settings.m_gain); ui->gainText->setText(tr("%1dB").arg(m_settings.m_gain)); + + int ncoHalfRange = (m_settings.m_devSampleRate * (1<<(m_settings.m_log2HardDecim)))/2; + ui->ncoFrequency->setValueRange(7, + (m_settings.m_centerFrequency - ncoHalfRange)/1000, + (m_settings.m_centerFrequency + ncoHalfRange)/1000); // frequency dial is in kHz + ui->ncoFrequency->setValue(m_settings.m_centerFrequency + m_settings.m_ncoFrequency); } void LimeSDRInputGUI::sendSettings() @@ -345,6 +353,25 @@ void LimeSDRInputGUI::on_centerFrequency_changed(quint64 value) sendSettings(); } +void LimeSDRInputGUI::on_ncoFrequency_changed(quint64 value) +{ + m_settings.m_ncoFrequency = (int64_t) value - (int64_t) m_settings.m_centerFrequency; + sendSettings(); +} + +void LimeSDRInputGUI::on_ncoEnable_toggled(bool checked) +{ + m_settings.m_ncoEnable = checked; + sendSettings(); +} + +void LimeSDRInputGUI::on_ncoReset_clicked(bool checked) +{ + m_settings.m_ncoFrequency = 0; + ui->ncoFrequency->setValue(m_settings.m_centerFrequency); + sendSettings(); +} + void LimeSDRInputGUI::on_dcOffset_toggled(bool checked) { m_settings.m_dcBlock = checked; @@ -393,7 +420,10 @@ void LimeSDRInputGUI::on_lpFIREnable_toggled(bool checked) void LimeSDRInputGUI::on_lpFIR_changed(quint64 value) { m_settings.m_lpfFIRBW = value * 1000; - sendSettings(); + + if (m_settings.m_lpfFIREnable) { // do not send the update if the FIR is disabled + sendSettings(); + } } void LimeSDRInputGUI::on_gain_valueChanged(int value) diff --git a/plugins/samplesource/limesdrinput/limesdrinputgui.h b/plugins/samplesource/limesdrinput/limesdrinputgui.h index 28fa87a6c..bffdc9e91 100644 --- a/plugins/samplesource/limesdrinput/limesdrinputgui.h +++ b/plugins/samplesource/limesdrinput/limesdrinputgui.h @@ -74,6 +74,9 @@ private slots: void on_startStop_toggled(bool checked); void on_record_toggled(bool checked); void on_centerFrequency_changed(quint64 value); + void on_ncoFrequency_changed(quint64 value); + void on_ncoEnable_toggled(bool checked); + void on_ncoReset_clicked(bool checked); void on_dcOffset_toggled(bool checked); void on_iqImbalance_toggled(bool checked); void on_sampleRate_changed(quint64 value); diff --git a/plugins/samplesource/limesdrinput/limesdrinputgui.ui b/plugins/samplesource/limesdrinput/limesdrinputgui.ui index 5c3453348..7d7b825e2 100644 --- a/plugins/samplesource/limesdrinput/limesdrinputgui.ui +++ b/plugins/samplesource/limesdrinput/limesdrinputgui.ui @@ -29,7 +29,7 @@ - BladeRF + LimeSDR Input @@ -190,43 +190,79 @@ - - - Qt::Horizontal + + + 6 - - - - - - - - S/s - - - - - + + 6 + + + 6 + + + 6 + + + - Automatic IQ imbalance correction + Enable the TSP NCO - IQ + NCO - - + + + + + 22 + 22 + + - Automatic DC offset removal + Reset the NCO to zero frequency - DC + R - - + + + + + 0 + 0 + + + + + 32 + 16 + + + + + Monospace + 12 + + + + Center frequency with NCO engaged (kHz) + + + + + + + kHz + + + + + Qt::Horizontal @@ -238,14 +274,20 @@ - - + + + + + 0 + 0 + + - Auto + SR - + @@ -267,16 +309,10 @@ - - - - - 0 - 0 - - + + - SR + S/s @@ -290,13 +326,47 @@ - + 2 2 + + + + Auto + + + + + + + Automatic DC offset removal + + + DC + + + + + + + Automatic IQ imbalance correction + + + IQ + + + + + + + Qt::Vertical + + + @@ -422,13 +492,6 @@ - - - - Qt::Horizontal - - - @@ -539,6 +602,13 @@ + + + + Qt::Horizontal + + + @@ -591,6 +661,13 @@ + + + + Qt::Horizontal + + + diff --git a/plugins/samplesource/limesdrinput/limesdrinputsettings.cpp b/plugins/samplesource/limesdrinput/limesdrinputsettings.cpp index 796064049..0da40ff7e 100644 --- a/plugins/samplesource/limesdrinput/limesdrinputsettings.cpp +++ b/plugins/samplesource/limesdrinput/limesdrinputsettings.cpp @@ -35,6 +35,8 @@ void LimeSDRInputSettings::resetToDefaults() m_lpfFIREnable = false; m_lpfFIRBW = 2.5e6f; m_gain = 30; + m_ncoEnable = false; + m_ncoFrequency = 0; } QByteArray LimeSDRInputSettings::serialize() const diff --git a/plugins/samplesource/limesdrinput/limesdrinputsettings.h b/plugins/samplesource/limesdrinput/limesdrinputsettings.h index c87f84291..548329184 100644 --- a/plugins/samplesource/limesdrinput/limesdrinputsettings.h +++ b/plugins/samplesource/limesdrinput/limesdrinputsettings.h @@ -45,6 +45,8 @@ struct LimeSDRInputSettings 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) LimeSDRInputSettings(); void resetToDefaults();