diff --git a/Cargo.toml b/Cargo.toml index f8c5895..f0b020e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,40 +1,58 @@ [package] name = "microbit-bsp" -version = "0.4.1" +version = "0.5.0" edition = "2021" description = "An embassy-based boards support package (BSP) for BBC Micro:bit v2" license = "MIT OR Apache-2.0" keywords = ["embedded", "async", "nordic", "nrf", "microbit"] categories = ["embedded", "hardware-support", "no-std", "asynchronous"] repository = "https://github.com/lulf/microbit-bsp" +rust-version = "1.83" [dependencies] -embassy-nrf = { version = "0.2.0", features = ["nrf52833","gpiote","time-driver-rtc1","nfc-pins-as-gpio","time"] } -embassy-time = { version = "0.3", default-features = false } -embassy-sync = { version = "0.6", default-features = false } -cortex-m = "0.7" +embassy-nrf = { version = "0.3", default-features = false, features = [ + "gpiote", + "nfc-pins-as-gpio", + "nrf52833", + "time-driver-rtc1", +] } +embassy-time = { version = "0.4", default-features = false } +embassy-sync = { version = "0.6.2" } +cortex-m = { version = "0.7.6" } embedded-hal = "1.0" lsm303agr = "1.1.0" futures = { version = "0.3", default-features = false } + defmt = { version = "0.3", optional = true } heapless = "0.8.0" # trouble bluetooth dependencies nrf-sdc = { git = "https://github.com/alexmoon/nrf-sdc.git", default-features = false, features = [ - "defmt", - "peripheral", - "central", - "nrf52833" -], rev = "3702af909d31cd81c62f15e1aa9d5f637ec935fa", optional = true} -nrf-mpsl = { git = "https://github.com/alexmoon/nrf-sdc.git", default-features = false, features = [ - "defmt", - "critical-section-impl" -], rev = "3702af909d31cd81c62f15e1aa9d5f637ec935fa", optional = true } + "defmt", + "peripheral", + "nrf52833", +], rev = "551a95436e999b4290b4a33383aa3d6747b63dd9", optional = true } +nrf-mpsl = { git = "https://github.com/alexmoon/nrf-sdc.git", default-features = false, features = [ + "defmt", + "critical-section-impl", +], rev = "551a95436e999b4290b4a33383aa3d6747b63dd9", optional = true } static_cell = { version = "2", optional = true } [features] default = ["defmt"] -defmt = ["dep:defmt", "embassy-nrf/defmt", "heapless/defmt-03"] -trouble = ["embassy-nrf/unstable-pac", "embassy-nrf/rt", "nrf-sdc", "nrf-mpsl", "static_cell"] \ No newline at end of file +defmt = [ + "dep:defmt", + "embassy-nrf/defmt", + "heapless/defmt-03", + "embassy-time/defmt", + "embassy-time/defmt-timestamp-uptime", +] +trouble = [ + "embassy-nrf/unstable-pac", + "embassy-nrf/rt", + "nrf-sdc", + "nrf-mpsl", + "static_cell", +] diff --git a/examples/accelerometer/Cargo.toml b/examples/accelerometer/Cargo.toml index 428f352..94be6fa 100644 --- a/examples/accelerometer/Cargo.toml +++ b/examples/accelerometer/Cargo.toml @@ -7,13 +7,19 @@ edition = "2021" microbit-bsp = { path = "../../" } embassy-futures = { version = "0.1", default-features = false } -embassy-executor = { version = "0.6", default-features = false, features = ["integrated-timers", "defmt", "arch-cortex-m", "executor-thread", "executor-interrupt", "task-arena-size-32768"] } -embassy-time = { version = "0.3", default-features = false, features = ["defmt-timestamp-uptime", "defmt"] } +embassy-executor = { version = "0.7", default-features = false, features = [ + "arch-cortex-m", + "defmt", + "executor-interrupt", + "executor-thread", + "task-arena-size-32768" + ] } +embassy-time = { version = "0.4", default-features = false, features = ["defmt-timestamp-uptime", "defmt"] } cortex-m-rt = "0.7" cortex-m = { version = "0.7", features = ["critical-section-single-core"] } -defmt = "0.3" +defmt = "0.3.10" defmt-rtt = "0.4" panic-probe = { version = "0.3", features = ["print-defmt"] } diff --git a/examples/accelerometer/src/main.rs b/examples/accelerometer/src/main.rs index 5ec6554..16ebae6 100644 --- a/examples/accelerometer/src/main.rs +++ b/examples/accelerometer/src/main.rs @@ -23,7 +23,7 @@ async fn main(_s: Spawner) { // Bind interrupt to the TWI/SPI peripheral. bind_interrupts!( struct InterruptRequests { - SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0 => InterruptHandler; + TWISPI0 => InterruptHandler; } ); diff --git a/examples/ble/Cargo.toml b/examples/ble/Cargo.toml index 0fb3e24..74c52b0 100644 --- a/examples/ble/Cargo.toml +++ b/examples/ble/Cargo.toml @@ -7,17 +7,22 @@ edition = "2021" microbit-bsp = { path = "../../" } embassy-futures = { version = "0.1", default-features = false } -embassy-executor = { version = "0.6", default-features = false, features = ["integrated-timers", "defmt", "arch-cortex-m", "executor-thread", "task-arena-size-32768"] } -embassy-time = { version = "0.3", default-features = false, features = ["defmt-timestamp-uptime"] } +embassy-executor = { version = "0.7", default-features = false, features = [ + "arch-cortex-m", + "defmt", + "executor-thread", + "task-arena-size-32768" + ] } +embassy-time = { version = "0.4", default-features = false, features = ["defmt-timestamp-uptime"] } nrf-softdevice = { version = "0.1.0", features = ["ble-peripheral", "ble-gatt-server", "s113", "nrf52833", "critical-section-impl", "defmt"] } -nrf-softdevice-s113 = { version = "0.1.0" } +nrf-softdevice-s113 = { version = "0.1.2" } heapless = "0.8" cortex-m-rt = "0.7" static_cell = "2" -defmt = "0.3" +defmt = "0.3.10" defmt-rtt = "0.4" panic-probe = { version = "0.3", features = ["print-defmt"] } diff --git a/examples/display/Cargo.toml b/examples/display/Cargo.toml index 553ea88..f2081e7 100644 --- a/examples/display/Cargo.toml +++ b/examples/display/Cargo.toml @@ -7,13 +7,19 @@ edition = "2021" microbit-bsp = { path = "../../" } embassy-futures = { version = "0.1", default-features = false } -embassy-executor = { version = "0.6", default-features = false, features = ["integrated-timers", "defmt", "arch-cortex-m", "executor-thread", "executor-interrupt", "task-arena-size-32768"] } -embassy-time = { version = "0.3", default-features = false, features = ["defmt-timestamp-uptime", "defmt"] } +embassy-executor = { version = "0.7.0", default-features = false, features = [ + "arch-cortex-m", + "defmt", + "executor-interrupt", + "executor-thread", + "task-arena-size-32768" + ] } +embassy-time = { version = "0.4.0", default-features = false, features = ["defmt-timestamp-uptime", "defmt"] } cortex-m-rt = "0.7" cortex-m = { version = "0.7", features = ["critical-section-single-core"] } -defmt = "0.3" +defmt = "0.3.10" defmt-rtt = "0.4" panic-probe = { version = "0.3", features = ["print-defmt"] } diff --git a/examples/speaker/Cargo.toml b/examples/speaker/Cargo.toml index 6881409..5492eb4 100644 --- a/examples/speaker/Cargo.toml +++ b/examples/speaker/Cargo.toml @@ -7,13 +7,19 @@ edition = "2021" microbit-bsp = { path = "../../" } embassy-futures = { version = "0.1", default-features = false } -embassy-executor = { version = "0.6", default-features = false, features = ["integrated-timers", "defmt", "arch-cortex-m", "executor-thread", "executor-interrupt", "task-arena-size-32768"] } -embassy-time = { version = "0.3", default-features = false, features = ["defmt-timestamp-uptime", "defmt"] } +embassy-executor = { version = "0.7", default-features = false, features = [ + "arch-cortex-m", + "defmt", + "executor-interrupt", + "executor-thread", + "task-arena-size-32768" + ] } +embassy-time = { version = "0.4", default-features = false, features = ["defmt-timestamp-uptime", "defmt"] } cortex-m-rt = "0.7" cortex-m = { version = "0.7", features = ["critical-section-single-core"] } -defmt = "0.3" +defmt = "0.3.10" defmt-rtt = "0.4" panic-probe = { version = "0.3", features = ["print-defmt"] } diff --git a/examples/trouble/Cargo.toml b/examples/trouble/Cargo.toml index b194e32..774e494 100644 --- a/examples/trouble/Cargo.toml +++ b/examples/trouble/Cargo.toml @@ -5,19 +5,24 @@ edition = "2021" resolver = "2" [dependencies] -embassy-executor = { version = "0.6", default-features = false, features = ["arch-cortex-m", "executor-thread", "defmt", "integrated-timers", "executor-interrupt", "task-arena-size-32768"] } -embassy-time = { version = "0.3", default-features = false, features = ["defmt", "defmt-timestamp-uptime"] } -microbit-bsp = { path = "../../", features = ["trouble"]} +embassy-executor = { version = "0.7", default-features = false, features = [ + "arch-cortex-m", + "defmt", + "executor-interrupt", + "executor-thread", + "task-arena-size-32768" + ] } +embassy-time = { version = "0.4", default-features = false, features = ["defmt", "defmt-timestamp-uptime"] } embassy-futures = "0.1.1" -embassy-sync = { version = "0.6", features = ["defmt"] } +embassy-sync = { version = "0.6.2", features = ["defmt"] } +microbit-bsp = { path = "../../", features = ["trouble"]} futures = { version = "0.3", default-features = false, features = ["async-await"]} -bt-hci = { version = "0.1.1", default-features = false, features = ["defmt"] } trouble-host = { git = "https://github.com/embassy-rs/trouble.git", features = [ "defmt", -] } +], rev = "a1daee269deee25fbb6236cb599a8fb5919c02b7"} -defmt = "0.3" +defmt = "0.3.10" defmt-rtt = "0.4.0" cortex-m = { version = "0.7.6" } diff --git a/examples/trouble/src/main.rs b/examples/trouble/src/main.rs index 71f8ebc..7244a14 100644 --- a/examples/trouble/src/main.rs +++ b/examples/trouble/src/main.rs @@ -3,10 +3,10 @@ use {defmt_rtt as _, panic_probe as _}; -use defmt::{error, info}; +use defmt::{info, warn}; use embassy_executor::Spawner; -use embassy_futures::join::join3; -use embassy_time::{Duration, Timer}; +use embassy_futures::select::select; +use embassy_time::Timer; use microbit_bsp::{ble::MultiprotocolServiceLayer, Config, Microbit}; use trouble_host::prelude::*; @@ -19,19 +19,21 @@ const CONNECTIONS_MAX: usize = 1; /// Max number of L2CAP channels. const L2CAP_CHANNELS_MAX: usize = 2; // Signal + att -type Resources = HostResources; - // GATT Server definition -#[gatt_server(attribute_data_size = 10)] +#[gatt_server] struct Server { battery_service: BatteryService, } // Battery service -#[gatt_service(uuid = "180f")] +#[gatt_service(uuid = service::BATTERY)] struct BatteryService { - #[characteristic(uuid = "2a19", read, notify)] + #[descriptor(uuid = descriptors::VALID_RANGE, read, value = [0, 100])] + #[descriptor(uuid = descriptors::MEASUREMENT_DESCRIPTION, read, value = "Battery Level")] + #[characteristic(uuid = characteristic::BATTERY_LEVEL, read, notify)] level: u8, + #[characteristic(uuid = "408813df-5dd4-1f87-ec11-cdb001100000", write, read, notify)] + status: bool, } #[embassy_executor::task] @@ -55,91 +57,145 @@ pub async fn run(controller: C) where C: Controller, { - let address = Address::random([0x41, 0x5A, 0xE3, 0x1E, 0x83, 0xE7]); + // Using a fixed "random" address can be useful for testing. In real scenarios, one would + // use e.g. the MAC 6 byte array as the address (how to get that varies by the platform). + let address = Address::random([0x42, 0x6A, 0xE3, 0x1E, 0x83, 0xE7]); info!("Our address = {:?}", address); - let mut resources = Resources::new(PacketQos::None); - let (stack, peripheral, _, runner) = trouble_host::new(controller, &mut resources) - .set_random_address(address) - .build(); - - let server = Server::new_with_config( - stack, - GapConfig::Peripheral(PeripheralConfig { - name: "Trouble", - appearance: &appearance::GENERIC_POWER, - }), - ) - .expect("Failed to create GATT server"); + let mut resources: HostResources = HostResources::new(); + let stack = trouble_host::new(controller, &mut resources).set_random_address(address); + let Host { + mut peripheral, runner, .. + } = stack.build(); info!("Starting advertising and GATT service"); - let _ = join3( - ble_task(runner), - gatt_task(&server), - advertise_task(peripheral, &server), - ) - .await; + let server = Server::new_with_config(GapConfig::Peripheral(PeripheralConfig { + name: "Trouble", + appearance: &appearance::power_device::GENERIC_POWER_DEVICE, + })) + .expect("Failed to create GATT server"); + + let app_task = async { + loop { + match advertise("Trouble Example", &mut peripheral).await { + Ok(conn) => { + // set up tasks when the connection is established to a central, so they don't run when no one is connected. + let a = gatt_events_task(&server, &conn); + let b = custom_task(&server, &conn, &stack); + // run until any task ends (usually because the connection has been closed), + // then return to advertising state. + select(a, b).await; + } + Err(e) => { + let e = defmt::Debug2Format(&e); + panic!("[adv] error: {:?}", e); + } + } + } + }; + select(ble_task(runner), app_task).await; } +/// This is a background task that is required to run forever alongside any other BLE tasks. async fn ble_task(mut runner: Runner<'_, C>) -> Result<(), BleHostError> { runner.run().await } -async fn gatt_task(server: &Server<'_, '_, C>) { +/// Stream Events until the connection closes. +/// +/// This function will handle the GATT events and process them. +/// This is how we interact with read and write requests. +async fn gatt_events_task(server: &Server<'_>, conn: &Connection<'_>) -> Result<(), Error> { + let level = server.battery_service.level; loop { - match server.next().await { - Ok(GattEvent::Write { - value_handle, - connection: _, - }) => { - info!("[gatt] Write event on {:?}", value_handle); - } - Ok(GattEvent::Read { - value_handle, - connection: _, - }) => { - info!("[gatt] Read event on {:?}", value_handle); + match conn.next().await { + ConnectionEvent::Disconnected { reason } => { + info!("[gatt] disconnected: {:?}", reason); + break; } - Err(e) => { - error!("[gatt] Error processing GATT events: {:?}", e); + ConnectionEvent::Gatt { data } => { + // We can choose to handle event directly without an attribute table + // let req = data.request(); + // .. + // data.reply(conn, Ok(AttRsp::Error { .. })) + + // But to simplify things, process it in the GATT server that handles + // the protocol details + match data.process(server).await { + // Server processing emits + Ok(Some(GattEvent::Read(event))) => { + if event.handle() == level.handle { + let value = server.get(&level); + info!("[gatt] Read Event to Level Characteristic: {:?}", value); + } + } + Ok(Some(GattEvent::Write(event))) => { + if event.handle() == level.handle { + info!("[gatt] Write Event to Level Characteristic: {:?}", event.data()); + } + } + Ok(_) => {} + Err(e) => { + warn!("[gatt] error processing event: {:?}", e); + } + } } } } + info!("[gatt] task finished"); + Ok(()) } -async fn advertise_task( - mut peripheral: Peripheral<'_, C>, - server: &Server<'_, '_, C>, -) -> Result<(), BleHostError> { - let mut adv_data = [0; 31]; +/// Create an advertiser to use to connect to a BLE Central, and wait for it to connect. +async fn advertise<'a, C: Controller>( + name: &'a str, + peripheral: &mut Peripheral<'a, C>, +) -> Result, BleHostError> { + let mut advertiser_data = [0; 31]; AdStructure::encode_slice( &[ AdStructure::Flags(LE_GENERAL_DISCOVERABLE | BR_EDR_NOT_SUPPORTED), AdStructure::ServiceUuids16(&[Uuid::Uuid16([0x0f, 0x18])]), - AdStructure::CompleteLocalName(b"Trouble"), + AdStructure::CompleteLocalName(name.as_bytes()), ], - &mut adv_data[..], + &mut advertiser_data[..], )?; + let advertiser = peripheral + .advertise( + &Default::default(), + Advertisement::ConnectableScannableUndirected { + adv_data: &advertiser_data[..], + scan_data: &[], + }, + ) + .await?; + info!("[adv] advertising"); + let conn = advertiser.accept().await?; + info!("[adv] connection established"); + Ok(conn) +} + +/// Example task to use the BLE notifier interface. +/// This task will notify the connected central of a counter value every 2 seconds. +/// It will also read the RSSI value every 2 seconds. +/// and will stop when the connection is closed by the central or an error occurs. +async fn custom_task(server: &Server<'_>, conn: &Connection<'_>, stack: &Stack<'_, C>) { + let mut tick: u8 = 0; + let level = server.battery_service.level; loop { - info!("[adv] advertising"); - let mut advertiser = peripheral - .advertise( - &Default::default(), - Advertisement::ConnectableScannableUndirected { - adv_data: &adv_data[..], - scan_data: &[], - }, - ) - .await?; - let conn = advertiser.accept().await?; - info!("[adv] connection established"); - // Keep connection alive - let mut tick: u8 = 0; - while conn.is_connected() { - Timer::after(Duration::from_secs(2)).await; - tick = tick.wrapping_add(1); - info!("[adv] notifying connection of tick {}", tick); - let _ = server.notify(&server.battery_service.level, &conn, &tick).await; - } + tick = tick.wrapping_add(1); + info!("[custom_task] notifying connection of tick {}", tick); + if level.notify(server, conn, &tick).await.is_err() { + info!("[custom_task] error notifying connection"); + break; + }; + // read RSSI (Received Signal Strength Indicator) of the connection. + if let Ok(rssi) = conn.rssi(stack).await { + info!("[custom_task] RSSI: {:?}", rssi); + } else { + info!("[custom_task] error getting RSSI"); + break; + }; + Timer::after_secs(2).await; } } diff --git a/src/accelerometer/mod.rs b/src/accelerometer/mod.rs index d68234e..c17a2e6 100644 --- a/src/accelerometer/mod.rs +++ b/src/accelerometer/mod.rs @@ -1,5 +1,5 @@ //! Accelerometer for the micro:bit -use embassy_nrf::interrupt::typelevel::{Binding, SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0}; +use embassy_nrf::interrupt::typelevel::{self, Binding}; use embassy_nrf::peripherals::{P0_08, P0_16, TWISPI0}; use embassy_nrf::twim; use embassy_nrf::twim::InterruptHandler; @@ -7,8 +7,8 @@ use embassy_sync::channel::DynamicSender; use embassy_time::{Duration, Ticker}; use lsm303agr::interface::I2cInterface; use lsm303agr::mode::MagOneShot; -use lsm303agr::{AccelMode, Acceleration, Error as LsmError, Lsm303agr, Status}; pub use lsm303agr::AccelOutputDataRate; +use lsm303agr::{AccelMode, Acceleration, Error as LsmError, Lsm303agr, Status}; type I2C<'d> = twim::Twim<'d, TWISPI0>; @@ -24,7 +24,7 @@ impl<'d> Accelerometer<'d> { /// Create and initialize the accelerometer pub fn new( twispi0: TWISPI0, - irq: impl Binding> + 'd, + irq: impl Binding> + 'd, sda: P0_16, scl: P0_08, ) -> Result { diff --git a/src/ble.rs b/src/ble.rs index 319b057..f0125d8 100644 --- a/src/ble.rs +++ b/src/ble.rs @@ -3,15 +3,14 @@ //! Used with `trouble-host` crate. use embassy_nrf::peripherals; -use embassy_nrf::{bind_interrupts, pac, rng, Peripheral}; +use embassy_nrf::{bind_interrupts, rng, Peripheral}; use nrf_sdc::{self as sdc, mpsl}; pub use nrf_sdc::{mpsl::MultiprotocolServiceLayer, Error as SoftdeviceError, SoftdeviceController}; use static_cell::StaticCell; /// Default memory allocation for softdevice controller in bytes. -/// - Minimum 2168 bytes, /// - maximum associated with [task-arena-size](https://docs.embassy.dev/embassy-executor/git/cortex-m/index.html) -const SDC_MEMORY_SIZE: usize = 3312; // bytes +const SDC_MEMORY_SIZE: usize = 1448; // bytes /// Softdevice Bluetooth Controller Builder. pub struct BleControllerBuilder<'d> { @@ -20,8 +19,6 @@ pub struct BleControllerBuilder<'d> { /// Softdevice Controller memory sdc_mem: sdc::Mem, // Required peripherals for the Multiprotocol Service Layer (MPSL) - clock: pac::CLOCK, - radio: pac::RADIO, rtc0: peripherals::RTC0, temp: peripherals::TEMP, ppi_ch19: peripherals::PPI_CH19, @@ -31,8 +28,8 @@ pub struct BleControllerBuilder<'d> { bind_interrupts!(struct Irqs { RNG => rng::InterruptHandler; - SWI0_EGU0 => nrf_sdc::mpsl::LowPrioInterruptHandler; - POWER_CLOCK => nrf_sdc::mpsl::ClockInterruptHandler; + EGU0_SWI0 => nrf_sdc::mpsl::LowPrioInterruptHandler; + CLOCK_POWER => nrf_sdc::mpsl::ClockInterruptHandler; RADIO => nrf_sdc::mpsl::HighPrioInterruptHandler; TIMER0 => nrf_sdc::mpsl::HighPrioInterruptHandler; RTC0 => nrf_sdc::mpsl::HighPrioInterruptHandler; @@ -70,31 +67,16 @@ where ppi_ch30: peripherals::PPI_CH30, ppi_ch31: peripherals::PPI_CH31, ) -> Self { - let pac_peripherals = pac::Peripherals::take().expect("pac::Peripherals is not initialized"); // Softdevice Controller peripherals let sdc_peripherals = sdc::Peripherals::new( - pac_peripherals.ECB, - pac_peripherals.AAR, - ppi_ch17, - ppi_ch18, - ppi_ch20, - ppi_ch21, - ppi_ch22, - ppi_ch23, - ppi_ch24, - ppi_ch25, - ppi_ch26, - ppi_ch27, - ppi_ch28, - ppi_ch29, + ppi_ch17, ppi_ch18, ppi_ch20, ppi_ch21, ppi_ch22, ppi_ch23, ppi_ch24, ppi_ch25, ppi_ch26, ppi_ch27, + ppi_ch28, ppi_ch29, ); let sdc_mem = sdc::Mem::::new(); Self { sdc_peripherals, sdc_mem, - clock: pac_peripherals.CLOCK, - radio: pac_peripherals.RADIO, rtc0, temp, ppi_ch19, @@ -132,7 +114,7 @@ where /// .ble /// .init(board.timer0, board.rng) /// .expect("BLE Stack failed to initialize"); - /// spawner.must_spawn(mpsl_task(&*mpsl)); + /// spawner.must_spawn(mpsl_task(mpsl)); /// /// run(sdc).await; /// } @@ -144,8 +126,6 @@ where ) -> Result<(SoftdeviceController<'d>, &'static MultiprotocolServiceLayer<'d>), nrf_sdc::Error> { let mpsl = { let p = mpsl::Peripherals::new( - self.clock, - self.radio, self.rtc0, timer0, self.temp,