ChanelAnalyzerNG: added PLL option

This commit is contained in:
f4exb 2018-05-12 06:01:54 +02:00
parent a3bd35ff27
commit 3ae7cda9be
12 changed files with 167 additions and 66 deletions

View File

@ -35,7 +35,7 @@ static int runQtApplication(int argc, char* argv[], qtwebapp::LoggerWithFile *lo
*/ */
QCoreApplication::setOrganizationName("f4exb"); QCoreApplication::setOrganizationName("f4exb");
QCoreApplication::setApplicationName("SDRangel"); QCoreApplication::setApplicationName("SDRangel");
QCoreApplication::setApplicationVersion("3.14.6"); QCoreApplication::setApplicationVersion("3.14.7");
#if 1 #if 1
qApp->setStyle(QStyleFactory::create("fusion")); qApp->setStyle(QStyleFactory::create("fusion"));

View File

@ -57,7 +57,7 @@ static int runQtApplication(int argc, char* argv[], qtwebapp::LoggerWithFile *lo
QCoreApplication::setOrganizationName("f4exb"); QCoreApplication::setOrganizationName("f4exb");
QCoreApplication::setApplicationName("SDRangelBench"); QCoreApplication::setApplicationName("SDRangelBench");
QCoreApplication::setApplicationVersion("3.14.6"); QCoreApplication::setApplicationVersion("3.14.7");
int catchSignals[] = {SIGQUIT, SIGINT, SIGTERM, SIGHUP}; int catchSignals[] = {SIGQUIT, SIGINT, SIGTERM, SIGHUP};
std::vector<int> vsig(catchSignals, catchSignals + sizeof(catchSignals) / sizeof(int)); std::vector<int> vsig(catchSignals, catchSignals + sizeof(catchSignals) / sizeof(int));

View File

@ -56,7 +56,7 @@ static int runQtApplication(int argc, char* argv[], qtwebapp::LoggerWithFile *lo
QCoreApplication::setOrganizationName("f4exb"); QCoreApplication::setOrganizationName("f4exb");
QCoreApplication::setApplicationName("SDRangelSrv"); QCoreApplication::setApplicationName("SDRangelSrv");
QCoreApplication::setApplicationVersion("3.14.6"); QCoreApplication::setApplicationVersion("3.14.7");
int catchSignals[] = {SIGQUIT, SIGINT, SIGTERM, SIGHUP}; int catchSignals[] = {SIGQUIT, SIGINT, SIGTERM, SIGHUP};
std::vector<int> vsig(catchSignals, catchSignals + sizeof(catchSignals) / sizeof(int)); std::vector<int> vsig(catchSignals, catchSignals + sizeof(catchSignals) / sizeof(int));

6
debian/changelog vendored
View File

@ -1,3 +1,9 @@
sdrangel (3.14.7-1) unstable; urgency=medium
* ChanelAnalyzerNG: added PLL option
-- Edouard Griffiths, F4EXB <f4exb06@gmail.com> Sun, 13 May 2018 20:14:18 +0200
sdrangel (3.14.6-1) unstable; urgency=medium sdrangel (3.14.6-1) unstable; urgency=medium
* Fixed keyboard input for negative values on realtive integer value dials * Fixed keyboard input for negative values on realtive integer value dials

View File

@ -35,6 +35,7 @@ const QString ChannelAnalyzerNG::m_channelId = "ChannelAnalyzerNG";
ChannelAnalyzerNG::ChannelAnalyzerNG(DeviceSourceAPI *deviceAPI) : ChannelAnalyzerNG::ChannelAnalyzerNG(DeviceSourceAPI *deviceAPI) :
ChannelSinkAPI(m_channelIdURI), ChannelSinkAPI(m_channelIdURI),
m_deviceAPI(deviceAPI), m_deviceAPI(deviceAPI),
m_pll(0,0.05,0.01),
m_sampleSink(0), m_sampleSink(0),
m_settingsMutex(QMutex::Recursive) m_settingsMutex(QMutex::Recursive)
{ {
@ -73,9 +74,10 @@ void ChannelAnalyzerNG::configure(MessageQueue* messageQueue,
Real Bandwidth, Real Bandwidth,
Real LowCutoff, Real LowCutoff,
int spanLog2, int spanLog2,
bool ssb) bool ssb,
bool pll)
{ {
Message* cmd = MsgConfigureChannelAnalyzer::create(channelSampleRate, Bandwidth, LowCutoff, spanLog2, ssb); Message* cmd = MsgConfigureChannelAnalyzer::create(channelSampleRate, Bandwidth, LowCutoff, spanLog2, ssb, pll);
messageQueue->push(cmd); messageQueue->push(cmd);
} }
@ -165,13 +167,15 @@ bool ChannelAnalyzerNG::handleMessage(const Message& cmd)
m_config.m_LowCutoff = cfg.getLoCutoff(); m_config.m_LowCutoff = cfg.getLoCutoff();
m_config.m_spanLog2 = cfg.getSpanLog2(); m_config.m_spanLog2 = cfg.getSpanLog2();
m_config.m_ssb = cfg.getSSB(); m_config.m_ssb = cfg.getSSB();
m_config.m_pll = cfg.getPLL();
qDebug() << "ChannelAnalyzerNG::handleMessage: MsgConfigureChannelAnalyzer:" qDebug() << "ChannelAnalyzerNG::handleMessage: MsgConfigureChannelAnalyzer:"
<< " m_channelSampleRate: " << m_config.m_channelSampleRate << " m_channelSampleRate: " << m_config.m_channelSampleRate
<< " m_Bandwidth: " << m_config.m_Bandwidth << " m_Bandwidth: " << m_config.m_Bandwidth
<< " m_LowCutoff: " << m_config.m_LowCutoff << " m_LowCutoff: " << m_config.m_LowCutoff
<< " m_spanLog2: " << m_config.m_spanLog2 << " m_spanLog2: " << m_config.m_spanLog2
<< " m_ssb: " << m_config.m_ssb; << " m_ssb: " << m_config.m_ssb
<< " m_pll: " << m_config.m_pll;
apply(); apply();
return true; return true;
@ -254,5 +258,6 @@ void ChannelAnalyzerNG::apply(bool force)
//m_settingsMutex.lock(); //m_settingsMutex.lock();
m_running.m_spanLog2 = m_config.m_spanLog2; m_running.m_spanLog2 = m_config.m_spanLog2;
m_running.m_ssb = m_config.m_ssb; m_running.m_ssb = m_config.m_ssb;
m_running.m_pll = m_config.m_pll;
//m_settingsMutex.unlock(); //m_settingsMutex.unlock();
} }

View File

@ -25,6 +25,7 @@
#include "dsp/interpolator.h" #include "dsp/interpolator.h"
#include "dsp/ncof.h" #include "dsp/ncof.h"
#include "dsp/fftfilt.h" #include "dsp/fftfilt.h"
#include "dsp/phaselock.h"
#include "audio/audiofifo.h" #include "audio/audiofifo.h"
#include "util/message.h" #include "util/message.h"
@ -45,20 +46,23 @@ public:
Real getLoCutoff() const { return m_LowCutoff; } Real getLoCutoff() const { return m_LowCutoff; }
int getSpanLog2() const { return m_spanLog2; } int getSpanLog2() const { return m_spanLog2; }
bool getSSB() const { return m_ssb; } bool getSSB() const { return m_ssb; }
bool getPLL() const { return m_pll; }
static MsgConfigureChannelAnalyzer* create( static MsgConfigureChannelAnalyzer* create(
int channelSampleRate, int channelSampleRate,
Real Bandwidth, Real Bandwidth,
Real LowCutoff, Real LowCutoff,
int spanLog2, int spanLog2,
bool ssb) bool ssb,
bool pll)
{ {
return new MsgConfigureChannelAnalyzer( return new MsgConfigureChannelAnalyzer(
channelSampleRate, channelSampleRate,
Bandwidth, Bandwidth,
LowCutoff, LowCutoff,
spanLog2, spanLog2,
ssb); ssb,
pll);
} }
private: private:
@ -67,19 +71,22 @@ public:
Real m_LowCutoff; Real m_LowCutoff;
int m_spanLog2; int m_spanLog2;
bool m_ssb; bool m_ssb;
bool m_pll;
MsgConfigureChannelAnalyzer( MsgConfigureChannelAnalyzer(
int channelSampleRate, int channelSampleRate,
Real Bandwidth, Real Bandwidth,
Real LowCutoff, Real LowCutoff,
int spanLog2, int spanLog2,
bool ssb) : bool ssb,
bool pll) :
Message(), Message(),
m_channelSampleRate(channelSampleRate), m_channelSampleRate(channelSampleRate),
m_Bandwidth(Bandwidth), m_Bandwidth(Bandwidth),
m_LowCutoff(LowCutoff), m_LowCutoff(LowCutoff),
m_spanLog2(spanLog2), m_spanLog2(spanLog2),
m_ssb(ssb) m_ssb(ssb),
m_pll(pll)
{ } { }
}; };
@ -133,12 +140,14 @@ public:
Real Bandwidth, Real Bandwidth,
Real LowCutoff, Real LowCutoff,
int spanLog2, int spanLog2,
bool ssb); bool ssb,
bool pll);
DownChannelizer *getChannelizer() { return m_channelizer; } DownChannelizer *getChannelizer() { return m_channelizer; }
int getInputSampleRate() const { return m_running.m_inputSampleRate; } int getInputSampleRate() const { return m_running.m_inputSampleRate; }
int getChannelSampleRate() const { return m_running.m_channelSampleRate; } int getChannelSampleRate() const { return m_running.m_channelSampleRate; }
double getMagSq() const { return m_magsq; } double getMagSq() const { return m_magsq; }
bool isPllLocked() const { return m_running.m_pll && m_pll.locked(); }
virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool positiveOnly); virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool positiveOnly);
virtual void start(); virtual void start();
@ -166,6 +175,7 @@ private:
Real m_LowCutoff; Real m_LowCutoff;
int m_spanLog2; int m_spanLog2;
bool m_ssb; bool m_ssb;
bool m_pll;
Config() : Config() :
m_frequency(0), m_frequency(0),
@ -174,7 +184,8 @@ private:
m_Bandwidth(5000), m_Bandwidth(5000),
m_LowCutoff(300), m_LowCutoff(300),
m_spanLog2(3), m_spanLog2(3),
m_ssb(false) m_ssb(false),
m_pll(false)
{} {}
}; };
@ -192,6 +203,7 @@ private:
bool m_useInterpolator; bool m_useInterpolator;
NCOF m_nco; NCOF m_nco;
SimplePhaseLock m_pll;
Interpolator m_interpolator; Interpolator m_interpolator;
Real m_interpolatorDistance; Real m_interpolatorDistance;
Real m_interpolatorDistanceRemain; Real m_interpolatorDistanceRemain;
@ -233,13 +245,33 @@ private:
Real im = m_sum.imag() / SDR_RX_SCALED; Real im = m_sum.imag() / SDR_RX_SCALED;
m_magsq = re*re + im*im; m_magsq = re*re + im*im;
if (m_running.m_ssb & !m_usb) if (m_running.m_pll)
{ // invert spectrum for LSB {
m_sampleBuffer.push_back(Sample(m_sum.imag(), m_sum.real())); Real ncopll[2];
m_pll.process(re, im, ncopll);
Real mixI = m_sum.real() * ncopll[0] - m_sum.imag() * ncopll[1];
Real mixQ = m_sum.real() * ncopll[1] + m_sum.imag() * ncopll[0];
if (m_running.m_ssb & !m_usb)
{ // invert spectrum for LSB
m_sampleBuffer.push_back(Sample(mixQ, mixI));
}
else
{
m_sampleBuffer.push_back(Sample(mixI, mixQ));
}
} }
else else
{ {
m_sampleBuffer.push_back(Sample(m_sum.real(), m_sum.imag())); if (m_running.m_ssb & !m_usb)
{ // invert spectrum for LSB
m_sampleBuffer.push_back(Sample(m_sum.imag(), m_sum.real()));
}
else
{
m_sampleBuffer.push_back(Sample(m_sum.real(), m_sum.imag()));
}
} }
m_sum = 0; m_sum = 0;

