kopia lustrzana https://github.com/mobilinkd/NucleoTNC
Update to version 2.4.4. Clock recovery improvements. Update the sample index more frequently to reduce EVM. Re-wrote the symbol deviation, offset, EVM code. More code comments.
rodzic
a85cbf0baa
commit
9e6f588d30
|
@ -1,4 +1,4 @@
|
||||||
// Copyright 2021 Mobilinkd LLC.
|
// Copyright 2021-2022 Mobilinkd LLC.
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
@ -19,8 +19,6 @@ namespace mobilinkd { namespace m17 {
|
||||||
template <typename FloatType = float, size_t SamplesPerSymbol = 10>
|
template <typename FloatType = float, size_t SamplesPerSymbol = 10>
|
||||||
struct ClockRecovery
|
struct ClockRecovery
|
||||||
{
|
{
|
||||||
static constexpr FloatType MAX_CLOCK_OFFSET = 0.0005; // 500ppm
|
|
||||||
|
|
||||||
KalmanFilter<FloatType, SamplesPerSymbol> kf_;
|
KalmanFilter<FloatType, SamplesPerSymbol> kf_;
|
||||||
size_t count_ = 0;
|
size_t count_ = 0;
|
||||||
int8_t sample_index_ = 0;
|
int8_t sample_index_ = 0;
|
||||||
|
@ -41,17 +39,10 @@ struct ClockRecovery
|
||||||
++count_;
|
++count_;
|
||||||
}
|
}
|
||||||
|
|
||||||
static FloatType clock_limiter(FloatType offset)
|
|
||||||
{
|
|
||||||
return std::min(MAX_CLOCK_OFFSET, std::max(-MAX_CLOCK_OFFSET, offset));
|
|
||||||
}
|
|
||||||
|
|
||||||
bool update(uint8_t sw)
|
bool update(uint8_t sw)
|
||||||
{
|
{
|
||||||
INFO("CR Update %d", int(sw));
|
if (count_ < 8)
|
||||||
if (count_ < 480)
|
|
||||||
{
|
{
|
||||||
sample_index_ = sw;
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,8 +68,7 @@ struct ClockRecovery
|
||||||
*/
|
*/
|
||||||
bool update()
|
bool update()
|
||||||
{
|
{
|
||||||
INFO("CR Update");
|
auto csw = std::fmod((sample_estimate_ + clock_estimate_ * count_), SamplesPerSymbol);
|
||||||
auto csw = std::fmod((sample_estimate_ + (1.0 + clock_estimate_) * count_), SamplesPerSymbol);
|
|
||||||
if (csw < 0.) csw += SamplesPerSymbol;
|
if (csw < 0.) csw += SamplesPerSymbol;
|
||||||
else if (csw >= SamplesPerSymbol) csw -= SamplesPerSymbol;
|
else if (csw >= SamplesPerSymbol) csw -= SamplesPerSymbol;
|
||||||
|
|
||||||
|
|
|
@ -1,129 +1,142 @@
|
||||||
// Copyright 2021 Rob Riggs <rob@mobilinkd.com>
|
// Copyright 2021-2022 Rob Riggs <rob@mobilinkd.com>
|
||||||
// All rights reserved.
|
// All rights reserved.
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "IirFilter.hpp"
|
#include "KalmanFilter.h"
|
||||||
|
#include "StandardDeviation.hpp"
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <array>
|
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
|
|
||||||
namespace mobilinkd { namespace m17 {
|
namespace mobilinkd { namespace m17 {
|
||||||
|
template <typename FloatType, size_t SYNC_WORD_LEN = 8>
|
||||||
/**
|
|
||||||
* Deviation and zero-offset estimator.
|
|
||||||
*
|
|
||||||
* Accepts samples which are periodically used to update estimates of the
|
|
||||||
* input signal deviation and zero offset.
|
|
||||||
*
|
|
||||||
* Samples must be provided at the ideal sample point (the point with the
|
|
||||||
* peak bit energy).
|
|
||||||
*
|
|
||||||
* Estimates are expected to be updated at each sync word. But they can
|
|
||||||
* be updated more frequently, such as during the preamble.
|
|
||||||
*/
|
|
||||||
template <typename FloatType>
|
|
||||||
class FreqDevEstimator
|
class FreqDevEstimator
|
||||||
{
|
{
|
||||||
using sample_filter_t = tnc::IirFilter<3>;
|
static constexpr FloatType DEVIATION = 1.;
|
||||||
|
|
||||||
// IIR with Nyquist of 1/4.
|
SymbolKalmanFilter<FloatType> minFilter_;
|
||||||
static constexpr std::array<float, 3> dc_b = { 0.09763107, 0.19526215, 0.09763107 };
|
SymbolKalmanFilter<FloatType> maxFilter_;
|
||||||
static constexpr std::array<float, 3> dc_a = { 1. , -0.94280904, 0.33333333 };
|
RunningStandardDeviation<FloatType, 184> stddev_;
|
||||||
|
FloatType idev_ = 1.;
|
||||||
static constexpr FloatType MAX_DC_ERROR = 0.2;
|
FloatType offset_ = 0.;
|
||||||
|
uint8_t count_ = 0;
|
||||||
FloatType min_est_ = 0.0;
|
FloatType min_ = 0.;
|
||||||
FloatType max_est_ = 0.0;
|
FloatType max_ = 0.;
|
||||||
FloatType min_cutoff_ = 0.0;
|
uint8_t minCount_ = 0;
|
||||||
FloatType max_cutoff_ = 0.0;
|
uint8_t maxCount_ = 0;
|
||||||
FloatType min_var_ = 0.0;
|
size_t updateCount_ = 0;
|
||||||
FloatType max_var_ = 0.0;
|
bool reset_ = true;
|
||||||
size_t min_count_ = 1;
|
|
||||||
size_t max_count_ = 1;
|
|
||||||
FloatType deviation_ = 0.0;
|
|
||||||
FloatType offset_ = 0.0;
|
|
||||||
FloatType error_ = 0.0;
|
|
||||||
FloatType idev_ = 1.0;
|
|
||||||
sample_filter_t dc_filter_{dc_b, dc_a};
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
void reset()
|
void reset()
|
||||||
{
|
{
|
||||||
min_est_ = 0.0;
|
idev_ = 1.;
|
||||||
max_est_ = 0.0;
|
offset_ = 0.;
|
||||||
min_var_ = 0.0;
|
count_ = 0;
|
||||||
max_var_ = 0.0;
|
min_ = 0.;
|
||||||
min_count_ = 1;
|
max_ = 0.;
|
||||||
max_count_ = 1;
|
minCount_ = 0;
|
||||||
min_cutoff_ = 0.0;
|
maxCount_ = 0;
|
||||||
max_cutoff_ = 0.0;
|
updateCount_ = 0;
|
||||||
}
|
stddev_.reset();
|
||||||
|
reset_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
void sample(FloatType sample)
|
/**
|
||||||
{
|
* This function takes the index samples from the correlator to build
|
||||||
if (sample < 1.5 * min_est_)
|
* the outer symbol samples for the frequency offset (signal DC offset)
|
||||||
{
|
* and the deviation (signal magnitude). It expects bursts of 8 samples,
|
||||||
min_count_ = 1;
|
* one for each symbol in a sync word.
|
||||||
min_est_ = sample;
|
*
|
||||||
min_var_ = 0.0;
|
* @param sample
|
||||||
min_cutoff_ = min_est_ * 0.666666;
|
*/
|
||||||
}
|
void sample(FloatType sample)
|
||||||
else if (sample < min_cutoff_)
|
{
|
||||||
{
|
count_ += 1;
|
||||||
min_count_ += 1;
|
|
||||||
min_est_ += sample;
|
|
||||||
FloatType var = (min_est_ / min_count_) - sample;
|
|
||||||
min_var_ += var * var;
|
|
||||||
}
|
|
||||||
else if (sample > 1.5 * max_est_)
|
|
||||||
{
|
|
||||||
max_count_ = 1;
|
|
||||||
max_est_ = sample;
|
|
||||||
max_var_ = 0.0;
|
|
||||||
max_cutoff_ = max_est_ * 0.666666;
|
|
||||||
}
|
|
||||||
else if (sample > max_cutoff_)
|
|
||||||
{
|
|
||||||
max_count_ += 1;
|
|
||||||
max_est_ += sample;
|
|
||||||
FloatType var = (max_est_ / max_count_) - sample;
|
|
||||||
max_var_ += var * var;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
if (sample < 0)
|
||||||
* Update the estimates for deviation, offset, and EVM (error). Note
|
{
|
||||||
* that the estimates for error are using a sloppy implementation for
|
minCount_ += 1;
|
||||||
* calculating variance to reduce the memory requirements. This is
|
min_ += sample;
|
||||||
* because this is designed for embedded use.
|
}
|
||||||
*/
|
else
|
||||||
void update()
|
{
|
||||||
{
|
maxCount_ += 1;
|
||||||
if (max_count_ < 2 || min_count_ < 2) return;
|
max_ += sample;
|
||||||
FloatType max_ = max_est_ / max_count_;
|
}
|
||||||
FloatType min_ = min_est_ / min_count_;
|
|
||||||
deviation_ = (max_ - min_) / 6.0;
|
|
||||||
if (deviation_ > 0) idev_ = 1.0 / deviation_;
|
|
||||||
offset_ = dc_filter_(std::max(std::min(max_ + min_, deviation_ * MAX_DC_ERROR), deviation_ * -MAX_DC_ERROR));
|
|
||||||
error_ = (std::sqrt(max_var_ / (max_count_ - 1)) + std::sqrt(min_var_ / (min_count_ - 1))) * 0.5 * idev_;
|
|
||||||
min_cutoff_ = offset_ - deviation_ * 2;
|
|
||||||
max_cutoff_ = offset_ + deviation_ * 2;
|
|
||||||
max_est_ = max_;
|
|
||||||
min_est_ = min_;
|
|
||||||
max_count_ = 1;
|
|
||||||
min_count_ = 1;
|
|
||||||
max_var_ = 0.0;
|
|
||||||
min_var_ = 0.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
FloatType deviation() const { return deviation_; }
|
if (count_ == SYNC_WORD_LEN)
|
||||||
FloatType offset() const { return offset_; }
|
{
|
||||||
FloatType error() const { return error_; }
|
auto minAvg = min_ / minCount_;
|
||||||
FloatType idev() const { return idev_; }
|
auto maxAvg = max_ / maxCount_;
|
||||||
|
if (reset_)
|
||||||
|
{
|
||||||
|
minFilter_.reset(minAvg);
|
||||||
|
maxFilter_.reset(maxAvg);
|
||||||
|
idev_ = 6.0 / (maxAvg - minAvg);
|
||||||
|
offset_ = maxAvg + minAvg;
|
||||||
|
reset_ = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
auto minFiltered = minFilter_.update(minAvg, count_ + updateCount_);
|
||||||
|
auto maxFiltered = maxFilter_.update(maxAvg, count_ + updateCount_);
|
||||||
|
idev_ = 6.0 / (maxFiltered[0] - minFiltered[0]);
|
||||||
|
offset_ = maxFiltered[0] + minFiltered[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
count_ = 0;
|
||||||
|
updateCount_ = 0;
|
||||||
|
min_ = 0.;
|
||||||
|
max_ = 0.;
|
||||||
|
minCount_ = 0;
|
||||||
|
maxCount_ = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FloatType normalize(FloatType sample) const
|
||||||
|
{
|
||||||
|
return (sample - offset_) * idev_;
|
||||||
|
}
|
||||||
|
|
||||||
|
FloatType evm() const { return stddev_.stdev(); }
|
||||||
|
|
||||||
|
void update() const {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Capture EVM of a symbol.
|
||||||
|
*
|
||||||
|
* @param sample is a normalized sample captured at the best sample point.
|
||||||
|
*/
|
||||||
|
void update(FloatType sample)
|
||||||
|
{
|
||||||
|
if (sample > 2)
|
||||||
|
{
|
||||||
|
stddev_.capture(sample - 3);
|
||||||
|
}
|
||||||
|
else if (sample > 0)
|
||||||
|
{
|
||||||
|
stddev_.capture(sample - 1);
|
||||||
|
}
|
||||||
|
else if (sample > -2)
|
||||||
|
{
|
||||||
|
stddev_.capture(sample + 1);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
stddev_.capture(sample + 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateCount_ += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
FloatType idev() const { return idev_; }
|
||||||
|
FloatType offset() const { return offset_; }
|
||||||
|
FloatType deviation() const { return DEVIATION / idev_; }
|
||||||
|
FloatType error() const { return evm(); }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
}} // mobilinkd::m17
|
}} // mobilinkd::m17
|
||||||
|
|
|
@ -63,10 +63,10 @@ struct KalmanFilter
|
||||||
auto K = P * blaze::trans(H) * (1.0 / S(0, 0));
|
auto K = P * blaze::trans(H) * (1.0 / S(0, 0));
|
||||||
|
|
||||||
// Normalize incoming index
|
// Normalize incoming index
|
||||||
if (z - x[0] < (SamplesPerSymbol / -2.0))
|
if (z - x[0] < (SamplesPerSymbol / -2.0)) // wrapped forwards 9 -> 0
|
||||||
z += SamplesPerSymbol;
|
z += SamplesPerSymbol;
|
||||||
else if (z - x[0] > (SamplesPerSymbol / 2.0))
|
else if (z - x[0] > (SamplesPerSymbol / 2.0)) // wrapped 0 -> 9
|
||||||
z-= SamplesPerSymbol;
|
z -= SamplesPerSymbol;
|
||||||
|
|
||||||
auto y = z - H * x;
|
auto y = z - H * x;
|
||||||
|
|
||||||
|
@ -80,4 +80,46 @@ struct KalmanFilter
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template <typename FloatType>
|
||||||
|
struct SymbolKalmanFilter
|
||||||
|
{
|
||||||
|
blaze::StaticVector<FloatType, 2> x;
|
||||||
|
blaze::StaticMatrix<FloatType, 2, 2> P;
|
||||||
|
blaze::StaticMatrix<FloatType, 2, 2> F;
|
||||||
|
blaze::StaticMatrix<FloatType, 1, 2> H = {{1., 0.}};
|
||||||
|
blaze::StaticMatrix<FloatType, 1, 1> R = {{0.5}};
|
||||||
|
blaze::StaticMatrix<FloatType, 2, 2> Q = {{6.25e-4, 1.25e-3},{1.25e-3, 2.50e-3}};
|
||||||
|
|
||||||
|
SymbolKalmanFilter()
|
||||||
|
{
|
||||||
|
reset(0.);
|
||||||
|
}
|
||||||
|
|
||||||
|
void reset(FloatType z)
|
||||||
|
{
|
||||||
|
x = {z, 0.};
|
||||||
|
P = {{4., 0.}, {0., 0.00000025}};
|
||||||
|
F = {{1., 1.}, {0., 1.}};
|
||||||
|
}
|
||||||
|
|
||||||
|
[[gnu::noinline]]
|
||||||
|
auto update(FloatType z, size_t dt)
|
||||||
|
{
|
||||||
|
F(0,1) = FloatType(dt);
|
||||||
|
|
||||||
|
x = F * x;
|
||||||
|
P = F * P * blaze::trans(F) + Q;
|
||||||
|
auto S = H * P * blaze::trans(H) + R;
|
||||||
|
auto K = P * blaze::trans(H) * (1.0 / S(0, 0));
|
||||||
|
|
||||||
|
auto y = z - H * x;
|
||||||
|
|
||||||
|
x += K * y;
|
||||||
|
|
||||||
|
// Normalize the filtered sample point
|
||||||
|
P = P - K * H * P;
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
}} // mobilinkd::m17
|
}} // mobilinkd::m17
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright 2018-2021 Rob Riggs <rob@mobilinkd.com>
|
// Copyright 2018-2022 Rob Riggs <rob@mobilinkd.com>
|
||||||
// All rights reserved.
|
// All rights reserved.
|
||||||
|
|
||||||
#include "KissHardware.hpp"
|
#include "KissHardware.hpp"
|
||||||
|
@ -43,13 +43,13 @@ int powerOffViaUSB(void)
|
||||||
namespace mobilinkd { namespace tnc { namespace kiss {
|
namespace mobilinkd { namespace tnc { namespace kiss {
|
||||||
|
|
||||||
#if defined(NUCLEOTNC)
|
#if defined(NUCLEOTNC)
|
||||||
const char FIRMWARE_VERSION[] = "2.4.3";
|
const char FIRMWARE_VERSION[] = "2.4.4";
|
||||||
const char HARDWARE_VERSION[] = "Mobilinkd NucleoTNC";
|
const char HARDWARE_VERSION[] = "Mobilinkd NucleoTNC";
|
||||||
#elif defined(STM32L433xx)
|
#elif defined(STM32L433xx)
|
||||||
const char FIRMWARE_VERSION[] = "2.4.3";
|
const char FIRMWARE_VERSION[] = "2.4.4";
|
||||||
const char HARDWARE_VERSION[] = "Mobilinkd TNC3 2.1.1";
|
const char HARDWARE_VERSION[] = "Mobilinkd TNC3 2.1.1";
|
||||||
#elif defined(STM32L4P5xx)
|
#elif defined(STM32L4P5xx)
|
||||||
const char FIRMWARE_VERSION[] = "2.4.3";
|
const char FIRMWARE_VERSION[] = "2.4.4";
|
||||||
const char HARDWARE_VERSION[] = "Mobilinkd TNC3+ Rev A";
|
const char HARDWARE_VERSION[] = "Mobilinkd TNC3+ Rev A";
|
||||||
#endif
|
#endif
|
||||||
Hardware& settings()
|
Hardware& settings()
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright 2020-2021 Rob Riggs <rob@mobilinkd.com>
|
// Copyright 2020-2022 Rob Riggs <rob@mobilinkd.com>
|
||||||
// All rights reserved.
|
// All rights reserved.
|
||||||
|
|
||||||
#pragma GCC diagnostic push
|
#pragma GCC diagnostic push
|
||||||
|
@ -23,7 +23,7 @@ namespace mobilinkd { namespace tnc {
|
||||||
//m17::Indicator dcd_indicator{GPIOA, GPIO_PIN_2};
|
//m17::Indicator dcd_indicator{GPIOA, GPIO_PIN_2};
|
||||||
//m17::Indicator str_indicator{GPIOA, GPIO_PIN_7};
|
//m17::Indicator str_indicator{GPIOA, GPIO_PIN_7};
|
||||||
|
|
||||||
static float scale = 1.f / 2560.f;
|
static float scale = 1.f / 32768.f;
|
||||||
|
|
||||||
static float dc_block(float x)
|
static float dc_block(float x)
|
||||||
{
|
{
|
||||||
|
@ -51,8 +51,8 @@ void M17Demodulator::start()
|
||||||
demod_filter.init(m17::rrc_taps_f);
|
demod_filter.init(m17::rrc_taps_f);
|
||||||
passall(kiss::settings().options & KISS_OPTION_PASSALL);
|
passall(kiss::settings().options & KISS_OPTION_PASSALL);
|
||||||
polarity = kiss::settings().rx_rev_polarity() ? -1 : 1;
|
polarity = kiss::settings().rx_rev_polarity() ? -1 : 1;
|
||||||
scale = 1.f / 2560.f * polarity;
|
scale = 1.f / 32768.f * polarity;
|
||||||
// audio::virtual_ground = (VREF + 1) / 2;
|
audio::virtual_ground = (VREF + 1) / 2;
|
||||||
|
|
||||||
hadc1.Init.OversamplingMode = ENABLE;
|
hadc1.Init.OversamplingMode = ENABLE;
|
||||||
if (HAL_ADC_Init(&hadc1) != HAL_OK)
|
if (HAL_ADC_Init(&hadc1) != HAL_OK)
|
||||||
|
@ -76,11 +76,26 @@ void M17Demodulator::start()
|
||||||
dcd_off();
|
dcd_off();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the deviation & offset from the stored correlator samples. This is
|
||||||
|
* called after a sync word has been detected.
|
||||||
|
*
|
||||||
|
* @pre @p sample_index is the best estimate of the sample point and has
|
||||||
|
* been set, either by the clock recovery system or from a sync word if
|
||||||
|
* the demodulator was in an unlocked state.
|
||||||
|
*
|
||||||
|
* @post @p dev has been updated with the latest sync word samples. And
|
||||||
|
* @p sync_sample_index is set to the current sync word tigger point.
|
||||||
|
*
|
||||||
|
* @param index is the current sync word trigger point.
|
||||||
|
*/
|
||||||
void M17Demodulator::update_values(uint8_t index)
|
void M17Demodulator::update_values(uint8_t index)
|
||||||
{
|
{
|
||||||
correlator.apply([this,index](float t){dev.sample(t);}, index);
|
// For deviation and offset to be accurate, this must be the stable
|
||||||
|
// sample_index. The sync word trigger point is too noisy, resulting
|
||||||
|
// in inaccurate frequency offset and deviation estimates.
|
||||||
|
correlator.apply([this,index](float t){dev.sample(t);}, sample_index);
|
||||||
dev.update();
|
dev.update();
|
||||||
idev = dev.idev();
|
|
||||||
sync_sample_index = index;
|
sync_sample_index = index;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -89,20 +104,23 @@ void M17Demodulator::dcd_on()
|
||||||
// Data carrier newly detected.
|
// Data carrier newly detected.
|
||||||
INFO("dcd = %d", int(dcd.level() * 1000));
|
INFO("dcd = %d", int(dcd.level() * 1000));
|
||||||
dcd_ = true;
|
dcd_ = true;
|
||||||
sync_count = 0;
|
if (demodState == DemodState::UNLOCKED)
|
||||||
dev.reset();
|
{
|
||||||
framer.reset();
|
sync_count = 0;
|
||||||
decoder.reset();
|
missing_sync_count = 0;
|
||||||
missing_sync_count = 0;
|
|
||||||
|
dev.reset();
|
||||||
|
framer.reset();
|
||||||
|
decoder.reset();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void M17Demodulator::dcd_off()
|
void M17Demodulator::dcd_off()
|
||||||
{
|
{
|
||||||
// Just lost data carrier.
|
// Just lost data carrier.
|
||||||
INFO("dcd = %d", int(dcd.level() * 1000));
|
INFO("dcd = %d", int(dcd.level() * 1000));
|
||||||
dcd_ = false;
|
|
||||||
demodState = DemodState::UNLOCKED;
|
demodState = DemodState::UNLOCKED;
|
||||||
adc_timing_adjust = 0;
|
dcd_ = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void M17Demodulator::initialize(const q15_t* input)
|
void M17Demodulator::initialize(const q15_t* input)
|
||||||
|
@ -154,8 +172,8 @@ void M17Demodulator::do_unlocked()
|
||||||
missing_sync_count = 0;
|
missing_sync_count = 0;
|
||||||
need_clock_reset_ = true;
|
need_clock_reset_ = true;
|
||||||
dev.reset();
|
dev.reset();
|
||||||
update_values(sync_index);
|
|
||||||
sample_index = sync_index;
|
sample_index = sync_index;
|
||||||
|
update_values(sync_index);
|
||||||
demodState = DemodState::LSF_SYNC;
|
demodState = DemodState::LSF_SYNC;
|
||||||
INFO("P sync %d", sync_index);
|
INFO("P sync %d", sync_index);
|
||||||
}
|
}
|
||||||
|
@ -166,12 +184,12 @@ void M17Demodulator::do_unlocked()
|
||||||
auto sync_updated = lsf_sync.updated();
|
auto sync_updated = lsf_sync.updated();
|
||||||
if (sync_updated)
|
if (sync_updated)
|
||||||
{
|
{
|
||||||
sync_count = 0;
|
sync_count = MAX_SYNC_COUNT;
|
||||||
missing_sync_count = 0;
|
missing_sync_count = 0;
|
||||||
need_clock_reset_ = true;
|
need_clock_reset_ = true;
|
||||||
dev.reset();
|
dev.reset();
|
||||||
update_values(sync_index);
|
|
||||||
sample_index = sync_index;
|
sample_index = sync_index;
|
||||||
|
update_values(sync_index);
|
||||||
demodState = DemodState::FRAME;
|
demodState = DemodState::FRAME;
|
||||||
if (sync_updated < 0)
|
if (sync_updated < 0)
|
||||||
{
|
{
|
||||||
|
@ -188,12 +206,12 @@ void M17Demodulator::do_unlocked()
|
||||||
sync_updated = packet_sync.updated();
|
sync_updated = packet_sync.updated();
|
||||||
if (sync_updated < 0)
|
if (sync_updated < 0)
|
||||||
{
|
{
|
||||||
sync_count = 0;
|
sync_count = MAX_SYNC_COUNT;
|
||||||
missing_sync_count = 0;
|
missing_sync_count = 0;
|
||||||
need_clock_reset_ = true;
|
need_clock_reset_ = true;
|
||||||
dev.reset();
|
dev.reset();
|
||||||
update_values(sync_index);
|
|
||||||
sample_index = sync_index;
|
sample_index = sync_index;
|
||||||
|
update_values(sync_index);
|
||||||
demodState = DemodState::FRAME;
|
demodState = DemodState::FRAME;
|
||||||
sync_word_type = M17FrameDecoder::SyncWordType::BERT;
|
sync_word_type = M17FrameDecoder::SyncWordType::BERT;
|
||||||
INFO("B sync %d", int(sync_index));
|
INFO("B sync %d", int(sync_index));
|
||||||
|
@ -217,6 +235,7 @@ void M17Demodulator::do_lsf_sync()
|
||||||
// INFO("PSync = %d", int(sync_triggered));
|
// INFO("PSync = %d", int(sync_triggered));
|
||||||
if (sync_triggered > 0.1)
|
if (sync_triggered > 0.1)
|
||||||
{
|
{
|
||||||
|
need_clock_update_ = true;
|
||||||
sync_count += 1;
|
sync_count += 1;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -225,6 +244,7 @@ void M17Demodulator::do_lsf_sync()
|
||||||
if (bert_triggered < 0)
|
if (bert_triggered < 0)
|
||||||
{
|
{
|
||||||
missing_sync_count = 0;
|
missing_sync_count = 0;
|
||||||
|
sync_count = MAX_SYNC_COUNT;
|
||||||
need_clock_update_ = true;
|
need_clock_update_ = true;
|
||||||
update_values(sample_index);
|
update_values(sample_index);
|
||||||
demodState = DemodState::FRAME;
|
demodState = DemodState::FRAME;
|
||||||
|
@ -234,6 +254,7 @@ void M17Demodulator::do_lsf_sync()
|
||||||
else if (std::abs(sync_triggered) > 0.1)
|
else if (std::abs(sync_triggered) > 0.1)
|
||||||
{
|
{
|
||||||
missing_sync_count = 0;
|
missing_sync_count = 0;
|
||||||
|
sync_count = MAX_SYNC_COUNT;
|
||||||
need_clock_update_ = true;
|
need_clock_update_ = true;
|
||||||
update_values(sample_index);
|
update_values(sample_index);
|
||||||
INFO("LSync = %d", int(sync_triggered));
|
INFO("LSync = %d", int(sync_triggered));
|
||||||
|
@ -253,15 +274,17 @@ void M17Demodulator::do_lsf_sync()
|
||||||
else if (++missing_sync_count > 192)
|
else if (++missing_sync_count > 192)
|
||||||
{
|
{
|
||||||
if (sync_count >= 10) {
|
if (sync_count >= 10) {
|
||||||
|
// Long preamble. Update clock and continue waiting for LSF.
|
||||||
missing_sync_count = 0;
|
missing_sync_count = 0;
|
||||||
need_clock_update_ = true;
|
need_clock_update_ = true;
|
||||||
INFO("long preamble");
|
INFO("long preamble");
|
||||||
} else {
|
} else {
|
||||||
|
// No sync word. Recycle.
|
||||||
sync_count = 0;
|
sync_count = 0;
|
||||||
demodState = DemodState::UNLOCKED;
|
demodState = DemodState::UNLOCKED;
|
||||||
|
missing_sync_count = 0;
|
||||||
dcd.unlock();
|
dcd.unlock();
|
||||||
INFO("l unlock %d", int(missing_sync_count));
|
INFO("l unlock %d", int(missing_sync_count));
|
||||||
missing_sync_count = 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -283,14 +306,18 @@ void M17Demodulator::do_stream_sync()
|
||||||
static bool eot_flag = false;
|
static bool eot_flag = false;
|
||||||
|
|
||||||
sync_count += 1;
|
sync_count += 1;
|
||||||
if (sync_count < MIN_SYNC_INDEX) {
|
if (sync_count < MIN_SYNC_COUNT) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (eot_sync.triggered(correlator) > 0.1) {
|
if (eot_sync.triggered(correlator) > EOT_TRIGGER_LEVEL) {
|
||||||
|
// Note the EOT flag but continue trying to decode. This is needed
|
||||||
|
// to avoid false triggers. If it is a true EOT, the stream will
|
||||||
|
// end the next time we try to capture a sync word.
|
||||||
sync_word_type = M17FrameDecoder::SyncWordType::STREAM;
|
sync_word_type = M17FrameDecoder::SyncWordType::STREAM;
|
||||||
demodState = DemodState::FRAME;
|
demodState = DemodState::FRAME;
|
||||||
eot_flag = true;
|
eot_flag = true;
|
||||||
|
missing_sync_count = 0;
|
||||||
INFO("EOT");
|
INFO("EOT");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -302,28 +329,31 @@ void M17Demodulator::do_stream_sync()
|
||||||
missing_sync_count = 0;
|
missing_sync_count = 0;
|
||||||
update_values(sync_index);
|
update_values(sync_index);
|
||||||
sync_word_type = M17FrameDecoder::SyncWordType::STREAM;
|
sync_word_type = M17FrameDecoder::SyncWordType::STREAM;
|
||||||
demodState = DemodState::FRAME;
|
demodState = DemodState::SYNC_WAIT;
|
||||||
INFO("s sync %d", int(sync_count));
|
INFO("s sync %d", int(sync_count));
|
||||||
eot_flag = false;
|
eot_flag = false;
|
||||||
}
|
}
|
||||||
else if (sync_count > MAX_SYNC_INDEX)
|
else if (sync_count > MAX_SYNC_COUNT)
|
||||||
{
|
{
|
||||||
// update_values(sync_index);
|
if (ber >= 0 && ber < STREAM_COST_LIMIT)
|
||||||
if (ber >= 0 && ber < 80)
|
|
||||||
{
|
{
|
||||||
// Sync word missed but we are still decoding a stream reasonably well.
|
// Sync word missed but we are still decoding a stream reasonably
|
||||||
|
// well. Don't increment the missing sync count, but it must not
|
||||||
|
// be 0 when a sync word is missed for clock recovery to work.
|
||||||
|
if (!missing_sync_count) missing_sync_count = 1;
|
||||||
sync_word_type = M17FrameDecoder::SyncWordType::STREAM;
|
sync_word_type = M17FrameDecoder::SyncWordType::STREAM;
|
||||||
demodState = DemodState::FRAME;
|
demodState = DemodState::FRAME;
|
||||||
if (!missing_sync_count) missing_sync_count = 1;
|
|
||||||
INFO("s bunsync");
|
INFO("s bunsync");
|
||||||
}
|
}
|
||||||
else if (eot_flag) {
|
else if (eot_flag) {
|
||||||
|
// EOT flag set, missing sync, and very high BER. Stream has ended.
|
||||||
demodState = DemodState::UNLOCKED;
|
demodState = DemodState::UNLOCKED;
|
||||||
dcd.unlock();
|
dcd.unlock();
|
||||||
INFO("s eot");
|
INFO("s eot");
|
||||||
}
|
}
|
||||||
else if (missing_sync_count < MAX_MISSING_SYNC)
|
else if (missing_sync_count < MAX_MISSING_SYNC)
|
||||||
{
|
{
|
||||||
|
// Sync word missed, very high error rate. Still trying to decode.
|
||||||
missing_sync_count += 1;
|
missing_sync_count += 1;
|
||||||
sync_word_type = M17FrameDecoder::SyncWordType::STREAM;
|
sync_word_type = M17FrameDecoder::SyncWordType::STREAM;
|
||||||
demodState = DemodState::FRAME;
|
demodState = DemodState::FRAME;
|
||||||
|
@ -331,6 +361,7 @@ void M17Demodulator::do_stream_sync()
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
// No EOT, but too many sync words missed. Recycle.
|
||||||
demodState = DemodState::UNLOCKED;
|
demodState = DemodState::UNLOCKED;
|
||||||
dcd.unlock();
|
dcd.unlock();
|
||||||
INFO("s unlock");
|
INFO("s unlock");
|
||||||
|
@ -347,7 +378,7 @@ void M17Demodulator::do_stream_sync()
|
||||||
void M17Demodulator::do_packet_sync()
|
void M17Demodulator::do_packet_sync()
|
||||||
{
|
{
|
||||||
sync_count += 1;
|
sync_count += 1;
|
||||||
if (sync_count < MIN_SYNC_INDEX) {
|
if (sync_count < MIN_SYNC_COUNT) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -359,27 +390,31 @@ void M17Demodulator::do_packet_sync()
|
||||||
missing_sync_count = 0;
|
missing_sync_count = 0;
|
||||||
update_values(sync_index);
|
update_values(sync_index);
|
||||||
sync_word_type = M17FrameDecoder::SyncWordType::PACKET;
|
sync_word_type = M17FrameDecoder::SyncWordType::PACKET;
|
||||||
demodState = DemodState::FRAME;
|
demodState = DemodState::SYNC_WAIT;
|
||||||
INFO("k sync");
|
INFO("k sync");
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
else if (sync_count > MAX_SYNC_INDEX)
|
else if (sync_count > MAX_SYNC_COUNT)
|
||||||
{
|
{
|
||||||
missing_sync_count += 1;
|
if (ber >= 0 && ber < PACKET_COST_LIMIT)
|
||||||
if (missing_sync_count < MAX_MISSING_SYNC)
|
|
||||||
{
|
|
||||||
sync_word_type = M17FrameDecoder::SyncWordType::PACKET;
|
|
||||||
demodState = DemodState::FRAME;
|
|
||||||
INFO("k unsync");
|
|
||||||
}
|
|
||||||
else if (ber >= 0 && ber < 80)
|
|
||||||
{
|
{
|
||||||
|
// Sync word missed but we are still decoding reasonably well.
|
||||||
|
if (!missing_sync_count) missing_sync_count = 1;
|
||||||
sync_word_type = M17FrameDecoder::SyncWordType::PACKET;
|
sync_word_type = M17FrameDecoder::SyncWordType::PACKET;
|
||||||
demodState = DemodState::FRAME;
|
demodState = DemodState::FRAME;
|
||||||
INFO("k bunsync");
|
INFO("k bunsync");
|
||||||
}
|
}
|
||||||
|
else if (missing_sync_count < MAX_MISSING_SYNC)
|
||||||
|
{
|
||||||
|
// Sync word missed, very high error rate. Still trying to decode.
|
||||||
|
// This may not be appropriate for packet mode.
|
||||||
|
missing_sync_count += 1;
|
||||||
|
sync_word_type = M17FrameDecoder::SyncWordType::PACKET;
|
||||||
|
demodState = DemodState::FRAME;
|
||||||
|
INFO("k unsync");
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
// Too many sync words missed. Recycle.
|
||||||
demodState = DemodState::UNLOCKED;
|
demodState = DemodState::UNLOCKED;
|
||||||
dcd.unlock();
|
dcd.unlock();
|
||||||
INFO("k unlock");
|
INFO("k unlock");
|
||||||
|
@ -394,7 +429,7 @@ void M17Demodulator::do_packet_sync()
|
||||||
void M17Demodulator::do_bert_sync()
|
void M17Demodulator::do_bert_sync()
|
||||||
{
|
{
|
||||||
sync_count += 1;
|
sync_count += 1;
|
||||||
if (sync_count < MIN_SYNC_INDEX) {
|
if (sync_count < MIN_SYNC_COUNT) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -407,23 +442,25 @@ void M17Demodulator::do_bert_sync()
|
||||||
update_values(sync_index);
|
update_values(sync_index);
|
||||||
sync_word_type = M17FrameDecoder::SyncWordType::BERT;
|
sync_word_type = M17FrameDecoder::SyncWordType::BERT;
|
||||||
INFO("b sync");
|
INFO("b sync");
|
||||||
demodState = DemodState::FRAME;
|
demodState = DemodState::SYNC_WAIT;
|
||||||
}
|
}
|
||||||
else if (sync_count > MAX_SYNC_INDEX)
|
else if (sync_count > MAX_SYNC_COUNT)
|
||||||
{
|
{
|
||||||
missing_sync_count += 1;
|
if (ber >= 0 && ber < STREAM_COST_LIMIT)
|
||||||
if (missing_sync_count < MAX_MISSING_SYNC)
|
|
||||||
{
|
|
||||||
sync_word_type = M17FrameDecoder::SyncWordType::BERT;
|
|
||||||
demodState = DemodState::FRAME;
|
|
||||||
INFO("b unsync");
|
|
||||||
}
|
|
||||||
else if (ber >= 0 && ber < 80)
|
|
||||||
{
|
{
|
||||||
|
// Sync word missed but we are still decoding a stream reasonably well.
|
||||||
|
if (!missing_sync_count) missing_sync_count = 1;
|
||||||
sync_word_type = M17FrameDecoder::SyncWordType::BERT;
|
sync_word_type = M17FrameDecoder::SyncWordType::BERT;
|
||||||
demodState = DemodState::FRAME;
|
demodState = DemodState::FRAME;
|
||||||
INFO("b bunsync");
|
INFO("b bunsync");
|
||||||
}
|
}
|
||||||
|
else if (missing_sync_count < MAX_MISSING_SYNC)
|
||||||
|
{
|
||||||
|
missing_sync_count += 1;
|
||||||
|
sync_word_type = M17FrameDecoder::SyncWordType::BERT;
|
||||||
|
demodState = DemodState::FRAME;
|
||||||
|
INFO("b unsync");
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
demodState = DemodState::UNLOCKED;
|
demodState = DemodState::UNLOCKED;
|
||||||
|
@ -433,24 +470,53 @@ void M17Demodulator::do_bert_sync()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wait for the sync_count to hit MAX_SYNC_COUNT. This is necessary for
|
||||||
|
* proper timing. Otherwise we can be off by 1 byte if the sync arrives
|
||||||
|
* a bit early. This may happen due to timing mismatch or noise causing
|
||||||
|
* the sync word correlator to trigger early.
|
||||||
|
*/
|
||||||
|
[[gnu::noinline]]
|
||||||
|
void M17Demodulator::do_sync_wait()
|
||||||
|
{
|
||||||
|
if (sync_count < MAX_SYNC_COUNT)
|
||||||
|
{
|
||||||
|
sync_count += 1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
need_clock_update_ = true;
|
||||||
|
demodState = DemodState::FRAME;
|
||||||
|
}
|
||||||
|
|
||||||
[[gnu::noinline]]
|
[[gnu::noinline]]
|
||||||
void M17Demodulator::do_frame(float filtered_sample, hdlc::IoFrame*& frame_result)
|
void M17Demodulator::do_frame(float filtered_sample, hdlc::IoFrame*& frame_result)
|
||||||
{
|
{
|
||||||
|
// Only do this when there is no chance of skipping a sample. So do
|
||||||
|
// this update as far from the sample point as possible. It should
|
||||||
|
// only ever change by +/- 1.
|
||||||
|
if (abs(int(sample_index - correlator.index())) == (SAMPLES_PER_SYMBOL / 2))
|
||||||
|
{
|
||||||
|
clock_recovery.update();
|
||||||
|
sample_index = clock_recovery.sample_index();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (correlator.index() != sample_index) return;
|
if (correlator.index() != sample_index) return;
|
||||||
|
|
||||||
float sample = filtered_sample - dev.offset();
|
float sample = dev.normalize(filtered_sample);
|
||||||
sample *= idev;
|
dev.update(sample);
|
||||||
|
|
||||||
auto n = mobilinkd::llr<float, 4>(sample);
|
auto n = mobilinkd::llr<float, 4>(sample);
|
||||||
int8_t* tmp;
|
int8_t* tmp;
|
||||||
auto len = framer(n, &tmp);
|
auto len = framer(n, &tmp);
|
||||||
if (len != 0)
|
if (len != 0)
|
||||||
{
|
{
|
||||||
need_clock_update_ = true;
|
sync_count = 0;
|
||||||
|
|
||||||
std::copy(tmp, tmp + len, buffer.begin());
|
std::copy(tmp, tmp + len, buffer.begin());
|
||||||
auto valid = decoder(sync_word_type, buffer, frame_result, ber);
|
auto valid = decoder(sync_word_type, buffer, frame_result, ber);
|
||||||
INFO("demod: %d, dt: %7d, evma: %5d, dev: %5d, freq: %5d, dcd: %d, index: %d, %d ber: %d",
|
INFO("demod: %d, dt: %4dppm, evma: %5d‰, dev: %5d, freq: %5d, dcd: %d, index: %d, %d ber: %d",
|
||||||
int(decoder.state()), int(clock_recovery.clock_estimate() * 1000000),
|
int(decoder.state()), int(clock_recovery.clock_estimate() * 1000000),
|
||||||
int(dev.error() * 1000), int(dev.deviation() * 1000),
|
int(dev.error() * 1000), int(dev.deviation() * 1000),
|
||||||
int(dev.offset() * 1000), int(dcd.level() * 1000),
|
int(dev.offset() * 1000), int(dcd.level() * 1000),
|
||||||
|
@ -474,8 +540,6 @@ void M17Demodulator::do_frame(float filtered_sample, hdlc::IoFrame*& frame_resul
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
sync_count = 0;
|
|
||||||
|
|
||||||
switch (valid)
|
switch (valid)
|
||||||
{
|
{
|
||||||
case M17FrameDecoder::DecodeResult::FAIL:
|
case M17FrameDecoder::DecodeResult::FAIL:
|
||||||
|
@ -486,10 +550,6 @@ void M17Demodulator::do_frame(float filtered_sample, hdlc::IoFrame*& frame_resul
|
||||||
frame_result = nullptr;
|
frame_result = nullptr;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case M17FrameDecoder::DecodeResult::EOS:
|
|
||||||
// demodState = DemodState::LSF_SYNC;
|
|
||||||
INFO("EOS");
|
|
||||||
break;
|
|
||||||
case M17FrameDecoder::DecodeResult::OK:
|
case M17FrameDecoder::DecodeResult::OK:
|
||||||
break;
|
break;
|
||||||
case M17FrameDecoder::DecodeResult::INCOMPLETE:
|
case M17FrameDecoder::DecodeResult::INCOMPLETE:
|
||||||
|
@ -549,25 +609,15 @@ hdlc::IoFrame* M17Demodulator::operator()(const q15_t* input)
|
||||||
}
|
}
|
||||||
else if (need_clock_update_) // must avoid update immediately after reset.
|
else if (need_clock_update_) // must avoid update immediately after reset.
|
||||||
{
|
{
|
||||||
if (!missing_sync_count) {
|
// We have a valid sync word. Update the filter.
|
||||||
// Have a real sync word match.
|
clock_recovery.update(sync_sample_index);
|
||||||
clock_recovery.update(sync_sample_index);
|
|
||||||
} else if (decoder.state() != M17FrameDecoder::State::LSF) {
|
|
||||||
// Not waiting on clock recovery.
|
|
||||||
clock_recovery.update();
|
|
||||||
}
|
|
||||||
sample_index = clock_recovery.sample_index();
|
|
||||||
need_clock_update_ = false;
|
need_clock_update_ = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Do this here, after the potential clock recovery reset above.
|
||||||
clock_recovery(filtered_sample);
|
clock_recovery(filtered_sample);
|
||||||
|
|
||||||
if (demodState != DemodState::UNLOCKED && correlator.index() == sample_index)
|
|
||||||
{
|
|
||||||
dev.sample(filtered_sample);
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (demodState)
|
switch (demodState)
|
||||||
{
|
{
|
||||||
case DemodState::UNLOCKED:
|
case DemodState::UNLOCKED:
|
||||||
|
@ -588,6 +638,9 @@ hdlc::IoFrame* M17Demodulator::operator()(const q15_t* input)
|
||||||
case DemodState::BERT_SYNC:
|
case DemodState::BERT_SYNC:
|
||||||
do_bert_sync();
|
do_bert_sync();
|
||||||
break;
|
break;
|
||||||
|
case DemodState::SYNC_WAIT:
|
||||||
|
do_sync_wait();
|
||||||
|
break;
|
||||||
case DemodState::FRAME:
|
case DemodState::FRAME:
|
||||||
do_frame(filtered_sample,frame_result);
|
do_frame(filtered_sample,frame_result);
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright 2020-2021 Rob Riggs <rob@mobilinkd.com>
|
// Copyright 2020-2022 Rob Riggs <rob@mobilinkd.com>
|
||||||
// All rights reserved.
|
// All rights reserved.
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
@ -44,14 +44,17 @@ struct M17Demodulator : IDemodulator
|
||||||
static constexpr float sample_rate = SAMPLE_RATE;
|
static constexpr float sample_rate = SAMPLE_RATE;
|
||||||
static constexpr float symbol_rate = SYMBOL_RATE;
|
static constexpr float symbol_rate = SYMBOL_RATE;
|
||||||
|
|
||||||
|
static constexpr int STREAM_COST_LIMIT = 80;
|
||||||
|
static constexpr int PACKET_COST_LIMIT = 60;
|
||||||
static constexpr uint8_t MAX_MISSING_SYNC = 10;
|
static constexpr uint8_t MAX_MISSING_SYNC = 10;
|
||||||
static constexpr uint8_t MIN_SYNC_INDEX = 79;
|
static constexpr uint8_t MIN_SYNC_COUNT = 78;
|
||||||
static constexpr uint8_t MAX_SYNC_INDEX = 88;
|
static constexpr uint8_t MAX_SYNC_COUNT = 87;
|
||||||
|
static constexpr float EOT_TRIGGER_LEVEL = 0.1;
|
||||||
|
|
||||||
using audio_filter_t = FirFilter<ADC_BLOCK_SIZE, m17::FILTER_TAP_NUM>;
|
using audio_filter_t = FirFilter<ADC_BLOCK_SIZE, m17::FILTER_TAP_NUM>;
|
||||||
using sync_word_t = m17::SyncWord<m17::Correlator>;
|
using sync_word_t = m17::SyncWord<m17::Correlator>;
|
||||||
|
|
||||||
enum class DemodState { UNLOCKED, LSF_SYNC, STREAM_SYNC, PACKET_SYNC, BERT_SYNC, FRAME };
|
enum class DemodState { UNLOCKED, LSF_SYNC, STREAM_SYNC, PACKET_SYNC, BERT_SYNC, SYNC_WAIT, FRAME };
|
||||||
|
|
||||||
audio_filter_t demod_filter;
|
audio_filter_t demod_filter;
|
||||||
std::array<float, ADC_BLOCK_SIZE> demod_buffer;
|
std::array<float, ADC_BLOCK_SIZE> demod_buffer;
|
||||||
|
@ -73,7 +76,6 @@ struct M17Demodulator : IDemodulator
|
||||||
DemodState demodState = DemodState::UNLOCKED;
|
DemodState demodState = DemodState::UNLOCKED;
|
||||||
M17FrameDecoder::SyncWordType sync_word_type = M17FrameDecoder::SyncWordType::LSF;
|
M17FrameDecoder::SyncWordType sync_word_type = M17FrameDecoder::SyncWordType::LSF;
|
||||||
uint8_t sample_index = 0;
|
uint8_t sample_index = 0;
|
||||||
float idev;
|
|
||||||
|
|
||||||
bool dcd_ = false;
|
bool dcd_ = false;
|
||||||
bool need_clock_reset_ = false;
|
bool need_clock_reset_ = false;
|
||||||
|
@ -100,6 +102,7 @@ struct M17Demodulator : IDemodulator
|
||||||
void do_packet_sync();
|
void do_packet_sync();
|
||||||
void do_stream_sync();
|
void do_stream_sync();
|
||||||
void do_bert_sync();
|
void do_bert_sync();
|
||||||
|
void do_sync_wait();
|
||||||
void do_frame(float filtered_sample, hdlc::IoFrame*& frame_result);
|
void do_frame(float filtered_sample, hdlc::IoFrame*& frame_result);
|
||||||
|
|
||||||
void stop() override
|
void stop() override
|
||||||
|
|
|
@ -161,7 +161,7 @@ struct M17FrameDecoder
|
||||||
|
|
||||||
enum class State {LSF, STREAM, BASIC_PACKET, FULL_PACKET, BERT};
|
enum class State {LSF, STREAM, BASIC_PACKET, FULL_PACKET, BERT};
|
||||||
enum class SyncWordType { LSF, STREAM, PACKET, BERT };
|
enum class SyncWordType { LSF, STREAM, PACKET, BERT };
|
||||||
enum class DecodeResult { FAIL, OK, EOS, INCOMPLETE };
|
enum class DecodeResult { FAIL, OK, INCOMPLETE };
|
||||||
|
|
||||||
State state_ = State::LSF;
|
State state_ = State::LSF;
|
||||||
|
|
||||||
|
@ -282,7 +282,6 @@ struct M17FrameDecoder
|
||||||
{
|
{
|
||||||
depuncture(buffer, tmp.lsf, P1);
|
depuncture(buffer, tmp.lsf, P1);
|
||||||
ber = viterbi_.decode(tmp.lsf, output.lsf);
|
ber = viterbi_.decode(tmp.lsf, output.lsf);
|
||||||
ber = ber > 60 ? ber - 60 : 0;
|
|
||||||
detail::to_bytes(output.lsf, current_lsf);
|
detail::to_bytes(output.lsf, current_lsf);
|
||||||
crc_.reset();
|
crc_.reset();
|
||||||
for (auto c : current_lsf) crc_(c);
|
for (auto c : current_lsf) crc_(c);
|
||||||
|
@ -442,15 +441,6 @@ struct M17FrameDecoder
|
||||||
if (ber < 128) stream->push_back(255 - ber * 2);
|
if (ber < 128) stream->push_back(255 - ber * 2);
|
||||||
else stream->push_back(0);
|
else stream->push_back(0);
|
||||||
|
|
||||||
#if 0 // Using EOT sync word now
|
|
||||||
if ((ber < 60) && (stream_segment[0] & 0x80))
|
|
||||||
{
|
|
||||||
INFO("EOS");
|
|
||||||
state_ = State::LSF;
|
|
||||||
result = DecodeResult::EOS;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Bogus CRC bytes to be dropped.
|
// Bogus CRC bytes to be dropped.
|
||||||
stream->push_back(0);
|
stream->push_back(0);
|
||||||
stream->push_back(0);
|
stream->push_back(0);
|
||||||
|
@ -616,17 +606,17 @@ struct M17FrameDecoder
|
||||||
* When in STREAM mode, the state machine can transition to either:
|
* When in STREAM mode, the state machine can transition to either:
|
||||||
*
|
*
|
||||||
* - STREAM when a any stream frame is received.
|
* - STREAM when a any stream frame is received.
|
||||||
* - LSF when the EOS indicator is set, or when a packet frame is received.
|
* - LSF when reset().
|
||||||
*
|
*
|
||||||
* When in BASIC_PACKET mode, the state machine can transition to either:
|
* When in BASIC_PACKET mode, the state machine can transition to either:
|
||||||
*
|
*
|
||||||
* - BASIC_PACKET when any packet frame is received.
|
* - BASIC_PACKET when any packet frame is received.
|
||||||
* - LSF when the EOS indicator is set, or when a stream frame is received.
|
* - LSF when a complete paket superframe is received.
|
||||||
*
|
*
|
||||||
* When in FULL_PACKET mode, the state machine can transition to either:
|
* When in FULL_PACKET mode, the state machine can transition to either:
|
||||||
*
|
*
|
||||||
* - FULL_PACKET when any packet frame is received.
|
* - FULL_PACKET when any packet frame is received.
|
||||||
* - LSF when the EOS indicator is set, or when a stream frame is received.
|
* - LSF when a complete packet superframe is received.
|
||||||
*/
|
*/
|
||||||
[[gnu::noinline]]
|
[[gnu::noinline]]
|
||||||
DecodeResult operator()(SyncWordType frame_type, buffer_t& buffer,
|
DecodeResult operator()(SyncWordType frame_type, buffer_t& buffer,
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright 2020 Mobilinkd LLC <rob@mobilinkd.com>
|
// Copyright 2020-2022 Mobilinkd LLC <rob@mobilinkd.com>
|
||||||
// All rights reserved.
|
// All rights reserved.
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
@ -56,4 +56,32 @@ struct StandardDeviation
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template <typename FloatType, size_t N>
|
||||||
|
struct RunningStandardDeviation
|
||||||
|
{
|
||||||
|
FloatType S{1.0};
|
||||||
|
FloatType alpha{1.0 / N};
|
||||||
|
|
||||||
|
void reset()
|
||||||
|
{
|
||||||
|
S = 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void capture(float sample)
|
||||||
|
{
|
||||||
|
S -= S * alpha;
|
||||||
|
S += (sample * sample) * alpha;
|
||||||
|
}
|
||||||
|
|
||||||
|
FloatType variance() const
|
||||||
|
{
|
||||||
|
return S;
|
||||||
|
}
|
||||||
|
|
||||||
|
FloatType stdev() const
|
||||||
|
{
|
||||||
|
return std::sqrt(variance());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
} // mobilinkd
|
} // mobilinkd
|
||||||
|
|
Ładowanie…
Reference in New Issue