mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-13 00:57:31 -05:00
Resolves #373 - Adds support for Tuner frequency error PPM monitoring
and auto-correction for R820T tuners. -Updated TunerModel and TunerViewPanel to add 2 new columns to display the current tuner's PPM setting and (when available) the measured frequency error averaged over 5 second intervals provided by certain decoders (e.g. P25 C4FM and LSM). Frequency error value from P25 decoders is provided from the Costas Loop PLL once 2 or more P25 message sync events have been observed in a short period. -Updated R820T tuner to auto-correct PPM when measured frequency error exceeds the +/- 0.4PPM threshold for a period of 30 seconds or more. Feature can be enabled/disabled by user in the tuner editor panel. Resolved several issues with P25 C4FM/LSM decoders that was impacting performance related to channel misalignment from tuner PPM offsets.
This commit is contained in:
@@ -0,0 +1,119 @@
|
||||
/*******************************************************************************
|
||||
* sdr-trunk
|
||||
* Copyright (C) 2014-2018 Dennis Sheirer
|
||||
*
|
||||
* 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, either 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 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/>
|
||||
*
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.dsp.psk.pll;
|
||||
|
||||
import io.github.dsheirer.dsp.symbol.ISyncDetectListener;
|
||||
import io.github.dsheirer.module.decode.p25.P25Decoder;
|
||||
import io.github.dsheirer.source.SourceEvent;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Monitors decode sync events to adaptively control PLL loop gain.
|
||||
*/
|
||||
public class AdaptivePLLGainMonitor implements ISyncDetectListener, IFrequencyErrorProcessor
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(AdaptivePLLGainMonitor.class);
|
||||
|
||||
private static final int MAX_SYNC_COUNT = 6;
|
||||
private CostasLoop mCostasLoop;
|
||||
private P25Decoder mP25Decoder;
|
||||
private PLLGain mPLLGain = PLLGain.LEVEL_1;
|
||||
private int mSyncCount;
|
||||
|
||||
/**
|
||||
* Constructs an adaptive monitor to monitor the sync state of a decoder and adjust the gain
|
||||
* level of the costas loop accordingly.
|
||||
*
|
||||
* @param costasLoop to receive adaptive gain updates.
|
||||
* @param p25Decoder to receive frequency error source events
|
||||
*/
|
||||
public AdaptivePLLGainMonitor(CostasLoop costasLoop, P25Decoder p25Decoder)
|
||||
{
|
||||
mCostasLoop = costasLoop;
|
||||
mCostasLoop.setPLLGain(mPLLGain);
|
||||
mCostasLoop.setFrequencyErrorProcessor(this);
|
||||
mP25Decoder = p25Decoder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync detection event. Updates the running sync count and updates the PLL gain level.
|
||||
*/
|
||||
@Override
|
||||
public void syncDetected()
|
||||
{
|
||||
mSyncCount++;
|
||||
update();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync loss event. Updates the running sync count and updates the PLL gain level.
|
||||
*/
|
||||
@Override
|
||||
public void syncLost()
|
||||
{
|
||||
mSyncCount -= 2;
|
||||
update();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the gain state for a sync detect or sync loss
|
||||
*/
|
||||
private void update()
|
||||
{
|
||||
if(mSyncCount < 0)
|
||||
{
|
||||
mSyncCount = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if(mSyncCount > MAX_SYNC_COUNT)
|
||||
{
|
||||
mSyncCount = MAX_SYNC_COUNT;
|
||||
return;
|
||||
}
|
||||
|
||||
PLLGain gain = PLLGain.fromSyncCount(mSyncCount);
|
||||
|
||||
//Only update the costas loop if the gain value changes
|
||||
if(gain != mPLLGain)
|
||||
{
|
||||
mPLLGain = gain;
|
||||
mCostasLoop.setPLLGain(mPLLGain);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the monitor and commands the PLL to use the highest gain level.
|
||||
*/
|
||||
public void reset()
|
||||
{
|
||||
mSyncCount = 0;
|
||||
mPLLGain = PLLGain.LEVEL_1;
|
||||
mCostasLoop.setPLLGain(mPLLGain);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processFrequencyError(long frequencyError)
|
||||
{
|
||||
//Only rebroadcast the costas loop frequency error if the gain level has progressed above the
|
||||
//default gain of 1, meaning that we have received at least 2 sync events
|
||||
if(mPLLGain != PLLGain.LEVEL_1)
|
||||
{
|
||||
mP25Decoder.broadcast(SourceEvent.frequencyErrorMeasurement(frequencyError));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -41,10 +41,12 @@ public class CostasLoop implements IPhaseLockedLoop
|
||||
private double mDamping = Math.sqrt(2.0) / 2.0;
|
||||
private double mAlphaGain;
|
||||
private double mBetaGain;
|
||||
private Tracking mTracking = Tracking.FASTEST;
|
||||
private PLLGain mPLLGain = PLLGain.LEVEL_1;
|
||||
private double mSymbolRate;
|
||||
private double mSampleRate;
|
||||
private int mCounter;
|
||||
private IFrequencyErrorProcessor mFrequencyErrorProcessor;
|
||||
private long mFrequencyError;
|
||||
private int mBaudCounter;
|
||||
|
||||
/**
|
||||
* Costas Loop for tracking and correcting frequency error in a received carrier signal.
|
||||
@@ -60,6 +62,17 @@ public class CostasLoop implements IPhaseLockedLoop
|
||||
updateLoopBandwidth();
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the listener to receive the measured frequency error as tracked by the PLL at one
|
||||
* second intervals.
|
||||
*
|
||||
* @param listener to receive frequency error measurement.
|
||||
*/
|
||||
public void setFrequencyErrorProcessor(IFrequencyErrorProcessor listener)
|
||||
{
|
||||
mFrequencyErrorProcessor = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Corrects the current phase tracker when an inverted output is detected. The inversion will take the form of
|
||||
* +/- 90 degrees or +/- 180 degrees, although the latter correction is equal regardless of the sign.
|
||||
@@ -89,21 +102,21 @@ public class CostasLoop implements IPhaseLockedLoop
|
||||
*/
|
||||
private void updateLoopBandwidth()
|
||||
{
|
||||
double bandwidth = TWO_PI / mTracking.getLoopBandwidth();
|
||||
double bandwidth = TWO_PI / mPLLGain.getLoopBandwidth();
|
||||
|
||||
mAlphaGain = (4.0 * mDamping * bandwidth) / (1.0 + (2.0 * mDamping * bandwidth) + (bandwidth * bandwidth));
|
||||
mBetaGain = (4.0 * bandwidth * bandwidth) / (1.0 + (2.0 * mDamping * bandwidth) + (bandwidth * bandwidth));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the tracking state for this costas loop. The tracking state affects the aggressiveness of the alpha/beta
|
||||
* Sets the PLLGain state for this costas loop. The PLLGain state affects the aggressiveness of the alpha/beta
|
||||
* gain values in synchronizing with the signal carrier.
|
||||
*
|
||||
* @param tracking
|
||||
* @param PLLGain
|
||||
*/
|
||||
public void setTracking(Tracking tracking)
|
||||
public void setPLLGain(PLLGain PLLGain)
|
||||
{
|
||||
mTracking = tracking;
|
||||
mPLLGain = PLLGain;
|
||||
updateLoopBandwidth();
|
||||
}
|
||||
|
||||
@@ -159,7 +172,7 @@ public class CostasLoop implements IPhaseLockedLoop
|
||||
mLoopFrequency += (mBetaGain * phaseError);
|
||||
mLoopPhase += mLoopFrequency + (mAlphaGain * phaseError);
|
||||
|
||||
/* Normalize phase between +/- 2 * PI */
|
||||
//Normalize phase between +/- 2 * PI
|
||||
if(mLoopPhase > TWO_PI)
|
||||
{
|
||||
mLoopPhase -= TWO_PI;
|
||||
@@ -170,7 +183,7 @@ public class CostasLoop implements IPhaseLockedLoop
|
||||
mLoopPhase += TWO_PI;
|
||||
}
|
||||
|
||||
/* Limit frequency to +/- maximum loop frequency */
|
||||
//Limit frequency to +/- maximum loop frequency
|
||||
if(mLoopFrequency > mMaximumLoopFrequency)
|
||||
{
|
||||
mLoopFrequency = mMaximumLoopFrequency;
|
||||
@@ -181,17 +194,25 @@ public class CostasLoop implements IPhaseLockedLoop
|
||||
mLoopFrequency = -mMaximumLoopFrequency;
|
||||
}
|
||||
|
||||
//TODO: broadcast channel offset to the tuner to use in auto-adjusting tuner PPM
|
||||
// mCounter++;
|
||||
//
|
||||
// if(mCounter > (5 * mSymbolRate))
|
||||
// {
|
||||
// mCounter = 0;
|
||||
// mLog.info("Current Costas Loop Frequency: " + mLoopFrequency + " [" +
|
||||
// (int)(mSampleRate / TWO_PI * mLoopFrequency) + " Hz Offset] sample rate [" + mSampleRate + "]");
|
||||
// }
|
||||
//Broadcast current frequency error measurement once a second to an external listener
|
||||
mBaudCounter++;
|
||||
|
||||
if(mBaudCounter > mSymbolRate)
|
||||
{
|
||||
mBaudCounter = 0;
|
||||
|
||||
mFrequencyError = (long)(mSampleRate / TWO_PI * mLoopFrequency);
|
||||
|
||||
if(mFrequencyErrorProcessor != null)
|
||||
{
|
||||
mFrequencyErrorProcessor.processFrequencyError(mFrequencyError);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the PLL internal tracking values
|
||||
*/
|
||||
@Override
|
||||
public void reset()
|
||||
{
|
||||
|
||||
@@ -16,27 +16,14 @@
|
||||
package io.github.dsheirer.dsp.psk.pll;
|
||||
|
||||
/**
|
||||
* Phase Locked Loop (PLL) tracking bandwidth - controls how fast/slow the PLL tracks the carrier frequency
|
||||
* Interface for passing frequency error measurements.
|
||||
*/
|
||||
public enum Tracking
|
||||
public interface IFrequencyErrorProcessor
|
||||
{
|
||||
FASTEST(50.0),
|
||||
FAST(100.0),
|
||||
NORMAL(200.0),
|
||||
SLOW(400.0);
|
||||
|
||||
private double mLoopBandwidth;
|
||||
|
||||
Tracking(double loopBandwidth)
|
||||
{
|
||||
mLoopBandwidth = loopBandwidth;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loop bandwidth setting
|
||||
* Provides a frequency error measurement to the listener.
|
||||
*
|
||||
* @param error value as measured by the provider
|
||||
*/
|
||||
public double getLoopBandwidth()
|
||||
{
|
||||
return mLoopBandwidth;
|
||||
}
|
||||
void processFrequencyError(long error);
|
||||
}
|
||||
87
src/main/java/io/github/dsheirer/dsp/psk/pll/PLLGain.java
Normal file
87
src/main/java/io/github/dsheirer/dsp/psk/pll/PLLGain.java
Normal file
@@ -0,0 +1,87 @@
|
||||
/*******************************************************************************
|
||||
* sdr-trunk
|
||||
* Copyright (C) 2014-2018 Dennis Sheirer
|
||||
*
|
||||
* 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, either 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 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/>
|
||||
*
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.dsp.psk.pll;
|
||||
|
||||
/**
|
||||
* Phase Locked Loop (PLL) tracking bandwidth - controls how fast/slow the PLL tracks the carrier frequency
|
||||
*/
|
||||
public enum PLLGain
|
||||
{
|
||||
LEVEL_1(50.0, 0, 1),
|
||||
LEVEL_2(75.0, 2, 4),
|
||||
LEVEL_3(100.0, 5, 6),
|
||||
LEVEL_4(125.0, 7, 8),
|
||||
LEVEL_5(150.0, 9, 10);
|
||||
|
||||
private double mLoopBandwidth;
|
||||
private int mRangeStart;
|
||||
private int mRangeEnd;
|
||||
|
||||
PLLGain(double loopBandwidth, int start, int end)
|
||||
{
|
||||
mLoopBandwidth = loopBandwidth;
|
||||
mRangeStart = start;
|
||||
mRangeEnd = end;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loop bandwidth setting
|
||||
*/
|
||||
public double getLoopBandwidth()
|
||||
{
|
||||
return mLoopBandwidth;
|
||||
}
|
||||
|
||||
public int getRangeStart()
|
||||
{
|
||||
return mRangeStart;
|
||||
}
|
||||
|
||||
public int getRangeEnd()
|
||||
{
|
||||
return mRangeEnd;
|
||||
}
|
||||
|
||||
/**
|
||||
* Look up PLL gain from a running sync detection count.
|
||||
*
|
||||
* @param syncCount 0-10
|
||||
* @return PLL gain appropriate for the sync detection value
|
||||
*/
|
||||
public static PLLGain fromSyncCount(int syncCount)
|
||||
{
|
||||
if(syncCount <= LEVEL_1.getRangeEnd())
|
||||
{
|
||||
return LEVEL_1;
|
||||
}
|
||||
else if(syncCount <= LEVEL_2.getRangeEnd())
|
||||
{
|
||||
return LEVEL_2;
|
||||
}
|
||||
else if(syncCount <= LEVEL_3.getRangeEnd())
|
||||
{
|
||||
return LEVEL_3;
|
||||
}
|
||||
else if(syncCount <= LEVEL_4.getRangeEnd())
|
||||
{
|
||||
return LEVEL_4;
|
||||
}
|
||||
else
|
||||
{
|
||||
return LEVEL_5;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user