View File

@ -237,6 +237,12 @@ void ChannelAnalyzerNGGUI::tick()
double powDb = CalcDb::dbPower(m_channelAnalyzer->getMagSq()); double powDb = CalcDb::dbPower(m_channelAnalyzer->getMagSq());
m_channelPowerDbAvg(powDb); m_channelPowerDbAvg(powDb);
ui->channelPower->setText(tr("%1 dB").arg((Real) m_channelPowerDbAvg, 0, 'f', 1)); ui->channelPower->setText(tr("%1 dB").arg((Real) m_channelPowerDbAvg, 0, 'f', 1));
if (m_channelAnalyzer->isPllLocked()) {
ui->pll->setStyleSheet("QToolButton { background-color : green; }");
} else {
ui->pll->setStyleSheet("QToolButton { background:rgb(79,79,79); }");
}
} }
void ChannelAnalyzerNGGUI::on_channelSampleRate_changed(quint64 value) void ChannelAnalyzerNGGUI::on_channelSampleRate_changed(quint64 value)
@ -251,6 +257,11 @@ void ChannelAnalyzerNGGUI::on_channelSampleRate_changed(quint64 value)
} }
} }
void ChannelAnalyzerNGGUI::on_pll_toggled(bool checked __attribute__((unused)))
{
applySettings();
}
void ChannelAnalyzerNGGUI::on_useRationalDownsampler_toggled(bool checked __attribute__((unused))) void ChannelAnalyzerNGGUI::on_useRationalDownsampler_toggled(bool checked __attribute__((unused)))
{ {
setNewFinalRate(m_spanLog2); setNewFinalRate(m_spanLog2);
@ -578,7 +589,8 @@ void ChannelAnalyzerNGGUI::applySettings()
ui->BW->value() * 100.0, ui->BW->value() * 100.0,
ui->lowCut->value() * 100.0, ui->lowCut->value() * 100.0,
m_spanLog2, m_spanLog2,
ui->ssb->isChecked()); ui->ssb->isChecked(),
ui->pll->isChecked());
} }
} }

