Skip to content

Commit 7616b5e

Browse files
feat(example): Add SNTP example to show how to update Rtc
1 parent 588140a commit 7616b5e

File tree

3 files changed

+375
-0
lines changed

3 files changed

+375
-0
lines changed
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
[target.'cfg(target_arch = "riscv32")']
2+
runner = "espflash flash --monitor"
3+
rustflags = [
4+
"-C", "link-arg=-Tlinkall.x",
5+
"-C", "force-frame-pointers",
6+
]
7+
8+
[target.'cfg(target_arch = "xtensa")']
9+
runner = "espflash flash --monitor"
10+
rustflags = [
11+
# GNU LD
12+
"-C", "link-arg=-Wl,-Tlinkall.x",
13+
"-C", "link-arg=-nostartfiles",
14+
15+
# LLD
16+
# "-C", "link-arg=-Tlinkall.x",
17+
# "-C", "linker=rust-lld",
18+
]
19+
20+
[env]
21+
ESP_LOG = "info"
22+
SSID = "SSID"
23+
PASSWORD = "PASSWORD"
24+
25+
[unstable]
26+
build-std = ["alloc", "core"]

examples/wifi/sntp/Cargo.toml

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
[package]
2+
name = "sntp"
3+
version = "0.0.0"
4+
edition = "2024"
5+
publish = false
6+
7+
[dependencies]
8+
cfg-if = "1.0.0"
9+
embassy-executor = { version = "0.7.0", features = ["nightly"] }
10+
embassy-net = { version = "0.6.0", features = [
11+
"dhcpv4",
12+
"medium-ethernet",
13+
"udp",
14+
"dns",
15+
] }
16+
embassy-time = "0.4.0"
17+
embedded-io-async = "0.6.1"
18+
esp-alloc = { path = "../../../esp-alloc" }
19+
esp-backtrace = { path = "../../../esp-backtrace", features = [
20+
"panic-handler",
21+
"println",
22+
] }
23+
esp-bootloader-esp-idf = { path = "../../../esp-bootloader-esp-idf" }
24+
esp-hal-embassy = { path = "../../../esp-hal-embassy" }
25+
esp-hal = { path = "../../../esp-hal", features = ["log-04", "unstable"] }
26+
esp-println = { path = "../../../esp-println", features = ["log-04"] }
27+
esp-preempt = { path = "../../../esp-preempt", features = ["log-04"] }
28+
log = "0.4.17"
29+
esp-radio = { path = "../../../esp-radio", features = [
30+
"log-04",
31+
"unstable",
32+
"wifi",
33+
] }
34+
static_cell = "2.1.0"
35+
sntpc = { version = "0.6.0", default-features = false, features = [
36+
"embassy-socket",
37+
] }
38+
jiff = { version = "0.2.10", default-features = false, features = ["static"] }
39+
40+
[features]
41+
esp32 = [
42+
"esp-backtrace/esp32",
43+
"esp-bootloader-esp-idf/esp32",
44+
"esp-hal-embassy/esp32",
45+
"esp-hal/esp32",
46+
"esp-preempt/esp32",
47+
"esp-radio/esp32",
48+
]
49+
esp32c2 = [
50+
"esp-backtrace/esp32c2",
51+
"esp-bootloader-esp-idf/esp32c2",
52+
"esp-hal-embassy/esp32c2",
53+
"esp-hal/esp32c2",
54+
"esp-preempt/esp32c2",
55+
"esp-radio/esp32c2",
56+
]
57+
esp32c3 = [
58+
"esp-backtrace/esp32c3",
59+
"esp-bootloader-esp-idf/esp32c3",
60+
"esp-hal-embassy/esp32c3",
61+
"esp-hal/esp32c3",
62+
"esp-preempt/esp32c3",
63+
"esp-radio/esp32c3",
64+
]
65+
esp32c6 = [
66+
"esp-backtrace/esp32c6",
67+
"esp-bootloader-esp-idf/esp32c6",
68+
"esp-hal-embassy/esp32c6",
69+
"esp-hal/esp32c6",
70+
"esp-preempt/esp32c6",
71+
"esp-radio/esp32c6",
72+
]
73+
esp32s2 = [
74+
"esp-backtrace/esp32s2",
75+
"esp-bootloader-esp-idf/esp32s2",
76+
"esp-hal-embassy/esp32s2",
77+
"esp-hal/esp32s2",
78+
"esp-preempt/esp32s2",
79+
"esp-radio/esp32s2",
80+
]
81+
esp32s3 = [
82+
"esp-backtrace/esp32s3",
83+
"esp-bootloader-esp-idf/esp32s3",
84+
"esp-hal-embassy/esp32s3",
85+
"esp-hal/esp32s3",
86+
"esp-preempt/esp32s3",
87+
"esp-radio/esp32s3",
88+
]
89+
90+
[profile.release]
91+
debug = true
92+
debug-assertions = true
93+
lto = "fat"
94+
codegen-units = 1

