Skip to content

Commit 477ab38

Browse files
committed
feat: add some l2cap benchmarks
Add some convenient benchmarks for l2cap peripheral and central.
1 parent 1fb2e13 commit 477ab38

9 files changed

+425
-0
lines changed

benchmarks/nrf-sdc/.cargo/config.toml

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
[target.'cfg(all(target_arch = "arm", target_os = "none"))']
2+
#runner = "probe-rs run --chip nRF52832_xxAA"
3+
runner = "probe-rs run --chip nRF52833_xxAA"
4+
#runner = "probe-rs run --chip nRF52840_xxAA"
5+
6+
[build]
7+
# Pick ONE of these compilation targets
8+
# target = "thumbv6m-none-eabi" # Cortex-M0 and Cortex-M0+
9+
# target = "thumbv7m-none-eabi" # Cortex-M3
10+
# target = "thumbv7em-none-eabi" # Cortex-M4 and Cortex-M7 (no FPU)
11+
target = "thumbv7em-none-eabihf" # Cortex-M4F and Cortex-M7F (with FPU)
12+
13+
[env]
14+
DEFMT_LOG = "trouble_host=info,info"

benchmarks/nrf-sdc/Cargo.toml

+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
[package]
2+
name = "trouble-nrf-sdc-tests"
3+
version = "0.1.0"
4+
edition = "2024"
5+
resolver = "2"
6+
7+
[dependencies]
8+
embassy-executor = { version = "0.7", default-features = false, features = ["arch-cortex-m", "executor-thread", "defmt", "executor-interrupt"] }
9+
embassy-time = { version = "0.4", default-features = false, features = ["defmt", "defmt-timestamp-uptime"] }
10+
embassy-nrf = { version = "0.3", default-features = false, features = ["defmt", "time-driver-rtc1", "gpiote", "unstable-pac", "rt"] }
11+
embassy-futures = "0.1.1"
12+
embassy-sync = { version = "0.6", features = ["defmt"] }
13+
trouble-host = { path = "../../host", default-features = false, features = ["defmt", "l2cap-rx-queue-size-4", "l2cap-rx-packet-pool-size-16", "l2cap-tx-queue-size-4", "central", "peripheral", "scan", "gatt", "controller-host-flow-control"] }
14+
15+
futures = { version = "0.3", default-features = false, features = ["async-await"]}
16+
nrf-sdc = { version = "0.1.0", default-features = false, features = ["defmt", "peripheral", "central"] }
17+
nrf-mpsl = { version = "0.1.0", default-features = false, features = ["defmt", "critical-section-impl"] }
18+
bt-hci = { version = "0.2", default-features = false, features = ["defmt"] }
19+
20+
defmt = "0.3"
21+
defmt-rtt = "0.4.0"
22+
23+
cortex-m = { version = "0.7.6" }
24+
cortex-m-rt = "0.7.0"
25+
panic-probe = { version = "0.3", features = ["print-defmt"] }
26+
static_cell = "2"
27+
28+
[profile.release]
29+
debug = 2
30+
31+
[patch.crates-io]
32+
nrf-sdc = { git = "https://github.com/alexmoon/nrf-sdc.git", rev = "551a95436e999b4290b4a33383aa3d6747b63dd9" }
33+
nrf-mpsl = { git = "https://github.com/alexmoon/nrf-sdc.git", rev = "551a95436e999b4290b4a33383aa3d6747b63dd9" }
34+
35+
#embassy-executor = {path = "../../../embassy/embassy-executor"}
36+
#embassy-nrf = {path = "../../../embassy/embassy-nrf"}
37+
#embassy-sync = {path = "../../../embassy/embassy-sync"}
38+
#embassy-futures = {path = "../../../embassy/embassy-futures"}
39+
#embassy-time = {path = "../../../embassy/embassy-time"}
40+
#embassy-time-driver = {path = "../../../embassy/embassy-time-driver"}
41+
#embassy-embedded-hal = {path = "../../../embassy/embassy-embedded-hal"}
42+
#embassy-hal-internal = {path = "../../../embassy/embassy-hal-internal"}
43+
#nrf-sdc = { path = "../../../nrf-sdc/nrf-sdc" }
44+
#nrf-mpsl = { path = "../../../nrf-sdc/nrf-mpsl" }
45+
#bt-hci = { path = "../../../bt-hci" }
46+
47+
[features]
48+
nrf52832 = [
49+
"embassy-executor/task-arena-size-32768",
50+
"embassy-nrf/nrf52832",
51+
"nrf-sdc/nrf52832",
52+
]
53+
nrf52833 = [
54+
"embassy-executor/task-arena-size-32768",
55+
"embassy-nrf/nrf52833",
56+
"nrf-sdc/nrf52833",
57+
]
58+
nrf52840 = [
59+
"embassy-executor/task-arena-size-65536",
60+
"embassy-nrf/nrf52840",
61+
"nrf-sdc/nrf52840",
62+
]

