stm32_eth/ptp/
mod.rs

1//! PTP access and configuration.
2//!
3//! See [`EthernetPTP`] for a more details.
4
5use crate::{dma::EthernetDMA, hal::rcc::Clocks, mac::EthernetMAC, peripherals::ETHERNET_PTP};
6
7mod timestamp;
8pub use timestamp::Timestamp;
9
10#[cfg(all(not(feature = "stm32f1xx-hal"), feature = "async-await"))]
11use {core::task::Poll, futures::task::AtomicWaker};
12
13mod subseconds;
14pub use subseconds::{Subseconds, NANOS_PER_SECOND, SUBSECONDS_PER_SECOND, SUBSECONDS_TO_SECONDS};
15
16mod pps_pin;
17pub use pps_pin::PPSPin;
18
19/// Access to the IEEE 1588v2 PTP peripheral present on the ethernet peripheral.
20///
21/// On STM32FXXX's, the PTP peripheral has/uses the following important parts:
22/// * HCLK (the chip's high speed clock, configured externally).
23/// * The global timestamp (`global_time`, a [`Timestamp`]).
24/// * A subsecond increment register (`subsecond_increment`, a [`Subseconds`] with a value of 0 to 255, see [`EthernetPTP::subsecond_increment`]).
25/// * An accumulator register (`accumulator`, an [`u32`]).
26/// * An addend register (`addend`, an [`u32`], see [`EthernetPTP::addend`] and [`EthernetPTP::set_addend`]).
27///
28/// To ensure that `global_time` advances at the correct rate, the system performs the following steps:
29/// 1. On every clock of HCLK, `addend` is added to `accumulator`.
30/// 2. If `accumulator` overflows during step 1, add `subsecond_increment` to `global_time`.
31///
32/// When a new [`EthernetPTP`] is created, it is assumed that the frequency of HCLK is exactly correct.
33/// Using HCLK, values for `subsecond_increment` and `addend` are calculated so that `global_time` represents
34/// real-time.
35///
36/// Subsequently, `addend` can be adjusted to compensate for possible errors in HCLK, using [`EthernetPTP::addend`] and [`EthernetPTP::set_addend`]
37///
38/// To assess the correctness of the current speed at which `global_time` is running, one can use the
39/// following equation:
40///
41/// ```no_compile
42/// clock_ratio = ((2^31 / subsecond_increment) / (HCLK_HZ * (addend / 2^32)))
43/// ```
44/// Values greater than 1 indicate that the provided `HCLK_HZ` is less than the actual frequency of HCLK, which should
45/// be compensated by increasing `addend`. Values less than 1 indicate that the provided `HCLK_HZ` is greater than the
46/// actual frequency of HCLK, which should be compensated by decreasing `addend`.
47///
48/// [`NonZeroU8`]: core::num::NonZeroU8
49pub struct EthernetPTP {
50    eth_ptp: ETHERNET_PTP,
51}
52
53impl EthernetPTP {
54    // Calculate the `addend` required for running `global_time` at
55    // the correct rate
56    const fn calculate_regs(hclk: u32) -> (Subseconds, u32) {
57        let half_hclk = hclk / 2;
58
59        // Calculate the closest `subsecond_increment` we can use if we want to update at a
60        // frequency of `half_hclk`
61        let stssi = Subseconds::nearest_increment(half_hclk);
62        let half_rate_subsec_increment_hz = stssi.hertz();
63
64        // Calculate the `addend` required for running `global_time` at
65        // the correct rate, given that we increment `global_time` by `stssi` every
66        // time `accumulator` overflows.
67        let tsa = ((half_rate_subsec_increment_hz as u64 * u32::MAX as u64) / hclk as u64) as u32;
68        (stssi, tsa)
69    }
70
71    pub(crate) fn new(
72        eth_ptp: ETHERNET_PTP,
73        clocks: Clocks,
74        // Note(_dma): this field exists to ensure that the PTP is not
75        // initialized before the DMA. If PTP is started before the DMA,
76        // it doesn't work.
77        _dma: &EthernetDMA,
78    ) -> Self {
79        // Mask timestamp interrupt register
80        EthernetMAC::mask_timestamp_trigger_interrupt();
81
82        let hclk = clocks.hclk().to_Hz();
83
84        let (stssi, tsa) = Self::calculate_regs(hclk);
85
86        // Setup PTP timestamping in fine mode.
87        eth_ptp.ptptscr.write(|w| {
88            // Enable snapshots for all frames.
89            #[cfg(not(feature = "stm32f1xx-hal"))]
90            let w = w.tssarfe().set_bit();
91
92            w.tse().set_bit().tsfcu().set_bit()
93        });
94
95        // Set up subsecond increment
96        eth_ptp
97            .ptpssir
98            .write(|w| unsafe { w.stssi().bits(stssi.raw() as u8) });
99
100        let mut me = Self { eth_ptp };
101
102        me.set_addend(tsa);
103        me.set_time(Timestamp::new_unchecked(false, 0, 0));
104
105        me
106    }
107
108    /// Get the configured subsecond increment.
109    pub fn subsecond_increment(&self) -> Subseconds {
110        Subseconds::new_unchecked(self.eth_ptp.ptpssir.read().stssi().bits() as u32)
111    }
112
113    /// Get the currently configured PTP clock addend.
114    pub fn addend(&self) -> u32 {
115        self.eth_ptp.ptptsar.read().bits()
116    }
117
118    /// Set the PTP clock addend.
119    #[inline(always)]
120    pub fn set_addend(&mut self, rate: u32) {
121        let ptp = &self.eth_ptp;
122        ptp.ptptsar.write(|w| unsafe { w.bits(rate) });
123
124        #[cfg(feature = "stm32f1xx-hal")]
125        {
126            while ptp.ptptscr.read().tsaru().bit_is_set() {}
127            ptp.ptptscr.modify(|_, w| w.tsaru().set_bit());
128            while ptp.ptptscr.read().tsaru().bit_is_set() {}
129        }
130
131        #[cfg(not(feature = "stm32f1xx-hal"))]
132        {
133            while ptp.ptptscr.read().ttsaru().bit_is_set() {}
134            ptp.ptptscr.modify(|_, w| w.ttsaru().set_bit());
135            while ptp.ptptscr.read().ttsaru().bit_is_set() {}
136        }
137    }
138
139    /// Set the current time.
140    pub fn set_time(&mut self, time: Timestamp) {
141        let ptp = &self.eth_ptp;
142
143        let seconds = time.seconds();
144        let subseconds = time.subseconds_signed();
145
146        ptp.ptptshur.write(|w| unsafe { w.bits(seconds) });
147        ptp.ptptslur.write(|w| unsafe { w.bits(subseconds) });
148
149        // Initialise timestamp
150        while ptp.ptptscr.read().tssti().bit_is_set() {}
151        ptp.ptptscr.modify(|_, w| w.tssti().set_bit());
152        while ptp.ptptscr.read().tssti().bit_is_set() {}
153    }
154
155    /// Add the provided time to the current time, atomically.
156    ///
157    /// If `time` is negative, it will instead be subtracted from the
158    /// system time.
159    pub fn update_time(&mut self, time: Timestamp) {
160        let ptp = &self.eth_ptp;
161
162        let seconds = time.seconds();
163        let subseconds = time.subseconds_signed();
164
165        ptp.ptptshur.write(|w| unsafe { w.bits(seconds) });
166        ptp.ptptslur.write(|w| unsafe { w.bits(subseconds) });
167
168        // Add timestamp to global time
169
170        let read_status = || {
171            let scr = ptp.ptptscr.read();
172            scr.tsstu().bit_is_set() || scr.tssti().bit_is_set()
173        };
174
175        while read_status() {}
176        ptp.ptptscr.modify(|_, w| w.tsstu().set_bit());
177        while ptp.ptptscr.read().tsstu().bit_is_set() {}
178    }
179
180    /// Get the current time
181    pub fn now() -> Timestamp {
182        Self::get_time()
183    }
184
185    /// Get the current time.
186    pub fn get_time() -> Timestamp {
187        let try_read_time = || {
188            // SAFETY: we only atomically read registers.
189            let eth_ptp = unsafe { &*ETHERNET_PTP::ptr() };
190
191            let seconds = eth_ptp.ptptshr.read().bits();
192            let subseconds = eth_ptp.ptptslr.read().bits();
193            let seconds_after = eth_ptp.ptptshr.read().bits();
194
195            if seconds == seconds_after {
196                Ok(Timestamp::from_parts(seconds, subseconds))
197            } else {
198                Err(())
199            }
200        };
201
202        loop {
203            if let Ok(res) = try_read_time() {
204                return res;
205            }
206        }
207    }
208
209    /// Enable the PPS output on the provided pin.
210    pub fn enable_pps<P>(&mut self, pin: P) -> P::Output
211    where
212        P: PPSPin,
213    {
214        pin.enable()
215    }
216}
217
218/// Setting and configuring target time interrupts on the STM32F107 does not
219/// make any sense: we can generate the interrupt, but it is impossible to
220/// clear the flag as the register required to do so does not exist.
221#[cfg(not(feature = "stm32f1xx-hal"))]
222impl EthernetPTP {
223    #[cfg(feature = "async-await")]
224    fn waker() -> &'static AtomicWaker {
225        static WAKER: AtomicWaker = AtomicWaker::new();
226        &WAKER
227    }
228
229    /// Configure the target time.
230    fn set_target_time(&mut self, timestamp: Timestamp) {
231        let (high, low) = (timestamp.seconds(), timestamp.subseconds_signed());
232        self.eth_ptp
233            .ptptthr
234            .write(|w| unsafe { w.ttsh().bits(high) });
235        self.eth_ptp
236            .ptpttlr
237            .write(|w| unsafe { w.ttsl().bits(low) });
238    }
239
240    /// Configure the target time interrupt.
241    ///
242    /// You must call [`EthernetPTP::interrupt_handler`] in the `ETH`
243    /// interrupt to detect (and clear) the correct status bits.
244    pub fn configure_target_time_interrupt(&mut self, timestamp: Timestamp) {
245        self.set_target_time(timestamp);
246        self.eth_ptp.ptptscr.modify(|_, w| w.tsite().set_bit());
247        EthernetMAC::unmask_timestamp_trigger_interrupt();
248    }
249
250    /// Wait until the specified time.
251    #[cfg(feature = "async-await")]
252    pub async fn wait_until(&mut self, timestamp: Timestamp) {
253        self.configure_target_time_interrupt(timestamp);
254        core::future::poll_fn(|ctx| {
255            if EthernetPTP::read_and_clear_interrupt_flag() {
256                Poll::Ready(())
257            } else if EthernetPTP::get_time().raw() >= timestamp.raw() {
258                Poll::Ready(())
259            } else {
260                EthernetPTP::waker().register(ctx.waker());
261                Poll::Pending
262            }
263        })
264        .await;
265    }
266
267    #[inline(always)]
268    fn read_and_clear_interrupt_flag() -> bool {
269        let eth_ptp = unsafe { &*ETHERNET_PTP::ptr() };
270        eth_ptp.ptptssr.read().tsttr().bit_is_set()
271    }
272
273    /// Handle the PTP parts of the `ETH` interrupt.
274    ///
275    /// Returns a boolean indicating whether or not the interrupt
276    /// was caused by a Timestamp trigger and clears the interrupt
277    /// flag.
278    pub fn interrupt_handler() -> bool {
279        // SAFETY: we only perform one atomic read.
280        let eth_mac = unsafe { &*crate::peripherals::ETHERNET_MAC::ptr() };
281
282        let is_tsint = eth_mac.macsr.read().tsts().bit_is_set();
283        if is_tsint {
284            EthernetMAC::mask_timestamp_trigger_interrupt();
285        }
286
287        #[cfg(feature = "async-await")]
288        if let Some(waker) = EthernetPTP::waker().take() {
289            waker.wake();
290        } else {
291            EthernetPTP::read_and_clear_interrupt_flag();
292        }
293
294        #[cfg(not(feature = "async-await"))]
295        EthernetPTP::read_and_clear_interrupt_flag();
296
297        is_tsint
298    }
299
300    /// Configure the PPS output frequency.
301    ///
302    /// The PPS output frequency becomes `2 ^ pps_freq`. `pps_freq` is
303    /// clamped to `[0..31]`.
304    pub fn set_pps_freq(&mut self, pps_freq: u8) {
305        let pps_freq = pps_freq.min(31);
306
307        // SAFETY: we atomically write to the PTPPPSCR register, which is
308        // not read or written to anywhere else. The SVD files are incorrectly
309        // saying that the bits in this register are read-only.
310        unsafe {
311            let ptpppscr = self.eth_ptp.ptpppscr.as_ptr() as *mut u32;
312            core::ptr::write_volatile(ptpppscr, pps_freq as u32);
313        }
314    }
315}
316
317#[cfg(all(test, not(target_os = "none")))]
318mod test {
319
320    use super::*;
321
322    // Test that we get accurate addend and subsecond_increment values
323    // with the provided clock speeds.
324    #[test]
325    fn hclk_to_regs() {
326        for hclk_hz in (25..180).map(|v| v * 1_000_000) {
327            let (stssi, tsa) = EthernetPTP::calculate_regs(hclk_hz);
328
329            let stssi = stssi.raw() as f64;
330            let tsa = tsa as f64;
331
332            // calculate the clock ratio
333            let clock_ratio = (SUBSECONDS_PER_SECOND as f64 / stssi)
334                / (hclk_hz as f64 * (tsa / 0xFFFF_FFFFu32 as f64));
335
336            let ppm = (clock_ratio - 1f64) * 1_000_000f64;
337
338            assert!(ppm <= 0.06, "{} at {}", ppm, hclk_hz);
339        }
340    }
341}