diff --git a/rmk-macro/src/codegen/chip/bind_interrupt.rs b/rmk-macro/src/codegen/chip/bind_interrupt.rs index 44704d3e2..507be9104 100644 --- a/rmk-macro/src/codegen/chip/bind_interrupt.rs +++ b/rmk-macro/src/codegen/chip/bind_interrupt.rs @@ -175,6 +175,12 @@ pub(crate) fn bind_interrupt_default(hardware: &Hardware, item_mod: &ItemMod) -> quote! { CLOCK_POWER => ::nrf_sdc::mpsl::ClockInterruptHandler; } }; + let support_subrating = if is_feature_enabled(&get_rmk_features(), "subrating") { + quote! { .support_connection_subrating_central() } + } else { + quote! {} + }; + let ble_config = communication.get_ble_config().unwrap(); let tx_power = if let Some(pwr) = ble_config.default_tx_power { quote! { .default_tx_power(#pwr)? } @@ -201,6 +207,7 @@ pub(crate) fn bind_interrupt_default(hardware: &Hardware, item_mod: &ItemMod) -> .support_dle_central() .support_phy_update_central() .support_phy_update_peripheral() + #support_subrating #use_2m_phy #tx_power .central_count(#num_peri)? diff --git a/rmk-macro/src/codegen/chip/chip_init.rs b/rmk-macro/src/codegen/chip/chip_init.rs index 106c10066..fae974f4b 100644 --- a/rmk-macro/src/codegen/chip/chip_init.rs +++ b/rmk-macro/src/codegen/chip/chip_init.rs @@ -5,6 +5,7 @@ use rmk_config::resolved::Hardware; use rmk_config::resolved::hardware::{BoardConfig, ChipModel, ChipSeries, CommunicationConfig}; use syn::{ItemFn, ItemMod}; +use crate::codegen::feature::{get_rmk_features, is_feature_enabled}; use crate::codegen::override_helper::{Overwritten, find_overwritten}; /// Expand chip initialization code @@ -94,7 +95,13 @@ pub(crate) fn chip_init_default(hardware: &Hardware, peripheral_id: Option 0 { - 6080 + (peri_num.saturating_sub(1)) * 2288 + // Subrating needs extra memory + // TODO: Check how much exactly + if is_feature_enabled(&get_rmk_features(), "subrating") { + 6400 + (peri_num.saturating_sub(1)) * 2288 + } else { + 6080 + (peri_num.saturating_sub(1)) * 2288 + } } else { 4696 } diff --git a/rmk-macro/src/codegen/split/peripheral.rs b/rmk-macro/src/codegen/split/peripheral.rs index ae0290b17..33f602506 100644 --- a/rmk-macro/src/codegen/split/peripheral.rs +++ b/rmk-macro/src/codegen/split/peripheral.rs @@ -155,6 +155,12 @@ fn expand_bind_interrupt_for_split_peripheral( quote! {} }; + let support_subrating = if is_feature_enabled(&get_rmk_features(), "subrating") { + quote! { .support_connection_subrating_peripheral() } + } else { + quote! {} + }; + let ble_config = communication.get_ble_config().unwrap(); let tx_power = if let Some(pwr) = ble_config.default_tx_power { quote! { .default_tx_power(#pwr)? } @@ -258,6 +264,7 @@ fn expand_bind_interrupt_for_split_peripheral( .support_dle_central() .support_phy_update_central() .support_phy_update_peripheral() + #support_subrating #use_2m_phy #tx_power .peripheral_count(1)? diff --git a/rmk/Cargo.toml b/rmk/Cargo.toml index 72b03330b..04d6f3797 100644 --- a/rmk/Cargo.toml +++ b/rmk/Cargo.toml @@ -252,6 +252,11 @@ _no_usb = [] ## Internal marker for 480 Mbps USB chips; activates 512-byte bulk packets. _usb_high_speed = [] +## Enable BLE connection subrating for split Connections +subrating = ["_ble"] +## Internal feature that indicates that subrating is not supported, this will be auto-activated for some chips +_no_subrating = [] + #! ### BLE feature flags #! #! ⚠️ Due to the limitation of docs.rs, functions gated by BLE features won't show in docs.rs. You have to head to [`examples`](https://github.com/rmk-rs/rmk/tree/main/examples) folder of RMK repo for their usages. @@ -282,7 +287,7 @@ esp32s3_ble = ["_esp_ble"] _esp_ble = ["_ble", "dep:esp-hal"] ## Enable feature if you want to use RP2040W with BLE. -pico_w_ble = ["_ble", "dep:embassy-rp"] +pico_w_ble = ["_ble", "_no_subrating", "dep:embassy-rp"] ## Enable feature if you want to use SF32LB52x with BLE. sf32lb52x_ble = ["_ble"] diff --git a/rmk/src/ble/mod.rs b/rmk/src/ble/mod.rs index 8c304dc1a..cfa059a2c 100644 --- a/rmk/src/ble/mod.rs +++ b/rmk/src/ble/mod.rs @@ -1,4 +1,6 @@ -use bt_hci::cmd::le::{LeReadLocalSupportedFeatures, LeSetPhy}; +use bt_hci::cmd::le::{LeReadLocalSupportedFeatures, LeSetHostFeature, LeSetPhy}; +#[cfg(feature = "subrating")] +use bt_hci::cmd::le::{LeSubrateRequest, LeSubrateRequestParams}; use bt_hci::controller::{ControllerCmdAsync, ControllerCmdSync}; use embassy_futures::join::join3; use embassy_futures::select::{Either, Either3, select, select3}; @@ -39,6 +41,9 @@ pub mod passkey; pub(crate) mod profile; pub(crate) mod sleep; +#[cfg(all(feature = "subrating", feature = "_no_subrating"))] +compile_error!("You may not enable feature `subrating` on unsupported platforms!"); + /// Max number of connections pub(crate) const CONNECTIONS_MAX: usize = crate::SPLIT_PERIPHERALS_NUM + 1; @@ -152,7 +157,10 @@ where impl<'a, 'b, 's, C> Runnable for BleTransport<'a, 'b, 's, C> where 's: 'b, - C: Controller + ControllerCmdAsync + ControllerCmdSync, + C: Controller + + ControllerCmdAsync + + ControllerCmdSync + + ControllerCmdSync, { async fn run(&mut self) -> ! { // Load the preferred connection from storage @@ -173,6 +181,20 @@ where let product_name = self.product_name; let connection_loop = async { + // Set subrating host support feature flag + #[cfg(feature = "subrating")] + { + const CONN_SUBRATING_HOST_BIT: u8 = 38; + let cmd = LeSetHostFeature::new(CONN_SUBRATING_HOST_BIT, 1); + if let Err(e) = stack.command(cmd).await { + error!("error setting host feature: {:?}", e); + } + } + + #[cfg(feature = "split")] + // Signal to indicate the stack is started + crate::split::ble::central::STACK_STARTED.signal(true); + loop { match select( advertise(product_name, &mut peripheral, server), @@ -284,6 +306,34 @@ pub(crate) async fn ble_task, P: Pa error!("[ble_task] runner error: {:?}", e); embassy_time::Timer::after_millis(100).await; } +<<<<<<< HEAD +||||||| parent of 31ec794c (Enable Bluetooth LE Connection Subrating for split communication) + + #[cfg(feature = "split")] + { + // Signal to indicate the stack is started + crate::split::ble::central::STACK_STARTED.signal(true); + if let Err(_e) = runner + .run_with_handler(&crate::split::ble::central::ScanHandler {}) + .await + { + error!("[ble_task] runner.run_with_handler error"); + embassy_time::Timer::after_millis(100).await; + } + } +======= + + #[cfg(feature = "split")] + { + if let Err(_e) = runner + .run_with_handler(&crate::split::ble::central::ScanHandler {}) + .await + { + error!("[ble_task] runner.run_with_handler error"); + embassy_time::Timer::after_millis(100).await; + } + } +>>>>>>> 31ec794c (Enable Bluetooth LE Connection Subrating for split communication) } } @@ -643,7 +693,7 @@ pub(crate) async fn set_conn_params< &RequestedConnParams { min_connection_interval: Duration::from_micros(7500), max_connection_interval: Duration::from_micros(7500), - max_latency: 30, + max_latency: 300, // let central sleep and save power min_event_length: Duration::from_secs(0), max_event_length: Duration::from_secs(0), supervision_timeout: Duration::from_secs(10), @@ -813,6 +863,47 @@ pub(crate) async fn update_conn_params< false } +/// Update the subrate factor. +/// +/// Returns whether the request reached the controller, so callers that mirror +/// the parameters in their own state don't record params that never landed. +#[cfg(feature = "subrating")] +pub(crate) async fn update_subrate_factor< + 'a, + 'b, + C: Controller + ControllerCmdAsync, + P: PacketPool, +>( + stack: &Stack<'a, C, P>, + params: LeSubrateRequestParams, +) -> bool { + for _ in 0..10 { + let subrate_request = LeSubrateRequest::from(params); + + match stack.async_command(subrate_request).await { + Ok(_) => return true, + Err(BleHostError::BleHost(Error::Hci(error))) => { + if 0x3A == error.to_status().into_inner() { + // Busy, retry + info!("[update_subrate_factor] HCI busy: {:?}", error); + embassy_time::Timer::after_millis(100).await; + continue; + } + error!("[update_subrate_factor] HCI error: {:?}", error); + return false; + } + Err(e) => { + #[cfg(feature = "defmt")] + let e = defmt::Debug2Format(&e); + error!("[update_subrate_factor] BLE host error: {:?}", e); + return false; + } + } + } + warn!("[update_conn_params] controller stayed busy, giving up"); + false +} + #[cfg(test)] mod tests { use std::sync::{Mutex, OnceLock}; diff --git a/rmk/src/split/ble/central.rs b/rmk/src/split/ble/central.rs index 979306981..3e97b3e56 100644 --- a/rmk/src/split/ble/central.rs +++ b/rmk/src/split/ble/central.rs @@ -1,7 +1,11 @@ use core::cell::RefCell; use bt_hci::cmd::le::{LeReadLocalSupportedFeatures, LeSetPhy, LeSetScanParams}; +#[cfg(feature = "subrating")] +use bt_hci::cmd::le::{LeSubrateRequest, LeSubrateRequestParams}; use bt_hci::controller::{ControllerCmdAsync, ControllerCmdSync}; +#[cfg(feature = "subrating")] +use bt_hci::param::ConnHandle; use embassy_futures::select::{Either, Either3, select, select3}; use embassy_sync::mutex::Mutex; use embassy_sync::signal::Signal; @@ -11,6 +15,8 @@ use trouble_host::prelude::*; use super::GattSplitMessage; use crate::ble::sleep::report_activity; +#[cfg(feature = "subrating")] +use crate::ble::update_subrate_factor; use crate::ble::{update_ble_phy, update_conn_params}; use crate::channel::FLASH_CHANNEL; use crate::event::{EventSubscriber, SleepStateEvent, SubscribableEvent}; @@ -47,10 +53,7 @@ struct BleSplitCentralServer { pub async fn scan_peripherals< 'b, 's: 'b, - C: Controller - + ControllerCmdSync - + ControllerCmdAsync - + ControllerCmdSync, + C: Controller + ControllerCmdSync + ControllerCmdAsync, >( stack: &'b Stack<'s, C, DefaultPacketPool>, addrs: &RefCell>>, @@ -157,10 +160,15 @@ impl EventHandler for ScanHandler { pub(crate) async fn run_ble_peripheral_manager< 'b, 's: 'b, - C: Controller + #[cfg(not(feature = "subrating"))] C: Controller + ControllerCmdSync + ControllerCmdAsync + ControllerCmdSync, + #[cfg(feature = "subrating")] C: Controller + + ControllerCmdSync + + ControllerCmdAsync + + ControllerCmdSync + + ControllerCmdAsync, const ROW: usize, const COL: usize, const ROW_OFFSET: usize, @@ -252,12 +260,24 @@ fn default_central_conn_param() -> RequestedConnParams { RequestedConnParams { min_connection_interval: Duration::from_micros(7500), max_connection_interval: Duration::from_micros(7500), - max_latency: 10, // 75ms + max_latency: 300, // 2250ms supervision_timeout: Duration::from_secs(10), ..Default::default() } } +#[cfg(feature = "subrating")] +fn default_central_subrate_params(handle: ConnHandle) -> LeSubrateRequestParams { + LeSubrateRequestParams { + handle, + subrate_min: 1, + subrate_max: 1, + max_latency: 300, // 2250ms + continuation_number: 0, + supervision_timeout: ::bt_hci::param::Duration::from_secs(10), + } +} + /// Parameters for the central -> peripheral link while the central sleeps. /// /// With a host connected, the central's radio is busy serving the host link @@ -284,10 +304,26 @@ fn sleep_central_conn_param() -> RequestedConnParams { } } +#[cfg(feature = "subrating")] +fn sleep_central_subrate_params(handle: ConnHandle) -> LeSubrateRequestParams { + LeSubrateRequestParams { + handle, + subrate_min: 60, // 450ms interval -> 457.5ms key press latency + subrate_max: 60, + max_latency: 7, // 3,6s sleep for peripheral + continuation_number: 2, // -> assure low latency reset of subrate factor. + supervision_timeout: ::bt_hci::param::Duration::from_secs(8), + } +} + async fn run_central_manager_task< 'b, 's: 'b, - C: Controller + ControllerCmdAsync + ControllerCmdSync, + #[cfg(not(feature = "subrating"))] C: Controller + ControllerCmdAsync + ControllerCmdSync, + #[cfg(feature = "subrating")] C: Controller + + ControllerCmdAsync + + ControllerCmdSync + + ControllerCmdAsync, P: PacketPool, const ROW: usize, const COL: usize, @@ -479,7 +515,8 @@ pub(crate) async fn wait_for_stack_started() { async fn follow_sleep_state< 'b, 's: 'b, - C: Controller + ControllerCmdAsync + ControllerCmdSync, + #[cfg(not(feature = "subrating"))] C: Controller + ControllerCmdAsync + ControllerCmdSync, + #[cfg(feature = "subrating")] C: Controller + ControllerCmdAsync + ControllerCmdAsync, P: PacketPool, >( stack: &'b Stack<'s, C, P>, @@ -502,13 +539,29 @@ async fn follow_sleep_state< if sleeping == applied { continue; } - let params = if sleeping { - sleep_central_conn_param() - } else { - default_central_conn_param() - }; - if update_conn_params(stack, conn, ¶ms).await { - applied = sleeping; + #[cfg(not(feature = "subrating"))] + { + let params = if sleeping { + sleep_central_conn_param() + } else { + default_central_conn_param() + }; + if update_conn_params(stack, conn, ¶ms).await { + applied = sleeping; + } + } + + #[cfg(feature = "subrating")] + { + let params = if sleeping { + sleep_central_subrate_params(conn.handle()) + } else { + default_central_subrate_params(conn.handle()) + }; + + if update_subrate_factor(stack, params).await { + applied = sleeping; + } } } } diff --git a/rmk/src/split/central.rs b/rmk/src/split/central.rs index 0aeec6975..9f7d19a42 100644 --- a/rmk/src/split/central.rs +++ b/rmk/src/split/central.rs @@ -1,6 +1,8 @@ #[cfg(feature = "_ble")] use core::cell::RefCell; +#[cfg(feature = "subrating")] +use bt_hci::cmd::le::LeSubrateRequest; #[cfg(not(feature = "_ble"))] use embedded_io_async::{Read, Write}; #[cfg(feature = "_ble")] @@ -30,10 +32,15 @@ pub async fn run_peripheral_manager< const COL: usize, const ROW_OFFSET: usize, const COL_OFFSET: usize, - #[cfg(feature = "_ble")] C: Controller + #[cfg(all(feature = "_ble", not(feature = "subrating")))] C: Controller + ControllerCmdSync + ControllerCmdAsync + ControllerCmdSync, + #[cfg(all(feature = "_ble", feature = "subrating"))] C: Controller + + ControllerCmdSync + + ControllerCmdAsync + + ControllerCmdSync + + ControllerCmdAsync, #[cfg(not(feature = "_ble"))] S: Read + Write, >( id: usize,