Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save paigeadelethompson/d3c6ea838820408ca5b07221790ae806 to your computer and use it in GitHub Desktop.
Save paigeadelethompson/d3c6ea838820408ca5b07221790ae806 to your computer and use it in GitHub Desktop.
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