kopia lustrzana https://github.com/mobilinkd/tnc3-firmware
Remove now-unused M17 components.
rodzic
8d1d34142b
commit
393e0acd83
|
@ -1,40 +0,0 @@
|
|||
// Copyright 2020 Mobilinkd LLC.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "IirFilter.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <algorithm>
|
||||
#include <numeric>
|
||||
#include <cmath>
|
||||
|
||||
namespace mobilinkd
|
||||
{
|
||||
|
||||
template <typename FloatType>
|
||||
struct CarrierDetect
|
||||
{
|
||||
using result_t = std::tuple<bool, FloatType>;
|
||||
|
||||
tnc::IirFilter<3> filter_;
|
||||
FloatType lock_;
|
||||
FloatType unlock_;
|
||||
bool locked_ = false;
|
||||
|
||||
CarrierDetect(std::array<FloatType, 3> const& b, std::array<FloatType, 3> const& a, FloatType lock_level, FloatType unlock_level)
|
||||
: filter_(b, a), lock_(lock_level), unlock_(unlock_level)
|
||||
{
|
||||
}
|
||||
|
||||
result_t operator()(FloatType value)
|
||||
{
|
||||
auto filtered = filter_(std::abs(value));
|
||||
if (locked_ && (filtered > unlock_)) locked_ = false;
|
||||
else if (!locked_ && (filtered < lock_)) locked_ = true;
|
||||
|
||||
return std::make_tuple(locked_, filtered);
|
||||
}
|
||||
};
|
||||
|
||||
} // mobilinkd
|
|
@ -1,96 +0,0 @@
|
|||
// Copyright 2020 Mobilinkd LLC.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <algorithm>
|
||||
#include <numeric>
|
||||
|
||||
namespace mobilinkd
|
||||
{
|
||||
|
||||
template <typename T, size_t N = 10>
|
||||
struct DeviationError
|
||||
{
|
||||
using float_type = T;
|
||||
using array_t = std::array<float_type, N>;
|
||||
|
||||
array_t minima_{0};
|
||||
array_t maxima_{0};
|
||||
size_t min_index_ = 0;
|
||||
size_t max_index_ = 0;
|
||||
bool min_rolled_ = false;
|
||||
bool max_rolled_ = false;
|
||||
size_t min_count_ = 0;
|
||||
size_t max_count_ = 0;
|
||||
float_type min_estimate_ = 0.0;
|
||||
float_type max_estimate_ = 0.0;
|
||||
|
||||
const float_type ZERO = 0.0;
|
||||
|
||||
DeviationError()
|
||||
{
|
||||
minima_.fill(0.0);
|
||||
maxima_.fill(0.0);
|
||||
}
|
||||
|
||||
float_type operator()(float_type sample)
|
||||
{
|
||||
if (sample > ZERO)
|
||||
{
|
||||
if (sample > max_estimate_ * 0.67 or max_count_ == 5)
|
||||
{
|
||||
max_count_ = 0;
|
||||
maxima_[max_index_++] = sample;
|
||||
if (max_index_ == N)
|
||||
{
|
||||
max_rolled_ = true;
|
||||
max_index_ = 0;
|
||||
}
|
||||
if (max_rolled_)
|
||||
{
|
||||
max_estimate_ = std::accumulate(std::begin(maxima_), std::end(maxima_), ZERO) / N;
|
||||
}
|
||||
else
|
||||
{
|
||||
max_estimate_ = std::accumulate(std::begin(maxima_), std::begin(maxima_) + max_index_, ZERO) / max_index_;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
++max_count_;
|
||||
}
|
||||
}
|
||||
else if (sample < 0)
|
||||
{
|
||||
if (sample < min_estimate_ * 0.67 or min_count_ == 5)
|
||||
{
|
||||
min_count_ = 0;
|
||||
minima_[min_index_++] = sample;
|
||||
if (min_index_ == N)
|
||||
{
|
||||
min_rolled_ = true;
|
||||
min_index_ = 0;
|
||||
}
|
||||
if (min_rolled_)
|
||||
{
|
||||
min_estimate_ = std::accumulate(std::begin(minima_), std::end(minima_), ZERO) / N;
|
||||
}
|
||||
else
|
||||
{
|
||||
min_estimate_ = std::accumulate(std::begin(minima_), std::begin(minima_) + min_index_, ZERO) / min_index_;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
++min_count_;
|
||||
}
|
||||
}
|
||||
|
||||
auto deviation = max_estimate_ - min_estimate_;
|
||||
auto deviation_error = std::min(6.0 / deviation, 100.0);
|
||||
return deviation_error;
|
||||
}
|
||||
};
|
||||
|
||||
} // mobilinkd
|
|
@ -1,63 +0,0 @@
|
|||
// Copyright 2020 Mobilinkd LLC.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "IirFilter.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <algorithm>
|
||||
#include <numeric>
|
||||
|
||||
namespace mobilinkd
|
||||
{
|
||||
|
||||
template <typename FloatType, size_t N = 32>
|
||||
struct FrequencyError
|
||||
{
|
||||
using float_type = FloatType;
|
||||
using array_t = std::array<FloatType, N>;
|
||||
using filter_type = tnc::IirFilter<3>;
|
||||
|
||||
array_t samples_{0};
|
||||
size_t index_ = 0;
|
||||
float_type accum_ = 0.0;
|
||||
filter_type filter_;
|
||||
|
||||
const float_type ZERO = 0.0;
|
||||
|
||||
FrequencyError(const std::array<float, 3>& b, const std::array<float, 3>& a)
|
||||
: filter_(b, a)
|
||||
{
|
||||
samples_.fill(0.0);
|
||||
}
|
||||
|
||||
auto operator()(float_type sample)
|
||||
{
|
||||
FloatType evm = 0;
|
||||
bool use = true;
|
||||
|
||||
if (sample > 2)
|
||||
{
|
||||
evm = sample - 3;
|
||||
}
|
||||
else if (sample >= -2)
|
||||
{
|
||||
use = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
evm = sample + 3;
|
||||
}
|
||||
|
||||
if (use)
|
||||
{
|
||||
accum_ = accum_ - samples_[index_] + evm;
|
||||
samples_[index_++] = evm;
|
||||
if (index_ == N) index_ = 0;
|
||||
}
|
||||
|
||||
return filter_(accum_ / N);
|
||||
}
|
||||
};
|
||||
|
||||
} // mobilinkd
|
122
TNC/Fsk4Demod.h
122
TNC/Fsk4Demod.h
|
@ -1,122 +0,0 @@
|
|||
// Copyright 2020 Mobilinkd LLC.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "FirFilter.h"
|
||||
#include "PhaseEstimator.h"
|
||||
#include "DeviationError.h"
|
||||
#include "FrequencyError.h"
|
||||
#include "SymbolEvm.h"
|
||||
|
||||
#include <array>
|
||||
#include <experimental/array>
|
||||
#include <optional>
|
||||
#include <tuple>
|
||||
|
||||
namespace mobilinkd
|
||||
{
|
||||
|
||||
namespace detail
|
||||
{
|
||||
inline const auto rrc_taps = std::experimental::make_array<int16_t>(
|
||||
-151, -100, -18, 80, 175, 246, 275, 249, 170, 49, -90, -219,
|
||||
-304, -318, -245, -88, 131, 373, 581, 695, 659, 437, 22, -556,
|
||||
-1229, -1890, -2409, -2641, -2452, -1738, -441, 1434, 3816, 6563,
|
||||
9480, 12334, 14880, 16891, 18179, 18622, 18179, 16891, 14880, 12334,
|
||||
9480, 6563, 3816, 1434, -441, -1738, -2452, -2641, -2409, -1890,
|
||||
-1229, -556, 22, 437, 659, 695, 581, 373, 131, -88, -245, -318, -304,
|
||||
-219, -90, 49, 170, 249, 275, 246, 175, 80, -18, -100, -151);
|
||||
|
||||
inline const auto evm_b = std::experimental::make_array<float>(0.02008337, 0.04016673, 0.02008337);
|
||||
inline const auto evm_a = std::experimental::make_array<float>(1.0, -1.56101808, 0.64135154);
|
||||
} // detail
|
||||
|
||||
struct Fsk4Demod
|
||||
{
|
||||
using demod_result_t = std::tuple<float, float, int, float>;
|
||||
using result_t = std::optional<std::tuple<float, float, int, float, float, float, float>>;
|
||||
|
||||
tnc::Q15FirFilter<320, std::tuple_size<decltype(detail::rrc_taps)>::value> rrc;
|
||||
PhaseEstimator<float> phase = PhaseEstimator<float>(48000, 4800);
|
||||
DeviationError<float> deviation;
|
||||
FrequencyError<float, 32> frequency;
|
||||
SymbolEvm<float, std::tuple_size<decltype(detail::evm_b)>::value> symbol_evm = makeSymbolEvm(makeIirFilter(detail::evm_b, detail::evm_a));
|
||||
|
||||
float sample_rate = 48000;
|
||||
float symbol_rate = 4800;
|
||||
float gain = 0.04;
|
||||
std::array<float, 3> samples{0};
|
||||
float t = 0;
|
||||
float dt = symbol_rate / sample_rate;
|
||||
float ideal_dt = dt;
|
||||
bool sample_now = false;
|
||||
float estimated_deviation = 1.0;
|
||||
float estimated_frequency_offset = 0.0;
|
||||
float evm_average = 0.0;
|
||||
|
||||
Fsk4Demod(float sample_rate, float symbol_rate, float gain = 0.04)
|
||||
: sample_rate(sample_rate)
|
||||
, symbol_rate(symbol_rate)
|
||||
, gain(gain * symbol_rate / sample_rate)
|
||||
, dt(symbol_rate / sample_rate)
|
||||
, ideal_dt(dt)
|
||||
{
|
||||
samples.fill(0.0);
|
||||
}
|
||||
|
||||
demod_result_t demod()
|
||||
{
|
||||
estimated_deviation = deviation(samples[1]);
|
||||
for (auto& sample : samples) sample *= estimated_deviation;
|
||||
|
||||
estimated_frequency_offset = frequency(samples[1]);
|
||||
for (auto& sample : samples) sample -= estimated_frequency_offset;
|
||||
|
||||
auto phase_estimate = phase(samples);
|
||||
if (samples[1] < 0) phase_estimate *= -1;
|
||||
|
||||
dt = ideal_dt - (phase_estimate * gain);
|
||||
t += dt;
|
||||
|
||||
auto [symbol, evm] = symbol_evm(samples[1]);
|
||||
evm_average = symbol_evm.evm();
|
||||
samples[0] = samples[2];
|
||||
|
||||
return std::make_tuple(samples[1], phase_estimate, symbol, evm);
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the sample. If a symbol is ready, return a tuple
|
||||
* containing the sample used, the estimated phase, the decoded
|
||||
* symbol, the EVM, the deviation error and the frequency error
|
||||
* (sample, phase, symbol, evm, ed, ef), otherwise None.
|
||||
*/
|
||||
result_t operator()(float sample)
|
||||
{
|
||||
auto filtered_sample = rrc(sample);
|
||||
|
||||
if (sample_now)
|
||||
{
|
||||
samples[2] = filtered_sample;
|
||||
sample_now = false;
|
||||
auto [prev_sample, phase_estimate, symbol, evm] = demod();
|
||||
return std::make_tuple(prev_sample, phase_estimate, symbol, evm, estimated_deviation, estimated_frequency_offset, evm_average);
|
||||
}
|
||||
|
||||
t += dt;
|
||||
if (t < 1.0)
|
||||
{
|
||||
samples[0] = filtered_sample;
|
||||
}
|
||||
else
|
||||
{
|
||||
t -= 1.0;
|
||||
samples[1] = filtered_sample;
|
||||
sample_now = true;
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
};
|
||||
|
||||
} // mobilinkd
|
|
@ -1,50 +0,0 @@
|
|||
// Copyright 2020 Mobilinkd LLC.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
|
||||
namespace mobilinkd
|
||||
{
|
||||
|
||||
/**
|
||||
* Estimate the phase of a sample by estimating the
|
||||
* tangent of the sample point. This is done by computing
|
||||
* the magnitude difference of the previous and following
|
||||
* samples. We do not correct for 0-crossing errors because
|
||||
* these errors have not affected the performance of clock
|
||||
* recovery.
|
||||
*/
|
||||
template <typename FloatType>
|
||||
struct PhaseEstimator
|
||||
{
|
||||
using samples_t = std::array<FloatType, 3>; // 3 samples in length
|
||||
|
||||
FloatType dx_;
|
||||
|
||||
PhaseEstimator(FloatType sample_rate, FloatType symbol_rate)
|
||||
: dx_(2.0 * symbol_rate / sample_rate)
|
||||
{
|
||||
assert(dx_ > 0.0);
|
||||
}
|
||||
|
||||
/**
|
||||
* This performs a rolling estimate of the phase.
|
||||
*
|
||||
* @param samples are three samples centered around the current sample point
|
||||
* (t-1, t, t+1).
|
||||
*/
|
||||
FloatType operator()(const samples_t& samples)
|
||||
{
|
||||
auto ratio = ((samples.at(2) - samples.at(0)) / 3.0f) / dx_;
|
||||
// Clamp +/-5.
|
||||
ratio = std::min(FloatType(5.0), ratio);
|
||||
ratio = std::max(FloatType(-5.0), ratio);
|
||||
|
||||
return ratio;
|
||||
}
|
||||
};
|
||||
|
||||
} // mobilinkd
|
|
@ -1,70 +0,0 @@
|
|||
// Copyright 2020 Mobilinkd LLC.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "IirFilter.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <algorithm>
|
||||
#include <numeric>
|
||||
|
||||
namespace mobilinkd
|
||||
{
|
||||
|
||||
template <typename FloatType, size_t N>
|
||||
struct SymbolEvm
|
||||
{
|
||||
using filter_type = tnc::IirFilter<N>;
|
||||
using symbol_t = int;
|
||||
using result_type = std::tuple<symbol_t, FloatType>;
|
||||
|
||||
filter_type filter_;
|
||||
FloatType evm_ = 0.0;
|
||||
|
||||
SymbolEvm(const std::array<FloatType, N>& b, const std::array<FloatType, N>& a)
|
||||
: filter_(b, a)
|
||||
{}
|
||||
|
||||
FloatType evm() const { return evm_; }
|
||||
|
||||
/**
|
||||
* Decode a normalized sample into a symbol. Symbols
|
||||
* are decoded into +3, +1, -1, -3. If an erasure limit
|
||||
* is set, symbols outside this limit are 'erased' and
|
||||
* returned as 0.
|
||||
*/
|
||||
result_type operator()(FloatType sample)
|
||||
{
|
||||
symbol_t symbol;
|
||||
FloatType evm;
|
||||
|
||||
sample = std::min(3.0f, std::max(-3.0f, sample));
|
||||
|
||||
if (sample > 2)
|
||||
{
|
||||
symbol = 3;
|
||||
evm = (sample - 3) * 0.333333f;
|
||||
}
|
||||
else if (sample > 0)
|
||||
{
|
||||
symbol = 1;
|
||||
evm = sample - 1;
|
||||
}
|
||||
else if (sample >= -2)
|
||||
{
|
||||
symbol = -1;
|
||||
evm = sample + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
symbol = -3;
|
||||
evm = (sample + 3) * 0.333333f;
|
||||
}
|
||||
|
||||
evm_ = filter_(evm);
|
||||
|
||||
return std::make_tuple(symbol, evm);
|
||||
}
|
||||
};
|
||||
|
||||
} // mobilinkd
|
Ładowanie…
Reference in New Issue