使用 ESP32 IDF 掃描 BLE 設備
內容
簡介
ESP32 是由 Espressif Systems 開發的一款低成本、低功耗的微控制器,具有 Wi-Fi 和 Bluetooth(包括 BLE,藍牙低功耗)雙模功能。它廣泛應用於物聯網(IoT)項目中,特別適合無線通信、智能家居、自動化系統等應用。它的高性能和豐富的外設使其在開發社群中非常受歡迎。
工作原理
BLE(Bluetooth Low Energy)是一種專為低功耗設備設計的藍牙技術,主要用於短距離數據傳輸。在 ESP32 上,BLE 功能可以用來進行設備掃描、廣播和數據交換。
1. BLE 掃描原理
當 ESP32 進行 BLE 掃描時,它會搜索周圍發送廣播訊號的 BLE 設備。這些廣播訊號中包含了關於設備的基本信息,如 MAC 地址(BDA, Bluetooth Device Address)、設備名稱、信號強度(RSSI),以及其他自定義的廣播數據。
掃描過程主要有兩種模式:
被動掃描:ESP32 只會接收廣播訊號,並不會發送掃描請求。
主動掃描:ESP32 在接收廣播訊號後,還會發送掃描請求,以獲取更多的設備信息。
2. ESP32 的 BLE 掃描運作流程
當我們啟動 ESP32 的 BLE 掃描功能時,ESP32 將進行以下步驟
設置掃描參數:透過 esp_ble_scan_params_t 結構體設定掃描間隔、窗口時間和掃描模式(主動或被動)。
啟動掃描:使用 esp_ble_gap_start_scanning() 函數啟動掃描,該函數會讓 ESP32 開始掃描附近的 BLE 設備。
處理掃描結果:每當 ESP32 掃描到一個設備,會觸發一個回調函數(callback function),並將掃描到的設備資料以事件的形式傳遞給應用程式。我們可以從這些資料中取得設備名稱、MAC 地址、RSSI 值等。
3. 名稱與 BDA 的替換
在 BLE 掃描過程中,ESP32 通常會以 BDA(Bluetooth Device Address, MAC 地址)來標識設備。但如果設備有廣播名稱(如 BLE 外設廣播的名稱),則我們可以替換顯示 BDA 的部分,以更人性化的方式展示設備名稱。這樣使用者在檢視掃描結果時,不僅能看到設備的地址,還能看到設備的名稱,提升了可讀性和辨識度。
安裝 VSCode 和 ESP-IDF VSCode 擴展
確保你已經安裝和配置好了 ESP-IDF 開發環境。您也可以參考 ESP32 入門到精通 – 在 VSCode 安裝 ESP-IDF 插件 這篇文章。
新建 ESP32 專案
利用在 VSCode 的 IDF 插件來新建一個 ESP32 專案可參考 ESP32 入門到精通 – 如何用 VSCode 創建 ESP32 專案。
VSCode 中的 Bluetooth 配置
1. 打開你的 ESP-IDF 專案。
2. 在 VSCode 中,按下 Ctrl + Shift + P,然後選擇 ESP-IDF: Configure Project。
3. 在彈出的 SDK Configuration Editor 中,找到 Bluetooth 部分。
4. 啟用 Enable Bluetooth。
5. 保存你的配置並重新編譯專案。
初始化 NVS 和藍牙
第一步是初始化非易失性存儲器(NVS)和藍牙功能。NVS 是 BLE 功能所需的,因為它用於持久存儲各種配置參數。
void init_nvs(void) {
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK(ret);
}
void init_ble(void) {
esp_err_t ret;
// Initialize the Bluetooth controller
esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
ret = esp_bt_controller_init(&bt_cfg);
if (ret) {
ESP_LOGE(TAG, "Failed to initialize BT controller: %s", esp_err_to_name(ret));
return;
}
// Enable the Bluetooth controller
ret = esp_bt_controller_enable(ESP_BT_MODE_BLE);
if (ret) {
ESP_LOGE(TAG, "Failed to enable BT controller: %s", esp_err_to_name(ret));
return;
}
// Initialize Bluedroid
ret = esp_bluedroid_init();
if (ret) {
ESP_LOGE(TAG, "Failed to initialize Bluedroid: %s", esp_err_to_name(ret));
return;
}
// Enable Bluedroid
ret = esp_bluedroid_enable();
if (ret) {
ESP_LOGE(TAG, "Failed to enable Bluedroid: %s", esp_err_to_name(ret));
return;
}
}
在這裡,我們使用 nvs_flash_init() 初始化 NVS 存儲,並使用 esp_bt_controller_init() 初始化藍牙控制器。
設置 BLE 掃描參數
藍牙功能啟用後,我們可以配置掃描參數,如掃描類型、地址類型和過濾策略。
esp_ble_scan_params_t ble_scan_params = {
.scan_type = BLE_SCAN_TYPE_ACTIVE,
.own_addr_type = BLE_ADDR_TYPE_PUBLIC,
.scan_filter_policy = BLE_SCAN_FILTER_ALLOW_ALL,
.scan_duplicate = BLE_SCAN_DUPLICATE_ENABLE,
.scan_interval = 0x50,
.scan_window = 0x30,
};
掃描類型:我們使用的是主動掃描,這意味著掃描器會向 BLE 設備發送請求以獲取更詳細的信息。
掃描過濾策略:允許掃描所有設備。
掃描間隔和窗口:這些定義了掃描發生的頻率和持續時間。調整這些值會影響響應速度和功耗。
處理掃描結果
接下來,我們可以註冊一個回調函數來處理掃描結果。esp_ble_gap_register_callback() 函數用來註冊事件處理函數,該函數將處理掃描結果並記錄設備名稱和 RSSI。
我們將使用一個輔助函數從廣播數據中提取設備名稱。如果沒有找到名稱,我們將設備標記為“未知設備”。
static void get_device_name(esp_ble_gap_cb_param_t *param, char *name, int name_len)
{
uint8_t *adv_name = NULL;
uint8_t adv_name_len = 0;
// Extract the device name from the advertisement data
adv_name = esp_ble_resolve_adv_data(param->scan_rst.ble_adv, ESP_BLE_AD_TYPE_NAME_CMPL, &adv_name_len);
if (adv_name != NULL && adv_name_len > 0) {
// Copy the device name to the output buffer
snprintf(name, name_len, "%.*s", adv_name_len, adv_name);
} else {
// If no name is found, show "Unknown device"
snprintf(name, name_len, "Unknown device");
}
}
此函數使用 esp_ble_resolve_adv_data() 從 BLE 廣播包中提取名稱。
接下來,讓我們創建 GAP 事件處理器來管理不同的 BLE 事件,特別是掃描結果事件。
void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param)
{
switch (event) {
case ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT:
if (param->scan_param_cmpl.status == ESP_BT_STATUS_SUCCESS) {
ESP_LOGI(TAG, "Scan parameters set, starting scan...");
esp_ble_gap_start_scanning(10); // Scan for 10 seconds
} else {
ESP_LOGE(TAG, "Failed to set scan parameters");
}
break;
case ESP_GAP_BLE_SCAN_RESULT_EVT:
if (param->scan_rst.search_evt == ESP_GAP_SEARCH_INQ_RES_EVT) {
// Get the device name
char device_name[64];
get_device_name(param, device_name, sizeof(device_name));
// Log the device name and RSSI
ESP_LOGI(TAG, "Device found: Name: %s, RSSI %d", device_name, param->scan_rst.rssi);
}
break;
case ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT:
ESP_LOGI(TAG, "Scan complete");
break;
default:
break;
}
}
在事件處理器中:
ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT:當掃描參數設置完成時,ESP32 開始掃描。
ESP_GAP_BLE_SCAN_RESULT_EVT:處理掃描結果,並記錄設備名稱和 RSSI。
ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT:掃描完成後,記錄一條完成訊息。
運行 BLE 掃描器
最後,將所有代碼組合到 app_main() 函數中...
void app_main(void)
{
// Initialize NVS
init_nvs();
// Initialize BLE
init_ble();
// Register GAP callback
esp_ble_gap_register_callback(gap_event_handler);
// Set scan parameters
esp_err_t ret = esp_ble_gap_set_scan_params(&ble_scan_params);
if (ret == ESP_OK) {
ESP_LOGI(TAG, "BLE scan parameters set successfully");
} else {
ESP_LOGE(TAG, "Failed to set scan params: %s", esp_err_to_name(ret));
}
}
這個函數初始化 NVS,啟用 BLE,註冊 GAP 回調,並設置掃描參數。
編譯和燒錄
在 VSCode 中找到 "ESP32-IDF : Build, Flash and Monitor" ICON 執行和燒錄。
查看結果
I (5010) BLE_SCAN: Device found: Name: MyBLEDevice, RSSI -65
I (6020) BLE_SCAN: Device found: Name: Unknown device, RSSI -72
結論
通過這段代碼,你現在可以使用 ESP32 掃描 BLE 設備並記錄它們的名稱和信號強度(RSSI)。BLE 是物聯網項目中的一個強大工具,而掃描設備只是開始。你可以進一步擴展此代碼來連接特定設備、讀取傳感器數據,甚至廣播自己的 BLE 資訊。
可以調整掃描間隔和窗口,或者修改日誌記錄來過濾特定的設備名稱或 RSSI。ESP32 在 BLE 項目上提供了極大的靈活性。