SatCat5
ptp_tracking.cc
1 // Copyright 2023-2025 The Aerospace Corporation.
3 // This file is a part of SatCat5, licensed under CERN-OHL-W v2 or later.
5 
6 #include <satcat5/log.h>
7 #include <satcat5/net_core.h>
8 #include <satcat5/ptp_tracking.h>
9 #include <satcat5/utils.h>
10 
11 namespace log = satcat5::log;
19 using satcat5::ptp::Time;
27 
28 // Enable additional diagnostics? (0/1/2)
29 static constexpr unsigned DEBUG_VERBOSE = 0;
30 
31 // Enable faster frequency acquisition?
32 #ifndef SATCAT5_PTRK_FASTACQ
33 #define SATCAT5_PTRK_FASTACQ 1
34 #endif
35 
36 TrackingController::TrackingController(TrackingClock* clk, Source* source)
37  : Callback(source)
38  , m_clocks(clk)
39  , m_tref(SATCAT5_CLOCK->now())
40  , m_freq_accum(0)
41  , m_lock_alarm(0)
42  , m_lock_usec(0)
43  , m_lock_state(LockState::ACQUIRE)
44 {
45  reset();
46 }
47 
49 {
50  update(-data.offset_from_master());
51 }
52 
53 void TrackingController::reset(bool linear)
54 {
55  // Reset all attached clock objects.
56  clock_rate(0);
57 
58  // Reset each filter in the chain.
59  Filter* filter = m_filters.head();
60  while (filter) {
61  filter->reset();
62  filter = m_filters.next(filter);
63  }
64 
65  // Reset lock/unlock state.
66  m_freq_accum = 0;
67  m_lock_alarm = 0;
68  m_lock_usec = 0;
69  m_lock_state = linear ? LockState::LINEAR : LockState::RESET;
70  m_tref = SATCAT5_CLOCK->now();
71 }
72 
73 void TrackingController::update(const Time& delta)
74 {
75  // Calculate time since the last received message.
76  // Use microsecond-resolution timers to support higher message rates.
77  u32 elapsed_usec = m_tref.increment_usec();
78  elapsed_usec = min_u32(1000000, elapsed_usec);
79  m_lock_usec = min_u32(1000000000, m_lock_usec + elapsed_usec);
80 
81  // Apply coarse adjustments if required, then fine tracking.
82  s64 input = coarse(delta);
83  filter(elapsed_usec, input);
84 }
85 
86 s64 TrackingController::coarse(const Time& delta)
87 {
88  constexpr Time ADJ_COARSE(SUBNS_PER_MSEC * 10);
89  constexpr Time ADJ_FREQ(SUBNS_PER_USEC * 1000);
90  constexpr Time ADJ_FINE(SUBNS_PER_USEC * 10);
91 
92  Time filter_input = delta;
93  if (m_lock_state == LockState::LINEAR) {
94  // Linear mode skips all coarse-acquisition logic.
95  } else if (delta.abs() > ADJ_COARSE) {
96  // Large errors may indicate that we've lost lock.
97  log::Log(log::INFO, "PTP-Track: Coarse").write_obj(delta);
98  if (m_lock_state == LockState::RESET || m_lock_alarm >= 3) {
99  // Initial startup or several consecutive alarms.
100  reset();
101  m_lock_state = LockState::ACQUIRE;
102  filter_input = clock_adjust(delta);
103  } else if (m_lock_usec >= 1000000) {
104  // Once locked, don't reset based on one outlier.
105  ++m_lock_alarm;
106  return 0;
107  }
108  } else if (SATCAT5_PTRK_FASTACQ && m_lock_state == LockState::ACQUIRE) {
109  // If enabled, keep making coarse phase adjustments after each reset.
110  // Cumulative adjustments are used to estimate coarse frequency.
111  if (delta.abs() < ADJ_FREQ) {
112  filter_input = clock_adjust(delta);
113  m_freq_accum += (delta - filter_input).delta_subns();
114  }
115  // Wait for at least one second to improve estimation quality.
116  // Goal is to get initial state within ~1ppm for faster pull-in.
117  if (m_lock_usec >= 1000000) {
118  log::Log(log::INFO, "PTP-Track: Adjust")
119  .write10(m_freq_accum).write10(m_lock_usec);
120  Filter* filter = m_filters.head();
121  while (filter) {
122  filter->rate(m_freq_accum, m_lock_usec);
123  filter = m_filters.next(filter);
124  }
125  m_lock_state = LockState::TRACK;
126  }
127  } else if (delta.abs() < ADJ_FINE) {
128  // Well-aligned measurements reset the consecutive-alarm counter.
129  m_lock_alarm = 0;
130  m_lock_state = LockState::TRACK;
131  }
132 
133  return filter_input.delta_subns();
134 }
135 
136 void TrackingController::filter(u32 elapsed_usec, s64 delta_subns)
137 {
138  // Apply each filter in the chain.
139  Filter* filter = m_filters.head();
140  s64 result = delta_subns;
141  while (filter) {
142  result = filter->update(result, elapsed_usec);
143  filter = m_filters.next(filter);
144  }
145 
146  // Keep this output sample?
147  if (result != INT64_MAX) clock_rate(result);
148 
149  // Additinoal diagnostics?
150  if (DEBUG_VERBOSE > 1) {
151  log::Log(log::DEBUG, "PTP-Track: Update")
152  .write("\r\n delta ").write10(delta_subns)
153  .write("\r\n elapsed").write10(elapsed_usec)
154  .write("\r\n output ").write10(result);
155  } else if (DEBUG_VERBOSE > 0) {
156  log::Log(log::DEBUG, "PTP-Track: Update")
157  .write10(delta_subns).write10(result);
158  }
159 }
160 
161 Time TrackingController::clock_adjust(const Time& amount)
162 {
163  // Clocks are added first-in-last-out, so final item is the primary.
164  Time result(0);
165  TrackingClock* item = m_clocks.head();
166  while (item) {
167  result = item->clock_adjust(amount);
168  item = m_clocks.next(item);
169  }
170  return result;
171 }
172 
173 void TrackingController::clock_rate(s64 offset)
174 {
175  TrackingClock* item = m_clocks.head();
176  while (item) {
177  item->clock_rate(offset);
178  item = m_clocks.next(item);
179  }
180 }
181 
182 static constexpr satcat5::ptp::CoeffPII DEFAULT_TIME_CONSTANT(3.0);
183 
186  satcat5::ptp::Source* source)
187  : TrackingController(clk, source)
188  , m_ctrl(DEFAULT_TIME_CONSTANT)
189 {
190  add_filter(&m_ampl);
191  add_filter(&m_ctrl);
192 }
193 
195  : Callback(source)
196  , m_clock(clk)
197 {
198  // Nothing else to initialize.
199 }
200 
202 {
203  Time delta = data.offset_from_master();
204  if (m_clock) m_clock->clock_adjust(-delta);
205 }
The Log class creates and formats one log message.
Definition: log.h:195
Log & write10(s32 val)
Print integer as a decimal value with no leading zeros.
Definition: log.cc:261
Log & write_obj(const T &obj)
Templated wrapper for custom output formatting.
Definition: log.h:251
Log & write(const char *str)
Formatting methods for various data types.
Definition: log.cc:198
PTP callback accepts each complete measurement from the Source.
Definition: ptp_source.h:38
Define the basic chain-of-filters API.
Definition: ptp_filters.h:38
virtual void rate(s64 delta_subns, u32 elapsed_usec)
Optional handler for fast-acquisition; override if required.
Definition: ptp_filters.h:48
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.
A source for ptp::Measurement events, usually a ptp::Client.
Definition: ptp_source.h:19
High-precision timestamp for use with PTP / IEEE1588.
Definition: ptp_time.h:41
s64 delta_subns() const
Convert time-differences to the designated unit.
Definition: ptp_time.cc:55
satcat5::ptp::Time abs() const
Standard arithmetic operations.
Definition: ptp_time.cc:112
Generic interface to a numerically-controlled reference clock.
Definition: ptp_tracking.h:52
virtual void clock_rate(s64 offset)=0
Adjust rate by a normalized frequency offset.
virtual satcat5::ptp::Time clock_adjust(const satcat5::ptp::Time &amount)=0
Make a one-time adjustment of the specified magnitude.
Bang-bang alternative to TrackingController.
Definition: ptp_tracking.h:201
void ptp_ready(const satcat5::ptp::Measurement &data) override
Event handler for ptp::Callback.
TrackingCoarse(satcat5::ptp::TrackingClock *clk, satcat5::ptp::Source *source)
Constructor links to a specific TrackingClock target.
Clock controller handling coarse and fine discipline.
Definition: ptp_tracking.h:91
void update(const satcat5::ptp::Time &delta)
Update filter state with a new measurement from the PTP client.
Definition: ptp_tracking.cc:73
void ptp_ready(const satcat5::ptp::Measurement &data) override
Event handler for ptp::Callback.
Definition: ptp_tracking.cc:48
void add_filter(satcat5::ptp::Filter *filter)
Add to the chain of processing filters.
Definition: ptp_tracking.h:118
void reset(bool linear=false)
Reset tracking filter(s) and begin free-wheeling.
Definition: ptp_tracking.cc:53
Simple all-in-one implementation of TrackingController.
Definition: ptp_tracking.h:184
TrackingSimple(satcat5::ptp::TrackingClock *clk, satcat5::ptp::Source *source)
Constructor links to a specific TrackingClock target.
T * next(const T *item) const
Fetch pointer to the next item.
Definition: list.h:261
Diagnostic logging to UART and/or Ethernet ports.
Generic network Dispatch API.
constexpr s64 SUBNS_PER_MSEC
Define commonly used scaling factors.
Definition: ptp_time.h:30
constexpr s64 USEC_PER_SEC
Define commonly used scaling factors.
Definition: ptp_time.h:26
constexpr s64 SUBNS_PER_USEC
Define commonly used scaling factors.
Definition: ptp_time.h:29
Closed-loop time-tracking clocks and tracking control.
Loop-filter coefficients for use with the "ControllerPII" class.
Definition: ptp_filters.h:295
Timestamps and metadata for a two-way time-transfer handshake.
satcat5::ptp::Time offset_from_master() const
Calculate PTP "offsetFromMaster".
unsigned increment_usec()
Measure elapsed time in microseconds, then increment by the returned quantized value.
Definition: timeref.cc:33
Miscellaneous mathematical utility functions.
constexpr T modulo(T a, T b)
Portability wrapper for platforms with signed division and modulo:
Definition: utils.h:203
constexpr u32 min_u32(u32 a, u32 b)
Min and max functions.
Definition: utils.h:103
constexpr T divide(T a, T b)
Portability wrapper for platforms with signed division and modulo:
Definition: utils.h:200