esp_radio/
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.espressif.com/projects/rust/'>browse the <code>esp-radio</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//! # Wireless support for Espressif ESP32 devices.
6//!
7//! This documentation is built for the
8#![cfg_attr(esp32, doc = "**ESP32**")]
9#![cfg_attr(esp32s2, doc = "**ESP32-S2**")]
10#![cfg_attr(esp32s3, doc = "**ESP32-S3**")]
11#![cfg_attr(esp32c2, doc = "**ESP32-C2**")]
12#![cfg_attr(esp32c3, doc = "**ESP32-C3**")]
13#![cfg_attr(esp32c6, doc = "**ESP32-C6**")]
14#![cfg_attr(esp32h2, doc = "**ESP32-H2**")]
15//! . Please ensure you are reading the correct documentation for your target
16//! device.
17//!
18//! ## Usage
19//!
20//! ### Importing
21//!
22//! Note that this crate currently requires you to enable the `unstable` feature
23//! on `esp-hal`.
24//!
25//! 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.
26//!
27//! You will also need a dynamic memory allocator, and a preemptive task scheduler in your
28//! application. For the dynamic allocator, we recommend using `esp-alloc`. For the task scheduler,
29//! the simplest option that is supported by us is `esp-rtos`, but you may use Ariel
30//! OS or other operating systems as well.
31//!
32//! ```toml
33//! [dependencies.esp-radio]
34//! # A supported chip needs to be specified, as well as specific use-case features
35#![doc = concat!(r#"features = [""#, chip!(), r#"", "wifi", "esp-now", "esp-alloc"]"#)]
36//! [dependencies.esp-rtos]
37#![doc = concat!(r#"features = [""#, chip!(), r#"", "esp-radio", "esp-alloc"]"#)]
38//! [dependencies.esp-alloc]
39#![doc = concat!(r#"features = [""#, chip!(), r#""]"#)]
40//! ```
41//! 
42//! ### Optimization Level
43//!
44//! It is necessary to build with optimization level 2 or 3 since otherwise, it
45//! might not even be able to connect or advertise.
46//!
47//! To make it work also for your debug builds add this to your `Cargo.toml`
48//! ```toml
49//! [profile.dev.package.esp-radio]
50//! opt-level = 3
51//! ```
52//! ## Globally disable logging
53//!
54//! `esp-radio` contains a lot of trace-level logging statements.
55//! For maximum performance you might want to disable logging via
56//! a feature flag of the `log` crate. See [documentation](https://docs.rs/log/0.4.19/log/#compile-time-filters).
57//! You should set it to `release_max_level_off`.
58//!
59//! ### Wi-Fi performance considerations
60//!
61//! The default configuration is quite conservative to reduce power and memory consumption.
62//!
63//! There are a number of settings which influence the general performance. Optimal settings are chip and applications specific.
64//! You can get inspiration from the [ESP-IDF examples](https://github.com/espressif/esp-idf/tree/release/v5.3/examples/wifi/iperf)
65//!
66//! Please note that the configuration keys are usually named slightly different and not all configuration keys apply.
67#![cfg_attr(
68    feature = "wifi",
69    doc = "By default the power-saving mode is [`PowerSaveMode::None`](crate::wifi::PowerSaveMode::None) and `ESP_PHY_CONFIG_PHY_ENABLE_USB` is enabled by default."
70)]
71//! In addition pay attention to these configuration keys:
72//! - `ESP_RADIO_RX_QUEUE_SIZE`
73//! - `ESP_RADIO_TX_QUEUE_SIZE`
74//! - `ESP_RADIO_MAX_BURST_SIZE`
75#![cfg_attr(
76    multi_core,
77    doc = concat!(
78        "### Running on the Second Core",
79        "\n\n",
80        "BLE and Wi-Fi can also be run on the second core.",
81        "\n\n",
82        "`esp_radio::init` is recommended to be called on the first core. The tasks ",
83        "created by `esp-radio` are pinned to the first core.",
84        "\n\n",
85        "It's also important to allocate adequate stack for the second core; in many ",
86        "cases 8kB is not enough, and 16kB or more may be required depending on your ",
87        "use case. Failing to allocate adequate stack may result in strange behaviour, ",
88        "such as your application silently failing at some point during execution."
89    )
90)]
91//! ## Feature flags
92//!
93//! Note that not all features are available on every MCU. For example, `ble`
94//! (and thus, `coex`) is not available on ESP32-S2.
95//!
96//! When using the `dump_packets` config you can use the extcap in
97//! `extras/esp-wifishark` to analyze the frames in Wireshark.
98//! For more information see
99//! [extras/esp-wifishark/README.md](../extras/esp-wifishark/README.md)
100#![doc = document_features::document_features!(feature_label = r#"<span class="stab portability"><code>{feature}</code></span>"#)]
101//! ## Additional configuration
102//!
103//! We've exposed some configuration options that don't fit into cargo
104//! features. These can be set via environment variables, or via cargo's `[env]`
105//! section inside `.cargo/config.toml`. Below is a table of tunable parameters
106//! for this crate:
107#![doc = ""]
108#![doc = include_str!(concat!(env!("OUT_DIR"), "/esp_radio_config_table.md"))]
109#![doc(html_logo_url = "https://avatars.githubusercontent.com/u/46717278")]
110#![no_std]
111#![cfg_attr(xtensa, feature(asm_experimental_arch))]
112#![cfg_attr(feature = "sys-logs", feature(c_variadic))]
113#![deny(rust_2018_idioms, rustdoc::all)]
114#![allow(rustdoc::bare_urls)]
115// allow until num-derive doesn't generate this warning anymore (unknown_lints because Xtensa
116// toolchain doesn't know about that lint, yet)
117#![allow(unknown_lints)]
118#![allow(non_local_definitions)]
119#![cfg_attr(
120    not(any(feature = "wifi", feature = "ble")),
121    allow(
122        unused,
123        reason = "There are a number of places where code is needed for either wifi or ble,
124        and cfg-ing them out would make the code less readable just to avoid warnings in the
125        less common case. Truly unused code will be flagged by the check that enables either
126        ble or wifi."
127    )
128)]
129#![cfg_attr(docsrs, feature(doc_cfg, custom_inner_attributes, proc_macro_hygiene))]
130
131#[macro_use]
132extern crate esp_metadata_generated;
133
134extern crate alloc;
135
136// MUST be the first module
137mod fmt;
138
139use core::marker::PhantomData;
140
141pub use common_adapter::{phy_calibration_data, set_phy_calibration_data};
142use esp_hal::{self as hal};
143use esp_radio_rtos_driver as preempt;
144use esp_sync::RawMutex;
145#[cfg(esp32)]
146use hal::analog::adc::{release_adc2, try_claim_adc2};
147use hal::{
148    clock::{Clocks, init_radio_clocks},
149    time::Rate,
150};
151
152use crate::radio::{setup_radio_isr, shutdown_radio_isr};
153#[cfg(feature = "wifi")]
154use crate::wifi::WifiError;
155
156// can't use instability on inline module definitions, see https://github.com/rust-lang/rust/issues/54727
157#[doc(hidden)]
158macro_rules! unstable_module {
159    ($(
160        $(#[$meta:meta])*
161        pub mod $module:ident;
162    )*) => {
163        $(
164            $(#[$meta])*
165            #[cfg(feature = "unstable")]
166            #[cfg_attr(docsrs, doc(cfg(feature = "unstable")))]
167            pub mod $module;
168
169            $(#[$meta])*
170            #[cfg(not(feature = "unstable"))]
171            #[cfg_attr(docsrs, doc(cfg(feature = "unstable")))]
172            #[allow(unused)]
173            pub(crate) mod $module;
174        )*
175    };
176}
177
178mod binary {
179    pub use esp_wifi_sys::*;
180}
181mod compat;
182
183mod radio;
184mod time;
185
186#[cfg(feature = "wifi")]
187pub mod wifi;
188
189unstable_module! {
190    #[cfg(feature = "esp-now")]
191    pub mod esp_now;
192    #[cfg(feature = "ble")]
193    pub mod ble;
194    #[cfg(feature = "ieee802154")]
195    pub mod ieee802154;
196}
197
198pub(crate) mod common_adapter;
199pub(crate) mod memory_fence;
200
201pub(crate) static ESP_RADIO_LOCK: RawMutex = RawMutex::new();
202
203// this is just to verify that we use the correct defaults in `build.rs`
204#[allow(clippy::assertions_on_constants)] // TODO: try assert_eq once it's usable in const context
205const _: () = {
206    cfg_if::cfg_if! {
207        if #[cfg(not(esp32h2))] {
208            core::assert!(binary::include::CONFIG_ESP_WIFI_STATIC_RX_BUFFER_NUM == 10);
209            core::assert!(binary::include::CONFIG_ESP_WIFI_DYNAMIC_RX_BUFFER_NUM == 32);
210            core::assert!(binary::include::WIFI_STATIC_TX_BUFFER_NUM == 0);
211            core::assert!(binary::include::CONFIG_ESP_WIFI_DYNAMIC_RX_BUFFER_NUM == 32);
212            core::assert!(binary::include::CONFIG_ESP_WIFI_AMPDU_RX_ENABLED == 1);
213            core::assert!(binary::include::CONFIG_ESP_WIFI_AMPDU_TX_ENABLED == 1);
214            core::assert!(binary::include::WIFI_AMSDU_TX_ENABLED == 0);
215            core::assert!(binary::include::CONFIG_ESP32_WIFI_RX_BA_WIN == 6);
216        }
217    };
218};
219
220#[derive(Debug, PartialEq, PartialOrd)]
221#[cfg_attr(feature = "defmt", derive(defmt::Format))]
222/// Controller for the ESP Radio driver.
223pub struct Controller<'d> {
224    _inner: PhantomData<&'d ()>,
225}
226
227impl Drop for Controller<'_> {
228    fn drop(&mut self) {
229        // Disable coexistence
230        #[cfg(coex)]
231        {
232            unsafe { crate::wifi::os_adapter::coex_disable() };
233            unsafe { crate::wifi::os_adapter::coex_deinit() };
234        }
235
236        shutdown_radio_isr();
237
238        #[cfg(esp32)]
239        // Allow using `ADC2` again
240        release_adc2(unsafe { esp_hal::Internal::conjure() });
241    }
242}
243
244/// Initialize for using Wi-Fi and or BLE.
245///
246/// Wi-Fi and BLE require a preemptive scheduler to be present. Without one, the underlying firmware
247/// can't operate. The scheduler must implement the interfaces in the `esp-radio-rtos-driver`
248/// crate. If you are using an embedded RTOS like Ariel OS, it needs to provide an appropriate
249/// implementation.
250///
251/// If you are not using an embedded RTOS, use the `esp-rtos` crate which provides the
252/// necessary functionality.
253///
254/// Make sure to **not** call this function while interrupts are disabled.
255///
256/// ## Errors
257///
258/// - The function may return an error if the scheduler is not initialized.
259#[cfg_attr(
260    esp32,
261    doc = " - The function may return an error if ADC2 is already in use."
262)]
263/// - The function may return an error if interrupts are disabled.
264/// - The function may return an error if initializing the underlying driver fails.
265///
266/// ## Example
267///
268/// For examples of the necessary setup, see your RTOS's documentation. If you are
269/// using the `esp-rtos` crate, you will need to initialize the scheduler before calling this
270/// function:
271///
272/// ```rust, no_run
273#[doc = esp_hal::before_snippet!()]
274/// use esp_hal::timer::timg::TimerGroup;
275///
276/// let timg0 = TimerGroup::new(peripherals.TIMG0);
277#[cfg_attr(
278    riscv,
279    doc = " let software_interrupt = SoftwareInterruptControl::new(peripherals.SW_INTERRUPT);"
280)]
281#[cfg_attr(riscv, doc = " esp_rtos::start(timg0.timer0, software_interrupt);")]
282#[cfg_attr(xtensa, doc = " esp_rtos::start(timg0.timer0);")]
283/// // You can now start esp-radio:
284/// let esp_radio_controller = esp_radio::init().unwrap();
285/// # }
286/// ```
287pub fn init<'d>() -> Result<Controller<'d>, InitializationError> {
288    #[cfg(esp32)]
289    if try_claim_adc2(unsafe { hal::Internal::conjure() }).is_err() {
290        return Err(InitializationError::Adc2IsUsed);
291    }
292
293    if crate::is_interrupts_disabled() {
294        return Err(InitializationError::InterruptsDisabled);
295    }
296
297    if !preempt::initialized() {
298        return Err(InitializationError::SchedulerNotInitialized);
299    }
300
301    // A minimum clock of 80MHz is required to operate Wi-Fi module.
302    const MIN_CLOCK: Rate = Rate::from_mhz(80);
303    let clocks = Clocks::get();
304    if clocks.cpu_clock < MIN_CLOCK {
305        return Err(InitializationError::WrongClockConfig);
306    }
307
308    crate::common_adapter::enable_wifi_power_domain();
309
310    setup_radio_isr();
311
312    wifi_set_log_verbose();
313    init_radio_clocks();
314
315    #[cfg(coex)]
316    match crate::wifi::coex_initialize() {
317        0 => {}
318        error => return Err(InitializationError::General(error)),
319    }
320
321    Ok(Controller {
322        _inner: PhantomData,
323    })
324}
325
326/// Returns true if at least some interrupt levels are disabled.
327fn is_interrupts_disabled() -> bool {
328    #[cfg(target_arch = "xtensa")]
329    return hal::xtensa_lx::interrupt::get_level() != 0
330        || hal::xtensa_lx::interrupt::get_mask() == 0;
331
332    #[cfg(target_arch = "riscv32")]
333    return !hal::riscv::register::mstatus::read().mie()
334        || hal::interrupt::current_runlevel() >= hal::interrupt::Priority::Priority1;
335}
336
337#[derive(Debug, Clone, Copy)]
338#[cfg_attr(feature = "defmt", derive(defmt::Format))]
339/// Error which can be returned during [`init`].
340#[non_exhaustive]
341pub enum InitializationError {
342    /// A general error occurred.
343    /// The internal error code is reported.
344    General(i32),
345    /// An error from the Wi-Fi driver.
346    #[cfg(feature = "wifi")]
347    WifiError(WifiError),
348    /// The current CPU clock frequency is too low.
349    WrongClockConfig,
350    /// Tried to initialize while interrupts are disabled.
351    /// This is not supported.
352    InterruptsDisabled,
353    /// The scheduler is not initialized.
354    SchedulerNotInitialized,
355    #[cfg(esp32)]
356    /// ADC2 is required by esp-radio, but it is in use by esp-hal.
357    Adc2IsUsed,
358}
359
360impl core::error::Error for InitializationError {}
361
362impl core::fmt::Display for InitializationError {
363    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
364        match self {
365            InitializationError::General(e) => write!(f, "A general error {e} occurred"),
366            #[cfg(feature = "wifi")]
367            InitializationError::WifiError(e) => {
368                write!(f, "Wi-Fi driver related error occured: {e}")
369            }
370            InitializationError::WrongClockConfig => {
371                write!(f, "The current CPU clock frequency is too low")
372            }
373            InitializationError::InterruptsDisabled => write!(
374                f,
375                "Attempted to initialize while interrupts are disabled (Unsupported)"
376            ),
377            InitializationError::SchedulerNotInitialized => {
378                write!(f, "The scheduler is not initialized")
379            }
380            #[cfg(esp32)]
381            InitializationError::Adc2IsUsed => write!(
382                f,
383                "ADC2 cannot be used with `radio` functionality on `esp32`"
384            ),
385        }
386    }
387}
388
389#[cfg(feature = "wifi")]
390impl From<WifiError> for InitializationError {
391    fn from(value: WifiError) -> Self {
392        InitializationError::WifiError(value)
393    }
394}
395
396/// Enable verbose logging within the Wi-Fi driver
397/// Does nothing unless the `sys-logs` feature is enabled.
398#[instability::unstable]
399pub fn wifi_set_log_verbose() {
400    #[cfg(all(feature = "sys-logs", not(esp32h2)))]
401    unsafe {
402        use crate::binary::include::{
403            esp_wifi_internal_set_log_level,
404            wifi_log_level_t_WIFI_LOG_VERBOSE,
405        };
406
407        esp_wifi_internal_set_log_level(wifi_log_level_t_WIFI_LOG_VERBOSE);
408    }
409}