M17: Demodulator: improve sampling point estimation

ClockRecovery module provides the best sampling point estimate based on
the previous baseband history, tracking clock drifts more promptly than
methods based on syncword correlation.

Signed-off-by: Silvano Seva <silseva@fastwebnet.it>
This commit is contained in:
Silvano Seva 2025-10-31 21:57:35 +01:00
parent dc81639713
commit b763d6456f
2 changed files with 42 additions and 56 deletions

View File

@ -41,6 +41,7 @@
#include "protocols/M17/Correlator.hpp" #include "protocols/M17/Correlator.hpp"
#include "protocols/M17/Synchronizer.hpp" #include "protocols/M17/Synchronizer.hpp"
#include "protocols/M17/DevEstimator.hpp" #include "protocols/M17/DevEstimator.hpp"
#include "protocols/M17/ClockRecovery.hpp"
namespace M17 namespace M17
{ {
@ -137,10 +138,8 @@ private:
/** /**
* State handler function for DemodState::SYNC_UPDATE * State handler function for DemodState::SYNC_UPDATE
*
* @param sample: current baseband sample
*/ */
void syncUpdateState(int16_t sample); void syncUpdateState();
/** /**
* M17 baseband signal sampled at 24kHz, half of an M17 frame is processed * M17 baseband signal sampled at 24kHz, half of an M17 frame is processed
@ -161,7 +160,7 @@ private:
UNLOCKED, ///< Not locked UNLOCKED, ///< Not locked
SYNCED, ///< Synchronized, validate syncword SYNCED, ///< Synchronized, validate syncword
LOCKED, ///< Locked LOCKED, ///< Locked
SYNC_UPDATE ///< Updating the sampling point SYNC_UPDATE ///< Updating the synchronization state
}; };
/** /**
@ -177,13 +176,14 @@ private:
std::unique_ptr<frame_t > demodFrame; ///< Frame being demodulated. std::unique_ptr<frame_t > demodFrame; ///< Frame being demodulated.
std::unique_ptr<frame_t > readyFrame; ///< Fully demodulated frame to be returned. std::unique_ptr<frame_t > readyFrame; ///< Fully demodulated frame to be returned.
bool newFrame; ///< A new frame has been fully decoded. bool newFrame; ///< A new frame has been fully decoded.
bool resetClockRec; ///< Clock recovery reset request.
bool updateSampPoint; ///< Sampling point update pending.
uint16_t frameIndex; ///< Index for filling the raw frame. uint16_t frameIndex; ///< Index for filling the raw frame.
uint32_t sampleIndex; ///< Sample index, from 0 to (SAMPLES_PER_SYMBOL - 1) uint32_t sampleIndex; ///< Sample index, from 0 to (SAMPLES_PER_SYMBOL - 1)
uint32_t samplingPoint; ///< Symbol sampling point uint32_t samplingPoint; ///< Symbol sampling point
uint32_t sampleCount; ///< Free-running sample counter uint32_t sampleCount; ///< Free-running sample counter
uint8_t missedSyncs; ///< Counter of missed synchronizations uint8_t missedSyncs; ///< Counter of missed synchronizations
uint32_t initCount; ///< Downcounter for initialization uint32_t initCount; ///< Downcounter for initialization
uint32_t syncCount; ///< Downcounter for resynchronization
float corrThreshold; ///< Correlation threshold float corrThreshold; ///< Correlation threshold
struct dcBlock dcBlock; ///< State of the DC removal filter struct dcBlock dcBlock; ///< State of the DC removal filter
@ -191,6 +191,7 @@ private:
Synchronizer < M17_SYNCWORD_SYMBOLS, SAMPLES_PER_SYMBOL > streamSync{{ -3, -3, -3, -3, +3, +3, -3, +3 }}; Synchronizer < M17_SYNCWORD_SYMBOLS, SAMPLES_PER_SYMBOL > streamSync{{ -3, -3, -3, -3, +3, +3, -3, +3 }};
Iir < 3 > sampleFilter{sfNum, sfDen}; Iir < 3 > sampleFilter{sfNum, sfDen};
DevEstimator devEstimator; DevEstimator devEstimator;
ClockRecovery< SAMPLES_PER_SYMBOL > clockRec;
}; };
} /* M17 */ } /* M17 */

View File

@ -249,7 +249,23 @@ bool M17Demodulator::update(const bool invertPhase)
if(invertPhase) elem = 0.0f - elem; if(invertPhase) elem = 0.0f - elem;
sample = static_cast< int16_t >(M17::rrc_24k(elem)); sample = static_cast< int16_t >(M17::rrc_24k(elem));
// Update correlator and sample filter for correlation thresholds // Clock recovery reset MUST come before sampling
if((sampleIndex == 0) && resetClockRec) {
clockRec.reset();
resetClockRec = false;
updateSampPoint = false;
}
// Update sample point only when "far enough" from the last sampling,
// to avoid sampling issues when SP rolls over.
int diff = samplingPoint - sampleIndex;
if(updateSampPoint && (std::abs(diff) == SAMPLES_PER_SYMBOL/2)) {
clockRec.update();
samplingPoint = clockRec.samplingPoint();
updateSampPoint = false;
}
clockRec.sample(sample);
correlator.sample(sample); correlator.sample(sample);
corrThreshold = sampleFilter(std::abs(sample)); corrThreshold = sampleFilter(std::abs(sample));
@ -277,7 +293,7 @@ bool M17Demodulator::update(const bool invertPhase)
break; break;
case DemodState::SYNC_UPDATE: case DemodState::SYNC_UPDATE:
syncUpdateState(sample); syncUpdateState();
break; break;
} }
@ -312,14 +328,6 @@ void M17Demodulator::quantize(stream_sample_t sample)
setSymbol(*demodFrame, frameIndex, symbol); setSymbol(*demodFrame, frameIndex, symbol);
frameIndex += 1; frameIndex += 1;
if(frameIndex >= M17_FRAME_SYMBOLS)
{
devEstimator.update();
std::swap(readyFrame, demodFrame);
frameIndex = 0;
newFrame = true;
}
} }
void M17Demodulator::reset() void M17Demodulator::reset()
@ -376,58 +384,35 @@ void M17Demodulator::lockedState(int16_t sample)
if(sampleIndex != samplingPoint) if(sampleIndex != samplingPoint)
return; return;
// Quantize and update frame at each sampling point
quantize(sample); quantize(sample);
devEstimator.sample(sample); devEstimator.sample(sample);
// When we have reached almost the end of a frame, switch if(frameIndex == M17_FRAME_SYMBOLS) {
// to syncpoint update. devEstimator.update();
if(frameIndex == (M17_FRAME_SYMBOLS - M17_SYNCWORD_SYMBOLS/2)) { std::swap(readyFrame, demodFrame);
frameIndex = 0;
newFrame = true;
updateSampPoint = true;
demodState = DemodState::SYNC_UPDATE; demodState = DemodState::SYNC_UPDATE;
syncCount = SYNCWORD_SAMPLES * 2;
} }
} }
void M17Demodulator::syncUpdateState(int16_t sample) void M17Demodulator::syncUpdateState()
{ {
// Keep filling the ongoing frame! uint8_t streamHd = hammingDistance((*demodFrame)[0], STREAM_SYNC_WORD[0])
if(sampleIndex == samplingPoint) {
quantize(sample);
devEstimator.sample(sample);
}
// Find the new correlation peak
int32_t syncThresh = static_cast< int32_t >(corrThreshold * 33.0f);
int8_t syncStatus = streamSync.update(correlator, syncThresh, -syncThresh);
// Correlation has to coincide with a syncword!
if((syncStatus != 0) && (frameIndex == M17_SYNCWORD_SYMBOLS)) {
uint8_t hd = hammingDistance((*demodFrame)[0], STREAM_SYNC_WORD[0])
+ hammingDistance((*demodFrame)[1], STREAM_SYNC_WORD[1]); + hammingDistance((*demodFrame)[1], STREAM_SYNC_WORD[1]);
// Valid sync found: update deviation and sample if(streamHd <= 1)
// point, then go back to locked state
if(hd <= 1) {
samplingPoint = streamSync.samplingIndex();
missedSyncs = 0; missedSyncs = 0;
demodState = DemodState::LOCKED; else
return; missedSyncs += 1;
}
}
// No syncword found within the window, increase the count // The lock is lost after four consecutive sync misses.
// of missed syncs and choose where to go. The lock is lost if(missedSyncs > 4)
// after four consecutive sync misses.
if(syncCount == 0) {
if(missedSyncs >= 4)
demodState = DemodState::UNLOCKED; demodState = DemodState::UNLOCKED;
else else
demodState = DemodState::LOCKED; demodState = DemodState::LOCKED;
missedSyncs += 1;
}
syncCount -= 1;
} }
constexpr std::array < float, 3 > M17Demodulator::sfNum; constexpr std::array < float, 3 > M17Demodulator::sfNum;