This example demonstrates how to implement Bluetooth BLE (Low Energy Bluetooth) device scanning on ESP32. After initializing the BLE device, the program periodically scans for BLE devices broadcasting in the vicinity, displaying the scanned device names and signal strength (RSSI) on the TFT LCD screen, and showing the total number of discovered devices after each scan round.
This content is based on V3.0 version. If errors occur after flashing the program, please try switching to another version.
ble_scan_test.ino fileint scanTime = 5; // Change to your desired scan duration (seconds)
BLEDevice::init("ESP BLEDevice"); // Change to your desired BLE device name
pBLEScan->setInterval(100); // Change scan interval
pBLEScan->setWindow(99); // Change scan window
Nr | BLE_DEV_Name | RSSI: Scanning has startedBLE Scan done! N Devices found. (N is the number of discovered devices)⚠️ Key Notes:
haveName() is true) will be displayedsetActiveScan(true)) sends scan requests to the peer, can obtain more broadcast data, but consumes more powerTypes of Devices Being Scanned:
Making Devices Discoverable:
Observing RSSI Values:
Multi-device Simultaneous Scanning:
setActiveScan(true)) consumes more power; switch to passive scan if low power consumption is neededThis example code is based on the ESP32-WROOM-32E development board. Before use, the scan duration and scan parameters can be modified according to actual needs.
The following parameters can be adjusted in the program:
int scanTime = 5; // Bluetooth scan time (seconds)
BLEDevice::init("ESP BLEDevice"); // BLE device name
pBLEScan->setActiveScan(true); // Whether to enable active scanning
pBLEScan->setInterval(100); // Scan interval
pBLEScan->setWindow(99); // Scan window
⚠️ Optional Parameters to Modify:
true for active scan (sends scan requests, obtains more data, higher power consumption); false for passive scan (only receives broadcast packets, lower power consumption)#include <TFT_eSPI.h>
#include <BLEDevice.h> // Bluetooth BLE device library
#include <BLEUtils.h>
#include <BLEScan.h> // Bluetooth BLE device scan function library
#include <BLEAdvertisedDevice.h> // Scanned Bluetooth devices (broadcast status)
int scanTime = 5; // Bluetooth scan time
BLEScan* pBLEScan; // Scan object
uint8_t show_index = 0;
char t_buf[100] = {0};
TFT_eSPI my_lcd = TFT_eSPI();
TFT_eSPI.h: TFT screen driver libraryBLEDevice.h: ESP32 BLE device basic library, provides BLE device initialization, scanning, connection and other functionsBLEUtils.h: BLE utility library, provides BLE-related auxiliary functionsBLEScan.h: BLE scan library, provides scan control and result retrieval functionsBLEAdvertisedDevice.h: BLE broadcast device library, encapsulates scanned device informationscanTime: Duration of a single scan round (seconds)pBLEScan: Scan object pointer, used to control the scanning processshow_index: Screen display line counter, maximum 17 linest_buf: String formatting buffermy_lcd: TFT screen objectMyAdvertisedDeviceCallbacks inherits from BLEAdvertisedDeviceCallbacks. Whenever ESP32 scans a BLE device that is broadcasting, the onResult() method is automatically called to display the device name and signal strength on the screen.
class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks
{
void onResult(BLEAdvertisedDevice advertisedDevice)
{
my_lcd.setTextColor(TFT_BLUE);
my_lcd.drawString("Nr | BLE_DEV_Name | RSSI", 10, 16);
if (advertisedDevice.haveName())
{
my_lcd.fillRect(10, 30+show_index * 12, my_lcd.width()-1, 12,TFT_WHITE);
my_lcd.drawNumber(show_index + 1, 10, 30 + show_index * 12);
my_lcd.drawString(advertisedDevice.getName().c_str(), 50, 30+show_index * 12);
if (advertisedDevice.haveRSSI())
{
sprintf(t_buf, "%4d", advertisedDevice.getRSSI());
my_lcd.drawString(t_buf, my_lcd.width()-50, 30+show_index * 12);
}
show_index++;
if (show_index == 17)
{
show_index = 0;
}
}
}
};
advertisedDevice: BLEAdvertisedDevice object, encapsulates the broadcast information of the BLE device scanned this timeThe program displays the following information on screen:
advertisedDevice.haveName(): Determines whether the device has broadcast a name; only devices with names will be displayedadvertisedDevice.getName().c_str(): Gets the string pointer of the device nameadvertisedDevice.haveRSSI(): Determines whether RSSI information is includedadvertisedDevice.getRSSI(): Gets the signal strength valuemy_lcd.fillRect(...): Clears the current line to avoid overlap of old and new datamy_lcd.drawNumber(): Displays device sequence numbermy_lcd.drawString(): Displays device name and RSSIshow_index auto-increments and resets to zero when reaching 17, implementing cyclic display⚠️ Notes:
show_index is a global variable that accumulates across multiple scan rounds and automatically resets to zero when reaching 17The setup() function completes initialization of the serial port, screen, and BLE device, and configures scan parameters.
void setup()
{
Serial.begin(115200);
my_lcd.begin();
my_lcd.setRotation(1);
my_lcd.fillScreen(TFT_WHITE);
my_lcd.setTextColor(TFT_RED);
my_lcd.setFreeFont(&FreeSans18pt7b);
my_lcd.drawString("BLE Scan Start", 34, 110);
BLEDevice::init("ESP BLEDevice");
pBLEScan = BLEDevice::getScan();
pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks());
pBLEScan->setActiveScan(true);
pBLEScan->setInterval(100);
pBLEScan->setWindow(99);
my_lcd.fillScreen(TFT_WHITE);
my_lcd.setTextFont(1);
}
Serial.begin(115200);
my_lcd.begin();
my_lcd.setRotation(1);
my_lcd.fillScreen(TFT_WHITE);
Serial.begin(115200): Initializes serial communication with baud rate 115200my_lcd.begin(): Initializes TFT screenmy_lcd.setRotation(1): Sets screen rotation to 1 degree (landscape mode)my_lcd.fillScreen(TFT_WHITE): Clears screen with white background⚠️ Note:
my_lcd.setTextColor(TFT_RED);
my_lcd.setFreeFont(&FreeSans18pt7b);
my_lcd.drawString("BLE Scan Start", 34, 110);
my_lcd.setFreeFont(&FreeSans18pt7b): Sets to large fontmy_lcd.drawString("BLE Scan Start", 34, 110): Displays startup prompt at the specified positionBLEDevice::init("ESP BLEDevice");
pBLEScan = BLEDevice::getScan();
pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks());
pBLEScan->setActiveScan(true);
pBLEScan->setInterval(100);
pBLEScan->setWindow(99);
BLEDevice::init("ESP BLEDevice"): Initializes BLE device, named "ESP BLEDevice"BLEDevice::getScan(): Gets scan object pointersetAdvertisedDeviceCallbacks(...): Registers the scan callback class instancesetActiveScan(true): Enables active scanning (sends scan requests to obtain more broadcast data)setInterval(100): Sets scan interval (unit 0.625ms, 100 means 62.5ms)setWindow(99): Sets scan window (unit 0.625ms, 99 means approximately 61.875ms)⚠️ Notes:
BLEDevice::init() must be called before using other BLE functionssetActiveScan(true) sends SCAN_REQ requests to broadcasting devices to obtain additional data in SCAN_RSP responses (such as full device names), but consumes more powermy_lcd.fillScreen(TFT_WHITE);
my_lcd.setTextFont(1);
The loop() function implements periodic scanning. Each scan round lasts 5 seconds; after displaying scan results, it clears the buffer and starts the next round after a 2-second interval.
void loop()
{
my_lcd.setTextColor(TFT_RED);
my_lcd.fillRect(10, 0, my_lcd.width()-1, 16,TFT_WHITE);
my_lcd.drawString("BLE scanning ...", 10, 3);
BLEScanResults *foundDevices = pBLEScan->start(scanTime, false);
sprintf(t_buf, "BLE Scan done! %d Devices found.",foundDevices->getCount());
my_lcd.fillRect(10, 0, my_lcd.width()-1, 16,TFT_WHITE);
my_lcd.drawString(t_buf, 10, 3);
pBLEScan->clearResults();
delay(2000);
}
my_lcd.fillRect(...): Clears the top status bar areamy_lcd.drawString("BLE scanning ...", 10, 3): Displays the scanning-in-progress promptpBLEScan->start(scanTime, false): Starts scanning, lasts scanTime seconds
false means do not continue from previous scan results, start a new scan count from zerofoundDevices->getCount(): Gets the total number of scanned devicessprintf(t_buf, ...): Formats the scan result stringpBLEScan->clearResults(): Clears the scan result buffer and releases memorydelay(2000): Delays 2 seconds before starting the next scan round⚠️ Notes:
pBLEScan->start(scanTime, false) blocks synchronously until the scan duration endsclearResults() must be called, otherwise scan results will accumulate in memory, causing memory leaksonResult() is called each time a broadcasting device is scannedgetCount() returns the number of all devices found during scanning (including unnamed ones), but the screen only displays named devices, so the number of rows displayed may not match the countThis example implements the ESP32 BLE device scanning function through the following steps:
Key functions used in the program:
BLEDevice::init(): Initialize BLE deviceBLEDevice::getScan(): Get scan objectpBLEScan->setAdvertisedDeviceCallbacks(): Register scan callbackpBLEScan->setActiveScan(): Set active/passive scan modepBLEScan->setInterval(): Set scan intervalpBLEScan->setWindow(): Set scan windowpBLEScan->start(): Start scanningpBLEScan->clearResults(): Clear scan resultsfoundDevices->getCount(): Get the number of scanned devicesadvertisedDevice.haveName(): Determine if name is includedadvertisedDevice.getName(): Get device nameadvertisedDevice.haveRSSI(): Determine if RSSI is includedadvertisedDevice.getRSSI(): Get signal strengthmy_lcd.drawString(): Display text on screenmy_lcd.drawNumber(): Display number on screenmy_lcd.fillRect(): Fill rectangular areaIf you need to modify the code for different application scenarios, refer to the following aspects:
int scanTime = 10; // Change to 10 seconds per scan round
BLEDevice::init("My_ESP32_BLE"); // Change to custom name
pBLEScan->setActiveScan(false); // Change to passive scan (low power consumption)
pBLEScan->setInterval(200); // Increase scan interval (reduce power consumption)
pBLEScan->setWindow(50); // Reduce scan window (reduce duty cycle)
void onResult(BLEAdvertisedDevice advertisedDevice)
{
my_lcd.fillRect(10, 30+show_index * 12, my_lcd.width()-1, 12,TFT_WHITE);
my_lcd.drawNumber(show_index + 1, 10, 30 + show_index * 12);
// Display device address as name replacement
if (advertisedDevice.haveName())
{
my_lcd.drawString(advertisedDevice.getName().c_str(), 50, 30+show_index * 12);
}
else
{
my_lcd.drawString(advertisedDevice.getAddress().toString().c_str(), 50, 30+show_index * 12);
}
if (advertisedDevice.haveRSSI())
{
sprintf(t_buf, "%4d", advertisedDevice.getRSSI());
my_lcd.drawString(t_buf, my_lcd.width()-50, 30+show_index * 12);
}
show_index++;
if (show_index == 17) show_index = 0;
}
void onResult(BLEAdvertisedDevice advertisedDevice)
{
Serial.printf("Device: %s, RSSI: %d, Addr: %s\n",
advertisedDevice.getName().c_str(),
advertisedDevice.getRSSI(),
advertisedDevice.getAddress().toString().c_str());
// Also display service UUID, manufacturer data, etc.
if (advertisedDevice.haveServiceUUID())
{
Serial.printf("Service UUID: %s\n", advertisedDevice.getServiceUUID().toString().c_str());
}
}
if (show_index == 10) // Change to display up to 10 rows
{
show_index = 0;
}
Note: When modifying the row count, the starting Y coordinate (30) and row height (12) need to be adjusted simultaneously to avoid display overlap.
void loop()
{
my_lcd.fillScreen(TFT_WHITE); // Clear screen before each scan round
my_lcd.drawString("BLE scanning ...", 10, 3);
// ... rest of scan logic
}
void loop()
{
BLEScanResults *foundDevices = pBLEScan->start(scanTime, false);
Serial.printf("Found %d devices:\n", foundDevices->getCount());
for (int i = 0; i < foundDevices->getCount(); i++)
{
BLEAdvertisedDevice dev = foundDevices->getDevice(i);
Serial.printf(" [%d] %s RSSI:%d\n", i+1,
dev.haveName() ? dev.getName().c_str() : "Unknown",
dev.getRSSI());
}
pBLEScan->clearResults();
delay(2000);
}
Cannot Scan Any BLE Devices
window should be less than or equal to intervalsetActiveScan to true to obtain more broadcast dataNumber of Scanned Devices Does Not Match Screen Display Rows
haveName() is true), while getCount() counts all scanned devices (including unnamed ones)Screen Display Garbled or Font Abnormal
setRotation(1) is correct; this example uses landscape modeTFT_eSPI library font configuration is correctRSSI Value is 0 or Abnormal
haveRSSI() to checkDevice List Not Refreshing or Same Device Repeatedly Displayed
show_index does not reset to zero, only cycles when reaching 17show_index = 0 before scanning in loop()Insufficient Memory or Program Crash
pBLEScan->clearResults() is called after each scan to release memorysetInterval and setWindow to avoid excessive resource usage by the scan taskScreen Display Area Overlap
show_index * 12 to ensure it does not exceed the screen's visible areaHigh Power Consumption During Active Scanning
setActiveScan(false)setInterval and decreasing setWindow can reduce the scan duty cycle