diff --git a/examples/apps/Cargo.toml b/examples/apps/Cargo.toml index 127f025..d970a96 100644 --- a/examples/apps/Cargo.toml +++ b/examples/apps/Cargo.toml @@ -18,6 +18,8 @@ embedded-io = "0.6" defmt = { version = "0.3", optional = true } log = { version = "0.4", optional = true } +# Required for Nordic Uart Service +heapless = "0.8.0" [features] diff --git a/examples/apps/src/ble_nus_peripheral.rs b/examples/apps/src/ble_nus_peripheral.rs new file mode 100644 index 0000000..f33871b --- /dev/null +++ b/examples/apps/src/ble_nus_peripheral.rs @@ -0,0 +1,152 @@ +/* Nordic Uart Service (NUS) peripheral example */ +use embassy_futures::{ + join::join3, + select::{select, Either}, +}; +use embassy_time::{Duration, Timer}; +use heapless::Vec; +use trouble_host::prelude::*; + +/// Size of L2CAP packets (ATT MTU is this - 4) +const L2CAP_MTU: usize = 251; + +/// Max number of connections +const CONNECTIONS_MAX: usize = 1; + +/// Max number of L2CAP channels. +const L2CAP_CHANNELS_MAX: usize = 2; // Signal + att + +pub const MTU: usize = 120; +// Aligned to 4 bytes + 3 bytes for header +pub const ATT_MTU: usize = MTU + 3; + +type Resources = HostResources; + +// GATT Server definition +#[gatt_server] +struct Server { + nrf_uart: NrfUartService, +} + +// NRF UART Service +#[gatt_service(uuid = "6E400001-B5A3-F393-E0A9-E50E24DCCA9E")] +struct NrfUartService { + #[characteristic(uuid = "6E400002-B5A3-F393-E0A9-E50E24DCCA9E", write)] + rx: Vec, + + #[characteristic(uuid = "6E400003-B5A3-F393-E0A9-E50E24DCCA9E", notify)] + tx: Vec, +} + +pub async fn run(controller: C) +where + C: Controller, +{ + let address = Address::random([0x41, 0x5A, 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 NUS", + appearance: &appearance::GENERIC_UNKNOWN, + }), + ) + .unwrap(); + + info!("Starting advertising and GATT service"); + let _ = join3( + ble_task(runner), + gatt_task(&server), + advertise_task(peripheral, &server), + ) + .await; +} + +async fn ble_task(mut runner: Runner<'_, C>) -> Result<(), BleHostError> { + runner.run().await +} + +async fn gatt_task(server: &Server<'_, '_, C>) -> Result<(), BleHostError> { + server.run().await +} + +async fn advertise_task( + mut peripheral: Peripheral<'_, C>, + server: &Server<'_, '_, C>, +) -> Result<(), BleHostError> { + let mut adv_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 NUS"), + ], + &mut adv_data[..], + )?; + 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?; + + let mut tick: u8 = 0; + let mut buf = Vec::::from_slice(&[0; ATT_MTU]).unwrap(); + + // Keep connection alive + loop { + match select(conn.next(), Timer::after(Duration::from_secs(2))).await { + Either::First(event) => match event { + ConnectionEvent::Disconnected { reason } => { + info!("[adv] disconnected: {:?}", reason); + break; + } + ConnectionEvent::Gatt { event, .. } => match event { + GattEvent::Read { value_handle } => { + /* + if value_handle == server.nrf_uart.rx_buf.handle { + let value = server.get(&rx_buf).unwrap(); + info!("[gatt] Read Event to rx_buf Characteristic: {:?}", value.len()); + } else if value_handle == tx_buf.handle { + let value = server.get(&tx_buf).unwrap(); + info!("[gatt] Read Event to tx_buf Characteristic: {:?}", value.len()); + } + */ + defmt::info!("Read..."); + } + GattEvent::Write { value_handle } => { + /* + if value_handle == rx_buf.handle { + let value = server.get(&rx_buf).unwrap(); + info!("[gatt] Write Event to rx_buf Characteristic: {:?}", value.len()); + } else if value_handle == tx_buf.handle { + let value = server.get(&tx_buf).unwrap(); + info!("[gatt] Write Event to tx_buf Characteristic: {:?}", value.len()); + } + */ + defmt::info!("Write..."); + } + }, + }, + Either::Second(_) => { + tick = tick.wrapping_add(1); + info!("[adv] notifying connection of tick {}", tick); + buf[0] = tick; + let _ = server.notify(&server.nrf_uart.tx, &conn, &buf).await; + } + } + } + } +} diff --git a/examples/apps/src/lib.rs b/examples/apps/src/lib.rs index 49862f8..aac127d 100644 --- a/examples/apps/src/lib.rs +++ b/examples/apps/src/lib.rs @@ -8,3 +8,5 @@ pub mod ble_bas_central; pub mod ble_bas_peripheral; pub mod ble_l2cap_central; pub mod ble_l2cap_peripheral; +/// Nordic UART Service (NUS) peripheral +pub mod ble_nus_peripheral; diff --git a/examples/nrf-sdc/Cargo.lock b/examples/nrf-sdc/Cargo.lock index 6cb5f49..14f5e45 100644 --- a/examples/nrf-sdc/Cargo.lock +++ b/examples/nrf-sdc/Cargo.lock @@ -1139,6 +1139,7 @@ dependencies = [ "embedded-hal 1.0.0", "embedded-hal-async", "embedded-io", + "heapless", "static_cell", "trouble-host", ] diff --git a/examples/nrf-sdc/src/bin/ble_nus_peripheral.rs b/examples/nrf-sdc/src/bin/ble_nus_peripheral.rs new file mode 100644 index 0000000..a5defc1 --- /dev/null +++ b/examples/nrf-sdc/src/bin/ble_nus_peripheral.rs @@ -0,0 +1,67 @@ +#![no_std] +#![no_main] + +use defmt::unwrap; +use embassy_executor::Spawner; +use embassy_nrf::peripherals::RNG; +use embassy_nrf::{bind_interrupts, rng}; +use nrf_sdc::mpsl::MultiprotocolServiceLayer; +use nrf_sdc::{self as sdc, mpsl}; +use static_cell::StaticCell; +use trouble_example_apps::ble_nus_peripheral as nrf_uart; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + RNG => rng::InterruptHandler; + SWI0_EGU0 => nrf_sdc::mpsl::LowPrioInterruptHandler; + POWER_CLOCK => nrf_sdc::mpsl::ClockInterruptHandler; + RADIO => nrf_sdc::mpsl::HighPrioInterruptHandler; + TIMER0 => nrf_sdc::mpsl::HighPrioInterruptHandler; + RTC0 => nrf_sdc::mpsl::HighPrioInterruptHandler; +}); + +#[embassy_executor::task] +async fn mpsl_task(mpsl: &'static MultiprotocolServiceLayer<'static>) -> ! { + mpsl.run().await +} + +fn build_sdc<'d, const N: usize>( + p: nrf_sdc::Peripherals<'d>, + rng: &'d mut rng::Rng, + mpsl: &'d MultiprotocolServiceLayer, + mem: &'d mut sdc::Mem, +) -> Result, nrf_sdc::Error> { + sdc::Builder::new()? + .support_adv()? + .support_peripheral()? + .peripheral_count(1)? + .build(p, rng, mpsl, mem) +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_nrf::init(Default::default()); + let mpsl_p = mpsl::Peripherals::new(p.RTC0, p.TIMER0, p.TEMP, p.PPI_CH19, p.PPI_CH30, p.PPI_CH31); + let lfclk_cfg = mpsl::raw::mpsl_clock_lfclk_cfg_t { + source: mpsl::raw::MPSL_CLOCK_LF_SRC_RC as u8, + rc_ctiv: mpsl::raw::MPSL_RECOMMENDED_RC_CTIV as u8, + rc_temp_ctiv: mpsl::raw::MPSL_RECOMMENDED_RC_TEMP_CTIV as u8, + accuracy_ppm: mpsl::raw::MPSL_DEFAULT_CLOCK_ACCURACY_PPM as u16, + skip_wait_lfclk_started: mpsl::raw::MPSL_DEFAULT_SKIP_WAIT_LFCLK_STARTED != 0, + }; + static MPSL: StaticCell = StaticCell::new(); + let mpsl = MPSL.init(unwrap!(mpsl::MultiprotocolServiceLayer::new(mpsl_p, Irqs, lfclk_cfg))); + spawner.must_spawn(mpsl_task(&*mpsl)); + + let sdc_p = sdc::Peripherals::new( + p.PPI_CH17, p.PPI_CH18, p.PPI_CH20, p.PPI_CH21, p.PPI_CH22, p.PPI_CH23, p.PPI_CH24, p.PPI_CH25, p.PPI_CH26, + p.PPI_CH27, p.PPI_CH28, p.PPI_CH29, + ); + + let mut rng = rng::Rng::new(p.RNG, Irqs); + + let mut sdc_mem = sdc::Mem::<5312>::new(); + let sdc = unwrap!(build_sdc(sdc_p, &mut rng, mpsl, &mut sdc_mem)); + + nrf_uart::run(sdc).await; +}