Skip to content

Commit

Permalink
Support the BLE central role
Browse files Browse the repository at this point in the history
* Add example of l2cap central that works with the l2cap peripheral
* Add Scanner for scanning for BLE peripherals
* Fix mps size calculation not taking into account l2cap header size
* Add l2cap sender wait for available remote credits when sending.
  • Loading branch information
lulf committed Mar 12, 2024
1 parent 965a596 commit 837e6cf
Show file tree
Hide file tree
Showing 12 changed files with 436 additions and 125 deletions.
2 changes: 1 addition & 1 deletion examples/nrf-sdc/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ embassy-futures = "0.1.1"
embassy-sync = "0.5"

futures = { version = "0.3", default-features = false, features = ["async-await"]}
nrf-sdc = { version = "0.1.0", default-features = false, features = ["defmt", "nrf52833", "peripheral"] }
nrf-sdc = { version = "0.1.0", default-features = false, features = ["defmt", "nrf52833", "peripheral", "central"] }
nrf-mpsl = { version = "0.1.0", default-features = false, features = ["defmt", "critical-section-impl"] }
bt-hci = { version = "0.1.0", default-features = false, features = ["defmt"] }
trouble-host = { version = "0.1.0", path = "../../host", features = ["defmt"] }
Expand Down
150 changes: 150 additions & 0 deletions examples/nrf-sdc/src/bin/ble_l2cap_central.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
#![no_std]
#![no_main]
#![feature(type_alias_impl_trait)]

use bt_hci::cmd::SyncCmd;
use bt_hci::param::BdAddr;
use defmt::{info, unwrap};
use embassy_executor::Spawner;
use embassy_futures::join::join;
use embassy_nrf::{bind_interrupts, pac};
use embassy_sync::blocking_mutex::raw::NoopRawMutex;
use embassy_time::{Duration, Timer};
use nrf_sdc::{self as sdc, mpsl, mpsl::MultiprotocolServiceLayer};
use sdc::rng_pool::RngPool;
use sdc::vendor::ZephyrWriteBdAddr;
use static_cell::StaticCell;
use trouble_host::{
adapter::AdvertiseConfig,
adapter::ScanConfig,
adapter::{Adapter, HostResources},
connection::Connection,
l2cap::L2capChannel,
scanner::Scanner,
PacketQos,
};

use {defmt_rtt as _, panic_probe as _};

bind_interrupts!(struct Irqs {
RNG => nrf_sdc::rng_pool::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 bd_addr() -> BdAddr {
unsafe {
let ficr = &*pac::FICR::ptr();
let high = u64::from((ficr.deviceid[1].read().bits() & 0x0000ffff) | 0x0000c000);
let addr = high << 32 | u64::from(ficr.deviceid[0].read().bits());
BdAddr::new(unwrap!(addr.to_le_bytes()[..6].try_into()))
}
}

fn build_sdc<'d, const N: usize>(
p: nrf_sdc::Peripherals<'d>,
rng: &'d RngPool<'d>,
mpsl: &'d MultiprotocolServiceLayer<'d>,
mem: &'d mut sdc::Mem<N>,
) -> Result<nrf_sdc::SoftdeviceController<'d>, nrf_sdc::Error> {
sdc::Builder::new()?
.support_scan()?
.support_central()?
.central_count(1)?
.buffer_cfg(27, 27, 20, 20)?
.build(p, rng, mpsl, mem)
}

