esp_wifi/
lib.rs

1#![cfg_attr(
2    all(docsrs, not(not_really_docsrs)),
3    doc = "<div style='padding:30px;background:#810;color:#fff;text-align:center;'><p>You might want to <a href='https://docs.esp-rs.org/esp-hal/'>browse the <code>esp-wifi</code> documentation on the esp-rs website</a> instead.</p><p>The documentation here on <a href='https://docs.rs'>docs.rs</a> is built for a single chip only (ESP32-C3, in particular), while on the esp-rs website you can select your exact chip from the list of supported devices. Available peripherals and their APIs might change depending on the chip.</p></div>\n\n<br/>\n\n"
4)]
5//! This documentation is built for the
6#![cfg_attr(esp32, doc = "**ESP32**")]
7#![cfg_attr(esp32s2, doc = "**ESP32-S2**")]
8#![cfg_attr(esp32s3, doc = "**ESP32-S3**")]
9#![cfg_attr(esp32c2, doc = "**ESP32-C2**")]
10#![cfg_attr(esp32c3, doc = "**ESP32-C3**")]
11#![cfg_attr(esp32c6, doc = "**ESP32-C6**")]
12#![cfg_attr(esp32h2, doc = "**ESP32-H2**")]
13//! . Please ensure you are reading the correct documentation for your target
14//! device.
15//!
16//! ## Usage
17//!
18//! ### Importing
19//!
20//! Note that this crate currently requires you to enable the `unstable` feature
21//! on `esp-hal`.
22//!
23//! Ensure that the right features are enabled for your chip. See [Examples](https://github.com/esp-rs/esp-hal/tree/main/examples#examples) for more examples.
24//!
25//! ```toml
26//! [dependencies.esp-wifi]
27//! # A supported chip needs to be specified, as well as specific use-case features
28#![doc = concat!(r#"features = [""#, esp_hal::chip!(), r#"", "wifi", "esp-now"]"#)]
29//! ```
30//! 
31//! ### Optimization Level
32//!
33//! It is necessary to build with optimization level 2 or 3 since otherwise, it
34//! might not even be able to connect or advertise.
35//!
36//! To make it work also for your debug builds add this to your `Cargo.toml`
37//! ```toml
38//! [profile.dev.package.esp-wifi]
39//! opt-level = 3
40//! ```
41//! ## Globally disable logging
42//!
43//! `esp-wifi` contains a lot of trace-level logging statements.
44//! For maximum performance you might want to disable logging via
45//! a feature flag of the `log` crate. See [documentation](https://docs.rs/log/0.4.19/log/#compile-time-filters).
46//! You should set it to `release_max_level_off`.
47//!
48//! ### WiFi performance considerations
49//!
50//! The default configuration is quite conservative to reduce power and memory consumption.
51//!
52//! There are a number of settings which influence the general performance. Optimal settings are chip and applications specific.
53//! You can get inspiration from the [ESP-IDF examples](https://github.com/espressif/esp-idf/tree/release/v5.3/examples/wifi/iperf)
54//!
55//! Please note that the configuration keys are usually named slightly different and not all configuration keys apply.
56//!
57//! By default the power-saving mode is [PowerSaveMode::Minimum](crate::config::PowerSaveMode::Minimum) and `ESP_WIFI_PHY_ENABLE_USB` is enabled by default.
58//!
59//! In addition pay attention to these configuration keys:
60//! - `ESP_WIFI_RX_QUEUE_SIZE`
61//! - `ESP_WIFI_TX_QUEUE_SIZE`
62//! - `ESP_WIFI_MAX_BURST_SIZE`
63//!
64//! # Features flags
65//!
66//! Note that not all features are available on every MCU. For example, `ble`
67//! (and thus, `coex`) is not available on ESP32-S2.
68//!
69//! When using the `dump_packets` config you can use the extcap in
70//! `extras/esp-wifishark` to analyze the frames in Wireshark.
71//! For more information see
72//! [extras/esp-wifishark/README.md](../extras/esp-wifishark/README.md)
73#![doc = document_features::document_features!(feature_label = r#"<span class="stab portability"><code>{feature}</code></span>"#)]
74//! ## Additional configuration
75//!
76//! We've exposed some configuration options that don't fit into cargo
77//! features. These can be set via environment variables, or via cargo's `[env]`
78//! section inside `.cargo/config.toml`. Below is a table of tunable parameters
79//! for this crate:
80#![doc = ""]
81#![doc = include_str!(concat!(env!("OUT_DIR"), "/esp_wifi_config_table.md"))]
82#![doc = ""]
83//! It's important to note that due to a [bug in cargo](https://github.com/rust-lang/cargo/issues/10358),
84//! any modifications to the environment, local or otherwise will only get
85//! picked up on a full clean build of the project.
86
87#![doc(html_logo_url = "https://avatars.githubusercontent.com/u/46717278")]
88#![no_std]
89#![cfg_attr(xtensa, feature(asm_experimental_arch))]
90#![cfg_attr(feature = "sys-logs", feature(c_variadic))]
91#![deny(rust_2018_idioms, rustdoc::all)]
92#![allow(rustdoc::bare_urls)]
93// allow until num-derive doesn't generate this warning anymore (unknown_lints because Xtensa
94// toolchain doesn't know about that lint, yet)
95#![allow(unknown_lints)]
96#![allow(non_local_definitions)]
97
98extern crate alloc;
99
100// MUST be the first module
101mod fmt;
102
103use core::marker::PhantomData;
104
105use common_adapter::chip_specific::phy_mem_init;
106use esp_config::*;
107#[cfg(not(feature = "esp32"))]
108use esp_hal::timer::systimer::Alarm;
109use esp_hal::{
110    self as hal,
111    clock::RadioClockController,
112    peripheral::Peripheral,
113    peripherals::RADIO_CLK,
114};
115use hal::{
116    clock::Clocks,
117    rng::{Rng, Trng},
118    time::Rate,
119    timer::{timg::Timer as TimgTimer, AnyTimer, PeriodicTimer},
120    Blocking,
121};
122use portable_atomic::Ordering;
123
124#[cfg(feature = "wifi")]
125use crate::wifi::WifiError;
126use crate::{
127    preempt::yield_task,
128    radio::{setup_radio_isr, shutdown_radio_isr},
129    tasks::init_tasks,
130};
131
132mod binary {
133    pub use esp_wifi_sys::*;
134}
135mod compat;
136
137#[cfg(feature = "builtin-scheduler")]
138mod preempt_builtin;
139
140pub mod preempt;
141
142mod radio;
143mod time;
144
145#[cfg(feature = "wifi")]
146pub mod wifi;
147
148#[cfg(feature = "ble")]
149pub mod ble;
150
151#[cfg(feature = "esp-now")]
152pub mod esp_now;
153
154pub mod config;
155
156pub(crate) mod common_adapter;
157
158#[doc(hidden)]
159pub mod tasks;
160
161pub(crate) mod memory_fence;
162
163// this is just to verify that we use the correct defaults in `build.rs`
164#[allow(clippy::assertions_on_constants)] // TODO: try assert_eq once it's usable in const context
165const _: () = {
166    cfg_if::cfg_if! {
167        if #[cfg(not(esp32h2))] {
168            core::assert!(binary::include::CONFIG_ESP_WIFI_STATIC_RX_BUFFER_NUM == 10);
169            core::assert!(binary::include::CONFIG_ESP_WIFI_DYNAMIC_RX_BUFFER_NUM == 32);
170            core::assert!(binary::include::WIFI_STATIC_TX_BUFFER_NUM == 0);
171            core::assert!(binary::include::CONFIG_ESP_WIFI_DYNAMIC_RX_BUFFER_NUM == 32);
172            core::assert!(binary::include::CONFIG_ESP_WIFI_AMPDU_RX_ENABLED == 1);
173            core::assert!(binary::include::CONFIG_ESP_WIFI_AMPDU_TX_ENABLED == 1);
174            core::assert!(binary::include::WIFI_AMSDU_TX_ENABLED == 0);
175            core::assert!(binary::include::CONFIG_ESP32_WIFI_RX_BA_WIN == 6);
176        }
177    };
178};
179
180#[derive(Debug)]
181#[cfg_attr(feature = "defmt", derive(defmt::Format))]
182/// Tunable parameters for the WiFi driver
183#[allow(unused)] // currently there are no ble tunables
184struct Config {
185    rx_queue_size: usize,
186    tx_queue_size: usize,
187    static_rx_buf_num: usize,
188    dynamic_rx_buf_num: usize,
189    static_tx_buf_num: usize,
190    dynamic_tx_buf_num: usize,
191    ampdu_rx_enable: bool,
192    ampdu_tx_enable: bool,
193    amsdu_tx_enable: bool,
194    rx_ba_win: usize,
195    max_burst_size: usize,
196    country_code: &'static str,
197    country_code_operating_class: u8,
198    mtu: usize,
199    tick_rate_hz: u32,
200    listen_interval: u16,
201    beacon_timeout: u16,
202    ap_beacon_timeout: u16,
203    failure_retry_cnt: u8,
204    scan_method: u32,
205}
206
207pub(crate) const CONFIG: config::EspWifiConfig = config::EspWifiConfig {
208    rx_queue_size: esp_config_int!(usize, "ESP_WIFI_CONFIG_RX_QUEUE_SIZE"),
209    tx_queue_size: esp_config_int!(usize, "ESP_WIFI_CONFIG_TX_QUEUE_SIZE"),
210    static_rx_buf_num: esp_config_int!(usize, "ESP_WIFI_CONFIG_STATIC_RX_BUF_NUM"),
211    dynamic_rx_buf_num: esp_config_int!(usize, "ESP_WIFI_CONFIG_DYNAMIC_RX_BUF_NUM"),
212    static_tx_buf_num: esp_config_int!(usize, "ESP_WIFI_CONFIG_STATIC_TX_BUF_NUM"),
213    dynamic_tx_buf_num: esp_config_int!(usize, "ESP_WIFI_CONFIG_DYNAMIC_TX_BUF_NUM"),
214    ampdu_rx_enable: esp_config_bool!("ESP_WIFI_CONFIG_AMPDU_RX_ENABLE"),
215    ampdu_tx_enable: esp_config_bool!("ESP_WIFI_CONFIG_AMPDU_TX_ENABLE"),
216    amsdu_tx_enable: esp_config_bool!("ESP_WIFI_CONFIG_AMSDU_TX_ENABLE"),
217    rx_ba_win: esp_config_int!(usize, "ESP_WIFI_CONFIG_RX_BA_WIN"),
218    max_burst_size: esp_config_int!(usize, "ESP_WIFI_CONFIG_MAX_BURST_SIZE"),
219    country_code: esp_config_str!("ESP_WIFI_CONFIG_COUNTRY_CODE"),
220    country_code_operating_class: esp_config_int!(
221        u8,
222        "ESP_WIFI_CONFIG_COUNTRY_CODE_OPERATING_CLASS"
223    ),
224    mtu: esp_config_int!(usize, "ESP_WIFI_CONFIG_MTU"),
225    tick_rate_hz: esp_config_int!(u32, "ESP_WIFI_CONFIG_TICK_RATE_HZ"),
226    listen_interval: esp_config_int!(u16, "ESP_WIFI_CONFIG_LISTEN_INTERVAL"),
227    beacon_timeout: esp_config_int!(u16, "ESP_WIFI_CONFIG_BEACON_TIMEOUT"),
228    ap_beacon_timeout: esp_config_int!(u16, "ESP_WIFI_CONFIG_AP_BEACON_TIMEOUT"),
229    failure_retry_cnt: esp_config_int!(u8, "ESP_WIFI_CONFIG_FAILURE_RETRY_CNT"),
230    scan_method: esp_config_int!(u32, "ESP_WIFI_CONFIG_SCAN_METHOD"),
231};
232
233// Validate the configuration at compile time
234#[allow(clippy::assertions_on_constants)]
235const _: () = {
236    // We explicitely use `core` assert here because this evaluation happens at
237    // compile time and won't bloat the binary
238    core::assert!(
239        CONFIG.rx_ba_win < CONFIG.dynamic_rx_buf_num,
240        "WiFi configuration check: rx_ba_win should not be larger than dynamic_rx_buf_num!"
241    );
242    core::assert!(CONFIG.rx_ba_win < (CONFIG.static_rx_buf_num * 2), "WiFi configuration check: rx_ba_win should not be larger than double of the static_rx_buf_num!");
243};
244
245type TimeBase = PeriodicTimer<'static, Blocking>;
246
247pub(crate) mod flags {
248    use portable_atomic::{AtomicBool, AtomicUsize};
249
250    pub(crate) static ESP_WIFI_INITIALIZED: AtomicBool = AtomicBool::new(false);
251    pub(crate) static WIFI: AtomicUsize = AtomicUsize::new(0);
252    pub(crate) static BLE: AtomicBool = AtomicBool::new(false);
253}
254
255#[derive(Debug, PartialEq, PartialOrd)]
256#[cfg_attr(feature = "defmt", derive(defmt::Format))]
257pub struct EspWifiController<'d> {
258    _inner: PhantomData<&'d ()>,
259}
260
261impl EspWifiController<'_> {
262    /// Is the WiFi part of the radio running
263    pub fn wifi(&self) -> bool {
264        crate::flags::WIFI.load(Ordering::Acquire) > 0
265    }
266
267    /// Is the BLE part of the radio running
268    pub fn ble(&self) -> bool {
269        crate::flags::BLE.load(Ordering::Acquire)
270    }
271
272    /// De-initialize the radio
273    pub fn deinit(self) -> Result<(), InitializationError> {
274        if crate::flags::ESP_WIFI_INITIALIZED.load(Ordering::Acquire) {
275            // safety: no other driver can be using this if this is callable
276            unsafe { deinit_unchecked() }
277        } else {
278            Ok(())
279        }
280    }
281
282    pub(crate) unsafe fn conjure() -> Self {
283        Self {
284            _inner: PhantomData,
285        }
286    }
287}
288
289impl Drop for EspWifiController<'_> {
290    fn drop(&mut self) {
291        if crate::flags::ESP_WIFI_INITIALIZED.load(Ordering::Acquire) {
292            // safety: no other driver can be using this if this is callable
293            unsafe { deinit_unchecked().ok() };
294        }
295    }
296}
297
298/// A trait to allow better UX for initializing esp-wifi.
299///
300/// This trait is meant to be used only for the `init` function.
301/// Calling `timers()` multiple times may panic.
302pub trait EspWifiTimerSource: private::Sealed {
303    /// Returns the timer source.
304    fn timer(self) -> TimeBase;
305}
306
307/// Helper trait to reduce boilerplate.
308///
309/// We can't blanket-implement for `Into<AnyTimer>` because of possible
310/// conflicting implementations.
311trait IntoAnyTimer: Into<AnyTimer> {}
312
313impl IntoAnyTimer for TimgTimer where Self: Into<AnyTimer> {}
314
315#[cfg(not(feature = "esp32"))]
316impl IntoAnyTimer for Alarm where Self: Into<AnyTimer> {}
317
318impl private::Sealed for AnyTimer {}
319impl IntoAnyTimer for AnyTimer {}
320
321impl<T> EspWifiTimerSource for T
322where
323    T: IntoAnyTimer + private::Sealed,
324{
325    fn timer(self) -> TimeBase {
326        TimeBase::new(self.into()).timer()
327    }
328}
329
330impl EspWifiTimerSource for TimeBase {
331    fn timer(self) -> TimeBase {
332        self
333    }
334}
335
336impl private::Sealed for TimeBase {}
337impl private::Sealed for TimgTimer where Self: Into<AnyTimer> {}
338#[cfg(not(feature = "esp32"))]
339impl private::Sealed for Alarm where Self: Into<AnyTimer> {}
340
341/// A marker trait for suitable Rng sources for esp-wifi
342pub trait EspWifiRngSource: rand_core::RngCore + private::Sealed {}
343
344impl EspWifiRngSource for Rng {}
345impl private::Sealed for Rng {}
346impl EspWifiRngSource for Trng<'_> {}
347impl private::Sealed for Trng<'_> {}
348
349/// Initialize for using WiFi and or BLE.
350///
351/// # The `timer` argument
352///
353/// The `timer` argument is a timer source that is used by the WiFi driver to
354/// schedule internal tasks. The timer source can be any of the following:
355///
356/// - A timg `Timer` instance
357/// - A systimer `Alarm` instance
358/// - An `AnyTimer` instance
359/// - A `OneShotTimer` instance
360///
361/// # Examples
362///
363/// ```rust, no_run
364#[doc = esp_hal::before_snippet!()]
365/// use esp_hal::{rng::Rng, timer::timg::TimerGroup};
366///
367/// let timg0 = TimerGroup::new(peripherals.TIMG0);
368/// let init = esp_wifi::init(
369///     timg0.timer0,
370///     Rng::new(peripherals.RNG),
371///     peripherals.RADIO_CLK,
372/// )
373/// .unwrap();
374/// # }
375/// ```
376pub fn init<'d, T: EspWifiTimerSource, R: EspWifiRngSource>(
377    timer: impl Peripheral<P = T> + 'd,
378    _rng: impl Peripheral<P = R> + 'd,
379    _radio_clocks: impl Peripheral<P = RADIO_CLK> + 'd,
380) -> Result<EspWifiController<'d>, InitializationError> {
381    // A minimum clock of 80MHz is required to operate WiFi module.
382    const MIN_CLOCK: Rate = Rate::from_mhz(80);
383    let clocks = Clocks::get();
384    if clocks.cpu_clock < MIN_CLOCK {
385        return Err(InitializationError::WrongClockConfig);
386    }
387
388    info!("esp-wifi configuration {:?}", crate::CONFIG);
389    crate::common_adapter::chip_specific::enable_wifi_power_domain();
390    phy_mem_init();
391
392    setup_radio_isr();
393
394    // Enable timer tick interrupt
395    #[cfg(feature = "builtin-scheduler")]
396    preempt_builtin::setup_timer(unsafe { timer.clone_unchecked() }.timer());
397
398    // This initializes the task switcher
399    preempt::enable();
400
401    init_tasks();
402    yield_task();
403
404    wifi_set_log_verbose();
405    init_clocks();
406
407    #[cfg(coex)]
408    match crate::wifi::coex_initialize() {
409        0 => {}
410        error => return Err(InitializationError::General(error)),
411    }
412
413    crate::flags::ESP_WIFI_INITIALIZED.store(true, Ordering::Release);
414
415    #[cfg(not(feature = "builtin-scheduler"))]
416    let _ = timer; // mark used to suppress warning
417
418    Ok(EspWifiController {
419        _inner: PhantomData,
420    })
421}
422
423/// Deinitializes the entire radio stack
424///
425/// This can be useful to shutdown the stack before going to sleep for example.
426///
427/// # Safety
428///
429/// The user must ensure that any use of the radio via the WIFI/BLE/ESP-NOW
430/// drivers are complete, else undefined behaviour may occur within those
431/// drivers.
432pub unsafe fn deinit_unchecked() -> Result<(), InitializationError> {
433    // Disable coexistence
434    #[cfg(coex)]
435    {
436        unsafe { crate::wifi::os_adapter::coex_disable() };
437        unsafe { crate::wifi::os_adapter::coex_deinit() };
438    }
439
440    let controller = unsafe { EspWifiController::conjure() };
441
442    // Peripheral drivers should already take care of shutting these down
443    // we have to check this in the case where a user calls `deinit_unchecked`
444    // directly.
445    if controller.wifi() {
446        #[cfg(feature = "wifi")]
447        crate::wifi::wifi_deinit()?;
448        crate::flags::WIFI.store(0, Ordering::Release);
449    }
450
451    if controller.ble() {
452        #[cfg(feature = "ble")]
453        crate::ble::ble_deinit();
454        crate::flags::BLE.store(false, Ordering::Release);
455    }
456
457    shutdown_radio_isr();
458
459    #[cfg(feature = "builtin-scheduler")]
460    preempt_builtin::disable_timer();
461
462    // This shuts down the task switcher and timer tick interrupt.
463    preempt::disable();
464
465    crate::flags::ESP_WIFI_INITIALIZED.store(false, Ordering::Release);
466
467    Ok(())
468}
469
470pub(crate) mod private {
471    pub trait Sealed {}
472}
473
474#[derive(Debug, Clone, Copy)]
475#[cfg_attr(feature = "defmt", derive(defmt::Format))]
476/// Error which can be returned during [`init`].
477pub enum InitializationError {
478    General(i32),
479    #[cfg(feature = "wifi")]
480    WifiError(WifiError),
481    WrongClockConfig,
482}
483
484#[cfg(feature = "wifi")]
485impl From<WifiError> for InitializationError {
486    fn from(value: WifiError) -> Self {
487        InitializationError::WifiError(value)
488    }
489}
490
491/// Enable verbose logging within the WiFi driver
492/// Does nothing unless the `sys-logs` feature is enabled.
493pub fn wifi_set_log_verbose() {
494    #[cfg(all(feature = "sys-logs", not(esp32h2)))]
495    unsafe {
496        use crate::binary::include::{
497            esp_wifi_internal_set_log_level,
498            wifi_log_level_t_WIFI_LOG_VERBOSE,
499        };
500
501        esp_wifi_internal_set_log_level(wifi_log_level_t_WIFI_LOG_VERBOSE);
502    }
503}
504
505fn init_clocks() {
506    let radio_clocks = unsafe { RADIO_CLK::steal() };
507    RadioClockController::new(radio_clocks).init_clocks();
508}