examples/wifi/sntp/src/main.rs

Lines changed: 255 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,255 @@
1+
//! Embassy SNTP example
2+
//!
3+
//!
4+
//! Set SSID and PASSWORD env variable before running this example.
5+
//!
6+
//! This gets an ip address via DHCP then performs an SNTP request to update the RTC time with the
7+
//! response. The RTC time is then compared with the received data parsed with jiff.
8+
//! You can change the timezone to your local timezone.
9+
10+
#![no_std]
11+
#![no_main]
12+
// TODO: Remove when embassy-executor upgraded to 0.8.0
13+
#![feature(impl_trait_in_assoc_type)]
14+
15+
use core::net::{IpAddr, SocketAddr};
16+
17+
use embassy_executor::Spawner;
18+
use embassy_net::{
19+
Runner, StackResources,
20+
dns::DnsQueryType,
21+
udp::{PacketMetadata, UdpSocket},
22+
};
23+
use embassy_time::{Duration, Timer};
24+
use esp_alloc as _;
25+
use esp_backtrace as _;
26+
use esp_hal::{clock::CpuClock, rng::Rng, rtc_cntl::Rtc, timer::timg::TimerGroup};
27+
use esp_println::println;
28+
use esp_radio::{
29+
Controller,
30+
wifi::{ClientConfig, Config, ScanConfig, WifiController, WifiDevice, WifiEvent, WifiState},
31+
};
32+
use log::{error, info};
33+
use sntpc::{NtpContext, NtpTimestampGenerator, get_time};
34+
35+
esp_bootloader_esp_idf::esp_app_desc!();
36+
37+
// When you are okay with using a nightly compiler it's better to use https://docs.rs/static_cell/2.1.0/static_cell/macro.make_static.html
38+
macro_rules! mk_static {
39+
($t:ty,$val:expr) => {{
40+
static STATIC_CELL: static_cell::StaticCell<$t> = static_cell::StaticCell::new();
41+
#[deny(unused_attributes)]
42+
let x = STATIC_CELL.uninit().write(($val));
43+
x
44+
}};
45+
}
46+
47+
const SSID: &str = env!("SSID");
48+
const PASSWORD: &str = env!("PASSWORD");
49+
const TIMEZONE: jiff::tz::TimeZone = jiff::tz::get!("UTC");
50+
const NTP_SERVER: &str = "pool.ntp.org";
51+
52+
/// Microseconds in a second
53+
const USEC_IN_SEC: u64 = 1_000_000;
54+
55+
#[derive(Clone, Copy)]
56+
struct Timestamp<'a> {
57+
rtc: &'a Rtc<'a>,
58+
current_time_us: u64,
59+
}
60+
61+
impl NtpTimestampGenerator for Timestamp<'_> {
62+
fn init(&mut self) {
63+
self.current_time_us = self.rtc.current_time_us();
64+
}
65+
66+
fn timestamp_sec(&self) -> u64 {
67+
self.current_time_us / 1_000_000
68+
}
69+
70+
fn timestamp_subsec_micros(&self) -> u32 {
71+
(self.current_time_us % 1_000_000) as u32
72+
}
73+
}
74+
75+
#[esp_hal_embassy::main]
76+
async fn main(spawner: Spawner) -> ! {
77+
esp_println::logger::init_logger_from_env();
78+
let config = esp_hal::Config::default().with_cpu_clock(CpuClock::max());
79+
let peripherals = esp_hal::init(config);
80+
let rtc = Rtc::new(peripherals.LPWR);
81+
82+
esp_alloc::heap_allocator!(size: 72 * 1024);
83+
84+
let timg0 = TimerGroup::new(peripherals.TIMG0);
85+
esp_preempt::init(timg0.timer0);
86+
87+
let esp_radio_ctrl = &*mk_static!(Controller<'static>, esp_radio::init().unwrap());
88+
89+
let (controller, interfaces) = esp_radio::wifi::new(esp_radio_ctrl, peripherals.WIFI).unwrap();
90+
91+
let wifi_interface = interfaces.sta;
92+
93+
cfg_if::cfg_if! {
94+
if #[cfg(feature = "esp32")] {
95+
let timg1 = TimerGroup::new(peripherals.TIMG1);
96+
esp_hal_embassy::init(timg1.timer0);
97+
} else {
98+
use esp_hal::timer::systimer::SystemTimer;
99+
let systimer = SystemTimer::new(peripherals.SYSTIMER);
100+
esp_hal_embassy::init(systimer.alarm0);
101+
}
102+
}
103+
104+
let config = embassy_net::Config::dhcpv4(Default::default());
105+
106+
let rng = Rng::new();
107+
let seed = (rng.random() as u64) << 32 | rng.random() as u64;
108+
109+
// Init network stack
110+
let (stack, runner) = embassy_net::new(
111+
wifi_interface,
112+
config,
113+
mk_static!(StackResources<3>, StackResources::<3>::new()),
114+
seed,
115+
);
116+
117+
spawner.spawn(connection(controller)).ok();
118+
spawner.spawn(net_task(runner)).ok();
119+
120+
let mut rx_meta = [PacketMetadata::EMPTY; 16];
121+
let mut rx_buffer = [0; 4096];
122+
let mut tx_meta = [PacketMetadata::EMPTY; 16];
123+
let mut tx_buffer = [0; 4096];
124+
125+
loop {
126+
if stack.is_link_up() {
127+
break;
128+
}
129+
Timer::after(Duration::from_millis(500)).await;
130+
}
131+
132+
println!("Waiting to get IP address...");
133+
loop {
134+
if let Some(config) = stack.config_v4() {
135+
println!("Got IP: {}", config.address);
136+
break;
137+
}
138+
Timer::after(Duration::from_millis(500)).await;
139+
}
140+
141+
let ntp_addrs = stack.dns_query(NTP_SERVER, DnsQueryType::A).await.unwrap();
142+
143+
if ntp_addrs.is_empty() {
144+
panic!("Failed to resolve DNS. Empty result");
145+
}
146+
147+
let mut socket = UdpSocket::new(
148+
stack,
149+
&mut rx_meta,
150+
&mut rx_buffer,
151+
&mut tx_meta,
152+
&mut tx_buffer,
153+
);
154+
155+
socket.bind(123).unwrap();
156+
157+
// Display initial Rtc time before synchronization
158+
let now = jiff::Timestamp::from_microsecond(rtc.current_time_us() as i64).unwrap();
159+
info!("Rtc: {now}");
160+
161+
loop {
162+
let addr: IpAddr = ntp_addrs[0].into();
163+
let result = get_time(
164+
SocketAddr::from((addr, 123)),
165+
&socket,
166+
NtpContext::new(Timestamp {
167+
rtc: &rtc,
168+
current_time_us: 0,
169+
}),
170+
)
171+
.await;
172+
173+
match result {
174+
Ok(time) => {
175+
// Set time immediately after receiving to reduce time offset.
176+
rtc.set_current_time_us(
177+
(time.sec() as u64 * USEC_IN_SEC)
178+
+ ((time.sec_fraction() as u64 * USEC_IN_SEC) >> 32),
179+
);
180+
181+
// Compare RTC to parsed time
182+
info!(
183+
"Response: {:?}\nTime: {}\nRtc : {}",
184+
time,
185+
// Create a Jiff Timestamp from seconds and nanoseconds
186+
jiff::Timestamp::from_second(time.sec() as i64)
187+
.unwrap()
188+
.checked_add(
189+
jiff::Span::new()
190+
.nanoseconds((time.seconds_fraction as i64 * 1_000_000_000) >> 32),
191+
)
192+
.unwrap()
193+
.to_zoned(TIMEZONE),
194+
jiff::Timestamp::from_microsecond(rtc.current_time_us() as i64)
195+
.unwrap()
196+
.to_zoned(TIMEZONE)
197+
);
198+
}
199+
Err(e) => {
200+
error!("Error getting time: {e:?}");
201+
}
202+
}
203+
204+
Timer::after(Duration::from_secs(10)).await;
205+
}
206+
}
207+
208+
#[embassy_executor::task]
209+
async fn connection(mut controller: WifiController<'static>) {
210+
println!("start connection task");
211+
println!("Device capabilities: {:?}", controller.capabilities());
212+
loop {
213+
if esp_radio::wifi::wifi_state() == WifiState::StaConnected {
214+
// wait until we're no longer connected
215+
controller.wait_for_event(WifiEvent::StaDisconnected).await;
216+
Timer::after(Duration::from_millis(5000)).await
217+
}
218+
if !matches!(controller.is_started(), Ok(true)) {
219+
let client_config = Config::Client({
220+
let mut config = ClientConfig::default();
221+
config.ssid = SSID.into();
222+
config.password = PASSWORD.into();
223+
config
224+
});
225+
controller.set_configuration(&client_config).unwrap();
226+
println!("Starting wifi");
227+
controller.start_async().await.unwrap();
228+
println!("Wifi started!");
229+
230+
println!("Scan");
231+
let scan_config = ScanConfig::default().with_max(10);
232+
let result = controller
233+
.scan_with_config_async(scan_config)
234+
.await
235+
.unwrap();
236+
for ap in result {
237+
println!("{:?}", ap);
238+
}
239+
}
240+
println!("About to connect...");
241+
242+
match controller.connect_async().await {
243+
Ok(_) => println!("Wifi connected!"),
244+
Err(e) => {
245+
println!("Failed to connect to wifi: {e:?}");
246+
Timer::after(Duration::from_millis(5000)).await
247+
}
248+
}
249+
}
250+
}
251+
252+
#[embassy_executor::task]
253+
async fn net_task(mut runner: Runner<'static, WifiDevice<'static>>) {
254+
runner.run().await
255+
}

0 commit comments

Comments
 (0)