Skip to main content

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#![doc = concat!("**", chip_pretty!(), "**")]
9//! . Please ensure you are reading the correct [documentation] for your target
10//! device.
11//!
12//! ## Overview
13//!
14//! esp-radio provides Wi-Fi, Bluetooth Low Energy (BLE), ESP-NOW and IEEE
15//! 802.15.4 drivers for Espressif microcontrollers. It builds on top of
16//! [esp-hal] and the vendor binary blobs to provide wireless connectivity in a
17//! `no_std` environment.
18//!
19//! Wi-Fi and BLE require a dynamic memory allocator and a preemptive task
20//! scheduler at runtime. We recommend [`esp-alloc`] and [`esp-rtos`] for this,
21//! though any allocator or RTOS (such as Ariel OS) that implements the required
22//! interfaces will work.
23#![cfg_attr(
24    feature = "ieee802154",
25    doc = "<div class=\"warning\"><b>Hint:</b> The scheduler is not required for IEEE 802.15.4.</div>"
26)]
27//! Drivers that don't currently have a stable API are marked as `unstable` in
28//! the documentation. Enabling the `unstable` feature on `esp-radio` requires
29//! you to also enable the `unstable` feature on `esp-hal` in the final binary
30//! crate.
31//!
32//! ### Quick start
33//!
34//! ```rust, no_run
35#![doc = esp_hal::before_snippet!()]
36//! use esp_hal::interrupt::software::SoftwareInterrupt;
37//! use esp_hal::ram;
38//! use esp_hal::timer::timg::TimerGroup;
39//!
40//! esp_alloc::heap_allocator!(#[ram(reclaimed)] size: 64 * 1024);
41//! esp_alloc::heap_allocator!(size: 36 * 1024);
42//!
43//! let timg0 = TimerGroup::new(peripherals.TIMG0);
44//!
45//! // THIS IS IMPORTANT FOR WIFI AND BLE: You MUST start the scheduler
46//! // before initializing the radio!
47//! esp_rtos::start(timg0.timer0);
48#![cfg_attr(
49    wifi_driver_supported,
50    doc = r#"
51
52if let Ok(controller) = esp_radio::wifi::WifiController::new(
53    peripherals.WIFI,
54    Default::default(),
55) {}
56"#
57)]
58#![cfg_attr(
59    all(bt_driver_supported, not(wifi_driver_supported)),
60    doc = r#"
61
62# use esp_radio::ble::controller::BleConnector;
63if let Ok(controller) = BleConnector::new(peripherals.BT, Default::default()) {}
64"#
65)]
66#![doc = esp_hal::after_snippet!()]
67//! ```
68//! ```toml
69//! [dependencies.esp-radio]
70//! # A supported chip needs to be specified, as well as specific use-case features
71#![doc = concat!(r#"features = [""#, chip!(), r#"", "wifi", "esp-now", "esp-alloc"]"#)]
72//! [dependencies.esp-rtos]
73#![doc = concat!(r#"features = [""#, chip!(), r#"", "esp-radio", "esp-alloc"]"#)]
74//! [dependencies.esp-alloc]
75#![doc = concat!(r#"features = [""#, chip!(), r#""]"#)]
76//! ```
77//! 
78//! ## Examples
79//!
80//! We have a number of [examples] in the esp-hal repository. We use
81//! an [xtask] to automate the building, running, and testing of code and
82//! examples within esp-hal.
83//!
84//! Invoke the following command in the root of the esp-hal repository to get
85//! started:
86//! ```bash
87//! cargo xtask help
88//! ```
89//! 
90//! We have a [book] that explains the full esp-hal ecosystem
91//! and how to get started, and a [training] that covers some common
92//! scenarios with examples.
93//!
94//! ## Optimization level
95//!
96//! The radio blobs require optimization level 2 or 3 to function correctly.
97//! Without it, Wi-Fi may fail to connect and BLE may fail to advertise.
98//!
99//! To apply this only to esp-radio in debug builds, add to your `Cargo.toml`:
100//! ```toml
101//! [profile.dev.package.esp-radio]
102//! opt-level = 3
103//! ```
104//! 
105//! ## Disabling logging
106//!
107//! `esp-radio` contains trace-level logging statements that may impact
108//! performance. To disable them, use the `log` crate's compile-time
109//! [filters](https://docs.rs/log/latest/log/#compile-time-filters) and set
110//! `release_max_level_off`.
111//!
112//! [documentation]: https://docs.espressif.com/projects/rust/esp-radio/latest/
113//! [esp-hal]: https://docs.espressif.com/projects/rust/esp-hal/latest/
114//! [`esp-alloc`]: https://docs.espressif.com/projects/rust/esp-alloc/latest/
115//! [`esp-rtos`]: https://docs.espressif.com/projects/rust/esp-rtos/latest/
116//! [examples]: https://github.com/esp-rs/esp-hal/tree/main/examples
117//! [xtask]: https://github.com/matklad/cargo-xtask
118//! [book]: https://docs.espressif.com/projects/rust/book/
119//! [training]: https://docs.espressif.com/projects/rust/no_std-training/
120#![cfg_attr(
121    multi_core,
122    doc = concat!(
123        "## Running on the second core",
124        "\n\n",
125        "BLE and Wi-Fi can also be run on the second core.",
126        "\n\n",
127        "`esp_radio::init` is recommended to be called on the first core. The tasks ",
128        "created by `esp-radio` are pinned to the first core.",
129        "\n\n",
130        "It's also important to allocate adequate stack for the second core; in many ",
131        "cases 8kB is not enough, and 16kB or more may be required depending on your ",
132        "use case. Failing to allocate adequate stack may result in strange behaviour, ",
133        "such as your application silently failing at some point during execution."
134    )
135)]
136//! ## Feature flags
137//!
138//! Note that not all features are available on every MCU. For example, `ble`
139//! (and thus, `coex`) is not available on ESP32-S2.
140//!
141//! When using the `dump_packets` config you can use the extcap in
142//! `extras/esp-wifishark` to analyze the frames in Wireshark.
143//! For more information see
144//! [extras/esp-wifishark/README.md](../extras/esp-wifishark/README.md)
145#![doc = document_features::document_features!(feature_label = r#"<span class="stab portability"><code>{feature}</code></span>"#)]
146//! ## Additional configuration
147//!
148//! We've exposed some configuration options that don't fit into cargo
149//! features. These can be set via environment variables, or via cargo's `[env]`
150//! section inside `.cargo/config.toml`. Below is a table of tunable parameters
151//! for this crate:
152#![doc = ""]
153#![doc = include_str!(concat!(env!("OUT_DIR"), "/esp_radio_config_table.md"))]
154#![doc(html_logo_url = "https://docs.espressif.com/projects/rust/esp-rs-grey-bg.svg")]
155#![no_std]
156#![deny(missing_docs, rust_2018_idioms, rustdoc::all)]
157#![cfg_attr(
158    not(any(feature = "wifi", feature = "ble")),
159    allow(
160        unused,
161        reason = "There are a number of places where code is needed for either wifi or ble,
162        and cfg-ing them out would make the code less readable just to avoid warnings in the
163        less common case. Truly unused code will be flagged by the check that enables either
164        ble or wifi."
165    )
166)]
167#![cfg_attr(docsrs, feature(doc_cfg, custom_inner_attributes, proc_macro_hygiene))]
168
169#[macro_use]
170extern crate esp_metadata_generated;
171
172extern crate alloc;
173
174// These modules rely on `#[macro_use]` so they must be the first ones declared
175mod coex_utils;
176mod fmt;
177pub(crate) mod reg_access;
178
179use core::marker::PhantomData;
180
181use esp_hal as hal;
182#[instability::unstable]
183pub use esp_phy::CalibrationResult;
184use esp_radio_rtos_driver as preempt;
185#[cfg(feature = "wifi")]
186use hal::{after_snippet, before_snippet};
187use sys::include::esp_phy_calibration_data_t;
188pub(crate) mod sys {
189    #[cfg(esp32)]
190    pub use esp_wifi_sys_esp32::*;
191    #[cfg(esp32c2)]
192    pub use esp_wifi_sys_esp32c2::*;
193    #[cfg(esp32c3)]
194    pub use esp_wifi_sys_esp32c3::*;
195    #[cfg(esp32c5)]
196    pub use esp_wifi_sys_esp32c5::*;
197    #[cfg(esp32c6)]
198    pub use esp_wifi_sys_esp32c6::*;
199    #[cfg(esp32c61)]
200    pub use esp_wifi_sys_esp32c61::*;
201    #[cfg(esp32h2)]
202    pub use esp_wifi_sys_esp32h2::*;
203    #[cfg(esp32s2)]
204    pub use esp_wifi_sys_esp32s2::*;
205    #[cfg(esp32s3)]
206    pub use esp_wifi_sys_esp32s3::*;
207    #[cfg(esp32s31)]
208    pub use esp_wifi_sys_esp32s31::*;
209}
210
211use crate::refcount::Refcount;
212#[cfg(feature = "wifi")]
213use crate::wifi::WifiError;
214
215// can't use instability on inline module definitions, see https://github.com/rust-lang/rust/issues/54727
216#[doc(hidden)]
217macro_rules! unstable_module {
218    ($(
219        $(#[$meta:meta])*
220        pub mod $module:ident;
221    )*) => {
222        $(
223            $(#[$meta])*
224            #[cfg(feature = "unstable")]
225            #[cfg_attr(docsrs, doc(cfg(feature = "unstable")))]
226            pub mod $module;
227
228            $(#[$meta])*
229            #[cfg(not(feature = "unstable"))]
230            #[cfg_attr(docsrs, doc(cfg(feature = "unstable")))]
231            #[allow(unused)]
232            pub(crate) mod $module;
233        )*
234    };
235}
236
237mod asynch;
238mod compat;
239#[cfg(esp32s31)]
240mod compiler_rt_abi;
241mod interrupt_dispatch;
242mod radio_clocks;
243mod refcount;
244mod time;
245
246#[cfg(feature = "wifi")]
247pub mod wifi;
248
249unstable_module! {
250    #[cfg(feature = "esp-now")]
251    #[cfg_attr(docsrs, doc(cfg(feature = "esp-now")))]
252    pub mod esp_now;
253    #[cfg(feature = "ble")]
254    #[cfg_attr(docsrs, doc(cfg(feature = "ble")))]
255    pub mod ble;
256    #[cfg(feature = "ieee802154")]
257    #[cfg_attr(docsrs, doc(cfg(feature = "ieee802154")))]
258    pub mod ieee802154;
259}
260
261pub(crate) mod common_adapter;
262
263#[cfg(all(feature = "ble", bt_controller = "npl"))]
264pub(crate) static ESP_RADIO_LOCK: esp_sync::RawMutex = esp_sync::RawMutex::new();
265
266// this is just to verify that we use the correct defaults in `build.rs`
267#[allow(clippy::assertions_on_constants)] // TODO: try assert_eq once it's usable in const context
268const _: () = {
269    #[cfg(wifi_driver_supported)]
270    {
271        core::assert!(sys::include::CONFIG_ESP_WIFI_STATIC_RX_BUFFER_NUM == 10);
272        core::assert!(sys::include::CONFIG_ESP_WIFI_DYNAMIC_RX_BUFFER_NUM == 32);
273        core::assert!(sys::include::WIFI_STATIC_TX_BUFFER_NUM == 0);
274        core::assert!(sys::include::CONFIG_ESP_WIFI_DYNAMIC_TX_BUFFER_NUM == 32);
275        core::assert!(sys::include::CONFIG_ESP_WIFI_AMPDU_RX_ENABLED == 1);
276        core::assert!(sys::include::CONFIG_ESP_WIFI_AMPDU_TX_ENABLED == 1);
277        core::assert!(sys::include::WIFI_AMSDU_TX_ENABLED == 0);
278        core::assert!(sys::include::CONFIG_ESP32_WIFI_RX_BA_WIN == 6);
279    };
280};
281
282#[procmacros::doc_replace]
283/// Initialize for using Wi-Fi and or BLE.
284///
285/// Wi-Fi and BLE require a preemptive scheduler to be present. Without one, the underlying firmware
286/// can't operate. The scheduler must implement the interfaces in the `esp-radio-rtos-driver`
287/// crate. If you are using an embedded RTOS like Ariel OS, it needs to provide an appropriate
288/// implementation.
289///
290/// If you are not using an embedded RTOS, use the `esp-rtos` crate which provides the
291/// necessary functionality.
292///
293/// Make sure to **not** call this function while interrupts are disabled.
294///
295/// ## Errors
296///
297/// - The function may return an error if the scheduler is not initialized.
298#[cfg_attr(
299    esp32,
300    doc = " - The function may return an error if ADC2 is already in use."
301)]
302/// - The function may return an error if interrupts are disabled.
303/// - The function may return an error if initializing the underlying driver fails.
304pub(crate) fn init() {
305    esp_hal::if_unstable_hal! {
306        #[cfg(esp32)]
307        if hal::analog::adc::try_claim_adc2(unsafe { hal::Internal::conjure() }).is_err() {
308            panic!(
309                "ADC2 is currently in use by esp-hal, but esp-radio requires it for Wi-Fi operation."
310            );
311        }
312        esp_hal::rtc_cntl::WakeLock::acquire();
313    }
314
315    if !preempt::initialized() {
316        panic!("The scheduler must be initialized before initializing the radio.");
317    }
318
319    // A minimum clock of 80MHz is required to operate Wi-Fi module.
320    const MIN_CLOCK: u32 = 80;
321    let cpu_clock = esp_hal::clock::cpu_clock().as_mhz();
322    if cpu_clock < MIN_CLOCK {
323        panic!(
324            "CPU clock {} MHz is too slow for Wi-Fi operation, minimum required is {} MHz",
325            cpu_clock, MIN_CLOCK
326        );
327    }
328
329    // Ungate the modem clocks first: `enable_wifi_power_domain` pulses the
330    // modem reset, which is ineffective while the clocks are gated — and
331    // esp-phy's clock guard has gated them again by the time we re-init.
332    // (ESP-IDF never gates these clocks, so its power-up reset always lands.)
333    radio_clocks::init_radio_clocks();
334
335    crate::common_adapter::enable_wifi_power_domain();
336
337    wifi_set_log_verbose();
338
339    #[cfg(feature = "coex")]
340    match crate::wifi::coex_initialize() {
341        0 => {}
342        error => panic!("Failed to initialize coexistence, error code: {}", error),
343    }
344
345    debug!("Radio initialized");
346}
347
348pub(crate) fn deinit() {
349    // Disable coexistence
350    #[cfg(feature = "coex")]
351    {
352        unsafe { crate::wifi::os_adapter::coex_disable() };
353        unsafe { crate::wifi::os_adapter::coex_deinit() };
354    }
355
356    #[cfg(feature = "wifi")]
357    wifi::shutdown_wifi_isr();
358    #[cfg(feature = "ble")]
359    ble::shutdown_ble_isr();
360
361    // Gate the BT clocks (the Wi-Fi driver gates its own clocks during
362    // `wifi_deinit`), power down the modem power domain, and gate the
363    // remaining modem clocks, mirroring ESP-IDF's fixed-mask clock control
364    // (`periph_ll_wifi_module_disable_clk_set_rst` and friends). This must
365    // only run once all radios are off: PHY teardown still needs the modem
366    // clocks.
367    #[cfg(feature = "ble")]
368    crate::radio_clocks::clocks_ll::enable_bt(false);
369    crate::common_adapter::disable_wifi_power_domain();
370    crate::radio_clocks::deinit_radio_clocks();
371
372    // After the modem power domain has been powered down, the PHY driver's
373    // internal init flag must be reset, otherwise the next `phy_wakeup_init`
374    // assumes retained PHY registers that the power-down wiped (mirrors
375    // ESP-IDF's `esp_phy_modem_deinit`, "Fix the issue caused by the power
376    // domain off. This issue is only on ESP32C3.").
377    #[cfg(esp32c3)]
378    unsafe {
379        crate::sys::include::phy_init_flag()
380    };
381
382    esp_hal::if_unstable_hal! {
383        // Allow using `ADC2` again
384        #[cfg(esp32)]
385        hal::analog::adc::release_adc2(unsafe { esp_hal::Internal::conjure() });
386
387        esp_hal::rtc_cntl::WakeLock::release();
388    }
389
390    debug!("Radio deinitialized");
391}
392
393/// Management of the global reference count
394/// and conditional hardware initialization/deinitialization.
395#[derive(Debug)]
396#[cfg_attr(feature = "defmt", derive(defmt::Format))]
397pub(crate) struct RadioRefGuard {
398    _private: PhantomData<()>,
399}
400
401static RADIO_REFCOUNT: Refcount = Refcount::new();
402
403impl RadioRefGuard {
404    /// Increments the refcount. If the old count was 0, it performs hardware init.
405    /// If hardware init fails, it rolls back the refcount only once.
406    pub(crate) fn new() -> Self {
407        debug!("Creating RadioRefGuard");
408
409        RADIO_REFCOUNT.increment(init);
410        RadioRefGuard {
411            _private: PhantomData,
412        }
413    }
414}
415
416impl Drop for RadioRefGuard {
417    /// Decrements the refcount. If the count drops to 0, it performs hardware de-init.
418    fn drop(&mut self) {
419        debug!("Dropping RadioRefGuard");
420
421        RADIO_REFCOUNT.decrement(deinit);
422    }
423}
424
425/// Enable verbose logging within the Wi-Fi driver
426/// Does nothing unless the `print-logs-from-driver` feature is enabled.
427#[instability::unstable]
428pub fn wifi_set_log_verbose() {
429    #[cfg(all(feature = "print-logs-from-driver", not(esp32h2)))]
430    unsafe {
431        use crate::sys::include::{
432            esp_wifi_internal_set_log_level,
433            wifi_log_level_t_WIFI_LOG_VERBOSE,
434        };
435
436        esp_wifi_internal_set_log_level(wifi_log_level_t_WIFI_LOG_VERBOSE);
437    }
438}
439
440/// Get calibration data.
441///
442/// Returns the last calibration result.
443///
444/// If [last_calibration_result] returns [CalibrationResult::DataCheckFailed], consider persisting
445/// the new data.
446#[instability::unstable]
447pub fn phy_calibration_data(data: &mut [u8; esp_phy::PHY_CALIBRATION_DATA_LENGTH]) {
448    let _ = esp_phy::backup_phy_calibration_data(data);
449}
450
451/// Set calibration data.
452///
453/// This will be used next time the phy gets initialized.
454#[instability::unstable]
455pub fn set_phy_calibration_data(data: &[u8; core::mem::size_of::<esp_phy_calibration_data_t>()]) {
456    // Although we're ignoring the result here, this doesn't change the behavior, as this just
457    // doesn't do anything in case an error is returned.
458    let _ = esp_phy::set_phy_calibration_data(data);
459}
460
461/// Get the last calibration result.
462///
463/// This can be used to know if any previously persisted calibration data is outdated/invalid and
464/// needs to get updated.
465#[instability::unstable]
466pub fn last_calibration_result() -> Option<CalibrationResult> {
467    esp_phy::last_calibration_result()
468}