1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
#![no_std]
#![no_main]
// BLE scan (Pi 3 only): brings the on-board BCM43438 Bluetooth controller
// up (see `bt_probe.rs`), then scans as a central and prints each nearby
// BLE device it hears -- address, signal strength, and name where the
// advertising data carries one. The inverse of `ble_advertise.rs`: this Pi
// is the scanner rather than the advertiser.
//
// Controller-level GAP scanning only (LE Set Scan Parameters/Enable +
// parsing LE Advertising Report events); no connection is made.
//
// Setup mirrors `bt_probe.rs`: the console is the mini UART (GPIO14/15,
// needs `core_freq=250` in `config.txt`), and the `.hcd` patchram blob is
// read off the SD card. In a `bt` directory on the boot partition, under
// an 8.3 name:
// BT.HCD -- Broadcom's BCM43430A1.hcd patchram blob
//
// Get BCM43430A1.hcd (the original Pi 3B's BCM43438; not the 3B+/Zero 2 W's
// BCM4345C0.hcd) from RPi-Distro/bluez-firmware at
// https://github.com/RPi-Distro/bluez-firmware/blob/master/broadcom/BCM43430A1.hcd
// or copy it from /lib/firmware/brcm/ on any Raspberry Pi OS install.
use core::fmt::Write;
use rpi_hal::bluetooth::{AdvReport, Bluetooth};
use rpi_hal::halt;
use rpi_hal::mailbox::Mailbox;
use rpi_hal::mini_uart::MiniUart;
use rpi_hal::pac;
use rpi_hal::sd::Sd;
use rpi_hal::timer::Timer;
use rpi_hal::uart::Uart;
#[path = "common/mod.rs"]
mod common;
use common::{firmware_from_sd, HCI_BAUD};
/// How long to wait for each advertising report before printing a "still
/// scanning" heartbeat, in milliseconds.
const REPORT_WAIT_MS: u32 = 2_000;
/// Distinct device addresses remembered so each is printed once. Printing
/// to the slow mini-UART console blocks the reader for milliseconds, during
/// which HCI events streaming in at 3 Mbaud overrun the PL011 RX FIFO — so
/// the loop must print rarely and otherwise drain continuously. Deduping by
/// address gives that: a burst of prints as devices are discovered, then a
/// fast, print-free steady state.
const MAX_DEVICES: usize = 64;
/// A short human label for an advertising event type.
fn event_label(event_type: u8) -> &'static str {
match event_type {
0x00 => "connectable",
0x01 => "directed",
0x02 => "scannable",
0x03 => "non-conn",
0x04 => "scan-rsp",
_ => "?",
}
}
/// Prints one report as `AA:BB:CC:DD:EE:FF -63 dBm connectable "Name"`.
fn print_report(console: &mut MiniUart, report: &AdvReport) {
let a = report.address;
let _ = write!(
console,
"{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x} {:>4} dBm {:<11}",
a[5],
a[4],
a[3],
a[2],
a[1],
a[0],
report.rssi,
event_label(report.event_type),
);
match report.name() {
Some(name) => {
let _ = writeln!(console, " \"{name}\"");
}
None => {
let _ = writeln!(console);
}
}
}
#[no_mangle]
pub extern "C" fn kmain() -> ! {
let peripherals = unsafe { pac::Peripherals::steal() };
let mut console = MiniUart::init(&peripherals.GPIO, &peripherals.AUX, peripherals.UART1);
let timer = Timer::new(peripherals.SYSTMR);
let mut mailbox = Mailbox::new(peripherals.VCMAILBOX);
// Read the firmware blob off the SD card first (this owns EMMC).
let _ = writeln!(console, "reading Bluetooth firmware from SD card...");
let sd = match Sd::init(&peripherals.GPIO, peripherals.EMMC, &mut mailbox, &timer) {
Ok(sd) => sd,
Err(e) => {
let _ = writeln!(console, "SD init failed: {e:?}");
halt();
}
};
let hcd = match firmware_from_sd(sd, &timer) {
Ok(hcd) => hcd,
Err(e) => {
let _ = writeln!(
console,
"reading {}/{} failed: {e:?}",
common::BT_DIR,
common::FIRMWARE_FILE
);
halt();
}
};
// Bring the controller up: commit the PL011 to its HCI pins, assert
// BT_ON, download the patchram firmware, and raise the link baud.
let _ = writeln!(console, "bringing up Bluetooth controller over HCI...");
let hci = Uart::init_bluetooth(&peripherals.GPIO, peripherals.UART0);
let mut bt = Bluetooth::new(hci, &mut mailbox, &timer);
if let Err(e) = bt.load_firmware(hcd, &timer) {
let _ = writeln!(console, "firmware load failed: {e:?}");
halt();
}
if let Err(e) = bt.set_baud(HCI_BAUD, &timer) {
let _ = writeln!(console, "baud bump failed: {e:?}");
halt();
}
let _ = writeln!(console, "controller ready");
// Start scanning and stream the reports. Duplicate filtering is on, so
// each device is reported once per scan session.
if let Err(e) = bt.start_scan(&timer) {
let _ = writeln!(console, "start scan failed: {e:?}");
halt();
}
let _ = writeln!(console, "scanning for BLE devices...");
// Remember which devices we've printed, and whether we've shown a name
// for each: a device's name often rides in its scan response, a
// separate report from its initial advertisement, so a device first
// seen without a name gets one follow-up line once the name arrives.
let mut addrs = [[0u8; 6]; MAX_DEVICES];
let mut named = [false; MAX_DEVICES];
let mut count = 0;
loop {
let report = match bt.next_advertising_report(&timer, REPORT_WAIT_MS) {
Ok(Some(report)) => report,
// Quiet air — nothing to print. Keep looping (and draining).
Ok(None) => continue,
Err(e) => {
let _ = writeln!(console, "scan read error: {e:?}");
halt();
}
};
match addrs[..count].iter().position(|a| *a == report.address) {
// New device: record and print it.
None => {
if count < MAX_DEVICES {
addrs[count] = report.address;
named[count] = report.name().is_some();
count += 1;
print_report(&mut console, &report);
if count == MAX_DEVICES {
let _ = writeln!(console, "(device table full; new devices not shown)");
}
}
}
// Seen before: print again only if we just learned its name,
// otherwise skip silently so the loop keeps draining fast.
Some(i) => {
if !named[i] && report.name().is_some() {
named[i] = true;
print_report(&mut console, &report);
}
}
}
}
}