2022-07-20 03:07:00 -04:00
|
|
|
// Copyright 2021 Rob Riggs <rob@mobilinkd.com>
|
2022-06-06 21:22:18 -04:00
|
|
|
// All rights reserved.
|
|
|
|
|
|
|
|
#pragma once
|
|
|
|
|
|
|
|
#include "IirFilter.h"
|
|
|
|
|
|
|
|
#include <algorithm>
|
|
|
|
#include <array>
|
|
|
|
#include <cmath>
|
|
|
|
#include <cstddef>
|
|
|
|
|
2022-06-10 13:17:56 -04:00
|
|
|
#include "export.h"
|
|
|
|
|
2022-07-04 17:03:07 -04:00
|
|
|
namespace modemm17 {
|
2022-06-06 21:22:18 -04:00
|
|
|
|
|
|
|
/**
|
|
|
|
* 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.
|
|
|
|
*/
|
2022-07-19 23:47:48 -04:00
|
|
|
class MODEMM17_API FreqDevEstimator
|
2022-06-06 21:22:18 -04:00
|
|
|
{
|
|
|
|
public:
|
2022-07-27 17:16:22 -04:00
|
|
|
using sample_filter_t = BaseIirFilter<3>;
|
2022-06-06 21:22:18 -04:00
|
|
|
|
|
|
|
void reset()
|
|
|
|
{
|
2022-07-27 17:16:22 -04:00
|
|
|
min_est_ = 0.0f;
|
|
|
|
max_est_ = 0.0f;
|
|
|
|
min_var_ = 0.0f;
|
|
|
|
max_var_ = 0.0f;
|
2022-06-06 21:22:18 -04:00
|
|
|
min_count_ = 0;
|
|
|
|
max_count_ = 0;
|
2022-07-27 17:16:22 -04:00
|
|
|
min_cutoff_ = 0.0f;
|
|
|
|
max_cutoff_ = 0.0f;
|
2022-06-06 21:22:18 -04:00
|
|
|
}
|
|
|
|
|
2022-06-09 14:12:35 -04:00
|
|
|
void sample(float sample)
|
2022-06-06 21:22:18 -04:00
|
|
|
{
|
|
|
|
if (sample < 1.5 * min_est_)
|
|
|
|
{
|
|
|
|
min_count_ = 1;
|
|
|
|
min_est_ = sample;
|
2022-07-27 17:16:22 -04:00
|
|
|
min_var_ = 0.0f;
|
|
|
|
min_cutoff_ = min_est_ * 0.666666f;
|
2022-06-06 21:22:18 -04:00
|
|
|
}
|
|
|
|
else if (sample < min_cutoff_)
|
|
|
|
{
|
|
|
|
min_count_ += 1;
|
|
|
|
min_est_ += sample;
|
2022-06-09 14:12:35 -04:00
|
|
|
float var = (min_est_ / min_count_) - sample;
|
2022-06-06 21:22:18 -04:00
|
|
|
min_var_ += var * var;
|
|
|
|
}
|
2022-07-27 17:16:22 -04:00
|
|
|
else if (sample > 1.5f * max_est_)
|
2022-06-06 21:22:18 -04:00
|
|
|
{
|
|
|
|
max_count_ = 1;
|
|
|
|
max_est_ = sample;
|
2022-07-27 17:16:22 -04:00
|
|
|
max_var_ = 0.0f;
|
|
|
|
max_cutoff_ = max_est_ * 0.666666f;
|
2022-06-06 21:22:18 -04:00
|
|
|
}
|
|
|
|
else if (sample > max_cutoff_)
|
|
|
|
{
|
|
|
|
max_count_ += 1;
|
|
|
|
max_est_ += sample;
|
2022-06-09 14:12:35 -04:00
|
|
|
float var = (max_est_ / max_count_) - sample;
|
2022-06-06 21:22:18 -04:00
|
|
|
max_var_ += var * var;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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()
|
|
|
|
{
|
2022-07-27 17:16:22 -04:00
|
|
|
if (max_count_ < 2 || min_count_ < 2) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-06-09 14:12:35 -04:00
|
|
|
float max_ = max_est_ / max_count_;
|
|
|
|
float min_ = min_est_ / min_count_;
|
2022-06-06 21:22:18 -04:00
|
|
|
deviation_ = (max_ - min_) / 6.0;
|
|
|
|
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;
|
2022-07-27 17:16:22 -04:00
|
|
|
|
|
|
|
if (deviation_ > 0) {
|
|
|
|
idev_ = 1.0 / deviation_;
|
|
|
|
}
|
|
|
|
|
|
|
|
min_cutoff_ = offset_ - deviation_ * 2;
|
2022-06-06 21:22:18 -04:00
|
|
|
max_cutoff_ = offset_ + deviation_ * 2;
|
|
|
|
max_est_ = max_;
|
|
|
|
min_est_ = min_;
|
|
|
|
max_count_ = 1;
|
|
|
|
min_count_ = 1;
|
2022-07-27 17:16:22 -04:00
|
|
|
max_var_ = 0.0f;
|
|
|
|
min_var_ = 0.0f;
|
2022-06-06 21:22:18 -04:00
|
|
|
}
|
|
|
|
|
2022-06-09 14:12:35 -04:00
|
|
|
float deviation() const { return deviation_; }
|
|
|
|
float offset() const { return offset_; }
|
|
|
|
float error() const { return error_; }
|
|
|
|
float idev() const { return idev_; }
|
2022-07-27 17:16:22 -04:00
|
|
|
|
|
|
|
private:
|
|
|
|
// IIR with Nyquist of 1/4.
|
|
|
|
static const std::array<float, 3> dc_b;
|
|
|
|
static const std::array<float, 3> dc_a;
|
|
|
|
static const float MAX_DC_ERROR;
|
|
|
|
|
|
|
|
float min_est_ = 0.0f;
|
|
|
|
float max_est_ = 0.0f;
|
|
|
|
float min_cutoff_ = 0.0f;
|
|
|
|
float max_cutoff_ = 0.0f;
|
|
|
|
float min_var_ = 0.0f;
|
|
|
|
float max_var_ = 0.0f;
|
|
|
|
size_t min_count_ = 0;
|
|
|
|
size_t max_count_ = 0;
|
|
|
|
float deviation_ = 0.0f;
|
|
|
|
float offset_ = 0.0f;
|
|
|
|
float error_ = 0.0f;
|
|
|
|
float idev_ = 1.0f;
|
|
|
|
sample_filter_t dc_filter_{dc_b, dc_a};
|
2022-06-06 21:22:18 -04:00
|
|
|
};
|
|
|
|
|
2022-07-04 17:03:07 -04:00
|
|
|
} // modemm17
|