From b563f2d11b5e9505461a17a53e529d1041a2c16b Mon Sep 17 00:00:00 2001 From: Denny Date: Tue, 27 Feb 2018 05:57:48 -0500 Subject: [PATCH] 330 WIP - created new symbol evaluator class that combines phase and timing error calculation with symbol decision. In the process of changing over to this new class and remove the previous implementations for the 3 values. This commit includes the partial implementation so I can access from another dev machine. --- .../dsheirer/dsp/psk/DQPSKDemodulator.java | 15 ++- .../dsp/psk/DQPSKSymbolEvaluator.java | 125 ++++++++++++++++++ .../dsp/psk/IDQPSKSymbolEvaluator.java | 48 +++++++ 3 files changed, 183 insertions(+), 5 deletions(-) create mode 100644 src/main/java/io/github/dsheirer/dsp/psk/DQPSKSymbolEvaluator.java create mode 100644 src/main/java/io/github/dsheirer/dsp/psk/IDQPSKSymbolEvaluator.java diff --git a/src/main/java/io/github/dsheirer/dsp/psk/DQPSKDemodulator.java b/src/main/java/io/github/dsheirer/dsp/psk/DQPSKDemodulator.java index 50326b13..2dfd2e21 100644 --- a/src/main/java/io/github/dsheirer/dsp/psk/DQPSKDemodulator.java +++ b/src/main/java/io/github/dsheirer/dsp/psk/DQPSKDemodulator.java @@ -29,9 +29,10 @@ public class DQPSKDemodulator implements ComplexSampleListener private final static Logger mLog = LoggerFactory.getLogger(DQPSKDemodulator.class); protected InterpolatingSampleBuffer mInterpolatingSampleBuffer; protected IPhaseLockedLoop mPLL; - protected IQPSKSymbolDecoder mDQPSKSymbolDecoder; + protected IQPSKSymbolDecoder mSymbolDecoder; private ISymbolPhaseErrorCalculator mSymbolPhaseErrorCalculator; - private DQPSKTimingErrorDetector mDQPSKTimingErrorDetector = new DQPSKTimingErrorDetector(); + private DQPSKTimingErrorDetector mSymbolTimingErrorDetector = new DQPSKTimingErrorDetector(); + private DQPSKSymbolEvaluator mSymbolEvaluator = new DQPSKSymbolEvaluator(); private Complex mReceivedSample = new Complex(0, 0); private Complex mPreviousPrecedingSample = new Complex(0, 0); @@ -61,7 +62,7 @@ public class DQPSKDemodulator implements ComplexSampleListener { mPLL = phaseLockedLoop; mSymbolPhaseErrorCalculator = symbolPhaseErrorCalculator; - mDQPSKSymbolDecoder = symbolDecoder; + mSymbolDecoder = symbolDecoder; mInterpolatingSampleBuffer = interpolatingSampleBuffer; } @@ -127,8 +128,10 @@ public class DQPSKDemodulator implements ComplexSampleListener mPrecedingSymbol.normalize(); mCurrentSymbol.normalize(); + mSymbolEvaluator.setSymbol(mCurrentSymbol); + //Symbol timing error calculation - mSymbolTimingError = mDQPSKTimingErrorDetector.getError(mPrecedingSymbol, mCurrentSymbol); + mSymbolTimingError = mSymbolTimingErrorDetector.getError(mPrecedingSymbol, mCurrentSymbol); mSymbolTimingError = clip(mSymbolTimingError, 0.5f); mInterpolatingSampleBuffer.resetAndAdjust(mSymbolTimingError); @@ -141,11 +144,13 @@ public class DQPSKDemodulator implements ComplexSampleListener //feedback to the PLL mPhaseError = mSymbolPhaseErrorCalculator.getPhaseError(mCurrentSymbol); + mLog.debug("Phase error - ORIG:" + mPhaseError + " NEW:" + mSymbolEvaluator.getPhaseError()); + mPLL.adjust(mPhaseError); if(mDibitListener != null) { - mDibitListener.receive(mDQPSKSymbolDecoder.decode(mCurrentSymbol)); + mDibitListener.receive(mSymbolDecoder.decode(mCurrentSymbol)); } } diff --git a/src/main/java/io/github/dsheirer/dsp/psk/DQPSKSymbolEvaluator.java b/src/main/java/io/github/dsheirer/dsp/psk/DQPSKSymbolEvaluator.java new file mode 100644 index 00000000..3ae2d214 --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/psk/DQPSKSymbolEvaluator.java @@ -0,0 +1,125 @@ +/******************************************************************************* + * 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 + * + ******************************************************************************/ +package io.github.dsheirer.dsp.psk; + +import io.github.dsheirer.dsp.symbol.Dibit; +import io.github.dsheirer.sample.complex.Complex; + +public class DQPSKSymbolEvaluator implements IDQPSKSymbolEvaluator +{ + private static final Complex ROTATE_FROM_PLUS_135 = Complex.fromAngle(-3.0 * Math.PI / 4.0); + private static final Complex ROTATE_FROM_PLUS_45 = Complex.fromAngle(-1.0 * Math.PI / 4.0); + private static final Complex ROTATE_FROM_MINUS_45 = Complex.fromAngle(1.0 * Math.PI / 4.0); + private static final Complex ROTATE_FROM_MINUS_135 = Complex.fromAngle(3.0 * Math.PI / 4.0); + + private Complex mSymbolError = new Complex(0,0); + private float mPhaseError = 0.0f; + private float mTimingError = 0.0f; + private float mTimingErrorSign = 1.0f; + private Dibit mSymbolDecision = Dibit.D00_PLUS_1; + + public DQPSKSymbolEvaluator() + { + } + + /** + * Sets the symbol to be evaluated and calculates the symbol decision and phase and timing errors of the symbol + * relative to the four QPSK reference symbol locations. After invoking this method, you can access the phase and + * timing errors and the symbol decision via their respective accessor methods. + * + * Phase and timing error values are calculated by determining the symbol's quadrant and multiplying the symbol + * by the complex conjugate of the reference symbol for that quadrant and then deriving the radian angle error + * value. Both phase and timing error use this angle error, however the timing error is corrected with the + * appropriate sign so that the error value indicates the correct error direction. + * + * @param complex symbol to be evaluated + */ + @Override + public void setSymbol(Complex complex) + { + mSymbolError.setValues(complex); + + if(mSymbolError.quadrature() > 0.0f) + { + if(mSymbolError.inphase() > 0.0f) + { + mSymbolDecision = Dibit.D00_PLUS_1; + mSymbolError.multiply(ROTATE_FROM_PLUS_45); + } + else + { + mSymbolDecision = Dibit.D01_PLUS_3; + mSymbolError.multiply(ROTATE_FROM_PLUS_135); + } + + mTimingErrorSign = 1.0f; + } + else + { + if(mSymbolError.inphase() > 0.0f) + { + mSymbolDecision = Dibit.D10_MINUS_1; + mSymbolError.multiply(ROTATE_FROM_MINUS_45); + } + else + { + mSymbolDecision = Dibit.D11_MINUS_3; + mSymbolError.multiply(ROTATE_FROM_MINUS_135); + } + + mTimingErrorSign = -1.0f; + } + + //Since we've rotated the error symbol back to 0 radians, the quadrature value closely approximates the + //arctan of the error angle relative to 0 radians and this provides our error value + mPhaseError = -mSymbolError.quadrature(); + + //Timing error is the same as phase error with the sign corrected according to the symbol's hemisphere + mTimingError = mPhaseError * mTimingErrorSign; + } + + /** + * Phase error of the symbol relative to the nearest reference symbol. + * @return phase error in radians of distance from the reference symbol. + */ + @Override + public float getPhaseError() + { + return mPhaseError; + } + + /** + * Timing error of the symbol relative to the nearest reference symbol. + * + * @return timing error in radians of angular distance from the reference symbol recognizing that the symbol + * originates at zero radians and rotates toward the intended reference symbol, therefore the error value indicates + * if the symbol was sampled early (-) or late (+) relative to the reference symbol. + */ + @Override + public float getTimingError() + { + return mTimingError; + } + + /** + * Reference symbol that is closest to the transmitted/sampled symbol. + */ + @Override + public Dibit getSymbolDecision() + { + return mSymbolDecision; + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/psk/IDQPSKSymbolEvaluator.java b/src/main/java/io/github/dsheirer/dsp/psk/IDQPSKSymbolEvaluator.java new file mode 100644 index 00000000..da650c53 --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/psk/IDQPSKSymbolEvaluator.java @@ -0,0 +1,48 @@ +/******************************************************************************* + * 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 + * + ******************************************************************************/ +package io.github.dsheirer.dsp.psk; + +import io.github.dsheirer.dsp.symbol.Dibit; +import io.github.dsheirer.sample.complex.Complex; + + +public interface IDQPSKSymbolEvaluator +{ + /** + * Sets the sampled symbol to be evaluated. Note: the symbol's gain value MUST be normalized to the unit circle. + * @param complex symbol + */ + void setSymbol(Complex complex); + + /** + * Phase error of the symbol relative to the closest reference symbol + * @return angular phase error in radians + */ + float getPhaseError(); + + /** + * Timing error of the symbol relative to the closest reference symbol + * @return angular timing error in radians where a positive value indicates early sampling and sample timing needs + * to be increased and a negative value indicates late sampling where sample timing needs to be decreased. + */ + float getTimingError(); + + /** + * Dibit decision indicating the reference symbol location closest to the symbol. + * @return symbol decision + */ + Dibit getSymbolDecision(); +}