Add Costas Loop PLL in Channel Analyzer

Add loop bandwidth and other PLL controls to Channel Analyzer GUI.
Fix bug where PLL lock frequency would be incorrect by the decimation
factor.
pull/797/head
Jon Beniston 2021-03-05 13:37:49 +00:00
rodzic 2389f0d55c
commit 5d5b221e83
18 zmienionych plików z 933 dodań i 73 usunięć

Wyświetl plik

@ -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

Wyświetl plik

@ -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<<index);
}
applySettings();
}
m_settings.m_fll = (index == 5);
void ChannelAnalyzerGUI::on_pllBandwidth_valueChanged(int value)
{
m_settings.m_pllBandwidth = value/1000.0;
QString bandwidthStr = QString::number(m_settings.m_pllBandwidth, 'f', 3);
ui->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();
}

Wyświetl plik

@ -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);

Wyświetl plik

@ -29,9 +29,9 @@
<property name="geometry">
<rect>
<x>0</x>
<y>10</y>
<width>631</width>
<height>81</height>
<y>0</y>
<width>524</width>
<height>101</height>
</rect>
</property>
<property name="windowTitle">
@ -127,6 +127,15 @@
</color>
</brush>
</colorrole>
<colorrole role="PlaceholderText">
<brush brushstyle="NoBrush">
<color alpha="128">
<red>26</red>
<green>26</green>
<blue>26</blue>
</color>
</brush>
</colorrole>
</active>
<inactive>
<colorrole role="Text">
@ -147,6 +156,15 @@
</color>
</brush>
</colorrole>
<colorrole role="PlaceholderText">
<brush brushstyle="NoBrush">
<color alpha="128">
<red>26</red>
<green>26</green>
<blue>26</blue>
</color>
</brush>
</colorrole>
</inactive>
<disabled>
<colorrole role="Text">
@ -167,6 +185,15 @@
</color>
</brush>
</colorrole>
<colorrole role="PlaceholderText">
<brush brushstyle="NoBrush">
<color alpha="128">
<red>26</red>
<green>26</green>
<blue>26</blue>
</color>
</brush>
</colorrole>
</disabled>
</palette>
</property>
@ -335,49 +362,6 @@
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="pllPskOrder">
<property name="maximumSize">
<size>
<width>40</width>
<height>16777215</height>
</size>
</property>
<property name="toolTip">
<string>PLL PSK order (1 for CW)</string>
</property>
<item>
<property name="text">
<string>1</string>
</property>
</item>
<item>
<property name="text">
<string>2</string>
</property>
</item>
<item>
<property name="text">
<string>4</string>
</property>
</item>
<item>
<property name="text">
<string>8</string>
</property>
</item>
<item>
<property name="text">
<string>16</string>
</property>
</item>
<item>
<property name="text">
<string>F</string>
</property>
</item>
</widget>
</item>
</layout>
</item>
<item>
@ -592,6 +576,274 @@
</item>
</layout>
</item>
<item>
<widget class="QWidget" name="pllToolbar" native="true">
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>2</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QComboBox" name="pllType">
<property name="toolTip">
<string>PLL type</string>
</property>
<item>
<property name="text">
<string>PLL</string>
</property>
</item>
<item>
<property name="text">
<string>FLL</string>
</property>
</item>
<item>
<property name="text">
<string>Costas Loop</string>
</property>
</item>
</widget>
</item>
<item>
<widget class="QComboBox" name="pllPskOrder">
<property name="minimumSize">
<size>
<width>70</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>70</width>
<height>16777215</height>
</size>
</property>
<property name="toolTip">
<string>PLL PSK order (1 for CW)</string>
</property>
<item>
<property name="text">
<string>CW</string>
</property>
</item>
<item>
<property name="text">
<string>BPSK</string>
</property>
</item>
<item>
<property name="text">
<string>QPSK</string>
</property>
</item>
<item>
<property name="text">
<string>8PSK</string>
</property>
</item>
<item>
<property name="text">
<string>16PSK</string>
</property>
</item>
</widget>
</item>
<item>
<widget class="Line" name="pllLine1">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="pllBandwidthLabel">
<property name="text">
<string>BW</string>
</property>
</widget>
</item>
<item>
<widget class="QDial" name="pllBandwidth">
<property name="maximumSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
<property name="toolTip">
<string>PLL loop bandwidth</string>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>100</number>
</property>
<property name="pageStep">
<number>1</number>
</property>
<property name="value">
<number>2</number>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="pllBandwidthText">
<property name="text">
<string>0.002</string>
</property>
</widget>
</item>
<item>
<widget class="Line" name="pllLine2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="pllDamplingFactor">
<property name="text">
<string>D</string>
</property>
</widget>
</item>
<item>
<widget class="QDial" name="pllDampingFactor">
<property name="maximumSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
<property name="toolTip">
<string>PLL damping factor</string>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>10</number>
</property>
<property name="pageStep">
<number>1</number>
</property>
<property name="value">
<number>5</number>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="pllDampingFactorText">
<property name="text">
<string>0.5</string>
</property>
</widget>
</item>
<item>
<widget class="Line" name="pllLine3">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="pllLoopGainLabel">
<property name="text">
<string>G</string>
</property>
</widget>
</item>
<item>
<widget class="QDial" name="pllLoopGain">
<property name="maximumSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
<property name="toolTip">
<string>PLL loop gain</string>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>1000</number>
</property>
<property name="pageStep">
<number>1</number>
</property>
<property name="value">
<number>10</number>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="pllLoopGainText">
<property name="text">
<string>10</string>
</property>
</widget>
</item>
<item>
<widget class="Line" name="pllLine4">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<spacer name="pllHorizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="pllLockFrequencyLabel">
<property name="text">
<string>Freq</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="pllLockFrequency">
<property name="minimumSize">
<size>
<width>60</width>
<height>0</height>
</size>
</property>
<property name="toolTip">
<string>PLL lock frequency</string>
</property>
<property name="text">
<string>-100000Hz</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="spectrumContainer" native="true">

Wyświetl plik

@ -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;
}

Wyświetl plik

@ -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;

Wyświetl plik

@ -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);
}

Wyświetl plik

@ -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;

Wyświetl plik

@ -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();
}

Wyświetl plik

@ -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

Wyświetl plik

@ -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 <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include "costasloop.h"
#include <cmath>
// 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<float> &out, const std::complex<float> cc1, const std::complex<float> 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<float> nco(std::cosf(-m_phase), std::sinf(-m_phase));
std::complex<float> 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());
}

Wyświetl plik

@ -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 <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef SDRBASE_DSP_COSTASLOOP_H_
#define SDRBASE_DSP_COSTASLOOP_H_
#include <QDebug>
#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<float>& 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<float> 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<float> sample) const // for BPSK
{
return (sample.real() * sample.imag());
}
float phaseDetector4(std::complex<float> 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<float> 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_ */

Wyświetl plik

@ -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:
</div>
<div id="generator">
<div class="content">
Generated 2021-03-01T10:47:56.898+01:00
Generated 2021-03-05T14:04:36.302+01:00
</div>
</div>
</div>

Wyświetl plik

@ -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

Wyświetl plik

@ -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

Wyświetl plik

@ -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:
</div>
<div id="generator">
<div class="content">
Generated 2021-03-01T10:47:56.898+01:00
Generated 2021-03-05T14:04:36.302+01:00
</div>
</div>
</div>

Wyświetl plik

@ -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;
}

Wyświetl plik

@ -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;