-
Notifications
You must be signed in to change notification settings - Fork 56
Passive scanning #118
base: master
Are you sure you want to change the base?
Passive scanning #118
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
[build] | ||
target = 'thumbv7em-none-eabi' | ||
|
||
[target.'cfg(all(target_arch = "arm", target_os = "none"))'] | ||
runner = 'arm-none-eabi-gdb' | ||
rustflags = [ | ||
"-C", "link-arg=-Tlink.x", | ||
] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
[package] | ||
authors = ["Danilo Bargen <[email protected]>"] | ||
description = "Rubble BLE stack demo showcasing passive beacon scanning on the nRF52 MCUs" | ||
categories = ["embedded", "no-std"] | ||
keywords = ["arm", "nrf", "bluetooth", "low", "energy"] | ||
repository = "https://github.com/jonas-schievink/rubble/" | ||
license = "0BSD" | ||
name = "nrf52-scanner" | ||
version = "0.0.0" | ||
edition = "2018" | ||
publish = false | ||
|
||
[dependencies] | ||
rubble = { path = "../../rubble", default-features = false } | ||
rubble-nrf5x = { path = "../../rubble-nrf5x" } | ||
demo-utils = { path = "../demo-utils" } | ||
cortex-m = "0.6.1" | ||
cortex-m-rtfm = "0.5.1" | ||
cortex-m-rt = "0.6.11" | ||
panic-halt = "0.2.0" | ||
|
||
nrf52810-hal = { version = "0.10", features = ["rt"], optional = true } | ||
nrf52832-hal = { version = "0.10", features = ["rt"], optional = true } | ||
nrf52840-hal = { version = "0.10", features = ["rt"], optional = true } | ||
|
||
# Disable documentation to avoid spurious rustdoc warnings | ||
[[bin]] | ||
name = "nrf52-scanner" | ||
doc = false | ||
test = false | ||
|
||
[features] | ||
52810 = ["rubble-nrf5x/52810", "nrf52810-hal"] | ||
52832 = ["rubble-nrf5x/52832", "nrf52832-hal"] | ||
52840 = ["rubble-nrf5x/52840", "nrf52840-hal"] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
# `nrf52-scanner` | ||
|
||
A passive beacon scanner. | ||
|
||
It does not enable logging and is kept simple and minimal. | ||
|
||
The demo works with the nRF52810, nRF52832, and nRF52840. To run it, one of the | ||
target devices has to be enabled via a Cargo feature: | ||
|
||
cargo run --feature 52810 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
#![no_std] | ||
#![no_main] | ||
#![warn(rust_2018_idioms)] | ||
|
||
// We need to import this crate explicitly so we have a panic handler | ||
use panic_halt as _; | ||
|
||
// Import the right HAL and PAC | ||
#[cfg(feature = "52810")] | ||
use nrf52810_hal as hal; | ||
|
||
#[cfg(feature = "52832")] | ||
use nrf52832_hal as hal; | ||
|
||
#[cfg(feature = "52840")] | ||
use nrf52840_hal as hal; | ||
|
||
use { | ||
rubble::{ | ||
beacon::{BeaconScanner, ScanCallback}, | ||
link::{ad_structure::AdStructure, filter::AllowAll, DeviceAddress, MIN_PDU_BUF}, | ||
time::{Duration, Timer}, | ||
}, | ||
rubble_nrf5x::{ | ||
radio::{BleRadio, PacketBuffer}, | ||
timer::BleTimer, | ||
}, | ||
}; | ||
|
||
pub struct BeaconScanCallback; | ||
|
||
impl ScanCallback for BeaconScanCallback { | ||
fn beacon<'a, I>(&mut self, _addr: DeviceAddress, _data: I) | ||
where | ||
I: Iterator<Item = AdStructure<'a>>, | ||
{ | ||
// Detected an advertisement frame! Do something with it here. | ||
} | ||
} | ||
|
||
#[rtfm::app(device = crate::hal::target, peripherals = true)] | ||
const APP: () = { | ||
struct Resources { | ||
#[init([0; MIN_PDU_BUF])] | ||
ble_tx_buf: PacketBuffer, | ||
#[init([0; MIN_PDU_BUF])] | ||
ble_rx_buf: PacketBuffer, | ||
radio: BleRadio, | ||
timer: BleTimer<hal::pac::TIMER0>, | ||
scanner: BeaconScanner<BeaconScanCallback, AllowAll>, | ||
} | ||
|
||
#[init(resources = [ble_tx_buf, ble_rx_buf])] | ||
fn init(ctx: init::Context) -> init::LateResources { | ||
// On reset, the internal high frequency clock is already used, but we | ||
// also need to switch to the external HF oscillator. This is needed | ||
// for Bluetooth to work. | ||
let _clocks = hal::clocks::Clocks::new(ctx.device.CLOCK).enable_ext_hfosc(); | ||
|
||
// Initialize BLE timer | ||
let mut timer = BleTimer::init(ctx.device.TIMER0); | ||
|
||
// Initialize radio | ||
let mut radio = BleRadio::new( | ||
ctx.device.RADIO, | ||
&ctx.device.FICR, | ||
ctx.resources.ble_tx_buf, | ||
ctx.resources.ble_rx_buf, | ||
); | ||
|
||
// Set up beacon scanner for continuous scanning. The advertisement | ||
// channel that is being listened on (scan window) will be switched | ||
// every 500 ms. | ||
let mut scanner = BeaconScanner::new(BeaconScanCallback); | ||
let scanner_cmd = scanner.configure(timer.now(), Duration::from_millis(500)); | ||
|
||
// Reconfigure radio and timer | ||
radio.configure_receiver(scanner_cmd.radio); | ||
timer.configure_interrupt(scanner_cmd.next_update); | ||
|
||
init::LateResources { | ||
radio, | ||
scanner, | ||
timer, | ||
} | ||
} | ||
|
||
#[task(binds = RADIO, resources = [radio, scanner, timer])] | ||
fn radio(ctx: radio::Context) { | ||
let timer = ctx.resources.timer; | ||
let scanner = ctx.resources.scanner; | ||
let radio = ctx.resources.radio; | ||
|
||
if let Some(next_update) = radio.recv_beacon_interrupt(scanner) { | ||
timer.configure_interrupt(next_update); | ||
} | ||
} | ||
|
||
#[task(binds = TIMER0, resources = [radio, timer, scanner])] | ||
fn timer0(ctx: timer0::Context) { | ||
let timer = ctx.resources.timer; | ||
let scanner = ctx.resources.scanner; | ||
let radio = ctx.resources.radio; | ||
|
||
// Clear interrupt | ||
if !timer.is_interrupt_pending() { | ||
return; | ||
} | ||
timer.clear_interrupt(); | ||
|
||
// Update scanner (switch to next advertisement channel) | ||
let cmd = scanner.timer_update(timer.now()); | ||
radio.configure_receiver(cmd.radio); | ||
timer.configure_interrupt(cmd.next_update); | ||
} | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -60,8 +60,12 @@ use { | |
}, | ||
pac::{radio::state::STATE_R, RADIO}, | ||
rubble::{ | ||
beacon::{BeaconScanner, ScanCallback}, | ||
config::Config, | ||
link::{advertising, data, Cmd, LinkLayer, RadioCmd, Transmitter, CRC_POLY, MIN_PDU_BUF}, | ||
link::{ | ||
advertising, data, filter::AddressFilter, Cmd, LinkLayer, NextUpdate, RadioCmd, | ||
Transmitter, CRC_POLY, MIN_PDU_BUF, | ||
}, | ||
phy::{AdvertisingChannel, DataChannel}, | ||
time::{Duration, Instant}, | ||
}, | ||
|
@@ -84,6 +88,21 @@ pub struct BleRadio { | |
rx_buf: Option<&'static mut PacketBuffer>, | ||
} | ||
|
||
/// Acknowledge the DSIABLED event | ||
macro_rules! acknowledge_disabled_event { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can't this be a method? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The early-return of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Whoa, I totally missed that this is possible with options as well, and not just results 🤯 |
||
($radio:expr) => { | ||
if $radio.events_disabled.read().bits() == 0 { | ||
return None; | ||
} | ||
|
||
// "Subsequent reads and writes cannot be moved ahead of preceding reads." | ||
compiler_fence(Ordering::Acquire); | ||
|
||
// Acknowledge DISABLED event | ||
$radio.events_disabled.reset(); | ||
}; | ||
} | ||
|
||
impl BleRadio { | ||
/// Initializes the radio in BLE mode and takes ownership of the RX and TX buffers. | ||
// TODO: Use type-safe clock configuration to ensure that chip uses ext. crystal | ||
|
@@ -303,25 +322,15 @@ impl BleRadio { | |
} | ||
} | ||
|
||
/// Call this when the `RADIO` interrupt fires. | ||
/// Call this when the `RADIO` interrupt fires when using the [`LinkLayer`]. | ||
/// | ||
/// Automatically reconfigures the radio according to the `RadioCmd` returned by the BLE stack. | ||
/// | ||
/// Returns when the `update` method should be called the next time. | ||
pub fn recv_interrupt<C: Config<Transmitter = Self>>( | ||
/// [`LinkLayer`]: ../../rubble/link/struct.LinkLayer.html | ||
pub fn recv_ll_interrupt<C: Config<Transmitter = Self>>( | ||
&mut self, | ||
timestamp: Instant, | ||
ll: &mut LinkLayer<C>, | ||
) -> Option<Cmd> { | ||
if self.radio.events_disabled.read().bits() == 0 { | ||
return None; | ||
} | ||
|
||
// "Subsequent reads and writes cannot be moved ahead of preceding reads." | ||
compiler_fence(Ordering::Acquire); | ||
|
||
// Acknowledge DISABLED event: | ||
self.radio.events_disabled.reset(); | ||
acknowledge_disabled_event!(self.radio); | ||
|
||
let crc_ok = self.radio.crcstatus.read().crcstatus().is_crcok(); | ||
|
||
|
@@ -357,6 +366,44 @@ impl BleRadio { | |
Some(cmd) | ||
} | ||
|
||
/// Call this when the `RADIO` interrupt fires when using the [`BeaconScanner`]. | ||
/// | ||
/// The radio is automatically reconfigured to listen on the next | ||
/// advertisement channel. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Only the timer interrupt should do this, right? |
||
/// | ||
/// Returns when the [`timer_update`] method should be called the next time. | ||
/// | ||
/// [`BeaconScanner`]: ../../rubble/beacon/struct.BeaconScanner.html | ||
/// [`timer_update`]: ../../rubble/beacon/struct.BeaconScanner.html#method.timer_update | ||
pub fn recv_beacon_interrupt<C: ScanCallback, F: AddressFilter>( | ||
&mut self, | ||
scanner: &mut BeaconScanner<C, F>, | ||
) -> Option<NextUpdate> { | ||
acknowledge_disabled_event!(self.radio); | ||
|
||
// Check CRC | ||
let crc_ok = self.radio.crcstatus.read().crcstatus().is_crcok(); | ||
|
||
// When we get here, the radio must have transitioned to DISABLED state. | ||
assert!(self.state().is_disabled()); | ||
|
||
// Parse header | ||
let header = advertising::Header::parse(*self.rx_buf.as_ref().unwrap()); | ||
|
||
// Check that `payload_length` is in bounds | ||
let rx_buf = self.rx_buf.as_ref().unwrap(); | ||
let pl_lim = cmp::min(2 + usize::from(header.payload_length()), rx_buf.len()); | ||
|
||
// Process payload | ||
let payload = &rx_buf[2..pl_lim]; | ||
let cmd = scanner.process_adv_packet(header, payload, crc_ok); | ||
|
||
// Reconfigure radio | ||
self.configure_receiver(cmd.radio); | ||
|
||
Some(cmd.next_update) | ||
} | ||
|
||
/// Perform preparations to receive or send on an advertising channel. | ||
/// | ||
/// This will disable the radio, configure the packet layout, set initial values for CRC and | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, I think this should at least do something with the data, maybe even just an
hprintln!
if that makes sense.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could add a
rprintln!
call throughrtt-target
. It would require connecting with an RTT client though (e.g. by flashing through cargo-embed, which is what I usually do).Might actually be nice for the demo, since it doesn't require wiring up and configuring UART (and in contrast to semihosting code still runs without a debug probe attached).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since
rtt-target
has a bunch of open soundness issues and is still fairly new, I'd prefer to go with semihosting instead (we already pull that in for a panic handler). Since this is a demo, it won't run without a debugger anyways (and if it did, it wouldn't output anything regardless).