|
| 1 | +#![no_std] |
| 2 | +#![no_main] |
| 3 | +#![feature(type_alias_impl_trait)] |
| 4 | + |
| 5 | +//! For build and run instructions, see README.md |
| 6 | +//! |
| 7 | +//! This example is used to demonstrate async abilities, and requires nightly to build. |
| 8 | +//! |
| 9 | +//! A very rudimentary PTP synchronization example built using RTIC, on the async branch. |
| 10 | +//! |
| 11 | +//! The example requires that at least two nodes are running at the same time, |
| 12 | +//! and the time synchronization that occurs does not explicitly compensate for |
| 13 | +//! network delays. |
| 14 | +//! |
| 15 | +//! All nodes send traffic to a specific MAC address (AB:CD:EF:12:34:56) with an unused |
| 16 | +//! EtherType (0xFFFF), containing nothing but the raw value of a [`Timestamp`]. Upon reception |
| 17 | +//! of such a frame, the node will parse the timestamp, compare it to when the frame was received |
| 18 | +//! according to the local time, and do one of following: |
| 19 | +//! |
| 20 | +//! 1. If the difference is larger than 20 microseconds, the current local time is set to the |
| 21 | +//! received value. |
| 22 | +//! 2. If the difference is smaller than or equal to 20 microseconds, the PTP addend value is updated |
| 23 | +//! to compensate for the observed difference. |
| 24 | +//! |
| 25 | +//! When using the internal oscillator of an STM32, step 2 will (almost) never occur, as the frequency |
| 26 | +//! drift and error with this clock is too great to accurately compensate for. However, |
| 27 | +//! if a more accurate High Speed External oscillator is connected to your MCU, even this very basic |
| 28 | +//! synchronization scheme can synchronize the rate of time on two nodes to within a few PPMs. |
| 29 | +//! |
| 30 | +//! To activate the HSE configuration for the examples, set the `STM32_ETH_EXAMPLE_HSE` environment variable |
| 31 | +//! to `oscillator` or `bypass` when compiling examples. |
| 32 | +
|
| 33 | +use defmt_rtt as _; |
| 34 | +use panic_probe as _; |
| 35 | + |
| 36 | +mod common; |
| 37 | + |
| 38 | +extern crate async_rtic as rtic; |
| 39 | + |
| 40 | +#[rtic::app(device = stm32_eth::stm32, dispatchers = [SPI1])] |
| 41 | +mod app { |
| 42 | + |
| 43 | + use async_rtic as rtic; |
| 44 | + |
| 45 | + use crate::common::EthernetPhy; |
| 46 | + |
| 47 | + use fugit::ExtU64; |
| 48 | + |
| 49 | + use arbiter::Arbiter; |
| 50 | + |
| 51 | + use ieee802_3_miim::{phy::PhySpeed, Phy}; |
| 52 | + use systick_monotonic::Systick; |
| 53 | + |
| 54 | + use stm32_eth::{ |
| 55 | + dma::{EthernetDMA, PacketId, RxRing, RxRingEntry, TxRing, TxRingEntry}, |
| 56 | + mac::Speed, |
| 57 | + ptp::{EthernetPTP, Subseconds, Timestamp}, |
| 58 | + Parts, |
| 59 | + }; |
| 60 | + |
| 61 | + use core::mem::MaybeUninit; |
| 62 | + |
| 63 | + #[local] |
| 64 | + struct Local {} |
| 65 | + |
| 66 | + #[shared] |
| 67 | + struct Shared {} |
| 68 | + |
| 69 | + #[monotonic(binds = SysTick, default = true)] |
| 70 | + type Monotonic = Systick<1000>; |
| 71 | + |
| 72 | + #[init(local = [ |
| 73 | + rx_ring: [RxRingEntry; 2] = [RxRingEntry::new(),RxRingEntry::new()], |
| 74 | + tx_ring: [TxRingEntry; 2] = [TxRingEntry::new(),TxRingEntry::new()], |
| 75 | + dma: MaybeUninit<EthernetDMA<'static, 'static>> = MaybeUninit::uninit(), |
| 76 | + arbiter: MaybeUninit<Arbiter<EthernetPTP> > = MaybeUninit::uninit(), |
| 77 | + ])] |
| 78 | + fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) { |
| 79 | + defmt::info!("Pre-init"); |
| 80 | + let core = cx.core; |
| 81 | + let p = cx.device; |
| 82 | + |
| 83 | + let rx_ring = cx.local.rx_ring; |
| 84 | + let tx_ring = cx.local.tx_ring; |
| 85 | + |
| 86 | + let (clocks, gpio, ethernet) = crate::common::setup_peripherals(p); |
| 87 | + let mono = Systick::new(core.SYST, clocks.hclk().raw()); |
| 88 | + |
| 89 | + defmt::info!("Setting up pins"); |
| 90 | + let (pins, mdio, mdc, pps) = crate::common::setup_pins(gpio); |
| 91 | + |
| 92 | + defmt::info!("Configuring ethernet"); |
| 93 | + |
| 94 | + let Parts { dma, mac, mut ptp } = |
| 95 | + stm32_eth::new_with_mii(ethernet, rx_ring, tx_ring, clocks, pins, mdio, mdc).unwrap(); |
| 96 | + |
| 97 | + let dma = cx.local.dma.write(dma); |
| 98 | + |
| 99 | + ptp.enable_pps(pps); |
| 100 | + |
| 101 | + let arbiter = cx.local.arbiter.write(Arbiter::new(ptp)); |
| 102 | + |
| 103 | + defmt::info!("Enabling interrupts"); |
| 104 | + dma.enable_interrupt(); |
| 105 | + |
| 106 | + let (rx, tx) = dma.split(); |
| 107 | + |
| 108 | + match EthernetPhy::from_miim(mac, 0) { |
| 109 | + Ok(mut phy) => { |
| 110 | + defmt::info!( |
| 111 | + "Resetting PHY as an extra step. Type: {}", |
| 112 | + phy.ident_string() |
| 113 | + ); |
| 114 | + |
| 115 | + phy.phy_init(); |
| 116 | + |
| 117 | + defmt::info!("Waiting for link up."); |
| 118 | + |
| 119 | + while !phy.phy_link_up() {} |
| 120 | + |
| 121 | + defmt::info!("Link up."); |
| 122 | + |
| 123 | + if let Some(speed) = phy.speed().map(|s| match s { |
| 124 | + PhySpeed::HalfDuplexBase10T => Speed::HalfDuplexBase10T, |
| 125 | + PhySpeed::FullDuplexBase10T => Speed::FullDuplexBase10T, |
| 126 | + PhySpeed::HalfDuplexBase100Tx => Speed::HalfDuplexBase100Tx, |
| 127 | + PhySpeed::FullDuplexBase100Tx => Speed::FullDuplexBase100Tx, |
| 128 | + }) { |
| 129 | + phy.get_miim().set_speed(speed); |
| 130 | + defmt::info!("Detected link speed: {}", speed); |
| 131 | + } else { |
| 132 | + defmt::warn!("Failed to detect link speed."); |
| 133 | + } |
| 134 | + } |
| 135 | + Err(_) => { |
| 136 | + defmt::info!("Not resetting unsupported PHY. Cannot detect link speed."); |
| 137 | + } |
| 138 | + }; |
| 139 | + |
| 140 | + sender::spawn(tx).ok(); |
| 141 | + receiver::spawn(rx, arbiter).ok(); |
| 142 | + ptp_scheduler::spawn(arbiter).ok(); |
| 143 | + |
| 144 | + (Shared {}, Local {}, init::Monotonics(mono)) |
| 145 | + } |
| 146 | + |
| 147 | + #[task] |
| 148 | + async fn receiver( |
| 149 | + _: receiver::Context, |
| 150 | + rx: &'static mut RxRing<'static>, |
| 151 | + ptp: &'static Arbiter<EthernetPTP>, |
| 152 | + ) { |
| 153 | + let mut packet_id_counter = 0; |
| 154 | + loop { |
| 155 | + let packet_id = PacketId(packet_id_counter); |
| 156 | + let packet = rx.recv(Some(packet_id.clone())).await; |
| 157 | + |
| 158 | + let dst_mac = &packet[0..6]; |
| 159 | + |
| 160 | + let rx_timestamp = if let Some(timestamp) = packet.timestamp() { |
| 161 | + timestamp |
| 162 | + } else { |
| 163 | + continue; |
| 164 | + }; |
| 165 | + |
| 166 | + defmt::debug!("RX timestamp: {}", rx_timestamp); |
| 167 | + |
| 168 | + if dst_mac == [0xAB, 0xCD, 0xEF, 0x12, 0x34, 0x56] { |
| 169 | + let mut timestamp_data = [0u8; 8]; |
| 170 | + timestamp_data.copy_from_slice(&packet[14..22]); |
| 171 | + let raw = i64::from_be_bytes(timestamp_data); |
| 172 | + |
| 173 | + let timestamp = Timestamp::new_raw(raw); |
| 174 | + |
| 175 | + defmt::debug!("Contained TX timestamp: {}", rx_timestamp); |
| 176 | + |
| 177 | + let diff = timestamp - rx_timestamp; |
| 178 | + |
| 179 | + defmt::info!("Difference between TX and RX time: {}", diff); |
| 180 | + |
| 181 | + let mut ptp = ptp.access().await; |
| 182 | + let addend = ptp.addend(); |
| 183 | + let nanos = diff.nanos() as u64; |
| 184 | + |
| 185 | + if nanos <= 20_000 { |
| 186 | + let p1 = ((nanos * addend as u64) / 1_000_000_000) as u32; |
| 187 | + |
| 188 | + defmt::debug!("Addend correction value: {}", p1); |
| 189 | + |
| 190 | + if diff.is_negative() { |
| 191 | + ptp.set_addend(addend - p1 / 2); |
| 192 | + } else { |
| 193 | + ptp.set_addend(addend + p1 / 2); |
| 194 | + }; |
| 195 | + } else { |
| 196 | + defmt::warn!("Updated time."); |
| 197 | + ptp.update_time(diff); |
| 198 | + } |
| 199 | + drop(ptp); |
| 200 | + } |
| 201 | + |
| 202 | + drop(packet); |
| 203 | + |
| 204 | + let polled_ts = rx.timestamp(&packet_id); |
| 205 | + |
| 206 | + assert_eq!(polled_ts, Ok(Some(rx_timestamp))); |
| 207 | + |
| 208 | + packet_id_counter += 1; |
| 209 | + packet_id_counter &= !0x8000_0000; |
| 210 | + } |
| 211 | + } |
| 212 | + |
| 213 | + #[task] |
| 214 | + async fn ptp_scheduler(_: ptp_scheduler::Context, ptp: &'static Arbiter<EthernetPTP>) { |
| 215 | + loop { |
| 216 | + let mut ptp = ptp.access().await; |
| 217 | + let start = EthernetPTP::now(); |
| 218 | + let int_time = start + Timestamp::new_raw(Subseconds::MAX_VALUE as i64); |
| 219 | + ptp.wait_until(int_time).await; |
| 220 | + let now = EthernetPTP::now(); |
| 221 | + |
| 222 | + defmt::info!("Got to PTP time after {}.", now - start); |
| 223 | + } |
| 224 | + } |
| 225 | + |
| 226 | + #[task] |
| 227 | + async fn sender(_: sender::Context, tx: &'static mut TxRing<'static>) { |
| 228 | + let mut tx_id_ctr = 0x8000_0000; |
| 229 | + |
| 230 | + const SIZE: usize = 42; |
| 231 | + |
| 232 | + loop { |
| 233 | + // Obtain the current time to use as the "TX time" of our frame. It is clearly |
| 234 | + // incorrect, but works well enough in low-activity systems (such as this example). |
| 235 | + let now = EthernetPTP::now(); |
| 236 | + let start = monotonics::now(); |
| 237 | + |
| 238 | + const DST_MAC: [u8; 6] = [0xAB, 0xCD, 0xEF, 0x12, 0x34, 0x56]; |
| 239 | + const SRC_MAC: [u8; 6] = [0x00, 0x00, 0xDE, 0xAD, 0xBE, 0xEF]; |
| 240 | + const ETH_TYPE: [u8; 2] = [0xFF, 0xFF]; // Custom/unknown ethertype |
| 241 | + |
| 242 | + let tx_id_val = tx_id_ctr; |
| 243 | + let packet_id = PacketId(tx_id_val); |
| 244 | + let mut tx_buffer = tx.prepare_packet(SIZE, Some(packet_id.clone())).await; |
| 245 | + // Write the Ethernet Header and the current timestamp value to |
| 246 | + // the frame. |
| 247 | + tx_buffer[0..6].copy_from_slice(&DST_MAC); |
| 248 | + tx_buffer[6..12].copy_from_slice(&SRC_MAC); |
| 249 | + tx_buffer[12..14].copy_from_slice(Ð_TYPE); |
| 250 | + tx_buffer[14..22].copy_from_slice(&now.raw().to_be_bytes()); |
| 251 | + |
| 252 | + tx_buffer.send(); |
| 253 | + |
| 254 | + tx_id_ctr += 1; |
| 255 | + tx_id_ctr |= 0x8000_0000; |
| 256 | + |
| 257 | + let timestamp = tx.timestamp(&packet_id).await; |
| 258 | + |
| 259 | + if let Ok(Some(timestamp)) = timestamp { |
| 260 | + defmt::info!("Tx timestamp: {}", timestamp); |
| 261 | + } |
| 262 | + |
| 263 | + monotonics::delay_until(start + 500.millis()).await; |
| 264 | + } |
| 265 | + } |
| 266 | + |
| 267 | + #[task(binds = ETH, priority = 2)] |
| 268 | + fn eth_interrupt(_: eth_interrupt::Context) { |
| 269 | + stm32_eth::eth_interrupt_handler(); |
| 270 | + } |
| 271 | +} |
0 commit comments