Skip to content

Commit 4d0ded4

Browse files
committed
Add async-rtic-timestamp example
1 parent 3520d66 commit 4d0ded4

File tree

2 files changed

+285
-2
lines changed

2 files changed

+285
-2
lines changed

Cargo.toml

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,16 @@ smoltcp = { version = "0.9", features = [ "medium-ethernet", "proto-ipv4", "sock
7878
[dev-dependencies.rtic]
7979
package = "cortex-m-rtic"
8080
version = "1.0"
81-
# git = "https://github.com/rtic-rs/cortex-m-rtic.git"
82-
# rev = "613b3c59fc841caa3ca8c4d7ac440fbfa2f71fd1"
81+
82+
[dev-dependencies.async-rtic]
83+
package = "cortex-m-rtic"
84+
git = "https://github.com/rtic-rs/cortex-m-rtic.git"
85+
rev = "613b3c59fc841caa3ca8c4d7ac440fbfa2f71fd1"
86+
87+
[dev-dependencies.arbiter]
88+
package = "rtic-arbiter"
89+
git = "https://github.com/rtic-rs/cortex-m-rtic.git"
90+
rev = "364edb707ebf7baa2e7bdd1b1e6b02156d7b5569"
8391

8492
# This isn't an actual example. It just exists so we can easily
8593
# test the common items :)
@@ -117,6 +125,10 @@ name = "timesync-server"
117125
path = "./examples/timesync/server.rs"
118126
required-features = [ "defmt", "ptp" ]
119127

128+
[[example]]
129+
name = "async-rtic-timestamp"
130+
required-features = [ "defmt", "ptp", "async-await" ]
131+
120132
[profile.release]
121133
debug = 2
122134
lto = true

examples/async-rtic-timestamp.rs

Lines changed: 271 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,271 @@
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(&ETH_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

Comments
 (0)