Skip to main content

m5stack_core/driver/radio/
ble.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2//! BLE radio driver wrapper around `esp-radio`.
3//!
4//! Initializes the ESP32/ESP32-S3 radio coprocessor and returns a
5//! `BleConnector` for use with the `trouble-host` BLE stack.
6//!
7//! Ref: <https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/bluetooth/index.html>
8use esp_hal::peripherals::BT;
9use esp_radio::ble::{Config, InvalidConfigError, controller::{BleConnector, BleInitError}};
10use thiserror_no_std::Error;
11
12#[derive(Debug, Error)]
13pub enum RadioError {
14    #[error("Failed to initialize BLE controller")]
15    BleInitError(#[from] BleInitError),
16
17    #[error("Failed to initialize BLE controller")]
18    BleConfigError(#[from] InvalidConfigError),
19}
20
21pub struct BleRadio {
22    pub ble_connector: BleConnector<'static>,
23}
24
25impl BleRadio {
26    /// Initialize BLE in scan-only mode with minimal buffer footprint.
27    ///
28    /// esp-radio's `Config::default()` sizes the BT-controller blob for
29    /// `max_connections=6` and `scan_duplicate_list_count=100`. On ESP32-S3
30    /// that defaults to ~36 KB of heap, which doesn't fit alongside LVGL on
31    /// cores3 (56 KB heap). Our use case is passive scanning of Victron
32    /// advertisements — we never open a connection, and only a handful of
33    /// distinct advertisers exist on the bus.
34    ///
35    /// `max_connections=1` is the minimum the controller accepts. Setting
36    /// it to 1 shrinks the per-connection static state. Combined with a
37    /// 10-entry duplicate-filter (the spec minimum, range 10–1000), this
38    /// frees ~10 KB on ESP32-S3 and a smaller amount on ESP32.
39    pub fn new(bt: BT<'static>) -> Result<Self, RadioError> {
40        // task_stack_size: BLE controller's own task stack (esp-radio default
41        // is 4096 B). The HIL hit a stack-guard watchpoint in
42        // `ld_sscan_frm_cbk` (controller's scan-frame callback): SP within
43        // 32 B of the per-task guard while a `r_rwbtdm_isr_wrapper` was
44        // nesting on top. Bumping to 8 KB gives the controller's deepest
45        // ISR-nested path room without resizing the main task stack region
46        // (which is pinned at [28, 80] KB by ESP_HAL_CONFIG_MAIN_STACK_*).
47        // Costs 4 KB of heap — well within budget on both targets.
48        let cfg = Config::default()
49            .with_task_priority(10)
50            .with_max_connections(1)
51            .with_scan_duplicate_list_count(10)
52            .with_task_stack_size(8192);
53        let ble_connector = BleConnector::new(bt, cfg)?;
54
55        Ok(Self { ble_connector })
56    }
57}