Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Nordic Uart Service (NUS) peripheral to examples #141

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions examples/apps/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
152 changes: 152 additions & 0 deletions examples/apps/src/ble_nus_peripheral.rs
Original file line number Diff line number Diff line change
@@ -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<C> = HostResources<C, CONNECTIONS_MAX, L2CAP_CHANNELS_MAX, L2CAP_MTU>;

// 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<u8, ATT_MTU>,

#[characteristic(uuid = "6E400003-B5A3-F393-E0A9-E50E24DCCA9E", notify)]
tx: Vec<u8, ATT_MTU>,
}

pub async fn run<C>(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<C: Controller>(mut runner: Runner<'_, C>) -> Result<(), BleHostError<C::Error>> {
runner.run().await
}

async fn gatt_task<C: Controller>(server: &Server<'_, '_, C>) -> Result<(), BleHostError<C::Error>> {
server.run().await
}

async fn advertise_task<C: Controller>(
mut peripheral: Peripheral<'_, C>,
server: &Server<'_, '_, C>,
) -> Result<(), BleHostError<C::Error>> {
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::<u8, ATT_MTU>::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;
}
}
}
}
}
2 changes: 2 additions & 0 deletions examples/apps/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
1 change: 1 addition & 0 deletions examples/nrf-sdc/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

67 changes: 67 additions & 0 deletions examples/nrf-sdc/src/bin/ble_nus_peripheral.rs
Original file line number Diff line number Diff line change
@@ -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<RNG>;
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<RNG>,
mpsl: &'d MultiprotocolServiceLayer,
mem: &'d mut sdc::Mem<N>,
) -> Result<nrf_sdc::SoftdeviceController<'d>, 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<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(
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;
}
Loading