Created
March 9, 2025 19:22
-
-
Save paigeadelethompson/d3c6ea838820408ca5b07221790ae806 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
To set up the nRF52840-DK with the nRF52 series as a BLE device in active scanning or sniffer mode using the Zephyr RTOS API, we’ll use Zephyr’s Bluetooth subsystem. The nRF52840 does not require a separate SoftDevice (like in the Nordic SDK) because Zephyr provides its own Bluetooth Controller and Host stack, fully integrated into the OS. Below, I’ll guide you through configuring the nRF52840-DK for both active scanning and sniffer-like behavior using Zephyr’s high-level APIs, avoiding raw HCI commands unless necessary. | |
Assumptions | |
You’re using the nRF52840-DK board (nrf52840dk_nrf52840 in Zephyr). | |
Zephyr’s Bluetooth subsystem is enabled in your project configuration. | |
You’re familiar with building and flashing Zephyr applications. | |
Step 1: Project Setup | |
First, ensure your Zephyr project is configured correctly for BLE. | |
1.1. Prerequisites | |
Install the Zephyr SDK and set up the environment (refer to Zephyr’s Getting Started Guide). | |
Create a new Zephyr application or use an existing one (e.g., samples/bluetooth). | |
1.2. Enable Bluetooth in prj.conf | |
Add the following to your prj.conf file to enable Bluetooth support: | |
kconfig | |
CONFIG_BT=y | |
CONFIG_BT_DEVICE_NAME="nRF52840 Sniffer" | |
CONFIG_BT_MAX_CONN=0 # No connections needed for scanning/sniffing | |
CONFIG_BT_OBSERVER=y # Enable observer role for scanning | |
CONFIG_BT_HCI=y # Enable HCI layer | |
CONFIG_BT_DEBUG_LOG=y # Enable debug logging (optional) | |
1.3. Board Selection | |
Ensure your build targets the nRF52840-DK: | |
bash | |
west build -b nrf52840dk_nrf52840 | |
Step 2: Active Scanning Mode | |
In active scanning mode, the nRF52840-DK will scan for BLE advertisements and request scan responses from advertising devices (if they support it). This is useful for discovering devices and their additional data. | |
2.1. Code Example | |
Here’s a complete example to set up active scanning using Zephyr’s bt_le_scan_start API: | |
c | |
#include <zephyr.h> | |
#include <bluetooth/bluetooth.h> | |
#include <bluetooth/hci.h> | |
#include <sys/printk.h> | |
static void scan_cb(const bt_addr_le_t *addr, int8_t rssi, uint8_t adv_type, | |
struct net_buf_simple *buf) | |
{ | |
char addr_str[BT_ADDR_LE_STR_LEN]; | |
bt_addr_le_to_str(addr, addr_str, sizeof(addr_str)); | |
printk("Device found: %s (RSSI %d), type %u\n", addr_str, rssi, adv_type); | |
/* Optional: Parse advertising data */ | |
if (buf->len) { | |
printk("Adv data (%d bytes): ", buf->len); | |
for (int i = 0; i < buf->len; i++) { | |
printk("%02x ", buf->data[i]); | |
} | |
printk("\n"); | |
} | |
} | |
void main(void) | |
{ | |
int err; | |
/* Initialize Bluetooth */ | |
err = bt_enable(NULL); | |
if (err) { | |
printk("Bluetooth init failed (err %d)\n", err); | |
return; | |
} | |
printk("Bluetooth initialized\n"); | |
/* Define scan parameters */ | |
struct bt_le_scan_param scan_param = { | |
.type = BT_LE_SCAN_TYPE_ACTIVE, /* Active scanning */ | |
.options = BT_LE_SCAN_OPT_NONE, /* No options (e.g., no filtering) */ | |
.interval = BT_GAP_SCAN_FAST_INTERVAL, /* 60 ms (0x0060) */ | |
.window = BT_GAP_SCAN_FAST_WINDOW, /* 30 ms (0x0030) */ | |
.timeout = 0 /* No timeout (continuous scanning) */ | |
}; | |
/* Start scanning */ | |
err = bt_le_scan_start(&scan_param, scan_cb); | |
if (err) { | |
printk("Scanning failed to start (err %d)\n", err); | |
return; | |
} | |
printk("Scanning started\n"); | |
/* Keep the main thread alive */ | |
while (1) { | |
k_sleep(K_SECONDS(1)); | |
} | |
} | |
2.2. Explanation | |
Bluetooth Initialization: bt_enable(NULL) initializes the Bluetooth stack synchronously. | |
Scan Parameters: | |
type = BT_LE_SCAN_TYPE_ACTIVE: Requests scan responses from advertisers. | |
interval and window: Define how often and how long the device scans (fast scanning settings from bt_gap.h). | |
timeout = 0: Continuous scanning (set a value in 10-ms units if you want it to stop after a period). | |
Callback (scan_cb): | |
Triggered for each advertisement or scan response received. | |
Provides the device address, RSSI, advertisement type, and raw advertising data. | |
Logging: Uses printk for simplicity; replace with LOG_INF from <logging/log.h> if you prefer Zephyr’s logging system. | |
2.3. Build and Flash | |
bash | |
west build -b nrf52840dk_nrf52840 | |
west flash | |
Monitor output with a terminal (e.g., minicom or screen) on the DK’s UART (default baud rate: 115200). | |
Step 3: Sniffer Mode | |
Zephyr doesn’t have a dedicated "sniffer mode" like Wireshark or Nordic’s nRF Sniffer, but you can approximate it with passive scanning and detailed logging of all advertisement packets. Passive scanning listens to advertisements without requesting scan responses, mimicking a sniffer. | |
3.1. Code Example | |
Modify the above code for passive scanning: | |
c | |
#include <zephyr.h> | |
#include <bluetooth/bluetooth.h> | |
#include <bluetooth/hci.h> | |
#include <sys/printk.h> | |
static void scan_cb(const bt_addr_le_t *addr, int8_t rssi, uint8_t adv_type, | |
struct net_buf_simple *buf) | |
{ | |
char addr_str[BT_ADDR_LE_STR_LEN]; | |
bt_addr_le_to_str(addr, addr_str, sizeof(addr_str)); | |
/* Log all advertisement packets */ | |
printk("[SNIFFER] Addr: %s, RSSI: %d, Type: 0x%02x, Data Len: %d\n", | |
addr_str, rssi, adv_type, buf->len); | |
if (buf->len) { | |
printk("Data: "); | |
for (int i = 0; i < buf->len; i++) { | |
printk("%02x ", buf->data[i]); | |
} | |
printk("\n"); | |
} | |
} | |
void main(void) | |
{ | |
int err; | |
/* Initialize Bluetooth */ | |
err = bt_enable(NULL); | |
if (err) { | |
printk("Bluetooth init failed (err %d)\n", err); | |
return; | |
} | |
printk("Bluetooth initialized\n"); | |
/* Define scan parameters for passive scanning */ | |
struct bt_le_scan_param scan_param = { | |
.type = BT_LE_SCAN_TYPE_PASSIVE, /* Passive scanning */ | |
.options = BT_LE_SCAN_OPT_NONE, /* No filtering */ | |
.interval = 0x0010, /* 10 ms interval */ | |
.window = 0x0010, /* 10 ms window (continuous) */ | |
.timeout = 0 /* No timeout */ | |
}; | |
/* Start scanning */ | |
err = bt_le_scan_start(&scan_param, scan_cb); | |
if (err) { | |
printk("Scanning failed to start (err %d)\n", err); | |
return; | |
} | |
printk("Sniffer mode started\n"); | |
while (1) { | |
k_sleep(K_SECONDS(1)); | |
} | |
} | |
3.2. Explanation | |
Passive Scanning: BT_LE_SCAN_TYPE_PASSIVE ensures the device only listens to advertisements without interacting with advertisers. | |
Interval/Window: Set to 10 ms for near-continuous listening (adjust as needed; smaller values increase power consumption). | |
Callback: Logs every packet with address, RSSI, type, and raw data, simulating sniffer behavior. | |
No SoftDevice: Zephyr’s BLE stack replaces the Nordic SoftDevice, so this runs directly on the nRF52840’s controller. | |
3.3. Enhancements for Sniffer Mode | |
Filter Duplicates: Add BT_LE_SCAN_OPT_FILTER_DUPLICATE to options if you want unique packets only. | |
Channel Hopping: Zephyr’s controller handles BLE channel hopping (37, 38, 39) automatically. | |
Export Data: Replace printk with a mechanism to send data over UART/USB to a host PC for analysis (e.g., with a Python script). | |
3.4. Build and Flash | |
Same as above: | |
bash | |
west build -b nrf52840dk_nrf52840 | |
west flash | |
Step 4: Testing | |
Use another BLE device (e.g., a phone with an app like nRF Connect) to advertise nearby. | |
Check the UART output for scanned advertisements or sniffer logs. | |
For active scanning, devices supporting scan responses will send additional data. | |
Key Differences from Your Original Code | |
Your original code used raw HCI commands (bt_hci_cmd_send_sync), which is lower-level and bypasses Zephyr’s BLE stack abstraction. While this works, it’s prone to errors (e.g., state conflicts) and less portable. The Zephyr API (bt_le_scan_start) is preferred because: | |
It ensures the stack and controller are in the correct state. | |
It abstracts hardware-specific details. | |
It’s better supported and documented in Zephyr. | |
Troubleshooting | |
Scan Fails (err != 0): Check err value (e.g., -EAGAIN means Bluetooth isn’t ready; -EINVAL means bad parameters). | |
No Devices Found: Verify advertising devices are in range and use a larger scan window/interval. | |
Logs Missing: Ensure CONFIG_BT_DEBUG_LOG=y and your terminal is connected. | |
Alternative: Nordic nRF Sniffer | |
If you need a true sniffer with Wireshark integration, consider Nordic’s nRF Sniffer for Bluetooth LE instead of a custom Zephyr solution. It uses the nRF52840-DK with special firmware, but it’s not integrated with Zephyr. | |
Let me know if you need further clarification or help with specific tweaks! | |
how can I switch to passive mode once active mode has been set ? | |
To switch from active scanning mode to passive scanning mode in a Bluetooth Low Energy (BLE) setup using Zephyr RTOS on the nRF52840-DK, you need to stop the current active scan, update the scan parameters to use passive mode, and then restart the scanning process with the new settings. Below is a detailed, step-by-step explanation along with sample code to guide you through the process. | |
Understanding Active vs. Passive Scanning | |
Active Scanning: The scanner listens for advertising packets and sends scan requests to advertisers to retrieve additional data (e.g., scan responses). | |
Passive Scanning: The scanner only listens for advertising packets without sending any scan requests, making it a more lightweight operation. | |
Since you’ve already set up active scanning (likely using bt_le_scan_start with BT_LE_SCAN_TYPE_ACTIVE), switching to passive mode requires modifying the scan type to BT_LE_SCAN_TYPE_PASSIVE. | |
Steps to Switch to Passive Mode | |
Stop the Current Scan | |
Use bt_le_scan_stop() to halt the active scanning process. This is necessary because BLE does not allow changing scan parameters while scanning is ongoing. | |
Check the return value to ensure the stop operation succeeds before proceeding. | |
Update Scan Parameters | |
Modify the struct bt_le_scan_param object to set type = BT_LE_SCAN_TYPE_PASSIVE. | |
Keep other parameters (e.g., interval, window) the same unless you need to adjust them for your use case. | |
Start Passive Scanning | |
Call bt_le_scan_start() with the updated parameters and the same callback function used for active scanning. | |
The callback can remain unchanged since it will still process advertisement data, though it won’t receive scan responses in passive mode. | |
Sample Code | |
Here’s a complete example to demonstrate how to switch from active to passive scanning: | |
c | |
#include <zephyr.h> | |
#include <bluetooth/bluetooth.h> | |
#include <bluetooth/hci.h> | |
#include <sys/printk.h> | |
// Define scan parameters globally | |
static struct bt_le_scan_param scan_param; | |
// Callback to handle scan results | |
static void scan_cb(const bt_addr_le_t *addr, int8_t rssi, uint8_t adv_type, | |
struct net_buf_simple *buf) | |
{ | |
// Process advertisement data here | |
printk("Device found: RSSI %d, Type %d\n", rssi, adv_type); | |
} | |
// Function to start scanning with a specified type | |
void start_scanning(uint8_t scan_type) | |
{ | |
int err; | |
// Set scan parameters | |
scan_param.type = scan_type; // Active or Passive | |
scan_param.options = BT_LE_SCAN_OPT_NONE; // No special options | |
scan_param.interval = BT_GAP_SCAN_FAST_INTERVAL; // Scan interval | |
scan_param.window = BT_GAP_SCAN_FAST_WINDOW; // Scan window | |
scan_param.timeout = 0; // No timeout (continuous scan) | |
// Start scanning | |
err = bt_le_scan_start(&scan_param, scan_cb); | |
if (err) { | |
printk("Scanning failed to start (err %d)\n", err); | |
} else { | |
printk("Scanning started in %s mode\n", | |
scan_type == BT_LE_SCAN_TYPE_ACTIVE ? "active" : "passive"); | |
} | |
} | |
// Function to switch to passive scanning | |
void switch_to_passive() | |
{ | |
int err; | |
// Stop the current scan | |
err = bt_le_scan_stop(); | |
if (err) { | |
printk("Failed to stop scanning (err %d)\n", err); | |
return; // Exit if stop fails | |
} | |
// Start scanning in passive mode | |
start_scanning(BT_LE_SCAN_TYPE_PASSIVE); | |
} | |
void main(void) | |
{ | |
int err; | |
// Initialize Bluetooth | |
err = bt_enable(NULL); | |
if (err) { | |
printk("Bluetooth init failed (err %d)\n", err); | |
return; | |
} | |
printk("Bluetooth initialized\n"); | |
// Start in active scanning mode | |
start_scanning(BT_LE_SCAN_TYPE_ACTIVE); | |
// Wait for 10 seconds, then switch to passive mode | |
k_sleep(K_SECONDS(10)); | |
switch_to_passive(); | |
// Keep the program running | |
while (1) { | |
k_sleep(K_SECONDS(1)); | |
} | |
} | |
Explanation of the Code | |
Global Scan Parameters: scan_param is defined globally so it can be reused and modified as needed. | |
start_scanning Function: This helper function configures and starts scanning with the specified type (BT_LE_SCAN_TYPE_ACTIVE or BT_LE_SCAN_TYPE_PASSIVE). It reuses the same callback (scan_cb) for simplicity. | |
switch_to_passive Function: This stops the current scan and calls start_scanning with passive mode. It includes error checking to ensure the stop operation succeeds. | |
Error Handling: Both bt_le_scan_stop and bt_le_scan_start return error codes (0 for success, negative values for failure), which are checked and logged. | |
Callback: The scan_cb function processes advertisement data. In passive mode, it will only receive advertisement packets (not scan responses), but no changes are needed since it can handle both types. | |
Key Considerations | |
Timing: Ensure bt_le_scan_stop() completes before calling bt_le_scan_start(). The code above handles this by checking the return value and only proceeding if the stop succeeds. | |
Parameters: The example uses BT_GAP_SCAN_FAST_INTERVAL and BT_GAP_SCAN_FAST_WINDOW. Adjust these if you need a different scanning duty cycle (e.g., to catch more packets in passive mode). | |
Concurrency: In Zephyr, the Bluetooth stack runs in its own thread, and the scan callback is invoked from that thread. Calling bt_le_scan_stop() from the main thread is generally safe, as the API is designed to handle synchronization internally. | |
Why This Approach Works | |
The Bluetooth specification requires scanning to be disabled before changing parameters like the scan type. Zephyr’s high-level APIs (bt_le_scan_stop and bt_le_scan_start) abstract this requirement, ensuring the controller’s state is managed correctly. Attempting to change parameters without stopping the scan could result in errors like "Command Disallowed," which this stop-and-start method avoids. | |
By following these steps and using the provided code, you can reliably switch your nRF52840-DK from active to passive scanning mode in Zephyr RTOS. | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment