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::SoftwareInterruptControl;
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//! let sw_interrupt = SoftwareInterruptControl::new(peripherals.SW_INTERRUPT);
45//!
46//! // THIS IS IMPORTANT FOR WIFI AND BLE: You MUST start the scheduler
47//! // before initializing the radio!
48//! esp_rtos::start(timg0.timer0, sw_interrupt.software_interrupt0);
49#![cfg_attr(
50    wifi_driver_supported,
51    doc = r#"
52
53if let Ok(controller) = esp_radio::wifi::WifiController::new(
54    peripherals.WIFI,
55    Default::default(),
56) {}
57"#
58)]
59#![cfg_attr(
60    all(bt_driver_supported, not(wifi_driver_supported)),
61    doc = r#"
62
63# use esp_radio::ble::controller::BleConnector;
64if let Ok(controller) = BleConnector::new(peripherals.BT, Default::default()) {}
65"#
66)]
67#![doc = esp_hal::after_snippet!()]
68//! ```
69//! ```toml
70//! [dependencies.esp-radio]
71//! # A supported chip needs to be specified, as well as specific use-case features
72#![doc = concat!(r#"features = [""#, chip!(), r#"", "wifi", "esp-now", "esp-alloc"]"#)]
73//! [dependencies.esp-rtos]
74#![doc = concat!(r#"features = [""#, chip!(), r#"", "esp-radio", "esp-alloc"]"#)]
75//! [dependencies.esp-alloc]
76#![doc = concat!(r#"features = [""#, chip!(), r#""]"#)]
77//! ```
78//! 
79//! ## Examples
80//!
81//! We have a number of [examples] in the esp-hal repository. We use
82//! an [xtask] to automate the building, running, and testing of code and
83//! examples within esp-hal.
84//!
85//! Invoke the following command in the root of the esp-hal repository to get
86//! started:
87//! ```bash
88//! cargo xtask help
89//! ```
90//! 
91//! We have a [book] that explains the full esp-hal ecosystem
92//! and how to get started, and a [training] that covers some common
93//! scenarios with examples.
94//!
95//! ## Optimization level
96//!
97//! The radio blobs require optimization level 2 or 3 to function correctly.
98//! Without it, Wi-Fi may fail to connect and BLE may fail to advertise.
99//!
100//! To apply this only to esp-radio in debug builds, add to your `Cargo.toml`:
101//! ```toml
102//! [profile.dev.package.esp-radio]
103//! opt-level = 3
104//! ```
105//! 
106//! ## Disabling logging
107//!
108//! `esp-radio` contains trace-level logging statements that may impact
109//! performance. To disable them, use the `log` crate's compile-time
110//! [filters](https://docs.rs/log/latest/log/#compile-time-filters) and set
111//! `release_max_level_off`.
112//!
113//! [documentation]: https://docs.espressif.com/projects/rust/esp-radio/latest/
114//! [esp-hal]: https://docs.espressif.com/projects/rust/esp-hal/latest/
115//! [`esp-alloc`]: https://docs.espressif.com/projects/rust/esp-alloc/latest/
116//! [`esp-rtos`]: https://docs.espressif.com/projects/rust/esp-rtos/latest/
117//! [examples]: https://github.com/esp-rs/esp-hal/tree/main/examples
118//! [xtask]: https://github.com/matklad/cargo-xtask
119//! [book]: https://docs.espressif.com/projects/rust/book/
120//! [training]: https://docs.espressif.com/projects/rust/no_std-training/
121#![cfg_attr(
122    multi_core,
123    doc = concat!(
124        "## Running on the second core",
125        "\n\n",
126        "BLE and Wi-Fi can also be run on the second core.",
127        "\n\n",
128        "`esp_radio::init` is recommended to be called on the first core. The tasks ",
129        "created by `esp-radio` are pinned to the first core.",
130        "\n\n",
131        "It's also important to allocate adequate stack for the second core; in many ",
132        "cases 8kB is not enough, and 16kB or more may be required depending on your ",
133        "use case. Failing to allocate adequate stack may result in strange behaviour, ",
134        "such as your application silently failing at some point during execution."
135    )
136)]
137//! ## Feature flags
138//!
139//! Note that not all features are available on every MCU. For example, `ble`
140//! (and thus, `coex`) is not available on ESP32-S2.
141//!
142//! When using the `dump_packets` config you can use the extcap in
143//! `extras/esp-wifishark` to analyze the frames in Wireshark.
144//! For more information see
145//! [extras/esp-wifishark/README.md](../extras/esp-wifishark/README.md)
146#![doc = document_features::document_features!(feature_label = r#"<span class="stab portability"><code>{feature}</code></span>"#)]
147//! ## Additional configuration
148//!
149//! We've exposed some configuration options that don't fit into cargo
150//! features. These can be set via environment variables, or via cargo's `[env]`
151//! section inside `.cargo/config.toml`. Below is a table of tunable parameters
152//! for this crate:
153#![doc = ""]
154#![doc = include_str!(concat!(env!("OUT_DIR"), "/esp_radio_config_table.md"))]
155#![doc(html_logo_url = "https://docs.espressif.com/projects/rust/esp-rs-grey-bg.svg")]
156#![no_std]
157#![deny(missing_docs, rust_2018_idioms, rustdoc::all)]
158#![cfg_attr(
159    not(any(feature = "wifi", feature = "ble")),
160    allow(
161        unused,
162        reason = "There are a number of places where code is needed for either wifi or ble,
163        and cfg-ing them out would make the code less readable just to avoid warnings in the
164        less common case. Truly unused code will be flagged by the check that enables either
165        ble or wifi."
166    )
167)]
168#![cfg_attr(docsrs, feature(doc_cfg, custom_inner_attributes, proc_macro_hygiene))]
169
170#[macro_use]
171extern crate esp_metadata_generated;
172
173extern crate alloc;
174
175// These modules rely on `#[macro_use]` so they must be the first ones declared
176mod coex_utils;
177mod fmt;
178pub(crate) mod reg_access;
179
180use core::marker::PhantomData;
181
182use esp_hal as hal;
183#[instability::unstable]
184pub use esp_phy::CalibrationResult;
185use esp_radio_rtos_driver as preempt;
186#[cfg(all(esp32, feature = "unstable"))]
187use hal::analog::adc::{release_adc2, try_claim_adc2};
188#[cfg(feature = "wifi")]
189use hal::{after_snippet, before_snippet};
190use sys::include::esp_phy_calibration_data_t;
191pub(crate) mod sys {
192    #[cfg(esp32)]
193    pub use esp_wifi_sys_esp32::*;
194    #[cfg(esp32c2)]
195    pub use esp_wifi_sys_esp32c2::*;
196    #[cfg(esp32c3)]
197    pub use esp_wifi_sys_esp32c3::*;
198    #[cfg(esp32c5)]
199    pub use esp_wifi_sys_esp32c5::*;
200    #[cfg(esp32c6)]
201    pub use esp_wifi_sys_esp32c6::*;
202    #[cfg(esp32c61)]
203    pub use esp_wifi_sys_esp32c61::*;
204    #[cfg(esp32h2)]
205    pub use esp_wifi_sys_esp32h2::*;
206    #[cfg(esp32s2)]
207    pub use esp_wifi_sys_esp32s2::*;
208    #[cfg(esp32s3)]
209    pub use esp_wifi_sys_esp32s3::*;
210}
211
212use crate::refcount::Refcount;
213#[cfg(feature = "wifi")]
214use crate::wifi::WifiError;
215
216// can't use instability on inline module definitions, see https://github.com/rust-lang/rust/issues/54727
217#[doc(hidden)]
218macro_rules! unstable_module {
219    ($(
220        $(#[$meta:meta])*
221        pub mod $module:ident;
222    )*) => {
223        $(
224            $(#[$meta])*
225            #[cfg(feature = "unstable")]
226            #[cfg_attr(docsrs, doc(cfg(feature = "unstable")))]
227            pub mod $module;
228
229            $(#[$meta])*
230            #[cfg(not(feature = "unstable"))]
231            #[cfg_attr(docsrs, doc(cfg(feature = "unstable")))]
232            #[allow(unused)]
233            pub(crate) mod $module;
234        )*
235    };
236}
237
238mod asynch;
239mod compat;
240mod interrupt_dispatch;
241mod radio_clocks;
242mod refcount;
243mod time;
244
245#[cfg(feature = "wifi")]
246pub mod wifi;
247
248unstable_module! {
249    #[cfg(feature = "esp-now")]
250    #[cfg_attr(docsrs, doc(cfg(feature = "esp-now")))]
251    pub mod esp_now;
252    #[cfg(feature = "ble")]
253    #[cfg_attr(docsrs, doc(cfg(feature = "ble")))]
254    pub mod ble;
255    #[cfg(feature = "ieee802154")]
256    #[cfg_attr(docsrs, doc(cfg(feature = "ieee802154")))]
257    pub mod ieee802154;
258}
259
260pub(crate) mod common_adapter;
261
262#[cfg(all(feature = "ble", bt_controller = "npl"))]
263pub(crate) static ESP_RADIO_LOCK: esp_sync::RawMutex = esp_sync::RawMutex::new();
264
265// this is just to verify that we use the correct defaults in `build.rs`
266#[allow(clippy::assertions_on_constants)] // TODO: try assert_eq once it's usable in const context
267const _: () = {
268    cfg_if::cfg_if! {
269        if #[cfg(wifi_driver_supported)] {
270            core::assert!(sys::include::CONFIG_ESP_WIFI_STATIC_RX_BUFFER_NUM == 10);
271            core::assert!(sys::include::CONFIG_ESP_WIFI_DYNAMIC_RX_BUFFER_NUM == 32);
272            core::assert!(sys::include::WIFI_STATIC_TX_BUFFER_NUM == 0);
273            core::assert!(sys::include::CONFIG_ESP_WIFI_DYNAMIC_TX_BUFFER_NUM == 32);
274            core::assert!(sys::include::CONFIG_ESP_WIFI_AMPDU_RX_ENABLED == 1);
275            core::assert!(sys::include::CONFIG_ESP_WIFI_AMPDU_TX_ENABLED == 1);
276            core::assert!(sys::include::WIFI_AMSDU_TX_ENABLED == 0);
277            core::assert!(sys::include::CONFIG_ESP32_WIFI_RX_BA_WIN == 6);
278        }
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    #[cfg(all(esp32, feature = "unstable"))]
306    if try_claim_adc2(unsafe { hal::Internal::conjure() }).is_err() {
307        panic!(
308            "ADC2 is currently in use by esp-hal, but esp-radio requires it for Wi-Fi operation."
309        );
310    }
311
312    if !preempt::initialized() {
313        panic!("The scheduler must be initialized before initializing the radio.");
314    }
315
316    // A minimum clock of 80MHz is required to operate Wi-Fi module.
317    const MIN_CLOCK: u32 = 80;
318    let cpu_clock = esp_hal::clock::cpu_clock().as_mhz();
319    if cpu_clock < MIN_CLOCK {
320        panic!(
321            "CPU clock {} MHz is too slow for Wi-Fi operation, minimum required is {} MHz",
322            cpu_clock, MIN_CLOCK
323        );
324    }
325
326    crate::common_adapter::enable_wifi_power_domain();
327
328    wifi_set_log_verbose();
329    radio_clocks::init_radio_clocks();
330
331    #[cfg(feature = "coex")]
332    match crate::wifi::coex_initialize() {
333        0 => {}
334        error => panic!("Failed to initialize coexistence, error code: {}", error),
335    }
336
337    debug!("Radio initialized");
338}
339
340pub(crate) fn deinit() {
341    // Disable coexistence
342    #[cfg(feature = "coex")]
343    {
344        unsafe { crate::wifi::os_adapter::coex_disable() };
345        unsafe { crate::wifi::os_adapter::coex_deinit() };
346    }
347
348    #[cfg(feature = "wifi")]
349    wifi::shutdown_wifi_isr();
350    #[cfg(feature = "ble")]
351    ble::shutdown_ble_isr();
352
353    #[cfg(all(esp32, feature = "unstable"))]
354    // Allow using `ADC2` again
355    release_adc2(unsafe { esp_hal::Internal::conjure() });
356
357    debug!("Radio deinitialized");
358}
359
360/// Management of the global reference count
361/// and conditional hardware initialization/deinitialization.
362#[derive(Debug)]
363#[cfg_attr(feature = "defmt", derive(defmt::Format))]
364pub(crate) struct RadioRefGuard {
365    _private: PhantomData<()>,
366}
367
368static RADIO_REFCOUNT: Refcount = Refcount::new();
369
370impl RadioRefGuard {
371    /// Increments the refcount. If the old count was 0, it performs hardware init.
372    /// If hardware init fails, it rolls back the refcount only once.
373    fn new() -> Self {
374        debug!("Creating RadioRefGuard");
375
376        RADIO_REFCOUNT.increment(init);
377        RadioRefGuard {
378            _private: PhantomData,
379        }
380    }
381}
382
383impl Drop for RadioRefGuard {
384    /// Decrements the refcount. If the count drops to 0, it performs hardware de-init.
385    fn drop(&mut self) {
386        debug!("Dropping RadioRefGuard");
387
388        RADIO_REFCOUNT.decrement(deinit);
389    }
390}
391
392/// Enable verbose logging within the Wi-Fi driver
393/// Does nothing unless the `print-logs-from-driver` feature is enabled.
394#[instability::unstable]
395pub fn wifi_set_log_verbose() {
396    #[cfg(all(feature = "print-logs-from-driver", not(esp32h2)))]
397    unsafe {
398        use crate::sys::include::{
399            esp_wifi_internal_set_log_level,
400            wifi_log_level_t_WIFI_LOG_VERBOSE,
401        };
402
403        esp_wifi_internal_set_log_level(wifi_log_level_t_WIFI_LOG_VERBOSE);
404    }
405}
406
407/// Get calibration data.
408///
409/// Returns the last calibration result.
410///
411/// If [last_calibration_result] returns [CalibrationResult::DataCheckFailed], consider persisting
412/// the new data.
413#[instability::unstable]
414pub fn phy_calibration_data(data: &mut [u8; esp_phy::PHY_CALIBRATION_DATA_LENGTH]) {
415    let _ = esp_phy::backup_phy_calibration_data(data);
416}
417
418/// Set calibration data.
419///
420/// This will be used next time the phy gets initialized.
421#[instability::unstable]
422pub fn set_phy_calibration_data(data: &[u8; core::mem::size_of::<esp_phy_calibration_data_t>()]) {
423    // Although we're ignoring the result here, this doesn't change the behavior, as this just
424    // doesn't do anything in case an error is returned.
425    let _ = esp_phy::set_phy_calibration_data(data);
426}
427
428/// Get the last calibration result.
429///
430/// This can be used to know if any previously persisted calibration data is outdated/invalid and
431/// needs to get updated.
432#[instability::unstable]
433pub fn last_calibration_result() -> Option<CalibrationResult> {
434    esp_phy::last_calibration_result()
435}