// Copyright 2015-2021 Mobilinkd LLC // All rights reserved. #pragma once #include "Hysteresis.hpp" #include "IirFilter.hpp" #include "FirFilter.hpp" #include "arm_math.h" #include #include #include #include #include namespace mobilinkd { namespace tnc { namespace pll { template struct PLLResult { FloatType jitter; bool sample; bool locked; }; // Lock low-pass filter taps (80Hz Bessel) // scipy.signal: // b, a = bessel(4, [80.0/(1200/2)], 'lowpass') // constexpr std::array lock_b = { 1.077063e-03,4.308253e-03,6.462379e-03,4.308253e-03,1.077063e-03, }; constexpr std::array lock_a = { 1.000000e+00,-2.774567e+00,2.962960e+00,-1.437990e+00,2.668296e-01, }; // 64 Hz loop filter. // scipy.signal: // loop_coeffs = firwin(9, [64.0/(1200/2)], width = None, // pass_zero = True, scale = True, window='hann') // constexpr std::array loop_coeffs = { // 0.08160962754214955, 0.25029850550446403, 0.3361837339067726, 0.2502985055044641, 0.08160962754214969 3.196252e-02,1.204223e-01,2.176819e-01,2.598666e-01,2.176819e-01,1.204223e-01,3.196252e-02 }; } // pll template struct BaseDigitalPLL { typedef T float_type; typedef pll::PLLResult result_type; float_type sample_rate_; float_type symbol_rate_; float_type sps_; ///< Samples per symbol float_type limit_; ///< Samples per symbol / 2 libafsk::BaseHysteresis lock_; FirFilter<1, pll::loop_coeffs.size()> loop_filter_{pll::loop_coeffs.data()}; IirFilter<5> lock_filter_{pll::lock_b, pll::lock_a}; bool last_; float_type count_; bool sample_; float_type jitter_; uint8_t bits_; BaseDigitalPLL(float_type sample_rate, float_type symbol_rate) : sample_rate_(sample_rate), symbol_rate_(symbol_rate) , sps_(sample_rate / symbol_rate) , limit_(sps_ / float_type(2.0)) , lock_(sps_ * float_type(0.03), sps_ * float_type(0.15), 1, 0) , last_(false), count_(0), sample_(false) , jitter_(0.0), bits_(1) {} result_type operator()(bool input) { sample_ = false; if (input != last_ or bits_ > 16) { // Record transition. last_ = input; if (count_ > limit_) { count_ -= sps_; } // Force lock off when no stimulus is present (squelch closed). const float_type adjust = bits_ > 16 ? 5.0 : 0.0; const float_type offset = count_ / bits_; const float_type jitter = loop_filter_(offset); const float_type abs_offset = std::abs(offset) + adjust; jitter_ = lock_filter_(abs_offset); count_ -= jitter / 2; bits_ = 1; } else { if (count_ > limit_) { sample_ = true; count_ -= sps_; ++bits_; } } count_ += 1; result_type result = {jitter_, sample_, locked()}; return result; } bool locked() { return lock_(jitter_); } bool sample() const { return sample_; } }; typedef BaseDigitalPLL DigitalPLL; typedef BaseDigitalPLL FastDigitalPLL; }} // mobilinkd::tnc