#[embassy_executor::main]
async fn main(spawner: Spawner) {
let p = embassy_nrf::init(Default::default());
let pac_p = pac::Peripherals::take().unwrap();

let mpsl_p = mpsl::Peripherals::new(
pac_p.CLOCK,
pac_p.RADIO,
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<MultiprotocolServiceLayer> = 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(
pac_p.ECB, pac_p.AAR, p.NVMC, 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 pool = [0; 256];
let rng = sdc::rng_pool::RngPool::new(p.RNG, Irqs, &mut pool, 64);

let mut sdc_mem = sdc::Mem::<6544>::new();
let sdc = unwrap!(build_sdc(sdc_p, &rng, mpsl, &mut sdc_mem));

info!("Our address = {:02x}", bd_addr());
unwrap!(ZephyrWriteBdAddr::new(bd_addr()).exec(&sdc).await);
Timer::after(Duration::from_millis(200)).await;

static HOST_RESOURCES: StaticCell<HostResources<NoopRawMutex, 4, 32, 27>> = StaticCell::new();
let host_resources = HOST_RESOURCES.init(HostResources::new(PacketQos::Guaranteed(4)));

static ADAPTER: StaticCell<Adapter<NoopRawMutex, 2, 4, 1, 1>> = StaticCell::new();
let adapter = ADAPTER.init(Adapter::new(host_resources));

let config = ScanConfig { params: None };
let mut scanner = unwrap!(adapter.scan(&sdc, config).await);

// NOTE: Modify this to match the address of the peripheral you want to connect to
let target: BdAddr = BdAddr::new([0xf5, 0x9f, 0x1a, 0x05, 0xe4, 0xee]);

info!("Scanning for peripheral...");
let _ = join(adapter.run(&sdc), async {
loop {
let reports = scanner.next().await;
for report in reports.iter() {
let report = report.unwrap();
if report.addr == target {
let conn = Connection::connect(adapter, report.addr).await;
info!("Connected, creating l2cap channel");
let mut ch1: L2capChannel<'_, 27> = unwrap!(L2capChannel::create(adapter, &conn, 0x2349).await);
info!("New l2cap channel created, sending some data!");
for i in 0..10 {
let mut tx = [i; 27];
let _ = unwrap!(ch1.send(&mut tx).await);
}
info!("Sent data, waiting for them to be sent back");
let mut rx = [0; 27];
for i in 0..10 {
let len = unwrap!(ch1.receive(&mut rx).await);
assert_eq!(len, rx.len());
assert_eq!(rx, [i; 27]);
}

info!("Received successfully!");

Timer::after(Duration::from_secs(60)).await;
}
}
}
})
.await;
}
32 changes: 15 additions & 17 deletions examples/nrf-sdc/src/bin/ble_l2cap_peripheral.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ use static_cell::StaticCell;
use trouble_host::{
ad_structure::{AdStructure, BR_EDR_NOT_SUPPORTED, LE_GENERAL_DISCOVERABLE},
adapter::AdvertiseConfig,
adapter::Config as BleConfig,
adapter::{Adapter, HostResources},
attribute::{AttributesBuilder, CharacteristicProp, ServiceBuilder, Uuid},
connection::Connection,
Expand Down Expand Up @@ -99,10 +98,10 @@ async fn main(spawner: Spawner) {
let mut pool = [0; 256];
let rng = sdc::rng_pool::RngPool::new(p.RNG, Irqs, &mut pool, 64);

let mut sdc_mem = sdc::Mem::<3128>::new();
let mut sdc_mem = sdc::Mem::<6224>::new();
let sdc = unwrap!(build_sdc(sdc_p, &rng, mpsl, &mut sdc_mem));

info!("Advertising as {:02x}", bd_addr());
info!("Our address = {:02x}", bd_addr());
unwrap!(ZephyrWriteBdAddr::new(bd_addr()).exec(&sdc).await);
Timer::after(Duration::from_millis(200)).await;

Expand All @@ -112,15 +111,13 @@ async fn main(spawner: Spawner) {
static ADAPTER: StaticCell<Adapter<NoopRawMutex, 2, 4, 1, 1>> = StaticCell::new();
let adapter = ADAPTER.init(Adapter::new(host_resources));

let config = BleConfig {
advertise: Some(AdvertiseConfig {
params: None,
data: &[
AdStructure::Flags(LE_GENERAL_DISCOVERABLE | BR_EDR_NOT_SUPPORTED),
AdStructure::ServiceUuids16(&[Uuid::Uuid16([0x0f, 0x18])]),
AdStructure::CompleteLocalName("Trouble"),
],
}),
let config = AdvertiseConfig {
params: None,
data: &[
AdStructure::Flags(LE_GENERAL_DISCOVERABLE | BR_EDR_NOT_SUPPORTED),
AdStructure::ServiceUuids16(&[Uuid::Uuid16([0x0f, 0x18])]),
AdStructure::CompleteLocalName("Trouble"),
],
};

let mut attributes: AttributesBuilder<'_, 10> = AttributesBuilder::new();
Expand All @@ -135,7 +132,6 @@ async fn main(spawner: Spawner) {

unwrap!(adapter.advertise(&sdc, config).await);

info!("Starting advertising and GATT service");
let _ = join(
adapter.run(&sdc),
async {
Expand All @@ -149,26 +145,28 @@ async fn main(spawner: Spawner) {
},
async {
loop {
info!("Waiting for connection...");
let conn = Connection::accept(adapter).await;
info!("New connection accepted!");

info!("Connection established");

let mut ch1: L2capChannel<'_, 27> = unwrap!(L2capChannel::accept(adapter, &conn, 0x2349).await);

info!("New l2cap channel created by remote!");
info!("L2CAP channel accepted");
let mut rx = [0; 27];
for i in 0..10 {
let len = unwrap!(ch1.receive(&mut rx).await);
assert_eq!(len, rx.len());
assert_eq!(rx, [i; 27]);
}

info!("Received successfully! Sending bytes back");
info!("L2CAP data received, echoing");
Timer::after(Duration::from_secs(1)).await;
for i in 0..10 {
let mut tx = [i; 27];
let _ = unwrap!(ch1.send(&mut tx).await);
}
info!("Bytes sent");
info!("L2CAP data echoed");

Timer::after(Duration::from_secs(60)).await;
}
Expand Down
1 change: 1 addition & 0 deletions host/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ resolver = "2"
bt-hci = { version = "0.1.0" }
embedded-io-async = { version = "0.6" }
embassy-sync = "0.5"
embassy-time = "0.3"
embassy-futures = "0.1"
futures = { version = "0.3", default-features = false }
heapless = "0.8"
Expand Down
Loading

0 comments on commit 837e6cf

Please sign in to comment.