benchmarks/nrf-sdc/build.rs

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
//! This build script copies the `memory.x` file from the crate root into
2+
//! a directory where the linker can always find it at build time.
3+
//! For many projects this is optional, as the linker always searches the
4+
//! project root directory -- wherever `Cargo.toml` is. However, if you
5+
//! are using a workspace or have a more complicated build setup, this
6+
//! build script becomes required. Additionally, by requesting that
7+
//! Cargo re-run the build script whenever `memory.x` is changed,
8+
//! updating `memory.x` ensures a rebuild of the application with the
9+
//! new memory settings.
10+
11+
use std::env;
12+
use std::fs::File;
13+
use std::io::Write;
14+
use std::path::PathBuf;
15+
16+
fn linker_data() -> &'static [u8] {
17+
#[cfg(feature = "nrf52832")]
18+
return include_bytes!("memory-nrf52832.x");
19+
#[cfg(feature = "nrf52833")]
20+
return include_bytes!("memory-nrf52833.x");
21+
#[cfg(feature = "nrf52840")]
22+
return include_bytes!("memory-nrf52840.x");
23+
#[cfg(not(any(feature = "nrf52832", feature = "nrf52833", feature = "nrf52840")))]
24+
unimplemented!("must select a target")
25+
}
26+
27+
fn main() {
28+
// Put `memory.x` in our output directory and ensure it's
29+
// on the linker search path.
30+
let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap());
31+
File::create(out.join("memory.x"))
32+
.unwrap()
33+
.write_all(linker_data())
34+
.unwrap();
35+
println!("cargo:rustc-link-search={}", out.display());
36+
37+
// By default, Cargo will re-run a build script whenever
38+
// any file in the project changes. By specifying `memory.x`
39+
// here, we ensure the build script is only re-run when
40+
// `memory.x` is changed.
41+
println!("cargo:rerun-if-changed=memory.x");
42+
43+
println!("cargo:rustc-link-arg-bins=--nmagic");
44+
println!("cargo:rustc-link-arg-bins=-Tlink.x");
45+
println!("cargo:rustc-link-arg-bins=-Tdefmt.x");
46+
}

benchmarks/nrf-sdc/memory-nrf52832.x

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
MEMORY
2+
{
3+
/* NOTE 1 K = 1 KiBi = 1024 bytes */
4+
FLASH : ORIGIN = 0x00000000, LENGTH = 512K
5+
RAM : ORIGIN = 0x20000000, LENGTH = 64K
6+
}

benchmarks/nrf-sdc/memory-nrf52833.x

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
MEMORY
2+
{
3+
/* NOTE 1 K = 1 KiBi = 1024 bytes */
4+
FLASH : ORIGIN = 0x00000000, LENGTH = 512K
5+
RAM : ORIGIN = 0x20000000, LENGTH = 128K
6+
}

