9 using satcat5::ptp::boxcar_filter;
10 using satcat5::ptp::median_filter;
32 static constexpr
unsigned DEBUG_VERBOSE = 0;
36 #ifndef SATCAT5_PTP_UNROLL_MEDIAN
37 #define SATCAT5_PTP_UNROLL_MEDIAN 9
41 #ifndef SATCAT5_PTRK_DITHER
42 #define SATCAT5_PTRK_DITHER 1
47 constexpr s64 SLEW_MAX_IN = s64(10 * SUBNS_PER_MSEC);
48 constexpr u64 SLEW_MAX_OUT = u64(10 * SUBNS_PER_MSEC);
51 static inline u32 next_dither() {
52 #if SATCAT5_PTRK_DITHER
60 template <
class T =
int128_t>
61 static inline T big_dither(
unsigned scale) {
62 T dither(next_dither());
63 if (scale > 32) dither <<= (scale - 32);
64 if (scale < 32) dither >>= (32 - scale);
69 static inline s64 wide_output(
const T& x,
unsigned scale) {
70 return s64((x + big_dither<T>(scale)) >> scale);
73 s64 satcat5::ptp::boxcar_filter(
const s64* data,
unsigned order) {
75 if (order == 0)
return data[0];
76 unsigned samps = 1u << order;
80 int128_t sum(next_dither() & u32(samps-1));
81 for (
unsigned a = 0 ; a < samps ; ++a) {
84 return s64(sum >> order);
87 static inline void sort2(s64& a, s64&b) {
91 s64 satcat5::ptp::median_filter(s64* tmp,
unsigned samps) {
93 if (samps <= 1)
return tmp[0];
98 if (SATCAT5_PTP_UNROLL_MEDIAN >= 3 && samps == 3) {
99 sort2(tmp[0], tmp[1]); sort2(tmp[1], tmp[2]);
100 sort2(tmp[0], tmp[1]);
return tmp[1];
101 }
else if (SATCAT5_PTP_UNROLL_MEDIAN >= 5 && samps == 5) {
102 sort2(tmp[0], tmp[1]); sort2(tmp[3], tmp[4]);
103 sort2(tmp[0], tmp[3]); sort2(tmp[1], tmp[4]);
104 sort2(tmp[1], tmp[2]); sort2(tmp[2], tmp[3]);
105 sort2(tmp[1], tmp[2]);
return tmp[2];
106 }
else if (SATCAT5_PTP_UNROLL_MEDIAN >= 7 && samps == 7) {
107 sort2(tmp[0], tmp[5]); sort2(tmp[0], tmp[3]);
108 sort2(tmp[1], tmp[6]); sort2(tmp[2], tmp[4]);
109 sort2(tmp[0], tmp[1]); sort2(tmp[3], tmp[5]);
110 sort2(tmp[2], tmp[6]); sort2(tmp[2], tmp[3]);
111 sort2(tmp[3], tmp[6]); sort2(tmp[4], tmp[5]);
112 sort2(tmp[1], tmp[4]); sort2(tmp[1], tmp[3]);
113 sort2(tmp[3], tmp[4]);
return tmp[3];
114 }
else if (SATCAT5_PTP_UNROLL_MEDIAN >= 9 && samps == 9) {
115 sort2(tmp[1], tmp[2]); sort2(tmp[4], tmp[5]);
116 sort2(tmp[7], tmp[8]); sort2(tmp[0], tmp[1]);
117 sort2(tmp[3], tmp[4]); sort2(tmp[6], tmp[7]);
118 sort2(tmp[1], tmp[2]); sort2(tmp[4], tmp[5]);
119 sort2(tmp[7], tmp[8]); sort2(tmp[0], tmp[3]);
120 sort2(tmp[5], tmp[8]); sort2(tmp[4], tmp[7]);
121 sort2(tmp[3], tmp[6]); sort2(tmp[1], tmp[4]);
122 sort2(tmp[2], tmp[5]); sort2(tmp[4], tmp[7]);
123 sort2(tmp[4], tmp[2]); sort2(tmp[6], tmp[4]);
124 sort2(tmp[4], tmp[2]);
return tmp[4];
128 return tmp[samps / 2];
132 AmplitudeReject::AmplitudeReject(
unsigned tau_msec)
134 , m_sigma(UINT64_MAX/2)
136 , m_tau_usec(1000*tau_msec)
141 void AmplitudeReject::reset() {
143 m_sigma = UINT64_MAX/2;
148 if (next == INT64_MAX)
return INT64_MAX;
151 const int128_t MIN128(m_min);
152 const int128_t MAX128(UINT64_MAX/2);
153 const int128_t SQRTPI2(u64(5382943231ull));
157 elapsed_usec = satcat5::util::min_u32(elapsed_usec, m_tau_usec/2);
158 uint128_t tau(elapsed_usec, 0);
159 tau /= uint128_t(m_tau_usec);
163 diff -= int128_t(m_mean);
166 m_mean += s64((diff * tau + big_dither(32)) >> 32u);
171 int128_t adiff = (SQRTPI2 * diff.abs() + big_dither(32)) >> 32u;
172 adiff -= int128_t(m_sigma);
176 int128_t sigma(m_sigma);
177 sigma += (adiff * tau + big_dither(32)) >> 32u;
178 if (sigma < MIN128) sigma = MIN128;
179 if (sigma > MAX128) sigma = MAX128;
180 m_sigma = u64(sigma);
183 int128_t thresh(m_sigma); thresh *= int128_t(u32(6));
184 return (diff.abs() < thresh) ? next : INT64_MAX;
189 , m_accum(INT128_ZERO)
190 , m_slew(SLEW_MAX_OUT)
197 if (DEBUG_VERBOSE > 0) {
198 auto level = coeff.
ok() ? log::DEBUG : log::ERROR;
199 log::Log(level,
"ControllerPI: Config")
202 }
else if (!coeff.
ok()) {
203 log::Log(log::ERROR,
"ControllerPI: Bad config.");
208 m_accum = INT128_ZERO;
213 delta_subns = satcat5::util::clamp(delta_subns, SLEW_MAX_IN);
214 int128_t
rate(delta_subns);
217 rate /= int128_t(elapsed_usec);
218 rate.clamp(int128_t(m_slew) << m_coeff.
SCALE);
224 if (delta_subns == INT64_MAX)
return INT64_MAX;
225 delta_subns = satcat5::util::clamp(delta_subns, SLEW_MAX_IN);
229 int128_t delta_i(delta_subns);
230 int128_t delta_p(delta_subns);
231 delta_i *= int128_t(m_coeff.ki);
232 delta_p *= int128_t(m_coeff.kp);
240 delta_i *= int128_t(elapsed_usec);
248 int128_t ymax(m_slew);
253 int128_t ysum(m_accum + delta_p);
254 ysum.clamp(ymax << m_coeff.
SCALE);
255 return wide_output(ysum, m_coeff.
SCALE);
260 , m_accum1(INT128_ZERO)
261 , m_accum2(INT256_ZERO)
262 , m_slew(SLEW_MAX_OUT)
269 if (DEBUG_VERBOSE > 0) {
270 auto level = coeff.
ok() ? log::DEBUG : log::ERROR;
271 log::Log(level,
"ControllerPII: Config")
275 }
else if (!coeff.
ok()) {
276 log::Log(log::ERROR,
"ControllerPII: Bad config.");
281 m_accum1 = INT128_ZERO;
282 m_accum2 = INT256_ZERO;
287 delta_subns = satcat5::util::clamp(delta_subns, SLEW_MAX_IN);
288 int256_t
rate(delta_subns);
291 rate /= int256_t(elapsed_usec);
292 rate.clamp(int256_t(SLEW_MAX_OUT) << m_coeff.
SCALE);
298 if (delta_subns == INT64_MAX)
return INT64_MAX;
299 delta_subns = satcat5::util::clamp(delta_subns, SLEW_MAX_IN);
303 int128_t delta_i(delta_subns);
304 int128_t delta_p(delta_subns);
305 delta_i *= int128_t(m_coeff.ki);
306 delta_p *= int128_t(m_coeff.kp);
315 delta_i *= int128_t(elapsed_usec);
321 int128_t ymax128(m_slew);
327 int256_t ymax256(m_slew);
328 int256_t delta_r(m_accum1);
329 delta_r *= int256_t(m_coeff.kr);
330 delta_r *= int256_t(elapsed_usec);
335 int128_t ysum((m_accum2 + big_dither(m_coeff.
SCALE2)) >> m_coeff.
SCALE2);
339 return wide_output(ysum, m_coeff.
SCALE1);
343 const unsigned window,
const s64* x,
const s64* y)
346 int128_t sum_x = INT128_ZERO, sum_y = INT128_ZERO;
347 for (
unsigned n = 0 ; n < window ; ++n) {
348 sum_x += int128_t(x[n]);
349 sum_y += int128_t(y[n]);
357 const int128_t win128((u32)window);
358 int256_t cov_xx(INT256_ZERO), cov_xy(INT256_ZERO);
359 for (
unsigned n = 0 ; n < window ; ++n) {
360 int256_t dx(int128_t(x[n]) * win128 - sum_x);
361 int256_t dy(int128_t(y[n]) * win128 - sum_y);
368 beta = int128_t((cov_xy <<
TSCALE).div_round(cov_xx));
370 alpha = int128_t((sum_y - xbeta).div_round(win128));
379 : m_coeff(coeff), m_accum(INT128_ZERO), m_window(window)
387 if (DEBUG_VERBOSE > 0) {
388 auto level = coeff.
ok() ? log::DEBUG : log::ERROR;
389 log::Log(level,
"ControllerLR: Config")
392 }
else if (!coeff.
ok()) {
393 log::Log(log::ERROR,
"ControllerLR: Bad config.");
399 delta_subns = satcat5::util::clamp(delta_subns, SLEW_MAX_IN);
400 int128_t
rate(delta_subns);
403 rate /= int128_t(elapsed_usec);
407 s64 ControllerLR_Inner::update_inner(
const u32* dt,
const s64* y) {
413 for (
unsigned n = m_window-1 ; n != 0 ; --n) {
414 x[n-1] = x[n] - dt[n];
418 s64 span_usec = -x[0];
419 if (span_usec < 2000)
return INT64_MIN;
425 int128_t delta(fit.alpha * int128_t(m_coeff.kw) + fit.beta);
428 m_accum += delta * int128_t(m_coeff.ki);
431 m_accum.
clamp(int128_t(SLEW_MAX_OUT) << fit.TSCALE);
432 return wide_output(m_accum, fit.TSCALE);
440 ptr = m_filters.
next(ptr);
445 m_accum = INT128_ZERO;
452 ptr->
rate(delta_subns, elapsed_usec);
453 ptr = m_filters.
next(ptr);
456 int128_t
rate(delta_subns);
458 rate /= int128_t(elapsed_usec);
465 m_accum = int128_t(next) << SCALE;
470 m_accum += incr(elapsed_usec);
471 s64 trend = wide_output(m_accum, SCALE);
473 s64 delta = next - trend;
476 delta = ptr->
update(delta, elapsed_usec);
477 ptr = m_filters.
next(ptr);
480 if (delta != INT64_MIN) m_rate = delta;
486 return wide_output(m_accum + incr(elapsed_usec), SCALE);
489 int128_t LinearPrediction::incr(u32 elapsed_usec)
const {
490 static constexpr u64 TICKS_PER_USEC = satcat5::util::round_u64(
491 satcat5::util::pow2d(SCALE) /
double(satcat5::ptp::USEC_PER_SEC));
492 return int128_t(m_rate) * int128_t(TICKS_PER_USEC) * int128_t(elapsed_usec);
497 return wide_output(int128_t(offset) * int128_t(m_scale), SHIFT);
502 int128_t temp(rate); temp <<= SHIFT;
503 return s64(temp.div_round(int128_t(m_scale)));
The Log class creates and formats one log message.
Log & write10(s32 val)
Print integer as a decimal value with no leading zeros.
Amplitude-based outlier rejection.
s64 update(s64 next, u32 elapsed_usec) override
Method called for each new input sample.
Helper class for "ControllerLR" is never used directly.
void set_coeff(const satcat5::ptp::CoeffLR &coeff)
Adjust loop bandwidth.
ControllerLR_Inner(const satcat5::ptp::CoeffLR &coeff, unsigned window)
Private constructor and destructor.
void rate(s64 delta, u32 elapsed_usec) override
Partial API from ptp::Filter.
Loop-filter for a proportional-integral (PI) controller.
void rate(s64 delta, u32 elapsed_usec) override
Required API from ptp::Filter.
s64 update(s64 next, u32 elapsed_usec) override
Required API from ptp::Filter.
ControllerPI(const satcat5::ptp::CoeffPI &coeff)
Constructor sets loop bandwidth, which can be changed later.
void reset() override
Required API from ptp::Filter.
void set_coeff(const satcat5::ptp::CoeffPI &coeff)
Adjust tracking-loop bandwidth.
Loop-filter for a proportional-double-integral (PII) controller.
s64 update(s64 next, u32 elapsed_usec) override
Required API from ptp::Filter.
ControllerPII(const satcat5::ptp::CoeffPII &coeff)
Constructor sets loop bandwidth, which can be changed later.
void rate(s64 delta, u32 elapsed_usec) override
Required API from ptp::Filter.
void reset() override
Required API from ptp::Filter.
void set_coeff(const satcat5::ptp::CoeffPII &coeff)
Adjust tracking-loop bandwidth.
Define the basic chain-of-filters API.
virtual void rate(s64 delta_subns, u32 elapsed_usec)
Optional handler for fast-acquisition; override if required.
virtual void reset()=0
Flush previous inputs and reset to a neutral state.
virtual s64 update(s64 next, u32 elapsed_usec)=0
Method called for each new input sample.
An inline filter that iteratively estimates linear trends.
s64 predict(u32 elapsed_usec) const
Extrapolate trendline relative to most recent update() event.
void rate(s64 delta, u32 elapsed_usec) override
Required API from ptp::Filter.
s64 update(s64 next, u32 elapsed_usec) override
Required API from ptp::Filter.
void reset() override
Required API from ptp::Filter.
Convert normalized frequency offset to ticks-per-clock.
s64 invert(s64 rate) const
Inverse conversion (ticks-per-clock ==> normalized rate)
s64 convert(s64 offset) const
Forward conversion (normalized rate ==> ticks-per-clock)
T * next(const T *item) const
Fetch pointer to the next item.
Simple cross-platform psuedorandom number generator (PRNG).
u32 next()
Range [0..2^32)
Chainable filters for use with ptp::TrackingController.
High-precision "Time" object for use with PTP / IEEE1588.
constexpr s64 SUBNS_PER_MSEC
Define commonly used scaling factors.
constexpr s64 SUBNS_PER_SEC
Define commonly used scaling factors.
constexpr s64 USEC_PER_SEC
Define commonly used scaling factors.
constexpr s64 SUBNS_PER_NSEC
Define commonly used scaling factors.
Loop-filter coefficients for use with the "ControllerLR" class.
bool ok() const
Are all coefficients large enough to mitigate rounding error?
Loop-filter coefficients for use with the "ControllerPI" class.
bool ok() const
Are all coefficients large enough to mitigate rounding error?
static constexpr unsigned SCALE
Fixed-point scaling of each coefficient by 2^-N.
Loop-filter coefficients for use with the "ControllerPII" class.
static constexpr unsigned SCALE2
Fixed-point scaling of each coefficient by 2^-N.
static constexpr unsigned SCALE
Fixed-point scaling of each coefficient by 2^-N.
bool ok() const
Are all coefficients large enough to mitigate rounding error?
static constexpr unsigned SCALE1
Fixed-point scaling of each coefficient by 2^-N.
Stateless linear regression calculator.
s64 extrapolate(s64 x) const
Extrapolate relative to the most recent sample.
satcat5::util::int128_t beta
Parameters for the best-fit line.
constexpr LinearRegression()
Placeholder constructor.
satcat5::util::int128_t alpha
Parameters for the best-fit line.
static constexpr unsigned TSCALE
Parameters for the best-fit line.
void clamp(const WideSigned< W > &limit_pos)
Clamp input to +/- limit_pos.
void swap_ref(T &x, T &y)
Swap two values using a temporary variable.
satcat5::util::Prng prng
Global instance of the Prng class.
void sort(T *begin, T *end)
Templated in-place stable sort for small arrays.
constexpr satcat5::util::int256_t INT256_ZERO(u32(0))
Shorthand for commonly used constants.
satcat5::util::WideSigned< 4 > int128_t
Shorthand for commonly used sizes.
constexpr satcat5::util::int128_t INT128_ZERO(u32(0))
Shorthand for commonly used constants.