diff --git a/plugins/channelrx/chanalyzer/chanalyzer.cpp b/plugins/channelrx/chanalyzer/chanalyzer.cpp index f6f57398c..0873811a7 100644 --- a/plugins/channelrx/chanalyzer/chanalyzer.cpp +++ b/plugins/channelrx/chanalyzer/chanalyzer.cpp @@ -139,7 +139,11 @@ void ChannelAnalyzer::applySettings(const ChannelAnalyzerSettings& settings, boo << " m_ssb: " << settings.m_ssb << " m_pll: " << settings.m_pll << " m_fll: " << settings.m_fll + << " m_costasLoop: " << settings.m_costasLoop << " m_pllPskOrder: " << settings.m_pllPskOrder + << " m_pllBandwidth: " << settings.m_pllBandwidth + << " m_pllDampingFactor: " << settings.m_pllDampingFactor + << " m_pllLoopGain: " << settings.m_pllLoopGain << " m_inputType: " << (int) settings.m_inputType; ChannelAnalyzerBaseband::MsgConfigureChannelAnalyzerBaseband *msg diff --git a/plugins/channelrx/chanalyzer/chanalyzergui.cpp b/plugins/channelrx/chanalyzer/chanalyzergui.cpp index 2ca683fa0..6ca79a07b 100644 --- a/plugins/channelrx/chanalyzer/chanalyzergui.cpp +++ b/plugins/channelrx/chanalyzer/chanalyzergui.cpp @@ -103,18 +103,85 @@ void ChannelAnalyzerGUI::displaySettings() void ChannelAnalyzerGUI::displayPLLSettings() { - if (m_settings.m_fll) - { - ui->pllPskOrder->setCurrentIndex(5); - } + if (m_settings.m_costasLoop) + ui->pllType->setCurrentIndex(2); + else if (m_settings.m_fll) + ui->pllType->setCurrentIndex(1); + else + ui->pllType->setCurrentIndex(0); + setPLLVisibility(); + + int i = 0; + for(; ((m_settings.m_pllPskOrder>>i) & 1) == 0; i++); + if (m_settings.m_costasLoop) + ui->pllPskOrder->setCurrentIndex(i==0 ? 0 : i-1); else - { - int i = 0; - for(; ((m_settings.m_pllPskOrder>>i) & 1) == 0; i++); ui->pllPskOrder->setCurrentIndex(i); - } ui->pll->setChecked(m_settings.m_pll); + ui->pllBandwidth->setValue((int)(m_settings.m_pllBandwidth*1000.0)); + QString bandwidthStr = QString::number(m_settings.m_pllBandwidth, 'f', 3); + ui->pllBandwidthText->setText(bandwidthStr); + ui->pllDampingFactor->setValue((int)(m_settings.m_pllDampingFactor*10.0)); + QString factorStr = QString::number(m_settings.m_pllDampingFactor, 'f', 1); + ui->pllDampingFactorText->setText(factorStr); + ui->pllLoopGain->setValue((int)(m_settings.m_pllLoopGain)); + QString gainStr = QString::number(m_settings.m_pllLoopGain, 'f', 0); + ui->pllLoopGainText->setText(gainStr); +} + +void ChannelAnalyzerGUI::setPLLVisibility() +{ + ui->pllToolbar->setVisible(m_settings.m_pll); + + // BW + ui->pllPskOrder->setVisible(!m_settings.m_fll); + ui->pllLine1->setVisible(!m_settings.m_fll); + ui->pllBandwidthLabel->setVisible(!m_settings.m_fll); + ui->pllBandwidth->setVisible(!m_settings.m_fll); + ui->pllBandwidthText->setVisible(!m_settings.m_fll); + ui->pllLine2->setVisible(!m_settings.m_fll); + + // Damping factor and gain + bool stdPll = !m_settings.m_fll && !m_settings.m_costasLoop; + ui->pllDamplingFactor->setVisible(stdPll); + ui->pllDampingFactor->setVisible(stdPll); + ui->pllDampingFactorText->setVisible(stdPll); + ui->pllLine3->setVisible(stdPll); + ui->pllLoopGainLabel->setVisible(stdPll); + ui->pllLoopGain->setVisible(stdPll); + ui->pllLoopGainText->setVisible(stdPll); + ui->pllLine4->setVisible(stdPll); + + // Order + ui->pllPskOrder->blockSignals(true); + ui->pllPskOrder->clear(); + if (stdPll) + { + ui->pllPskOrder->addItem("CW"); + ui->pllPskOrder->addItem("BPSK"); + ui->pllPskOrder->addItem("QPSK"); + ui->pllPskOrder->addItem("8PSK"); + ui->pllPskOrder->addItem("16PSK"); + } + else if (m_settings.m_costasLoop) + { + ui->pllPskOrder->addItem("BPSK"); + ui->pllPskOrder->addItem("QPSK"); + ui->pllPskOrder->addItem("8PSK"); + if (m_settings.m_pllPskOrder < 2) + m_settings.m_pllPskOrder = 2; + else if (m_settings.m_pllPskOrder > 8) + m_settings.m_pllPskOrder = 8; + } + int i = 0; + for(; ((m_settings.m_pllPskOrder>>i) & 1) == 0; i++); + if (m_settings.m_costasLoop) + ui->pllPskOrder->setCurrentIndex(i==0 ? 0 : i-1); + else + ui->pllPskOrder->setCurrentIndex(i); + ui->pllPskOrder->blockSignals(false); + arrangeRollups(); } void ChannelAnalyzerGUI::setSpectrumDisplay() @@ -212,9 +279,10 @@ void ChannelAnalyzerGUI::tick() if (ui->pll->isChecked()) { - double sampleRate = ((double) m_channelAnalyzer->getChannelSampleRate()) / m_channelAnalyzer->getDecimation(); + double sampleRate = (double) m_channelAnalyzer->getChannelSampleRate(); int freq = (m_channelAnalyzer->getPllFrequency() * sampleRate) / (2.0*M_PI); ui->pll->setToolTip(tr("PLL lock. Freq = %1 Hz").arg(freq)); + ui->pllLockFrequency->setText(tr("%1 Hz").arg(freq)); } } @@ -232,16 +300,48 @@ void ChannelAnalyzerGUI::on_pll_toggled(bool checked) } m_settings.m_pll = checked; + setPLLVisibility(); + applySettings(); +} + +void ChannelAnalyzerGUI::on_pllType_currentIndexChanged(int index) +{ + m_settings.m_fll = (index == 1); + m_settings.m_costasLoop = (index == 2); + setPLLVisibility(); applySettings(); } void ChannelAnalyzerGUI::on_pllPskOrder_currentIndexChanged(int index) { - if (index < 5) { + if (m_settings.m_costasLoop) + m_settings.m_pllPskOrder = (1<<(index+1)); + else m_settings.m_pllPskOrder = (1<pllBandwidthText->setText(bandwidthStr); + applySettings(); +} + +void ChannelAnalyzerGUI::on_pllDampingFactor_valueChanged(int value) +{ + m_settings.m_pllDampingFactor = value/10.0; + QString factorStr = QString::number(m_settings.m_pllDampingFactor, 'f', 1); + ui->pllDampingFactorText->setText(factorStr); + applySettings(); +} + +void ChannelAnalyzerGUI::on_pllLoopGain_valueChanged(int value) +{ + m_settings.m_pllLoopGain = value; + QString gainStr = QString::number(m_settings.m_pllLoopGain, 'f', 0); + ui->pllLoopGainText->setText(gainStr); applySettings(); } diff --git a/plugins/channelrx/chanalyzer/chanalyzergui.h b/plugins/channelrx/chanalyzer/chanalyzergui.h index e81019461..9a6987f9e 100644 --- a/plugins/channelrx/chanalyzer/chanalyzergui.h +++ b/plugins/channelrx/chanalyzer/chanalyzergui.h @@ -81,6 +81,7 @@ private: void applySettings(bool force = false); void displaySettings(); void displayPLLSettings(); + void setPLLVisibility(); void setSpectrumDisplay(); bool handleMessage(const Message& message); @@ -91,7 +92,11 @@ private slots: void on_deltaFrequency_changed(qint64 value); void on_rationalDownSamplerRate_changed(quint64 value); void on_pll_toggled(bool checked); + void on_pllType_currentIndexChanged(int index); void on_pllPskOrder_currentIndexChanged(int index); + void on_pllBandwidth_valueChanged(int value); + void on_pllDampingFactor_valueChanged(int value); + void on_pllLoopGain_valueChanged(int value); void on_useRationalDownsampler_toggled(bool checked); void on_signalSelect_currentIndexChanged(int index); void on_rrcFilter_toggled(bool checked); diff --git a/plugins/channelrx/chanalyzer/chanalyzergui.ui b/plugins/channelrx/chanalyzer/chanalyzergui.ui index 19aaf5e83..cfad3ab2a 100644 --- a/plugins/channelrx/chanalyzer/chanalyzergui.ui +++ b/plugins/channelrx/chanalyzer/chanalyzergui.ui @@ -29,9 +29,9 @@ 0 - 10 - 631 - 81 + 0 + 524 + 101 @@ -127,6 +127,15 @@ + + + + 26 + 26 + 26 + + + @@ -147,6 +156,15 @@ + + + + 26 + 26 + 26 + + + @@ -167,6 +185,15 @@ + + + + 26 + 26 + 26 + + + @@ -335,49 +362,6 @@ - - - - - 40 - 16777215 - - - - PLL PSK order (1 for CW) - - - - 1 - - - - - 2 - - - - - 4 - - - - - 8 - - - - - 16 - - - - - F - - - - @@ -592,6 +576,274 @@ + + + + + 0 + + + 2 + + + 0 + + + 0 + + + + + PLL type + + + + PLL + + + + + FLL + + + + + Costas Loop + + + + + + + + + 70 + 0 + + + + + 70 + 16777215 + + + + PLL PSK order (1 for CW) + + + + CW + + + + + BPSK + + + + + QPSK + + + + + 8PSK + + + + + 16PSK + + + + + + + + Qt::Vertical + + + + + + + BW + + + + + + + + 24 + 24 + + + + PLL loop bandwidth + + + 1 + + + 100 + + + 1 + + + 2 + + + + + + + 0.002 + + + + + + + Qt::Vertical + + + + + + + D + + + + + + + + 24 + 24 + + + + PLL damping factor + + + 1 + + + 10 + + + 1 + + + 5 + + + + + + + 0.5 + + + + + + + Qt::Vertical + + + + + + + G + + + + + + + + 24 + 24 + + + + PLL loop gain + + + 1 + + + 1000 + + + 1 + + + 10 + + + + + + + 10 + + + + + + + Qt::Vertical + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Freq + + + + + + + + 60 + 0 + + + + PLL lock frequency + + + -100000Hz + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + diff --git a/plugins/channelrx/chanalyzer/chanalyzersettings.cpp b/plugins/channelrx/chanalyzer/chanalyzersettings.cpp index f8a313509..51cd93f5b 100644 --- a/plugins/channelrx/chanalyzer/chanalyzersettings.cpp +++ b/plugins/channelrx/chanalyzer/chanalyzersettings.cpp @@ -42,9 +42,13 @@ void ChannelAnalyzerSettings::resetToDefaults() m_ssb = false; m_pll = false; m_fll = false; + m_costasLoop = false; m_rrc = false; m_rrcRolloff = 35; // 0.35 m_pllPskOrder = 1; + m_pllBandwidth = 0.002f; + m_pllDampingFactor = 0.5f; + m_pllLoopGain = 10.0f; m_inputType = InputSignal; m_rgbColor = QColor(128, 128, 128).rgb(); m_title = "Channel Analyzer"; @@ -71,6 +75,10 @@ QByteArray ChannelAnalyzerSettings::serialize() const s.writeString(15, m_title); s.writeBool(16, m_rrc); s.writeU32(17, m_rrcRolloff); + s.writeFloat(18, m_pllBandwidth); + s.writeFloat(19, m_pllDampingFactor); + s.writeFloat(20, m_pllLoopGain); + s.writeBool(21, m_costasLoop); return s.final(); } @@ -118,6 +126,10 @@ bool ChannelAnalyzerSettings::deserialize(const QByteArray& data) d.readString(15, &m_title, "Channel Analyzer"); d.readBool(16, &m_rrc, false); d.readU32(17, &m_rrcRolloff, 35); + d.readFloat(18, &m_pllBandwidth, 0.002f); + d.readFloat(19, &m_pllDampingFactor, 0.5f); + d.readFloat(20, &m_pllLoopGain, 10.0f); + d.readBool(21, &m_costasLoop, false); return true; } diff --git a/plugins/channelrx/chanalyzer/chanalyzersettings.h b/plugins/channelrx/chanalyzer/chanalyzersettings.h index b230c8752..5a3ac8deb 100644 --- a/plugins/channelrx/chanalyzer/chanalyzersettings.h +++ b/plugins/channelrx/chanalyzer/chanalyzersettings.h @@ -40,9 +40,13 @@ struct ChannelAnalyzerSettings bool m_ssb; bool m_pll; bool m_fll; + bool m_costasLoop; bool m_rrc; quint32 m_rrcRolloff; //!< in 100ths unsigned int m_pllPskOrder; + float m_pllBandwidth; + float m_pllDampingFactor; + float m_pllLoopGain; InputType m_inputType; quint32 m_rgbColor; QString m_title; diff --git a/plugins/channelrx/chanalyzer/chanalyzersink.cpp b/plugins/channelrx/chanalyzer/chanalyzersink.cpp index dfd1586dc..15a80ac14 100644 --- a/plugins/channelrx/chanalyzer/chanalyzersink.cpp +++ b/plugins/channelrx/chanalyzer/chanalyzersink.cpp @@ -30,6 +30,7 @@ ChannelAnalyzerSink::ChannelAnalyzerSink() : m_channelSampleRate(48000), m_channelFrequencyOffset(0), m_sinkSampleRate(48000), + m_costasLoop(0.002, 2), m_sampleSink(nullptr) { m_usb = true; @@ -38,7 +39,8 @@ ChannelAnalyzerSink::ChannelAnalyzerSink() : DSBFilter = new fftfilt(m_settings.m_bandwidth / m_channelSampleRate, 2*m_ssbFftLen); RRCFilter = new fftfilt(m_settings.m_bandwidth / m_channelSampleRate, 2*m_ssbFftLen); m_corr = new fftcorr(2*m_corrFFTLen); // 8k for 4k effective samples - m_pll.computeCoefficients(0.002f, 0.5f, 10.0f); // bandwidth, damping factor, loop gain + m_pll.computeCoefficients(m_settings.m_pllBandwidth, m_settings.m_pllDampingFactor, m_settings.m_pllLoopGain); + m_costasLoop.computeCoefficients(m_settings.m_pllBandwidth); applyChannelSettings(m_channelSampleRate, m_sinkSampleRate, m_channelFrequencyOffset, true); applySettings(m_settings, true); @@ -123,21 +125,28 @@ void ChannelAnalyzerSink::processOneSample(Complex& c, fftfilt::cmplx *sideband) if (m_settings.m_pll) { - if (m_settings.m_fll) + // Use -fPLL to mix (exchange PLL real and image in the complex multiplication) + if (m_settings.m_costasLoop) + { + m_costasLoop.feed(re, im); + mix = si * std::conj(m_costasLoop.getComplex()); + feedOneSample(mix, m_costasLoop.getComplex()); + } + else if (m_settings.m_fll) { m_fll.feed(re, im); - // Use -fPLL to mix (exchange PLL real and image in the complex multiplication) mix = si * std::conj(m_fll.getComplex()); + feedOneSample(mix, m_fll.getComplex()); } else { m_pll.feed(re, im); - // Use -fPLL to mix (exchange PLL real and image in the complex multiplication) mix = si * std::conj(m_pll.getComplex()); + feedOneSample(mix, m_pll.getComplex()); } } - - feedOneSample(m_settings.m_pll ? mix : si, m_settings.m_fll ? m_fll.getComplex() : m_pll.getComplex()); + else + feedOneSample(si, si); } } @@ -230,7 +239,11 @@ void ChannelAnalyzerSink::applySettings(const ChannelAnalyzerSettings& settings, << " m_ssb: " << settings.m_ssb << " m_pll: " << settings.m_pll << " m_fll: " << settings.m_fll + << " m_costasLoop: " << settings.m_costasLoop << " m_pllPskOrder: " << settings.m_pllPskOrder + << " m_pllBandwidth: " << settings.m_pllBandwidth + << " m_pllDampingFactor: " << settings.m_pllDampingFactor + << " m_pllLoopGain: " << settings.m_pllLoopGain << " m_inputType: " << (int) settings.m_inputType; bool doApplySampleRate = false; @@ -247,6 +260,7 @@ void ChannelAnalyzerSink::applySettings(const ChannelAnalyzerSettings& settings, { m_pll.reset(); m_fll.reset(); + m_costasLoop.reset(); } } @@ -257,11 +271,30 @@ void ChannelAnalyzerSink::applySettings(const ChannelAnalyzerSettings& settings, } } + if (settings.m_costasLoop != m_settings.m_costasLoop || force) + { + if (settings.m_costasLoop) { + m_costasLoop.reset(); + } + } + if (settings.m_pllPskOrder != m_settings.m_pllPskOrder || force) { if (settings.m_pllPskOrder < 32) { m_pll.setPskOrder(settings.m_pllPskOrder); } + if (settings.m_pllPskOrder < 16) { + m_costasLoop.setPskOrder(settings.m_pllPskOrder); + } + } + + if ((settings.m_pllBandwidth != m_settings.m_pllBandwidth) + || (settings.m_pllDampingFactor != m_settings.m_pllDampingFactor) + || (settings.m_pllLoopGain != m_settings.m_pllLoopGain) + || force) + { + m_pll.computeCoefficients(settings.m_pllBandwidth, settings.m_pllDampingFactor, settings.m_pllLoopGain); + m_costasLoop.computeCoefficients(settings.m_pllBandwidth); } if ((settings.m_rationalDownSample != m_settings.m_rationalDownSample) || @@ -280,15 +313,42 @@ void ChannelAnalyzerSink::applySettings(const ChannelAnalyzerSettings& settings, } } +bool ChannelAnalyzerSink::isPllLocked() const +{ + if (m_settings.m_pll) + return m_pll.locked(); + else + return false; +} + Real ChannelAnalyzerSink::getPllFrequency() const { - if (m_settings.m_fll) { + if (m_settings.m_costasLoop) + return m_costasLoop.getFreq(); + else if (m_settings.m_fll) return m_fll.getFreq(); - } else if (m_settings.m_pll) { + else if (m_settings.m_pll) return m_pll.getFreq(); - } else { + else return 0.0; - } +} + +Real ChannelAnalyzerSink::getPllPhase() const +{ + if (m_settings.m_costasLoop) + return m_costasLoop.getPhiHat(); + else if (m_settings.m_pll) + return m_pll.getPhiHat(); + else + return 0.0f; +} + +Real ChannelAnalyzerSink::getPllDeltaPhase() const +{ + if (m_settings.m_pll) + return m_pll.getDeltaPhi(); + else + return 0.0f; } int ChannelAnalyzerSink::getActualSampleRate() @@ -307,5 +367,6 @@ void ChannelAnalyzerSink::applySampleRate() setFilters(sampleRate, m_settings.m_bandwidth, m_settings.m_lowCutoff); m_pll.setSampleRate(sampleRate); m_fll.setSampleRate(sampleRate); + m_costasLoop.setSampleRate(sampleRate); RRCFilter->create_rrc_filter(m_settings.m_bandwidth / (float) sampleRate, m_settings.m_rrcRolloff / 100.0); } diff --git a/plugins/channelrx/chanalyzer/chanalyzersink.h b/plugins/channelrx/chanalyzer/chanalyzersink.h index 1e5269c19..21f5cf156 100644 --- a/plugins/channelrx/chanalyzer/chanalyzersink.h +++ b/plugins/channelrx/chanalyzer/chanalyzersink.h @@ -26,6 +26,7 @@ #include "dsp/fftfilt.h" #include "dsp/phaselockcomplex.h" #include "dsp/freqlockcomplex.h" +#include "dsp/costasloop.h" #include "audio/audiofifo.h" #include "util/movingaverage.h" @@ -46,10 +47,10 @@ public: double getMagSq() const { return m_magsq; } double getMagSqAvg() const { return (double) m_channelPowerAvg; } - bool isPllLocked() const { return m_settings.m_pll && m_pll.locked(); } + bool isPllLocked() const; Real getPllFrequency() const; - Real getPllDeltaPhase() const { return m_pll.getDeltaPhi(); } - Real getPllPhase() const { return m_pll.getPhiHat(); } + Real getPllDeltaPhase() const; + Real getPllPhase() const; void setSampleSink(BasebandSampleSink* sampleSink) { m_sampleSink = sampleSink; } static const unsigned int m_corrFFTLen; @@ -70,6 +71,7 @@ private: Real m_interpolatorDistanceRemain; PhaseLockComplex m_pll; FreqLockComplex m_fll; + CostasLoop m_costasLoop; DecimatorC m_decimator; fftfilt* SSBFilter; diff --git a/plugins/channelrx/chanalyzer/chanalyzerwebapiadapter.cpp b/plugins/channelrx/chanalyzer/chanalyzerwebapiadapter.cpp index 63bcab1f9..c6b63e824 100644 --- a/plugins/channelrx/chanalyzer/chanalyzerwebapiadapter.cpp +++ b/plugins/channelrx/chanalyzer/chanalyzerwebapiadapter.cpp @@ -55,9 +55,13 @@ void ChannelAnalyzerWebAPIAdapter::webapiFormatChannelSettings( response.getChannelAnalyzerSettings()->setSsb(settings.m_ssb ? 1 : 0); response.getChannelAnalyzerSettings()->setPll(settings.m_pll ? 1 : 0); response.getChannelAnalyzerSettings()->setFll(settings.m_fll ? 1 : 0); + response.getChannelAnalyzerSettings()->setCostasLoop(settings.m_costasLoop ? 1 : 0); response.getChannelAnalyzerSettings()->setRrc(settings.m_rrc ? 1 : 0); response.getChannelAnalyzerSettings()->setRrcRolloff(settings.m_rrcRolloff); response.getChannelAnalyzerSettings()->setPllPskOrder(settings.m_pllPskOrder); + response.getChannelAnalyzerSettings()->setPllBandwidth(settings.m_pllBandwidth); + response.getChannelAnalyzerSettings()->setPllDampingFactor(settings.m_pllBandwidth); + response.getChannelAnalyzerSettings()->setPllLoopGain(settings.m_pllLoopGain); response.getChannelAnalyzerSettings()->setInputType((int) settings.m_inputType); response.getChannelAnalyzerSettings()->setRgbColor(settings.m_rgbColor); response.getChannelAnalyzerSettings()->setTitle(new QString(settings.m_title)); @@ -190,9 +194,21 @@ void ChannelAnalyzerWebAPIAdapter::webapiUpdateChannelSettings( if (channelSettingsKeys.contains("pll")) { settings.m_pll = response.getChannelAnalyzerSettings()->getPll() != 0; } + if (channelSettingsKeys.contains("costasLoop")) { + settings.m_costasLoop = response.getChannelAnalyzerSettings()->getCostasLoop() != 0; + } if (channelSettingsKeys.contains("pllPskOrder")) { settings.m_pllPskOrder = response.getChannelAnalyzerSettings()->getPllPskOrder(); } + if (channelSettingsKeys.contains("pllBandwidth")) { + settings.m_pllBandwidth = response.getChannelAnalyzerSettings()->getPllBandwidth(); + } + if (channelSettingsKeys.contains("pllDampingFactor")) { + settings.m_pllDampingFactor = response.getChannelAnalyzerSettings()->getPllDampingFactor(); + } + if (channelSettingsKeys.contains("pllLoopGain")) { + settings.m_pllLoopGain = response.getChannelAnalyzerSettings()->getPllLoopGain(); + } if (channelSettingsKeys.contains("rgbColor")) { settings.m_rgbColor = response.getChannelAnalyzerSettings()->getRgbColor(); } diff --git a/sdrbase/CMakeLists.txt b/sdrbase/CMakeLists.txt index a055cbebf..09ecdf38b 100644 --- a/sdrbase/CMakeLists.txt +++ b/sdrbase/CMakeLists.txt @@ -97,6 +97,7 @@ set(sdrbase_SOURCES dsp/ctcssfrequencies.cpp dsp/channelsamplesink.cpp dsp/channelsamplesource.cpp + dsp/costasloop.cpp dsp/cwkeyer.cpp dsp/cwkeyersettings.cpp dsp/datafifo.cpp @@ -255,6 +256,7 @@ set(sdrbase_HEADERS dsp/channelsamplesink.h dsp/channelsamplesource.h dsp/complex.h + dsp/costasloop.h dsp/ctcssdetector.h dsp/ctcssfrequencies.h dsp/cwkeyer.h diff --git a/sdrbase/dsp/costasloop.cpp b/sdrbase/dsp/costasloop.cpp new file mode 100644 index 000000000..d3c5022f0 --- /dev/null +++ b/sdrbase/dsp/costasloop.cpp @@ -0,0 +1,110 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright 2006-2021 Free Software Foundation, Inc. // +// Copyright (C) 2018 Edouard Griffiths, F4EXB // +// Copyright (C) 2021 Jon Beniston, M7RCE // +// // +// Based on the Costas Loop from GNU Radio // +// // +// 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 // +// (at your option) any later version. // +// // +// 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 "costasloop.h" +#include + +// Loop bandwidth supposedly ~ 2pi/100 rads/sample +// pskOrder 2, 4 or 8 +CostasLoop::CostasLoop(float loopBW, unsigned int pskOrder) : + m_maxFreq(1.0f), + m_minFreq(-1.0f), + m_pskOrder(pskOrder) +{ + computeCoefficients(loopBW); + reset(); +} + +CostasLoop::~CostasLoop() +{ +} + +void CostasLoop::reset() +{ + m_y.real(1.0f); + m_y.imag(0.0f); + m_freq = 0.0f; + m_phase = 0.0f; + m_freq = 0.0f; + m_error = 0.0f; +} + +// 2nd order loop with critical damping +void CostasLoop::computeCoefficients(float loopBW) +{ + float damping = sqrtf(2.0f) / 2.0f; + float denom = (1.0 + 2.0 * damping * loopBW + loopBW * loopBW); + m_alpha = (4 * damping * loopBW) / denom; + m_beta = (4 * loopBW * loopBW) / denom; +} + +void CostasLoop::setSampleRate(unsigned int sampleRate) +{ + reset(); +} + +static float branchlessClip(float x, float clip) +{ + return 0.5f * (std::abs(x + clip) - std::abs(x - clip)); +} + +// Don't use built-in complex.h multiply to avoid NaN/INF checking +static void fastComplexMultiply(std::complex &out, const std::complex cc1, const std::complex cc2) +{ + float o_r, o_i; + + o_r = (cc1.real() * cc2.real()) - (cc1.imag() * cc2.imag()); + o_i = (cc1.real() * cc2.imag()) + (cc1.imag() * cc2.real()); + + out.real(o_r); + out.imag(o_i); +} + +void CostasLoop::feed(float re, float im) +{ + std::complex nco(std::cosf(-m_phase), std::sinf(-m_phase)); + + std::complex in, out; + in.real(re); + in.imag(im); + fastComplexMultiply(out, in, nco); + + switch (m_pskOrder) + { + case 2: + m_error = phaseDetector2(out); + break; + case 4: + m_error = phaseDetector4(out); + break; + case 8: + m_error = phaseDetector8(out); + break; + } + m_error = branchlessClip(m_error, 1.0f); + + advanceLoop(m_error); + phaseWrap(); + frequencyLimit(); + + m_y.real(-nco.real()); + m_y.imag(nco.imag()); +} diff --git a/sdrbase/dsp/costasloop.h b/sdrbase/dsp/costasloop.h new file mode 100644 index 000000000..77c2e6d79 --- /dev/null +++ b/sdrbase/dsp/costasloop.h @@ -0,0 +1,120 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright 2006-2021 Free Software Foundation, Inc. // +// Copyright (C) 2018 Edouard Griffiths, F4EXB // +// Copyright (C) 2021 Jon Beniston, M7RCE // +// // +// Based on the Costas Loop from GNU Radio // +// // +// 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 // +// (at your option) any later version. // +// // +// 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 SDRBASE_DSP_COSTASLOOP_H_ +#define SDRBASE_DSP_COSTASLOOP_H_ + +#include + +#include "dsp/dsptypes.h" +#include "export.h" + +/** Costas Loop for phase and frequency tracking. */ +class SDRBASE_API CostasLoop +{ +public: + CostasLoop(float loopBW, unsigned int pskOrder); + ~CostasLoop(); + + void computeCoefficients(float loopBW); + void setPskOrder(unsigned int pskOrder) { m_pskOrder = pskOrder; } + void reset(); + void setSampleRate(unsigned int sampleRate); + void feed(float re, float im); + const std::complex& getComplex() const { return m_y; } + float getReal() const { return m_y.real(); } + float getImag() const { return m_y.imag(); } + float getFreq() const { return m_freq; } + float getPhiHat() const { return m_phase; } + +private: + + std::complex m_y; + float m_phase; + float m_freq; + float m_error; + float m_maxFreq; + float m_minFreq; + float m_alpha; + float m_beta; + unsigned int m_pskOrder; + + void advanceLoop(float error) + { + m_freq = m_freq + m_beta * error; + m_phase = m_phase + m_freq + m_alpha * error; + } + + void phaseWrap() + { + while (m_phase > (2 * M_PI)) + m_phase -= 2 * M_PI; + while (m_phase < (-2 * M_PI)) + m_phase += 2 * M_PI; + } + + void frequencyLimit() + { + if (m_freq > m_maxFreq) + m_freq = m_maxFreq; + else if (m_freq < m_minFreq) + m_freq = m_minFreq; + } + + void setMaxFreq(float freq) + { + m_maxFreq = freq; + } + + void setMinFreq(float freq) + { + m_minFreq = freq; + } + + float phaseDetector2(std::complex sample) const // for BPSK + { + return (sample.real() * sample.imag()); + } + + float phaseDetector4(std::complex sample) const // for QPSK + { + return ((sample.real() > 0.0f ? 1.0f : -1.0f) * sample.imag() - + (sample.imag() > 0.0f ? 1.0f : -1.0f) * sample.real()); + }; + + float phaseDetector8(std::complex sample) const // for 8PSK + { + const float K = (sqrtf(2.0) - 1); + if (fabsf(sample.real()) >= fabsf(sample.imag())) + { + return ((sample.real() > 0.0f ? 1.0f : -1.0f) * sample.imag() - + (sample.imag() > 0.0f ? 1.0f : -1.0f) * sample.real() * K); + } + else + { + return ((sample.real() > 0.0f ? 1.0f : -1.0f) * sample.imag() * K - + (sample.imag() > 0.0f ? 1.0f : -1.0f) * sample.real()); + } + }; + +}; + +#endif /* SDRBASE_DSP_COSTASLOOP_H_ */ diff --git a/sdrbase/resources/webapi/doc/html2/index.html b/sdrbase/resources/webapi/doc/html2/index.html index 2b28c539c..3726384ad 100644 --- a/sdrbase/resources/webapi/doc/html2/index.html +++ b/sdrbase/resources/webapi/doc/html2/index.html @@ -2586,6 +2586,10 @@ margin-bottom: 20px; "type" : "integer", "description" : "Boolean" }, + "costasLoop" : { + "type" : "integer", + "description" : "Boolean" + }, "rrc" : { "type" : "integer", "description" : "Boolean" @@ -2597,6 +2601,18 @@ margin-bottom: 20px; "pllPskOrder" : { "type" : "integer" }, + "pllBandwidth" : { + "type" : "number", + "format" : "float" + }, + "pllDampingFactor" : { + "type" : "number", + "format" : "float" + }, + "pllLoopGain" : { + "type" : "number", + "format" : "float" + }, "inputType" : { "type" : "integer", "description" : "see ChannelAnalyzerSettings::InputType" @@ -45623,7 +45639,7 @@ except ApiException as e:
- Generated 2021-03-01T10:47:56.898+01:00 + Generated 2021-03-05T14:04:36.302+01:00
diff --git a/sdrbase/resources/webapi/doc/swagger/include/ChannelAnalyzer.yaml b/sdrbase/resources/webapi/doc/swagger/include/ChannelAnalyzer.yaml index 377aa1af7..06b3b0749 100644 --- a/sdrbase/resources/webapi/doc/swagger/include/ChannelAnalyzer.yaml +++ b/sdrbase/resources/webapi/doc/swagger/include/ChannelAnalyzer.yaml @@ -23,6 +23,9 @@ ChannelAnalyzerSettings: fll: description: Boolean type: integer + costasLoop: + description: Boolean + type: integer rrc: description: Boolean type: integer @@ -31,6 +34,15 @@ ChannelAnalyzerSettings: type: integer pllPskOrder: type: integer + pllBandwidth: + type: number + format: float + pllDampingFactor: + type: number + format: float + pllLoopGain: + type: number + format: float inputType: description: see ChannelAnalyzerSettings::InputType type: integer diff --git a/swagger/sdrangel/api/swagger/include/ChannelAnalyzer.yaml b/swagger/sdrangel/api/swagger/include/ChannelAnalyzer.yaml index c109ca69d..02ae5b61e 100644 --- a/swagger/sdrangel/api/swagger/include/ChannelAnalyzer.yaml +++ b/swagger/sdrangel/api/swagger/include/ChannelAnalyzer.yaml @@ -23,6 +23,9 @@ ChannelAnalyzerSettings: fll: description: Boolean type: integer + costasLoop: + description: Boolean + type: integer rrc: description: Boolean type: integer @@ -31,6 +34,15 @@ ChannelAnalyzerSettings: type: integer pllPskOrder: type: integer + pllBandwidth: + type: number + format: float + pllDampingFactor: + type: number + format: float + pllLoopGain: + type: number + format: float inputType: description: see ChannelAnalyzerSettings::InputType type: integer diff --git a/swagger/sdrangel/code/html2/index.html b/swagger/sdrangel/code/html2/index.html index 2b28c539c..3726384ad 100644 --- a/swagger/sdrangel/code/html2/index.html +++ b/swagger/sdrangel/code/html2/index.html @@ -2586,6 +2586,10 @@ margin-bottom: 20px; "type" : "integer", "description" : "Boolean" }, + "costasLoop" : { + "type" : "integer", + "description" : "Boolean" + }, "rrc" : { "type" : "integer", "description" : "Boolean" @@ -2597,6 +2601,18 @@ margin-bottom: 20px; "pllPskOrder" : { "type" : "integer" }, + "pllBandwidth" : { + "type" : "number", + "format" : "float" + }, + "pllDampingFactor" : { + "type" : "number", + "format" : "float" + }, + "pllLoopGain" : { + "type" : "number", + "format" : "float" + }, "inputType" : { "type" : "integer", "description" : "see ChannelAnalyzerSettings::InputType" @@ -45623,7 +45639,7 @@ except ApiException as e:
- Generated 2021-03-01T10:47:56.898+01:00 + Generated 2021-03-05T14:04:36.302+01:00
diff --git a/swagger/sdrangel/code/qt5/client/SWGChannelAnalyzerSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGChannelAnalyzerSettings.cpp index 2c3735567..cda2e09ed 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannelAnalyzerSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGChannelAnalyzerSettings.cpp @@ -46,12 +46,20 @@ SWGChannelAnalyzerSettings::SWGChannelAnalyzerSettings() { m_pll_isSet = false; fll = 0; m_fll_isSet = false; + costas_loop = 0; + m_costas_loop_isSet = false; rrc = 0; m_rrc_isSet = false; rrc_rolloff = 0; m_rrc_rolloff_isSet = false; pll_psk_order = 0; m_pll_psk_order_isSet = false; + pll_bandwidth = 0.0f; + m_pll_bandwidth_isSet = false; + pll_damping_factor = 0.0f; + m_pll_damping_factor_isSet = false; + pll_loop_gain = 0.0f; + m_pll_loop_gain_isSet = false; input_type = 0; m_input_type_isSet = false; rgb_color = 0; @@ -88,12 +96,20 @@ SWGChannelAnalyzerSettings::init() { m_pll_isSet = false; fll = 0; m_fll_isSet = false; + costas_loop = 0; + m_costas_loop_isSet = false; rrc = 0; m_rrc_isSet = false; rrc_rolloff = 0; m_rrc_rolloff_isSet = false; pll_psk_order = 0; m_pll_psk_order_isSet = false; + pll_bandwidth = 0.0f; + m_pll_bandwidth_isSet = false; + pll_damping_factor = 0.0f; + m_pll_damping_factor_isSet = false; + pll_loop_gain = 0.0f; + m_pll_loop_gain_isSet = false; input_type = 0; m_input_type_isSet = false; rgb_color = 0; @@ -122,6 +138,10 @@ SWGChannelAnalyzerSettings::cleanup() { + + + + if(title != nullptr) { delete title; } @@ -162,12 +182,20 @@ SWGChannelAnalyzerSettings::fromJsonObject(QJsonObject &pJson) { ::SWGSDRangel::setValue(&fll, pJson["fll"], "qint32", ""); + ::SWGSDRangel::setValue(&costas_loop, pJson["costasLoop"], "qint32", ""); + ::SWGSDRangel::setValue(&rrc, pJson["rrc"], "qint32", ""); ::SWGSDRangel::setValue(&rrc_rolloff, pJson["rrcRolloff"], "qint32", ""); ::SWGSDRangel::setValue(&pll_psk_order, pJson["pllPskOrder"], "qint32", ""); + ::SWGSDRangel::setValue(&pll_bandwidth, pJson["pllBandwidth"], "float", ""); + + ::SWGSDRangel::setValue(&pll_damping_factor, pJson["pllDampingFactor"], "float", ""); + + ::SWGSDRangel::setValue(&pll_loop_gain, pJson["pllLoopGain"], "float", ""); + ::SWGSDRangel::setValue(&input_type, pJson["inputType"], "qint32", ""); ::SWGSDRangel::setValue(&rgb_color, pJson["rgbColor"], "qint32", ""); @@ -221,6 +249,9 @@ SWGChannelAnalyzerSettings::asJsonObject() { if(m_fll_isSet){ obj->insert("fll", QJsonValue(fll)); } + if(m_costas_loop_isSet){ + obj->insert("costasLoop", QJsonValue(costas_loop)); + } if(m_rrc_isSet){ obj->insert("rrc", QJsonValue(rrc)); } @@ -230,6 +261,15 @@ SWGChannelAnalyzerSettings::asJsonObject() { if(m_pll_psk_order_isSet){ obj->insert("pllPskOrder", QJsonValue(pll_psk_order)); } + if(m_pll_bandwidth_isSet){ + obj->insert("pllBandwidth", QJsonValue(pll_bandwidth)); + } + if(m_pll_damping_factor_isSet){ + obj->insert("pllDampingFactor", QJsonValue(pll_damping_factor)); + } + if(m_pll_loop_gain_isSet){ + obj->insert("pllLoopGain", QJsonValue(pll_loop_gain)); + } if(m_input_type_isSet){ obj->insert("inputType", QJsonValue(input_type)); } @@ -339,6 +379,16 @@ SWGChannelAnalyzerSettings::setFll(qint32 fll) { this->m_fll_isSet = true; } +qint32 +SWGChannelAnalyzerSettings::getCostasLoop() { + return costas_loop; +} +void +SWGChannelAnalyzerSettings::setCostasLoop(qint32 costas_loop) { + this->costas_loop = costas_loop; + this->m_costas_loop_isSet = true; +} + qint32 SWGChannelAnalyzerSettings::getRrc() { return rrc; @@ -369,6 +419,36 @@ SWGChannelAnalyzerSettings::setPllPskOrder(qint32 pll_psk_order) { this->m_pll_psk_order_isSet = true; } +float +SWGChannelAnalyzerSettings::getPllBandwidth() { + return pll_bandwidth; +} +void +SWGChannelAnalyzerSettings::setPllBandwidth(float pll_bandwidth) { + this->pll_bandwidth = pll_bandwidth; + this->m_pll_bandwidth_isSet = true; +} + +float +SWGChannelAnalyzerSettings::getPllDampingFactor() { + return pll_damping_factor; +} +void +SWGChannelAnalyzerSettings::setPllDampingFactor(float pll_damping_factor) { + this->pll_damping_factor = pll_damping_factor; + this->m_pll_damping_factor_isSet = true; +} + +float +SWGChannelAnalyzerSettings::getPllLoopGain() { + return pll_loop_gain; +} +void +SWGChannelAnalyzerSettings::setPllLoopGain(float pll_loop_gain) { + this->pll_loop_gain = pll_loop_gain; + this->m_pll_loop_gain_isSet = true; +} + qint32 SWGChannelAnalyzerSettings::getInputType() { return input_type; @@ -451,6 +531,9 @@ SWGChannelAnalyzerSettings::isSet(){ if(m_fll_isSet){ isObjectUpdated = true; break; } + if(m_costas_loop_isSet){ + isObjectUpdated = true; break; + } if(m_rrc_isSet){ isObjectUpdated = true; break; } @@ -460,6 +543,15 @@ SWGChannelAnalyzerSettings::isSet(){ if(m_pll_psk_order_isSet){ isObjectUpdated = true; break; } + if(m_pll_bandwidth_isSet){ + isObjectUpdated = true; break; + } + if(m_pll_damping_factor_isSet){ + isObjectUpdated = true; break; + } + if(m_pll_loop_gain_isSet){ + isObjectUpdated = true; break; + } if(m_input_type_isSet){ isObjectUpdated = true; break; } diff --git a/swagger/sdrangel/code/qt5/client/SWGChannelAnalyzerSettings.h b/swagger/sdrangel/code/qt5/client/SWGChannelAnalyzerSettings.h index 4c73e87a3..557c0ba77 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannelAnalyzerSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGChannelAnalyzerSettings.h @@ -71,6 +71,9 @@ public: qint32 getFll(); void setFll(qint32 fll); + qint32 getCostasLoop(); + void setCostasLoop(qint32 costas_loop); + qint32 getRrc(); void setRrc(qint32 rrc); @@ -80,6 +83,15 @@ public: qint32 getPllPskOrder(); void setPllPskOrder(qint32 pll_psk_order); + float getPllBandwidth(); + void setPllBandwidth(float pll_bandwidth); + + float getPllDampingFactor(); + void setPllDampingFactor(float pll_damping_factor); + + float getPllLoopGain(); + void setPllLoopGain(float pll_loop_gain); + qint32 getInputType(); void setInputType(qint32 input_type); @@ -126,6 +138,9 @@ private: qint32 fll; bool m_fll_isSet; + qint32 costas_loop; + bool m_costas_loop_isSet; + qint32 rrc; bool m_rrc_isSet; @@ -135,6 +150,15 @@ private: qint32 pll_psk_order; bool m_pll_psk_order_isSet; + float pll_bandwidth; + bool m_pll_bandwidth_isSet; + + float pll_damping_factor; + bool m_pll_damping_factor_isSet; + + float pll_loop_gain; + bool m_pll_loop_gain_isSet; + qint32 input_type; bool m_input_type_isSet;