View File

@ -92,6 +92,7 @@ private:
private slots: private slots:
void on_deltaFrequency_changed(qint64 value); void on_deltaFrequency_changed(qint64 value);
void on_channelSampleRate_changed(quint64 value); void on_channelSampleRate_changed(quint64 value);
void on_pll_toggled(bool checked);
void on_useRationalDownsampler_toggled(bool checked); void on_useRationalDownsampler_toggled(bool checked);
void on_BW_valueChanged(int value); void on_BW_valueChanged(int value);
void on_lowCut_valueChanged(int value); void on_lowCut_valueChanged(int value);

View File

@ -179,6 +179,24 @@
</item> </item>
<item> <item>
<layout class="QHBoxLayout" name="ChannelSamplingLayout"> <layout class="QHBoxLayout" name="ChannelSamplingLayout">
<item>
<widget class="QToolButton" name="pll">
<property name="toolTip">
<string>PLL lock</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/unlocked.png</normaloff>
<normalon>:/locked.png</normalon>:/unlocked.png</iconset>
</property>
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
<item> <item>
<widget class="ButtonSwitch" name="useRationalDownsampler"> <widget class="ButtonSwitch" name="useRationalDownsampler">
<property name="toolTip"> <property name="toolTip">

View File

@ -23,7 +23,7 @@
const PluginDescriptor ChannelAnalyzerNGPlugin::m_pluginDescriptor = { const PluginDescriptor ChannelAnalyzerNGPlugin::m_pluginDescriptor = {
QString("Channel Analyzer NG"), QString("Channel Analyzer NG"),
QString("3.14.5"), QString("3.14.7"),
QString("(c) Edouard Griffiths, F4EXB"), QString("(c) Edouard Griffiths, F4EXB"),
QString("https://github.com/f4exb/sdrangel"), QString("https://github.com/f4exb/sdrangel"),
true, true,

View File

@ -262,61 +262,85 @@ void PhaseLock::process(const Real& sample_in, Real *samples_out)
processPhase(samples_out); processPhase(samples_out);
// Multiply locked tone with input. // Multiply locked tone with input.
Real x = sample_in; Real phasor_i = m_psin * sample_in;
Real phasor_i = m_psin * x; Real phasor_q = m_pcos * sample_in;
Real phasor_q = m_pcos * x;
// Run IQ phase error through low-pass filter. // Actual PLL
phasor_i = m_phasor_b0 * phasor_i process_phasor(phasor_i, phasor_q);
- m_phasor_a1 * m_phasor_i1 }
- m_phasor_a2 * m_phasor_i2;
phasor_q = m_phasor_b0 * phasor_q
- m_phasor_a1 * m_phasor_q1
- m_phasor_a2 * m_phasor_q2;
m_phasor_i2 = m_phasor_i1;
m_phasor_i1 = phasor_i;
m_phasor_q2 = m_phasor_q1;
m_phasor_q1 = phasor_q;
// Convert I/Q ratio to estimate of phase error. void PhaseLock::process(const Real& real_in, const Real& imag_in, Real *samples_out)
Real phase_err; {
m_pps_events.clear();
// Generate locked pilot tone.
m_psin = sin(m_phase);
m_pcos = cos(m_phase);
// Generate output
processPhase(samples_out);
// Multiply locked tone with input.
Real phasor_i = m_psin * real_in - m_pcos * imag_in;
Real phasor_q = m_pcos * real_in + m_psin * imag_in;
// Actual PLL
process_phasor(phasor_i, phasor_q);
}
void PhaseLock::process_phasor(Real& phasor_i, Real& phasor_q)
{
// Run IQ phase error through low-pass filter.
phasor_i = m_phasor_b0 * phasor_i
- m_phasor_a1 * m_phasor_i1
- m_phasor_a2 * m_phasor_i2;
phasor_q = m_phasor_b0 * phasor_q
- m_phasor_a1 * m_phasor_q1
- m_phasor_a2 * m_phasor_q2;
m_phasor_i2 = m_phasor_i1;
m_phasor_i1 = phasor_i;
m_phasor_q2 = m_phasor_q1;
m_phasor_q1 = phasor_q;
// Convert I/Q ratio to estimate of phase error.
Real phase_err;
if (phasor_i > std::abs(phasor_q)) { if (phasor_i > std::abs(phasor_q)) {
// We are within +/- 45 degrees from lock. // We are within +/- 45 degrees from lock.
// Use simple linear approximation of arctan. // Use simple linear approximation of arctan.
phase_err = phasor_q / phasor_i; phase_err = phasor_q / phasor_i;
} else if (phasor_q > 0) { } else if (phasor_q > 0) {
// We are lagging more than 45 degrees behind the input. // We are lagging more than 45 degrees behind the input.
phase_err = 1; phase_err = 1;
} else { } else {
// We are more than 45 degrees ahead of the input. // We are more than 45 degrees ahead of the input.
phase_err = -1; phase_err = -1;
} }
// Detect pilot level (conservative). // Detect pilot level (conservative).
// m_pilot_level = std::min(m_pilot_level, phasor_i); // m_pilot_level = std::min(m_pilot_level, phasor_i);
m_pilot_level = phasor_i; m_pilot_level = phasor_i;
// Run phase error through loop filter and update frequency estimate. // Run phase error through loop filter and update frequency estimate.
m_freq += m_loopfilter_b0 * phase_err m_freq += m_loopfilter_b0 * phase_err
+ m_loopfilter_b1 * m_loopfilter_x1; + m_loopfilter_b1 * m_loopfilter_x1;
m_loopfilter_x1 = phase_err; m_loopfilter_x1 = phase_err;
// Limit frequency to allowable range. // Limit frequency to allowable range.
m_freq = std::max(m_minfreq, std::min(m_maxfreq, m_freq)); m_freq = std::max(m_minfreq, std::min(m_maxfreq, m_freq));
// Update locked phase. // Update locked phase.
m_phase += m_freq; m_phase += m_freq;
if (m_phase > 2.0 * M_PI) if (m_phase > 2.0 * M_PI)
{ {
m_phase -= 2.0 * M_PI; m_phase -= 2.0 * M_PI;
m_pilot_periods++; m_pilot_periods++;
// Generate pulse-per-second. // Generate pulse-per-second.
if (m_pilot_periods == pilot_frequency) if (m_pilot_periods == pilot_frequency)
{ {
m_pilot_periods = 0; m_pilot_periods = 0;
} }
} }
// Update lock status. // Update lock status.
if (2 * m_pilot_level > m_minsignal) if (2 * m_pilot_level > m_minsignal)
@ -328,7 +352,7 @@ void PhaseLock::process(const Real& sample_in, Real *samples_out)
} }
else else
{ {
m_lock_cnt = 0; m_lock_cnt = 0;
} }
// Drop PPS events when pilot not locked. // Drop PPS events when pilot not locked.

View File

@ -73,6 +73,7 @@ public:
* This is the in flow version * This is the in flow version
*/ */
void process(const Real& sample_in, Real *samples_out); void process(const Real& sample_in, Real *samples_out);
void process(const Real& real_in, const Real& imag_in, Real *samples_out);
/** Return true if the phase-locked loop is locked. */ /** Return true if the phase-locked loop is locked. */
bool locked() const bool locked() const
@ -111,6 +112,8 @@ private:
quint64 m_pps_cnt; quint64 m_pps_cnt;
quint64 m_sample_cnt; quint64 m_sample_cnt;
std::vector<PpsEvent> m_pps_events; std::vector<PpsEvent> m_pps_events;
void process_phasor(Real& phasor_i, Real& phasor_q);
}; };
class SimplePhaseLock : public PhaseLock class SimplePhaseLock : public PhaseLock