mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-12 16:47: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:
@@ -556,6 +556,10 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
|
||||
case NOTIFICATION_FREQUENCY_CORRECTION_CHANGE:
|
||||
//ignore
|
||||
break;
|
||||
case NOTIFICATION_MEASURED_FREQUENCY_ERROR_SYNC_LOCKED:
|
||||
//Rebroadcast so that the tuner source can process this event
|
||||
mSourceEventBroadcaster.broadcast(sourceEvent);
|
||||
break;
|
||||
default:
|
||||
mLog.error("Received unrecognized source event from polyphase channel source [" +
|
||||
sourceEvent.getEvent() + "]");
|
||||
|
||||
@@ -1,171 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* 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.filter.channelizer;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public class PolyphaseChannelResultsBuffer
|
||||
{
|
||||
private long mTimestamp;
|
||||
private int mPointer = 0;
|
||||
private int mMaxSize;
|
||||
private float[][] mChannelResults;
|
||||
|
||||
/**
|
||||
* Buffer containing an array of polyphase channelizer complex channel results.
|
||||
* @param timestamp reference relative to the first channel results.
|
||||
* @param maxSize of channel results to store in this buffer
|
||||
*/
|
||||
public PolyphaseChannelResultsBuffer(long timestamp, int maxSize)
|
||||
{
|
||||
mTimestamp = timestamp;
|
||||
mMaxSize = maxSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reference timestamp relative to the first channel results contained in this buffer.
|
||||
* @return reference timestamp in milliseconds since epoch
|
||||
*/
|
||||
public long getTimestamp()
|
||||
{
|
||||
return mTimestamp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this buffer is full
|
||||
*/
|
||||
public boolean isFull()
|
||||
{
|
||||
return mPointer >= mMaxSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this buffer is empty
|
||||
*/
|
||||
public boolean isEmpty()
|
||||
{
|
||||
return mPointer == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the channel results to this buffer
|
||||
* @param channelResults to add
|
||||
* @throws IllegalArgumentException if the buffer is full or if the size of the channel results array is a different
|
||||
* length than any of the preceeding channel results that were added to this buffer
|
||||
*/
|
||||
public void add(float[] channelResults) throws IllegalArgumentException
|
||||
{
|
||||
if(mPointer >= mMaxSize)
|
||||
{
|
||||
throw new IllegalArgumentException("Buffer is full - cannot add new channel results");
|
||||
}
|
||||
|
||||
if(mPointer == 0)
|
||||
{
|
||||
mChannelResults = new float[mMaxSize][channelResults.length];
|
||||
}
|
||||
else
|
||||
{
|
||||
int currentLength = mChannelResults[0].length;
|
||||
|
||||
if(channelResults.length != currentLength)
|
||||
{
|
||||
throw new IllegalArgumentException("Channel results length [" + channelResults.length + "] cannot be " +
|
||||
"a different length than the channel results that are already contained in this buffer [" +
|
||||
currentLength + "]");
|
||||
}
|
||||
}
|
||||
|
||||
mChannelResults[mPointer++] = channelResults;
|
||||
}
|
||||
|
||||
/**
|
||||
* Channel results array contained in this buffer
|
||||
*/
|
||||
public float[][] getChannelResults()
|
||||
{
|
||||
//If this buffer is full, return all of the channel results
|
||||
if(mPointer == mMaxSize)
|
||||
{
|
||||
return mChannelResults;
|
||||
}
|
||||
//Otherwise, truncate the results to return
|
||||
else
|
||||
{
|
||||
return Arrays.copyOf(mChannelResults, mPointer);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts a single I/Q interleaved sample buffer from the channel results
|
||||
* @param iChannelIndex to extract for the inphase sample. The q index is assumed to be one greater than i.
|
||||
* @return float[] containing interleaved I and Q samples
|
||||
* @throws IllegalArgumentException if this buffer doesn't contain any samples or if the requested channel is
|
||||
* not within the channel range of the contained channel results.
|
||||
*/
|
||||
public float[] getChannel(int iChannelIndex)
|
||||
{
|
||||
if(isEmpty())
|
||||
{
|
||||
throw new IllegalArgumentException("Buffer is empty - cannot extract channel [" + iChannelIndex + "]");
|
||||
}
|
||||
|
||||
if(!isValidChannelIndex(iChannelIndex))
|
||||
{
|
||||
throw new IllegalArgumentException("Channel [" + iChannelIndex + "] is not valid for the contained channel " +
|
||||
"results -- max channel is " + getMaxChannelIndex());
|
||||
}
|
||||
|
||||
float[] samples = new float[mChannelResults.length * 2];
|
||||
int pointer = 0;
|
||||
|
||||
int qChannelIndex = iChannelIndex + 1;
|
||||
|
||||
for(int x = 0; x < mPointer; x++)
|
||||
{
|
||||
float[] channelResults = mChannelResults[x];
|
||||
|
||||
samples[pointer++] = channelResults[iChannelIndex];
|
||||
samples[pointer++] = channelResults[qChannelIndex];
|
||||
}
|
||||
|
||||
return samples;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the channel argument is valid for the contained channel results.
|
||||
* @param channelIndex number (0 to N) to check
|
||||
* @return false if the buffer is empty or if the channel is greater than the contained channel results
|
||||
*/
|
||||
private boolean isValidChannelIndex(int channelIndex)
|
||||
{
|
||||
return channelIndex <= getMaxChannelIndex();
|
||||
}
|
||||
|
||||
/**
|
||||
* Maximum channel index represented in the channel results contained in this buffer.
|
||||
* @return max channel number, or 0 if the buffer is empty
|
||||
*/
|
||||
private int getMaxChannelIndex()
|
||||
{
|
||||
if(isEmpty())
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return mChannelResults[0].length;
|
||||
}
|
||||
}
|
||||
@@ -237,4 +237,11 @@ public class PolyphaseChannelSource extends TunerChannelSource
|
||||
mPolyphaseChannelOutputProcessor.setFrequencyOffset(getFrequencyOffset());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return "POLYPHASE [" + mPolyphaseChannelOutputProcessor.getInputChannelCount() + "] " +
|
||||
getTunerChannel().getFrequency();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,6 +62,12 @@ public abstract class ChannelOutputProcessor implements IPolyphaseChannelOutputP
|
||||
mChannelResultsQueue = new OverflowableReusableBufferTransferQueue<>((int)(sampleRate * 3), (int)(sampleRate * 0.5));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPolyphaseChannelIndexCount()
|
||||
{
|
||||
return mInputChannelCount;
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
{
|
||||
if(mChannelResultsQueue != null)
|
||||
|
||||
@@ -59,6 +59,11 @@ public interface IPolyphaseChannelOutputProcessor
|
||||
*/
|
||||
void setPolyphaseChannelIndices(List<Integer> indexes);
|
||||
|
||||
/**
|
||||
* List of current polyphase channel indices for the output processor
|
||||
*/
|
||||
int getPolyphaseChannelIndexCount();
|
||||
|
||||
/**
|
||||
* Updates the synthesis filter taps for this output processor
|
||||
* @param filter
|
||||
|
||||
@@ -134,6 +134,12 @@ public class LTRDecoder implements Listener<ReusableBuffer>, ISyncDetectListener
|
||||
mSynchronizationMonitor.syncDetected();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void syncLost()
|
||||
{
|
||||
//no-op
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the buffer samples by converting all samples to boolean values reflecting if the sample value is
|
||||
* greater than zero (or not). Average symbol timing offset is calculated for the full buffer and the offset is
|
||||
|
||||
@@ -30,13 +30,13 @@ import org.slf4j.LoggerFactory;
|
||||
*
|
||||
* Synchronization States:
|
||||
*
|
||||
* -COARSE - default state. demoted from MEDIUM after one message period with no sync detection.
|
||||
* -COARSE - default state. demoted from MEDIUM after one message period with no sync detection.
|
||||
*
|
||||
* -MEDIUM - promoted from COARSE when one sync detection occurs after two message periods, or demoted from FINE
|
||||
* after two message periods with no sync detections.
|
||||
* -MEDIUM - promoted from COARSE when one sync detection occurs after two message periods, or demoted from FINE
|
||||
* after two message periods with no sync detections.
|
||||
*
|
||||
* -FINE - promoted from MEDIUM after two or more consecutive sync detections within the preceding two or more
|
||||
* message periods.
|
||||
* -FINE - promoted from MEDIUM after two or more consecutive sync detections within the preceding two or more
|
||||
* message periods.
|
||||
*/
|
||||
public class SynchronizationMonitor implements ISyncDetectListener
|
||||
{
|
||||
@@ -50,6 +50,7 @@ public class SynchronizationMonitor implements ISyncDetectListener
|
||||
|
||||
/**
|
||||
* Constructs the synchronization monitor for the specified message symbol length.
|
||||
*
|
||||
* @param messageLength in symbols.
|
||||
*/
|
||||
public SynchronizationMonitor(int messageLength)
|
||||
@@ -127,6 +128,12 @@ public class SynchronizationMonitor implements ISyncDetectListener
|
||||
updateSyncState();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void syncLost()
|
||||
{
|
||||
//no-op
|
||||
}
|
||||
|
||||
/**
|
||||
* Increments the symbol count for each decoded symbol.
|
||||
*/
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,13 @@
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.dsp.symbol;
|
||||
|
||||
/**
|
||||
* Listener interface to be notified each time a sync pattern has been detected and/or when the sync
|
||||
* has been lost.
|
||||
*/
|
||||
public interface ISyncDetectListener
|
||||
{
|
||||
void syncDetected();
|
||||
void syncDetected();
|
||||
|
||||
void syncLost();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user