benchmarks/nrf-sdc/memory-nrf52840.x

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
MEMORY
2+
{
3+
/* NOTE 1 K = 1 KiBi = 1024 bytes */
4+
/* These values correspond to the NRF52840 */
5+
FLASH : ORIGIN = 0x00000000, LENGTH = 1024K
6+
RAM : ORIGIN = 0x20000000, LENGTH = 256K
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
#![no_std]
2+
#![no_main]
3+
4+
use defmt::{info, unwrap};
5+
use embassy_executor::Spawner;
6+
use embassy_futures::join::join;
7+
use embassy_nrf::peripherals::RNG;
8+
use embassy_nrf::{bind_interrupts, rng};
9+
use embassy_time::{Duration, Instant, Timer};
10+
use nrf_sdc::mpsl::MultiprotocolServiceLayer;
11+
use nrf_sdc::{self as sdc, mpsl};
12+
use static_cell::StaticCell;
13+
use trouble_host::prelude::*;
14+
15+
use {defmt_rtt as _, panic_probe as _};
16+
17+
bind_interrupts!(struct Irqs {
18+
RNG => rng::InterruptHandler<RNG>;
19+
EGU0_SWI0 => nrf_sdc::mpsl::LowPrioInterruptHandler;
20+
CLOCK_POWER => nrf_sdc::mpsl::ClockInterruptHandler;
21+
RADIO => nrf_sdc::mpsl::HighPrioInterruptHandler;
22+
TIMER0 => nrf_sdc::mpsl::HighPrioInterruptHandler;
23+
RTC0 => nrf_sdc::mpsl::HighPrioInterruptHandler;
24+
});
25+
26+
#[embassy_executor::task]
27+
async fn mpsl_task(mpsl: &'static MultiprotocolServiceLayer<'static>) -> ! {
28+
mpsl.run().await
29+
}
30+
31+
const L2CAP_TXQ: u8 = 4;
32+
const L2CAP_RXQ: u8 = 4;
33+
const L2CAP_MTU: usize = 251;
34+
const CONNECTIONS_MAX: usize = 1;
35+
const L2CAP_CHANNELS_MAX: usize = 1;
36+
37+
fn build_sdc<'d, const N: usize>(
38+
p: nrf_sdc::Peripherals<'d>,
39+
rng: &'d mut rng::Rng<RNG>,
40+
mpsl: &'d MultiprotocolServiceLayer,
41+
mem: &'d mut sdc::Mem<N>,
42+
) -> Result<nrf_sdc::SoftdeviceController<'d>, nrf_sdc::Error> {
43+
sdc::Builder::new()?
44+
.support_scan()?
45+
.support_central()?
46+
.central_count(1)?
47+
.buffer_cfg(L2CAP_MTU as u8, L2CAP_MTU as u8, L2CAP_TXQ, L2CAP_RXQ)?
48+
.build(p, rng, mpsl, mem)
49+
}
50+
51+
#[embassy_executor::main]
52+
async fn main(spawner: Spawner) {
53+
let p = embassy_nrf::init(Default::default());
54+
let mpsl_p = mpsl::Peripherals::new(p.RTC0, p.TIMER0, p.TEMP, p.PPI_CH19, p.PPI_CH30, p.PPI_CH31);
55+
let lfclk_cfg = mpsl::raw::mpsl_clock_lfclk_cfg_t {
56+
source: mpsl::raw::MPSL_CLOCK_LF_SRC_RC as u8,
57+
rc_ctiv: mpsl::raw::MPSL_RECOMMENDED_RC_CTIV as u8,
58+
rc_temp_ctiv: mpsl::raw::MPSL_RECOMMENDED_RC_TEMP_CTIV as u8,
59+
accuracy_ppm: mpsl::raw::MPSL_DEFAULT_CLOCK_ACCURACY_PPM as u16,
60+
skip_wait_lfclk_started: mpsl::raw::MPSL_DEFAULT_SKIP_WAIT_LFCLK_STARTED != 0,
61+
};
62+
static MPSL: StaticCell<MultiprotocolServiceLayer> = StaticCell::new();
63+
let mpsl = MPSL.init(unwrap!(mpsl::MultiprotocolServiceLayer::new(mpsl_p, Irqs, lfclk_cfg)));
64+
spawner.must_spawn(mpsl_task(&*mpsl));
65+
66+
let sdc_p = sdc::Peripherals::new(
67+
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,
68+
p.PPI_CH27, p.PPI_CH28, p.PPI_CH29,
69+
);
70+
71+
let mut rng = rng::Rng::new(p.RNG, Irqs);
72+
73+
let mut sdc_mem = sdc::Mem::<16384>::new();
74+
let sdc = unwrap!(build_sdc(sdc_p, &mut rng, mpsl, &mut sdc_mem));
75+
76+
Timer::after(Duration::from_millis(200)).await;
77+
78+
let address: Address = Address::random([0xff, 0x8f, 0x1b, 0x05, 0xe4, 0xff]);
79+
let mut resources: HostResources<CONNECTIONS_MAX, L2CAP_CHANNELS_MAX, L2CAP_MTU> = HostResources::new();
80+
let stack = trouble_host::new(sdc, &mut resources).set_random_address(address);
81+
let Host {
82+
mut central,
83+
mut runner,
84+
..
85+
} = stack.build();
86+
87+
let target: Address = Address::random([0xff, 0x8f, 0x1a, 0x05, 0xe4, 0xff]);
88+
let config = ConnectConfig {
89+
connect_params: Default::default(),
90+
scan_config: ScanConfig {
91+
filter_accept_list: &[(target.kind, &target.addr)],
92+
..Default::default()
93+
},
94+
};
95+
96+
let _ = join(runner.run(), async {
97+
loop {
98+
let conn = unwrap!(central.connect(&config).await);
99+
const PAYLOAD_LEN: usize = 492;
100+
let config = L2capChannelConfig {
101+
flow_policy: CreditFlowPolicy::MinThreshold(4),
102+
initial_credits: Some(8),
103+
mtu: PAYLOAD_LEN as u16,
104+
};
105+
let mut ch1 = unwrap!(L2capChannel::create(&stack, &conn, 0x2349, &config).await);
106+
info!("sending l2cap data");
107+
let mut last = Instant::now();
108+
let mut bytes: u64 = 0;
109+
for i in 0..500 {
110+
let tx = [(i % 255) as u8; PAYLOAD_LEN];
111+
unwrap!(ch1.send::<_, L2CAP_MTU>(&stack, &tx).await);
112+
bytes += PAYLOAD_LEN as u64;
113+
let duration = Instant::now() - last;
114+
if duration.as_secs() > 10 {
115+
info!("throughput: {} bytes/sec", bytes / duration.as_secs());
116+
bytes = 0;
117+
last = Instant::now();
118+
}
119+
}
120+
info!("waiting for echo data");
121+
let mut rx = [0; PAYLOAD_LEN];
122+
for i in 0..500 {
123+
let len = unwrap!(ch1.receive(&stack, &mut rx).await);
124+
assert_eq!(len, rx.len());
125+
assert_eq!(rx, [(i % 255) as u8; PAYLOAD_LEN]);
126+
}
127+
128+
info!("done");
129+
Timer::after(Duration::from_secs(2)).await;
130+
break;
131+
}
132+
})
133+
.await;
134+
}

0 commit comments

Comments
 (0)