kopia lustrzana https://github.com/mobilinkd/tnc3-firmware
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
2e923c212e
commit
c0166f0674
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2021 Mobilinkd LLC.
|
||||
// Copyright 2021-2022 Mobilinkd LLC.
|
||||
|
||||
#pragma once
|
||||
|
||||
|
@ -41,10 +41,8 @@ struct ClockRecovery
|
|||
|
||||
bool update(uint8_t sw)
|
||||
{
|
||||
INFO("CR Update %d", int(sw));
|
||||
if (count_ < 480)
|
||||
if (count_ < 8)
|
||||
{
|
||||
sample_index_ = sw;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -70,7 +68,6 @@ struct ClockRecovery
|
|||
*/
|
||||
bool update()
|
||||
{
|
||||
INFO("CR Update");
|
||||
auto csw = std::fmod((sample_estimate_ + clock_estimate_ * count_), SamplesPerSymbol);
|
||||
if (csw < 0.) 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "IirFilter.hpp"
|
||||
#include "KalmanFilter.h"
|
||||
#include "StandardDeviation.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <cstddef>
|
||||
|
||||
namespace mobilinkd { namespace m17 {
|
||||
|
||||
/**
|
||||
* 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>
|
||||
template <typename FloatType, size_t SYNC_WORD_LEN = 8>
|
||||
class FreqDevEstimator
|
||||
{
|
||||
using sample_filter_t = tnc::IirFilter<3>;
|
||||
static constexpr FloatType DEVIATION = 1.;
|
||||
|
||||
// IIR with Nyquist of 1/4.
|
||||
static constexpr std::array<float, 3> dc_b = { 0.09763107, 0.19526215, 0.09763107 };
|
||||
static constexpr std::array<float, 3> dc_a = { 1. , -0.94280904, 0.33333333 };
|
||||
|
||||
static constexpr FloatType MAX_DC_ERROR = 0.2;
|
||||
|
||||
FloatType min_est_ = 0.0;
|
||||
FloatType max_est_ = 0.0;
|
||||
FloatType min_cutoff_ = 0.0;
|
||||
FloatType max_cutoff_ = 0.0;
|
||||
FloatType min_var_ = 0.0;
|
||||
FloatType max_var_ = 0.0;
|
||||
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};
|
||||
SymbolKalmanFilter<FloatType> minFilter_;
|
||||
SymbolKalmanFilter<FloatType> maxFilter_;
|
||||
RunningStandardDeviation<FloatType, 184> stddev_;
|
||||
FloatType idev_ = 1.;
|
||||
FloatType offset_ = 0.;
|
||||
uint8_t count_ = 0;
|
||||
FloatType min_ = 0.;
|
||||
FloatType max_ = 0.;
|
||||
uint8_t minCount_ = 0;
|
||||
uint8_t maxCount_ = 0;
|
||||
size_t updateCount_ = 0;
|
||||
bool reset_ = true;
|
||||
|
||||
public:
|
||||
|
||||
void reset()
|
||||
{
|
||||
min_est_ = 0.0;
|
||||
max_est_ = 0.0;
|
||||
min_var_ = 0.0;
|
||||
max_var_ = 0.0;
|
||||
min_count_ = 1;
|
||||
max_count_ = 1;
|
||||
min_cutoff_ = 0.0;
|
||||
max_cutoff_ = 0.0;
|
||||
}
|
||||
void reset()
|
||||
{
|
||||
idev_ = 1.;
|
||||
offset_ = 0.;
|
||||
count_ = 0;
|
||||
min_ = 0.;
|
||||
max_ = 0.;
|
||||
minCount_ = 0;
|
||||
maxCount_ = 0;
|
||||
updateCount_ = 0;
|
||||
stddev_.reset();
|
||||
reset_ = true;
|
||||
}
|
||||
|
||||
void sample(FloatType sample)
|
||||
{
|
||||
if (sample < 1.5 * min_est_)
|
||||
{
|
||||
min_count_ = 1;
|
||||
min_est_ = sample;
|
||||
min_var_ = 0.0;
|
||||
min_cutoff_ = min_est_ * 0.666666;
|
||||
}
|
||||
else if (sample < min_cutoff_)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* This function takes the index samples from the correlator to build
|
||||
* the outer symbol samples for the frequency offset (signal DC offset)
|
||||
* and the deviation (signal magnitude). It expects bursts of 8 samples,
|
||||
* one for each symbol in a sync word.
|
||||
*
|
||||
* @param sample
|
||||
*/
|
||||
void sample(FloatType sample)
|
||||
{
|
||||
count_ += 1;
|
||||
|
||||
/**
|
||||
* Update the estimates for deviation, offset, and EVM (error). Note
|
||||
* that the estimates for error are using a sloppy implementation for
|
||||
* calculating variance to reduce the memory requirements. This is
|
||||
* because this is designed for embedded use.
|
||||
*/
|
||||
void update()
|
||||
{
|
||||
if (max_count_ < 2 || min_count_ < 2) return;
|
||||
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;
|
||||
}
|
||||
if (sample < 0)
|
||||
{
|
||||
minCount_ += 1;
|
||||
min_ += sample;
|
||||
}
|
||||
else
|
||||
{
|
||||
maxCount_ += 1;
|
||||
max_ += sample;
|
||||
}
|
||||
|
||||
FloatType deviation() const { return deviation_; }
|
||||
FloatType offset() const { return offset_; }
|
||||
FloatType error() const { return error_; }
|
||||
FloatType idev() const { return idev_; }
|
||||
if (count_ == SYNC_WORD_LEN)
|
||||
{
|
||||
auto minAvg = min_ / minCount_;
|
||||
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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2018-2021 Rob Riggs <rob@mobilinkd.com>
|
||||
// Copyright 2018-2022 Rob Riggs <rob@mobilinkd.com>
|
||||
// All rights reserved.
|
||||
|
||||
#include "KissHardware.hpp"
|
||||
|
@ -43,13 +43,13 @@ int powerOffViaUSB(void)
|
|||
namespace mobilinkd { namespace tnc { namespace kiss {
|
||||
|
||||
#if defined(NUCLEOTNC)
|
||||
const char FIRMWARE_VERSION[] = "2.4.3";
|
||||
const char FIRMWARE_VERSION[] = "2.4.4";
|
||||
const char HARDWARE_VERSION[] = "Mobilinkd NucleoTNC";
|
||||
#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";
|
||||
#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";
|
||||
#endif
|
||||
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.
|
||||
|
||||
#pragma GCC diagnostic push
|
||||
|
@ -23,7 +23,7 @@ namespace mobilinkd { namespace tnc {
|
|||
//m17::Indicator dcd_indicator{GPIOA, GPIO_PIN_2};
|
||||
//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)
|
||||
{
|
||||
|
@ -51,8 +51,8 @@ void M17Demodulator::start()
|
|||
demod_filter.init(m17::rrc_taps_f);
|
||||
passall(kiss::settings().options & KISS_OPTION_PASSALL);
|
||||
polarity = kiss::settings().rx_rev_polarity() ? -1 : 1;
|
||||
scale = 1.f / 2560.f * polarity;
|
||||
// audio::virtual_ground = (VREF + 1) / 2;
|
||||
scale = 1.f / 32768.f * polarity;
|
||||
audio::virtual_ground = (VREF + 1) / 2;
|
||||
|
||||
hadc1.Init.OversamplingMode = ENABLE;
|
||||
if (HAL_ADC_Init(&hadc1) != HAL_OK)
|
||||
|
@ -76,11 +76,26 @@ void M17Demodulator::start()
|
|||
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)
|
||||
{
|
||||
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();
|
||||
idev = dev.idev();
|
||||
sync_sample_index = index;
|
||||
}
|
||||
|
||||
|
@ -157,8 +172,8 @@ void M17Demodulator::do_unlocked()
|
|||
missing_sync_count = 0;
|
||||
need_clock_reset_ = true;
|
||||
dev.reset();
|
||||
update_values(sync_index);
|
||||
sample_index = sync_index;
|
||||
update_values(sync_index);
|
||||
demodState = DemodState::LSF_SYNC;
|
||||
INFO("P sync %d", sync_index);
|
||||
}
|
||||
|
@ -169,12 +184,12 @@ void M17Demodulator::do_unlocked()
|
|||
auto sync_updated = lsf_sync.updated();
|
||||
if (sync_updated)
|
||||
{
|
||||
sync_count = 0;
|
||||
sync_count = MAX_SYNC_COUNT;
|
||||
missing_sync_count = 0;
|
||||
need_clock_reset_ = true;
|
||||
dev.reset();
|
||||
update_values(sync_index);
|
||||
sample_index = sync_index;
|
||||
update_values(sync_index);
|
||||
demodState = DemodState::FRAME;
|
||||
if (sync_updated < 0)
|
||||
{
|
||||
|
@ -191,12 +206,12 @@ void M17Demodulator::do_unlocked()
|
|||
sync_updated = packet_sync.updated();
|
||||
if (sync_updated < 0)
|
||||
{
|
||||
sync_count = 0;
|
||||
sync_count = MAX_SYNC_COUNT;
|
||||
missing_sync_count = 0;
|
||||
need_clock_reset_ = true;
|
||||
dev.reset();
|
||||
update_values(sync_index);
|
||||
sample_index = sync_index;
|
||||
update_values(sync_index);
|
||||
demodState = DemodState::FRAME;
|
||||
sync_word_type = M17FrameDecoder::SyncWordType::BERT;
|
||||
INFO("B sync %d", int(sync_index));
|
||||
|
@ -220,6 +235,7 @@ void M17Demodulator::do_lsf_sync()
|
|||
// INFO("PSync = %d", int(sync_triggered));
|
||||
if (sync_triggered > 0.1)
|
||||
{
|
||||
need_clock_update_ = true;
|
||||
sync_count += 1;
|
||||
return;
|
||||
}
|
||||
|
@ -228,6 +244,7 @@ void M17Demodulator::do_lsf_sync()
|
|||
if (bert_triggered < 0)
|
||||
{
|
||||
missing_sync_count = 0;
|
||||
sync_count = MAX_SYNC_COUNT;
|
||||
need_clock_update_ = true;
|
||||
update_values(sample_index);
|
||||
demodState = DemodState::FRAME;
|
||||
|
@ -237,6 +254,7 @@ void M17Demodulator::do_lsf_sync()
|
|||
else if (std::abs(sync_triggered) > 0.1)
|
||||
{
|
||||
missing_sync_count = 0;
|
||||
sync_count = MAX_SYNC_COUNT;
|
||||
need_clock_update_ = true;
|
||||
update_values(sample_index);
|
||||
INFO("LSync = %d", int(sync_triggered));
|
||||
|
@ -256,15 +274,17 @@ void M17Demodulator::do_lsf_sync()
|
|||
else if (++missing_sync_count > 192)
|
||||
{
|
||||
if (sync_count >= 10) {
|
||||
// Long preamble. Update clock and continue waiting for LSF.
|
||||
missing_sync_count = 0;
|
||||
need_clock_update_ = true;
|
||||
INFO("long preamble");
|
||||
} else {
|
||||
// No sync word. Recycle.
|
||||
sync_count = 0;
|
||||
demodState = DemodState::UNLOCKED;
|
||||
missing_sync_count = 0;
|
||||
dcd.unlock();
|
||||
INFO("l unlock %d", int(missing_sync_count));
|
||||
missing_sync_count = 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
|
@ -290,10 +310,14 @@ void M17Demodulator::do_stream_sync()
|
|||
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;
|
||||
demodState = DemodState::FRAME;
|
||||
eot_flag = true;
|
||||
missing_sync_count = 0;
|
||||
INFO("EOT");
|
||||
return;
|
||||
}
|
||||
|
@ -311,22 +335,25 @@ void M17Demodulator::do_stream_sync()
|
|||
}
|
||||
else if (sync_count > MAX_SYNC_COUNT)
|
||||
{
|
||||
// update_values(sync_index);
|
||||
if (ber >= 0 && ber < 80)
|
||||
if (ber >= 0 && ber < STREAM_COST_LIMIT)
|
||||
{
|
||||
// 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;
|
||||
demodState = DemodState::FRAME;
|
||||
if (!missing_sync_count) missing_sync_count = 1;
|
||||
INFO("s bunsync");
|
||||
}
|
||||
else if (eot_flag) {
|
||||
// EOT flag set, missing sync, and very high BER. Stream has ended.
|
||||
demodState = DemodState::UNLOCKED;
|
||||
dcd.unlock();
|
||||
INFO("s eot");
|
||||
}
|
||||
else if (missing_sync_count < MAX_MISSING_SYNC)
|
||||
{
|
||||
// Sync word missed, very high error rate. Still trying to decode.
|
||||
missing_sync_count += 1;
|
||||
sync_word_type = M17FrameDecoder::SyncWordType::STREAM;
|
||||
demodState = DemodState::FRAME;
|
||||
|
@ -334,6 +361,7 @@ void M17Demodulator::do_stream_sync()
|
|||
}
|
||||
else
|
||||
{
|
||||
// No EOT, but too many sync words missed. Recycle.
|
||||
demodState = DemodState::UNLOCKED;
|
||||
dcd.unlock();
|
||||
INFO("s unlock");
|
||||
|
@ -364,25 +392,29 @@ void M17Demodulator::do_packet_sync()
|
|||
sync_word_type = M17FrameDecoder::SyncWordType::PACKET;
|
||||
demodState = DemodState::SYNC_WAIT;
|
||||
INFO("k sync");
|
||||
return;
|
||||
}
|
||||
else if (sync_count > MAX_SYNC_COUNT)
|
||||
{
|
||||
missing_sync_count += 1;
|
||||
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)
|
||||
if (ber >= 0 && ber < PACKET_COST_LIMIT)
|
||||
{
|
||||
// Sync word missed but we are still decoding reasonably well.
|
||||
if (!missing_sync_count) missing_sync_count = 1;
|
||||
sync_word_type = M17FrameDecoder::SyncWordType::PACKET;
|
||||
demodState = DemodState::FRAME;
|
||||
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
|
||||
{
|
||||
// Too many sync words missed. Recycle.
|
||||
demodState = DemodState::UNLOCKED;
|
||||
dcd.unlock();
|
||||
INFO("k unlock");
|
||||
|
@ -414,19 +446,21 @@ void M17Demodulator::do_bert_sync()
|
|||
}
|
||||
else if (sync_count > MAX_SYNC_COUNT)
|
||||
{
|
||||
missing_sync_count += 1;
|
||||
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)
|
||||
if (ber >= 0 && ber < STREAM_COST_LIMIT)
|
||||
{
|
||||
// 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;
|
||||
demodState = DemodState::FRAME;
|
||||
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
|
||||
{
|
||||
demodState = DemodState::UNLOCKED;
|
||||
|
@ -436,6 +470,12 @@ 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()
|
||||
{
|
||||
|
@ -464,17 +504,19 @@ void M17Demodulator::do_frame(float filtered_sample, hdlc::IoFrame*& frame_resul
|
|||
|
||||
if (correlator.index() != sample_index) return;
|
||||
|
||||
float sample = filtered_sample - dev.offset();
|
||||
sample *= idev;
|
||||
float sample = dev.normalize(filtered_sample);
|
||||
dev.update(sample);
|
||||
|
||||
auto n = mobilinkd::llr<float, 4>(sample);
|
||||
int8_t* tmp;
|
||||
auto len = framer(n, &tmp);
|
||||
if (len != 0)
|
||||
{
|
||||
sync_count = 0;
|
||||
|
||||
std::copy(tmp, tmp + len, buffer.begin());
|
||||
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(dev.error() * 1000), int(dev.deviation() * 1000),
|
||||
int(dev.offset() * 1000), int(dcd.level() * 1000),
|
||||
|
@ -498,8 +540,6 @@ void M17Demodulator::do_frame(float filtered_sample, hdlc::IoFrame*& frame_resul
|
|||
break;
|
||||
}
|
||||
|
||||
sync_count = 0;
|
||||
|
||||
switch (valid)
|
||||
{
|
||||
case M17FrameDecoder::DecodeResult::FAIL:
|
||||
|
@ -510,10 +550,6 @@ void M17Demodulator::do_frame(float filtered_sample, hdlc::IoFrame*& frame_resul
|
|||
frame_result = nullptr;
|
||||
}
|
||||
break;
|
||||
case M17FrameDecoder::DecodeResult::EOS:
|
||||
// demodState = DemodState::LSF_SYNC;
|
||||
INFO("EOS");
|
||||
break;
|
||||
case M17FrameDecoder::DecodeResult::OK:
|
||||
break;
|
||||
case M17FrameDecoder::DecodeResult::INCOMPLETE:
|
||||
|
@ -573,13 +609,8 @@ hdlc::IoFrame* M17Demodulator::operator()(const q15_t* input)
|
|||
}
|
||||
else if (need_clock_update_) // must avoid update immediately after reset.
|
||||
{
|
||||
if (!missing_sync_count) {
|
||||
// Have a real sync word match.
|
||||
clock_recovery.update(sync_sample_index);
|
||||
} else if (decoder.state() != M17FrameDecoder::State::LSF) {
|
||||
// Not waiting on clock recovery.
|
||||
clock_recovery.update();
|
||||
}
|
||||
// We have a valid sync word. Update the filter.
|
||||
clock_recovery.update(sync_sample_index);
|
||||
need_clock_update_ = false;
|
||||
}
|
||||
}
|
||||
|
@ -587,11 +618,6 @@ hdlc::IoFrame* M17Demodulator::operator()(const q15_t* input)
|
|||
// Do this here, after the potential clock recovery reset above.
|
||||
clock_recovery(filtered_sample);
|
||||
|
||||
if (demodState != DemodState::UNLOCKED && correlator.index() == sample_index)
|
||||
{
|
||||
dev.sample(filtered_sample);
|
||||
}
|
||||
|
||||
switch (demodState)
|
||||
{
|
||||
case DemodState::UNLOCKED:
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2020-2021 Rob Riggs <rob@mobilinkd.com>
|
||||
// Copyright 2020-2022 Rob Riggs <rob@mobilinkd.com>
|
||||
// All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
@ -44,6 +44,8 @@ struct M17Demodulator : IDemodulator
|
|||
static constexpr float sample_rate = SAMPLE_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 MIN_SYNC_COUNT = 78;
|
||||
static constexpr uint8_t MAX_SYNC_COUNT = 87;
|
||||
|
@ -74,7 +76,6 @@ struct M17Demodulator : IDemodulator
|
|||
DemodState demodState = DemodState::UNLOCKED;
|
||||
M17FrameDecoder::SyncWordType sync_word_type = M17FrameDecoder::SyncWordType::LSF;
|
||||
uint8_t sample_index = 0;
|
||||
float idev;
|
||||
|
||||
bool dcd_ = false;
|
||||
bool need_clock_reset_ = false;
|
||||
|
|
|
@ -161,7 +161,7 @@ struct M17FrameDecoder
|
|||
|
||||
enum class State {LSF, STREAM, BASIC_PACKET, FULL_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;
|
||||
|
||||
|
@ -282,7 +282,6 @@ struct M17FrameDecoder
|
|||
{
|
||||
depuncture(buffer, tmp.lsf, P1);
|
||||
ber = viterbi_.decode(tmp.lsf, output.lsf);
|
||||
ber = ber > 60 ? ber - 60 : 0;
|
||||
detail::to_bytes(output.lsf, current_lsf);
|
||||
crc_.reset();
|
||||
for (auto c : current_lsf) crc_(c);
|
||||
|
@ -442,15 +441,6 @@ struct M17FrameDecoder
|
|||
if (ber < 128) stream->push_back(255 - ber * 2);
|
||||
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.
|
||||
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:
|
||||
*
|
||||
* - 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:
|
||||
*
|
||||
* - 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:
|
||||
*
|
||||
* - 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]]
|
||||
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.
|
||||
|
||||
#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
|
||||
|
|
Ładowanie…
Reference in New Issue