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