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.

master v2.4.4
Rob Riggs 2022-05-29 17:09:09 -05:00
rodzic a85cbf0baa
commit 9e6f588d30
8 zmienionych plików z 340 dodań i 221 usunięć

Wyświetl plik

@ -1,4 +1,4 @@
// Copyright 2021 Mobilinkd LLC.
// Copyright 2021-2022 Mobilinkd LLC.
#pragma once
@ -19,8 +19,6 @@ namespace mobilinkd { namespace m17 {
template <typename FloatType = float, size_t SamplesPerSymbol = 10>
struct ClockRecovery
{
static constexpr FloatType MAX_CLOCK_OFFSET = 0.0005; // 500ppm
KalmanFilter<FloatType, SamplesPerSymbol> kf_;
size_t count_ = 0;
int8_t sample_index_ = 0;
@ -41,17 +39,10 @@ struct ClockRecovery
++count_;
}
static FloatType clock_limiter(FloatType offset)
{
return std::min(MAX_CLOCK_OFFSET, std::max(-MAX_CLOCK_OFFSET, offset));
}
bool update(uint8_t sw)
{
INFO("CR Update %d", int(sw));
if (count_ < 480)
if (count_ < 8)
{
sample_index_ = sw;
return false;
}
@ -77,8 +68,7 @@ struct ClockRecovery
*/
bool update()
{
INFO("CR Update");
auto csw = std::fmod((sample_estimate_ + (1.0 + clock_estimate_) * count_), SamplesPerSymbol);
auto csw = std::fmod((sample_estimate_ + clock_estimate_ * count_), SamplesPerSymbol);
if (csw < 0.) csw += SamplesPerSymbol;
else if (csw >= SamplesPerSymbol) csw -= SamplesPerSymbol;

Wyświetl plik

@ -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

Wyświetl plik

@ -63,10 +63,10 @@ struct KalmanFilter
auto K = P * blaze::trans(H) * (1.0 / S(0, 0));
// Normalize incoming index
if (z - x[0] < (SamplesPerSymbol / -2.0))
if (z - x[0] < (SamplesPerSymbol / -2.0)) // wrapped forwards 9 -> 0
z += SamplesPerSymbol;
else if (z - x[0] > (SamplesPerSymbol / 2.0))
z-= SamplesPerSymbol;
else if (z - x[0] > (SamplesPerSymbol / 2.0)) // wrapped 0 -> 9
z -= SamplesPerSymbol;
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

Wyświetl plik

@ -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()

Wyświetl plik

@ -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;
}
@ -89,20 +104,23 @@ void M17Demodulator::dcd_on()
// Data carrier newly detected.
INFO("dcd = %d", int(dcd.level() * 1000));
dcd_ = true;
sync_count = 0;
dev.reset();
framer.reset();
decoder.reset();
missing_sync_count = 0;
if (demodState == DemodState::UNLOCKED)
{
sync_count = 0;
missing_sync_count = 0;
dev.reset();
framer.reset();
decoder.reset();
}
}
void M17Demodulator::dcd_off()
{
// Just lost data carrier.
INFO("dcd = %d", int(dcd.level() * 1000));
dcd_ = false;
demodState = DemodState::UNLOCKED;
adc_timing_adjust = 0;
dcd_ = false;
}
void M17Demodulator::initialize(const q15_t* input)
@ -154,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);
}
@ -166,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)
{
@ -188,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));
@ -217,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;
}
@ -225,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;
@ -234,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));
@ -253,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
@ -283,14 +306,18 @@ void M17Demodulator::do_stream_sync()
static bool eot_flag = false;
sync_count += 1;
if (sync_count < MIN_SYNC_INDEX) {
if (sync_count < MIN_SYNC_COUNT) {
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;
}
@ -302,28 +329,31 @@ void M17Demodulator::do_stream_sync()
missing_sync_count = 0;
update_values(sync_index);
sync_word_type = M17FrameDecoder::SyncWordType::STREAM;
demodState = DemodState::FRAME;
demodState = DemodState::SYNC_WAIT;
INFO("s sync %d", int(sync_count));
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 < 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;
@ -331,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");
@ -347,7 +378,7 @@ void M17Demodulator::do_stream_sync()
void M17Demodulator::do_packet_sync()
{
sync_count += 1;
if (sync_count < MIN_SYNC_INDEX) {
if (sync_count < MIN_SYNC_COUNT) {
return;
}
@ -359,27 +390,31 @@ void M17Demodulator::do_packet_sync()
missing_sync_count = 0;
update_values(sync_index);
sync_word_type = M17FrameDecoder::SyncWordType::PACKET;
demodState = DemodState::FRAME;
demodState = DemodState::SYNC_WAIT;
INFO("k sync");
return;
}
else if (sync_count > MAX_SYNC_INDEX)
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");
@ -394,7 +429,7 @@ void M17Demodulator::do_packet_sync()
void M17Demodulator::do_bert_sync()
{
sync_count += 1;
if (sync_count < MIN_SYNC_INDEX) {
if (sync_count < MIN_SYNC_COUNT) {
return;
}
@ -407,23 +442,25 @@ void M17Demodulator::do_bert_sync()
update_values(sync_index);
sync_word_type = M17FrameDecoder::SyncWordType::BERT;
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 (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;
@ -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]]
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;
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)
{
need_clock_update_ = true;
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),
@ -474,8 +540,6 @@ void M17Demodulator::do_frame(float filtered_sample, hdlc::IoFrame*& frame_resul
break;
}
sync_count = 0;
switch (valid)
{
case M17FrameDecoder::DecodeResult::FAIL:
@ -486,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:
@ -549,25 +609,15 @@ 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();
}
sample_index = clock_recovery.sample_index();
// We have a valid sync word. Update the filter.
clock_recovery.update(sync_sample_index);
need_clock_update_ = false;
}
}
// 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:
@ -588,6 +638,9 @@ hdlc::IoFrame* M17Demodulator::operator()(const q15_t* input)
case DemodState::BERT_SYNC:
do_bert_sync();
break;
case DemodState::SYNC_WAIT:
do_sync_wait();
break;
case DemodState::FRAME:
do_frame(filtered_sample,frame_result);
break;

Wyświetl plik

@ -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,14 +44,17 @@ 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_INDEX = 79;
static constexpr uint8_t MAX_SYNC_INDEX = 88;
static constexpr uint8_t MIN_SYNC_COUNT = 78;
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 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;
std::array<float, ADC_BLOCK_SIZE> demod_buffer;
@ -73,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;
@ -100,6 +102,7 @@ struct M17Demodulator : IDemodulator
void do_packet_sync();
void do_stream_sync();
void do_bert_sync();
void do_sync_wait();
void do_frame(float filtered_sample, hdlc::IoFrame*& frame_result);
void stop() override

Wyświetl plik

@ -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,

Wyświetl plik

@ -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