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