SatCat5
cfgbus_pps.cc
1 // Copyright 2024-2025 The Aerospace Corporation.
3 // This file is a part of SatCat5, licensed under CERN-OHL-W v2 or later.
5 
6 #include <satcat5/cfgbus_pps.h>
7 #include <satcat5/ptp_tracking.h>
8 
13 
14 PpsInput::PpsInput(Register reg, PpsInput::Edge mode)
15  : m_reg(reg)
16  , m_callback(0)
17  , m_pps_time(0)
18  , m_mode(mode)
19  , m_offset(0)
20  , m_period(0)
21 {
22  reset(mode);
23  timer_every(50);
24 }
25 
27  // Set the configuration register.
28  *m_reg = u32(mode);
29  m_mode = mode;
30 
31  // One-second or half-second interval?
32  m_period = satcat5::ptp::SUBNS_PER_SEC;
33  if (mode == Edge::BOTH) m_period /= 2;
34 }
35 
36 bool PpsInput::read_pulse() {
37  // Bit-masks used for the FIFO register.
38  constexpr u32 REG_LAST = (1u << 31);
39  constexpr u32 REG_VALID = (1u << 30);
40  // TODO: constexpr u32 REG_RISE = (1u << 24);
41  constexpr u32 REG_DATA = (1u << 24) - 1;
42 
43  // Any data available?
44  bool ok = false;
45  u32 reg0 = *m_reg;
46  if (reg0 & REG_VALID) {
47  // Read the rest of the pulse descriptor (4 words total).
48  u32 reg1 = *m_reg;
49  u32 reg2 = *m_reg;
50  u32 reg3 = *m_reg;
51 
52  // Is the pulse descriptor valid?
53  ok = (reg1 & REG_VALID) && (reg2 & REG_VALID) && (reg3 & REG_LAST);
54  if (ok) {
55  // Read the 48-bit whole-second component in reg0 and reg1 and the
56  // 48-bit fractional-second component in reg2 and reg3.
57  u64 secs = u64(reg0 & REG_DATA) << 24 | u64(reg1 & REG_DATA);
58  u64 subns = u64(reg2 & REG_DATA) << 24 | u64(reg3 & REG_DATA);
59 
60  // No ptp::Time constructor for 64-bit subns with seconds.
61  m_pps_time = satcat5::ptp::Time(secs, 0)
62  + satcat5::ptp::Time((s64) subns); // Safe cast of 48-bit value.
63 
64  // Calculate phase-difference from nominal.
65  // PPS signal should be aligned to the GPS epoch + m_offset.
66  // After subtraction, range is now -0.5 to +1.5 seconds.
67  s64 phase = s64(subns) - m_offset;
68 
69  // Normalize to the nearest half-period, depending on mode.
70  // (i.e., 0 to 999 msec remaps to +/-500 or +/-250 msec.)
71  // TODO: In both-edges mode, use polarity bit REG_RISE to prevent
72  // false-lock at a 500 millisecond offset from the intended phase.
73  while (phase > m_period/2) phase -= m_period;
74 
75  // If a callback exists, notify it.
76  // Timestamp of +0.1 sec means our local clock is running fast,
77  // so slow it down by applying a negative control signal.
78  satcat5::ptp::Time delta(-phase);
79  if (m_callback) m_callback->pps_event(m_pps_time, delta);
80  }
81  }
82 
83  return ok;
84 }
85 
87  // Keep reading until we exhaust the FIFO.
88  while (read_pulse()) {}
89 }
90 
91 PpsOutput::PpsOutput(Register reg, bool rising)
92  : m_reg(reg)
93  , m_offset(0)
94  , m_rising(rising)
95 {
96  timer_once(50); // delay configuration to make sure module out of reset
97 }
98 
100  configure();
101 }
102 
103 void PpsOutput::set_offset(s64 offset) {
104  m_offset = offset;
105  configure();
106 }
107 
108 void PpsOutput::set_polarity(bool rising) {
109  m_rising = rising;
110  configure();
111 }
112 
113 // Update requires two consecutive writes, then a read.
114 static inline u32 wide_write(Register& reg, u64 val) {
115  *reg = u32(val >> 32); // Write MSBs
116  *reg = u32(val >> 0); // Write LSBs
117  return *reg; // Read + Discard
118 }
119 
120 void PpsOutput::configure() {
121  constexpr u64 REG_RISING = (1ull << 63);
122  constexpr u64 REG_OFFSET = (1ull << 48) - 1;
123 
124  // Format the configuration word.
125  u64 cfg = u64(m_offset) & REG_OFFSET;
126  if (m_rising) cfg |= REG_RISING;
127 
128  // Update the hardware configuration register.
129  // Note: Cast to void prevents unused-value warnings.
130  (void)wide_write(m_reg, cfg);
131 }
132 
133 RefOutput::RefOutput(Register reg_phase, Register reg_period, bool rising)
134  : PpsOutput(reg_phase, rising)
135  , m_reg_period(reg_period)
136  , m_freq_hz(0)
137 {
138  set_frequency(1);
139 }
140 
141 void RefOutput::set_frequency(u32 freq_hz) {
142  m_freq_hz = freq_hz;
143  u64 subns = satcat5::util::div_round<u64>(
144  u64(satcat5::ptp::SUBNS_PER_SEC), u64(freq_hz));
145  (void)wide_write(m_reg_period, subns);
146 }
Pulse-per-second (PPS) input and output.
Driver for the PPS input block (ptp_pps_in.vhd).
Definition: cfgbus_pps.h:41
Edge
Detect rising edges, falling edges, or both.
Definition: cfgbus_pps.h:46
void reset(Edge mode=Edge::RISING)
Clear FIFO and set the active edge (rising or falling).
Definition: cfgbus_pps.cc:26
void timer_event() override
Child class MUST override this method.
Definition: cfgbus_pps.cc:86
Driver for the PPS output block (ptp_pps_out.vhd).
Definition: cfgbus_pps.h:95
void set_polarity(bool rising)
Set the rising- or falling-edge polarity of this output.
Definition: cfgbus_pps.cc:108
PpsOutput(satcat5::cfg::Register reg, bool rising=true)
Link this driver to the hardware control register.
Definition: cfgbus_pps.cc:91
void set_offset(s64 offset)
Adjust the phase-offset for this output.
Definition: cfgbus_pps.cc:103
void timer_event() override
Child class MUST override this method.
Definition: cfgbus_pps.cc:99
Variant of PpsOutput with frequency-control enabled.
Definition: cfgbus_pps.h:131
RefOutput(satcat5::cfg::Register reg_phase, satcat5::cfg::Register reg_period, bool rising=true)
Link this driver to the hardware control register.
Definition: cfgbus_pps.cc:133
void set_frequency(u32 freq_hz)
Adjust the frequency for this output.
Definition: cfgbus_pps.cc:141
Pointer-like wrapper for one or more ConfigBus registers.
Definition: cfgbus_core.h:76
void timer_once(unsigned msec)
Configure a one-time notification after X milliseconds.
Definition: polling.cc:316
void timer_every(unsigned msec)
Configure a repeating notification every X milliseconds.
Definition: polling.cc:321
High-precision timestamp for use with PTP / IEEE1588.
Definition: ptp_time.h:41
Closed-loop time-tracking clocks and tracking control.