Skip to content

Commit

Permalink
Add Nordic Uart Service (NUS) peripheral to examples
Browse files Browse the repository at this point in the history
  • Loading branch information
plaes committed Oct 22, 2024
1 parent 971db39 commit 8a5bcee
Show file tree
Hide file tree
Showing 5 changed files with 245 additions and 0 deletions.
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
133 changes: 133 additions & 0 deletions examples/apps/src/ble_nus_peripheral.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
/* Nordic Uart Service (NUS) peripheral example */
use embassy_futures::join::join3;
use embassy_sync::blocking_mutex::raw::NoopRawMutex;
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

const MAX_ATTRIBUTES: usize = 32;

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
#[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 mut table: AttributeTable<'_, NoopRawMutex, MAX_ATTRIBUTES> = AttributeTable::new();

// Generic Access Service (mandatory)
let id = b"Trouble Example Device";
let mut svc = table.add_service(Service::new(0x1800));
let _ = svc.add_characteristic_ro(0x2a00, id);
svc.build();

// Generic attribute service (mandatory)
table.add_service(Service::new(0x1801));

let server = Server::new(stack, &mut table);

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>) {
loop {
match server.next().await {
Ok(GattEvent::Write { handle, connection: _ }) => {
let _ = server.get(handle, |value| {
info!("[gatt] Write event on {:?}. Value written: {:?}", handle, value);
});
}
Ok(GattEvent::Read { handle, connection: _ }) => {
info!("[gatt] Read event on {:?}", handle);
}
Err(e) => {
error!("[gatt] Error processing GATT events: {:?}", e);
}
}
}
}

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::CompleteLocalName(b"Trouble NRF UART"),
],
&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?;

/* TODO: Implement "echo" and push rx bytes back to tx? */
let mut tx = [0; ATT_MTU];
let mut tick: u8 = 0;
while conn.is_connected() {
Timer::after(Duration::from_secs(2)).await;
tick = tick.wrapping_add(1);
tx[0] = tick;
info!("[adv] notifying connection of tick {}", tick);
let _ = server.notify(server.nrf_uart.tx, &conn, &tx[..]).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;
30 changes: 30 additions & 0 deletions examples/nrf-sdc/Cargo.lock

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

78 changes: 78 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,78 @@
#![no_std]
#![no_main]

use defmt::unwrap;
use embassy_executor::Spawner;
use embassy_nrf::peripherals::RNG;
use embassy_nrf::{bind_interrupts, pac, 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 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.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;
}

0 comments on commit 8a5bcee

Please sign in to comment.