前言
BLE(藍牙低功耗,Bluetooth Low Energy)是物聯網時代最重要的短距無線通訊技術。從小米手環到智慧門鎖、從感測器信標到醫療設備,BLE 以極低的功耗實現了可靠的資料交換。ESP32 內建雙模藍牙(Classic + BLE),支援藍牙 5.0 標準,是開發 BLE 應用的理想平台。本文將從 BLE 協定棧架構開始,深入剖析廣播/掃描/連線機制與 GATT 服務模型,並提供 ESP32 Arduino 與 IDF 兩套完整開發範例。
一、BLE 技術概述
1.1 什麼是 BLE?
BLE 由 Nokia 在 2006 年以 Wibree 之名提出,2010 年納入 Bluetooth 4.0 標準。與經典藍牙(BR/EDR)最大的差異在於:
- 極低功耗:峰值電流僅 ~15 mA,休眠可達 1 μA
- 快速連線:從廣播到建立連線只需 3~6 ms
- 不同拓撲:支援廣播(Broadcast)、星狀(Piconet)、網狀(Mesh)
- 彈性資料格式:透過 GATT Profile 自訂服務與特徵值

圖 6:經典藍牙 (BR/EDR) 與 BLE 差異比較 — BLE 在功耗、延遲、傳輸量上做出取捨

圖 5:BLE 核心參數一覽 — 2.4 GHz ISM 頻段、40 個頻道、1/2 Mbps 傳輸速率
二、BLE 協定棧架構
圖 4:BLE 協定棧分為 Controller(底層硬體)與 Host(協定層),中間由 HCI 介面分隔
2.1 Controller 層(硬體)
| 層級 | 功能 | 說明 |
|---|---|---|
| LE PHY | 物理層 | 1M/2M/LE Coded 三種調變方式,2.4 GHz GFSK |
| Link Layer | 鏈結層 | 5 種狀態機:Standby/Advertising/Scanning/Initiating/Connected |
| HCI | 主機控制器介面 | Host 與 Controller 之間的通訊協定(UART/USB/SDIO) |
2.2 Host 層(軟體)
| 層級 | 功能 | 說明 |
|---|---|---|
| L2CAP | 邏輯鏈結控制與適應協定 | 封裝/分段/重組上層資料,提供 MTU 協商 |
| ATT | 屬性協定 | Client-Server 架構,支援 Read/Write/Notify/Indicate |
| GATT | 通用屬性規範 | 定義 Service → Characteristic → Descriptor 層級 |
| GAP | 通用存取規範 | 控制廣播、掃描、連線建立與安全管理 |
| SMP | 安全管理協定 | 配對(Pairing)、綁定(Bonding)、加密(Encryption) |
三、BLE 廣播與掃描
BLE 裝置在未連線狀態下透過廣播通告自己的存在。BLE 在 2.4 GHz 頻段劃分 40 個頻道(0~39),其中 37/38/39 三個為主要廣播頻道。
圖 1:BLE 廣播事件 — 廣播者在三個主要頻道(37/38/39)輪流發送 Adv PDU
3.1 廣播類型
| 類型 | 名稱 | 說明 |
|---|---|---|
| 0x00 | 可連線廣播 (Connectable) | 掃描者可發起連線請求 |
| 0x01 | 可連線定向廣播 | 指定特定裝置可連線 |
| 0x02 | 不可連線廣播 (Non-connectable) | 僅廣播,不接受連線(信標) |
| 0x03 | 可掃描廣播 (Scannable) | 可回應掃描請求但不接收連線 |
3.2 ESP32 BLE 廣播程式碼(Arduino)
// esp32_ble_beacon.ino — BLE 廣播範例(iBeacon)
#include
#include
#include
// iBeacon 資料格式
// UUID: 廠商自定義(此處使用範例 UUID)
#define BEACON_UUID_REV "e42c4fee-9a77-4b9a-b1b3-7f5b5f1e3d2c"
#define MAJOR 1
#define MINOR 100
#define TX_POWER -59 // 1 公尺處 RSSI 參考值
void setup() {
Serial.begin(115200);
BLEDevice::init("MyBLEDevice");
// 建立 iBeacon 廣播資料
BLEAdvertisementData advData;
advData.setFlags(0x06); // LE General Discoverable + BR/EDR not supported
// 自訂 iBeacon 格式
std::string iBeaconData = "";
iBeaconData += (char)0x02; // Apple Company ID (低字節)
iBeaconData += (char)0x15; // Apple Company ID (高字節)
iBeaconData += (char)0x15; // Proximity UUID (16 bytes)
// 填入自訂 UUID(簡化範例)
uint8_t uuid[16] = { 0xE4, 0x2C, 0x4F, 0xEE, 0x9A, 0x77, 0x4B, 0x9A,
0xB1, 0xB3, 0x7F, 0x5B, 0x5F, 0x1E, 0x3D, 0x2C };
for (int i = 0; i < 16; i++) iBeaconData += (char)uuid[i]; iBeaconData += (char)(MAJOR >> 8); // Major (高)
iBeaconData += (char)(MAJOR & 0xFF); // Major (低)
iBeaconData += (char)(MINOR >> 8); // Minor (高)
iBeaconData += (char)(MINOR & 0xFF); // Minor (低)
iBeaconData += (char)TX_POWER; // TX Power
advData.setManufacturerData(iBeaconData);
advData.setName("MyBLEDevice");
// 開始廣播
BLEServer *pServer = BLEDevice::createServer();
BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
pAdvertising->setAdvertisementData(advData);
pAdvertising->start();
Serial.println("BLE Beacon broadcasting...");
}
void loop() {
delay(1000);
}
3.3 ESP32 BLE Scanner — 掃描附近藍牙裝置
// esp32_ble_scanner.ino — BLE 掃描範例
#include
#include
#include
class MyAdvertisedDeviceCallbacks : public BLEAdvertisedDeviceCallbacks {
void onResult(BLEAdvertisedDevice advertisedDevice) {
Serial.printf("Device: %s | Addr: %s | RSSI: %d dBm | TX: %d\n",
advertisedDevice.getName().c_str(),
advertisedDevice.getAddress().toString().c_str(),
advertisedDevice.getRSSI(),
advertisedDevice.getTXPower());
}
};
void setup() {
Serial.begin(115200);
BLEDevice::init("");
BLEScan *pBLEScan = BLEDevice::getScan();
pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks());
pBLEScan->setActiveScan(true); // 主動掃描(請求 Scan Response)
pBLEScan->setInterval(100); // 掃描間隔 (ms)
pBLEScan->setWindow(99); // 掃描視窗 (ms)
Serial.println("Scanning for BLE devices...");
while (1) {
BLEScanResults results = pBLEScan->start(5); // 掃描 5 秒
Serial.printf("Found %d devices\n", results.getCount());
pBLEScan->clearResults();
}
}
四、BLE 連線與資料交換
圖 2:BLE Connection Events — Master 與 Slave 在每個 Connection Interval 交換資料
4.1 連線參數
| 參數 | 範圍 | 說明 |
|---|---|---|
| Connection Interval | 7.5 ms ~ 4.0 s | 兩個連線事件之間的時間間隔(1.25 ms 步進) |
| Slave Latency | 0 ~ 499 | Slave 可跳過的連線事件數(節省功耗) |
| Supervision Timeout | 10 ms ~ 32 s | 若逾時未收到封包,視為連線中斷 |
4.2 GATT 服務模型
圖 3:GATT Profile — Service(服務)包含多個 Characteristic(特徵值),Client 透過 Read/Write/Notify 操作
4.3 ESP32 BLE GATT Server(Arduino)
// esp32_ble_gatts.ino — BLE GATT Server 範例
#include
#include
#include
#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8"
BLECharacteristic *pCharacteristic;
bool deviceConnected = false;
class MyServerCallbacks : public BLEServerCallbacks {
void onConnect(BLEServer* pServer) {
deviceConnected = true;
Serial.println("Device connected!");
}
void onDisconnect(BLEServer* pServer) {
deviceConnected = false;
Serial.println("Device disconnected, restarting advertising...");
pServer->getAdvertising()->start();
}
};
class MyCharCallbacks : public BLECharacteristicCallbacks {
void onWrite(BLECharacteristic *pChar) {
std::string rxValue = pChar->getValue();
if (rxValue.length() > 0) {
Serial.print("Received: ");
for (int i = 0; i < rxValue.length(); i++) { Serial.print(rxValue[i]); } Serial.println(); } } }; void setup() { Serial.begin(115200); BLEDevice::init("ESP32 BLE Sensor"); BLEServer *pServer = BLEDevice::createServer(); pServer->setCallbacks(new MyServerCallbacks());
BLEService *pService = pServer->createService(SERVICE_UUID);
pCharacteristic = pService->createCharacteristic(
CHARACTERISTIC_UUID,
BLECharacteristic::PROPERTY_READ |
BLECharacteristic::PROPERTY_WRITE |
BLECharacteristic::PROPERTY_NOTIFY
);
pCharacteristic->setCallbacks(new MyCharCallbacks());
pCharacteristic->setValue("Hello BLE");
pService->start();
BLEAdvertising *pAdvertising = pServer->getAdvertising();
pAdvertising->addServiceUUID(SERVICE_UUID);
pAdvertising->start();
Serial.println("BLE GATT Server ready!");
}
void loop() {
if (deviceConnected) {
// 模擬感測器資料推送
static int counter = 0;
char buf[16];
snprintf(buf, sizeof(buf), "val=%d", counter++);
pCharacteristic->setValue(buf);
pCharacteristic->notify(); // 主動通知 Client
}
delay(1000);
}
4.4 ESP32 BLE GATT Client(IDF)
// esp32_ble_gattc.c — ESP-IDF BLE Client 範例 (片段)
#include "esp_nimble_hci.h"
#include "nimble/nimble_port.h"
#include "nimble/nimble_port_freertos.h"
#include "host/ble_hs.h"
#include "services/gap/ble_svc_gap.h"
// 掃描並連線到指定名稱的裝置
static void ble_scan_and_connect(void)
{
struct ble_gap_disc_params disc_params;
memset(&disc_params, 0, sizeof(disc_params));
disc_params.filter_duplicates = 1;
disc_params.passive = 0; // 主動掃描
disc_params.itvl = 100; // 掃描間隔 (0.625ms 單位)
disc_params.window = 99; // 掃描視窗
ble_gap_disc(BLE_OWN_ADDR_PUBLIC, 10000, &disc_params,
ble_gap_disc_cb, NULL);
}
// 發現服務後讀取特徵值
static int ble_gatt_svc_access_cb(uint16_t conn_handle,
const struct ble_gatt_error *error,
const struct ble_gatt_svc *service, void *arg)
{
// 枚舉所有特徵值,找到目標後執行讀取或註冊 Notification
return 0;
}
五、BLE 省電策略
BLE 的核心優勢在於超低功耗,但省電需要正確的設定策略:
| 策略 | 效果 | 實作方式 |
|---|---|---|
| 增大 Connection Interval | 減少每秒收發次數 | 從 30ms 改為 100ms 可降低 70% 功耗 |
| 啟用 Slave Latency | 允許跳過部分事件 | Latency=9 可跳過 9/10 的事件 |
| 使用 Notify 代替 Polling | Server 主動推送 | Client 不需定期輪詢 |
| 縮短廣播時間 | 減少空中的時間 | 連線後停止廣播,或使用快速廣播後休眠 |
| 資料長度擴展 (DLE) | 減少封包數量 | MTU 從 23 bytes 擴展到 251 bytes |
| WiFi 共存管理 | 避免射頻衝突 | ESP32 內建 Coexistence 機制,自動協調 BLE/WiFi |
六、STM32WB BLE(簡介)
STM32WB 系列(如 STM32WB55)是 ST 推出的雙核無線 MCU,整合 BLE 5.0 與 802.15.4(Thread/Zigbee)。與 ESP32 使用外部 RAM 運行 BLE 協定棧不同,STM32WB 採用雙核心架構:
- Cortex-M4(主核):運行應用程式
- Cortex-M0+(網絡核):運行 BLE 協定棧(RF Stack)
兩個核心透過 IPCC(跨處理器通訊控制器)進行 IPC 通訊,M4 透過 ST BLE API 操作 M0+ 上的協定棧。
// STM32WB — BLE 初始化(使用 STM32Cube FW_WB)
#include "stm32wbxx_hal.h"
#include "ble_common.h"
// BLE 初始化(在 M0+ 核心上執行)
void BLE_Init(void)
{
// 啟動 M0+ 網絡核心
LL_C2_PWR_SetPowerMode(LL_PWR_MODE_STOP1);
LL_HSEM_1StepLock(HSEM, CFG_HW_RCC_SEMID);
LL_RCC_ForceBackupDomain();
LL_RCC_SelectLSEMode();
LL_RCC_SetLSEDrive();
LL_RCC_LSE_Enable();
// 載入 BLE 協定棧(由 STM32Cube 自動生成)
// 透過 hci_init() 啟動 BLE Controller
// 並透過 aci_gap_init() 建立 GAP/GATT 服務
// 註冊事件回呼
hci_register_evt_callback(ble_evt_callback);
}
// 發送 BLE 資料
void BLE_Send(uint8_t *data, uint16_t len)
{
aci_gatt_notify(conn_handle, service_handle, char_handle, len, data);
}
七、常見問題與陷阱
- 廣播資料過長:BLE 廣播封包最大 31 bytes(Adv PDU)+ 31 bytes(Scan Response),超出需使用 Extended Advertising(BT 5.0)
- MTU 未協商:預設 ATT MTU 僅 23 bytes,連線後應透過 GATT Exchange MTU 提升到 247+ bytes
- Notify 未註冊:Client 必須先寫入 0x01:00 到 CCCD(Client Characteristic Configuration Descriptor)才能接收 Notify
- TX Power 太大:ESP32 最大 TX Power +8 dBm,過大會增加功耗且可能不符合各國法規(CE/FCC 限制)
- WiFi/BLE 干擾:ESP32 的 WiFi 與 BLE 共用 2.4 GHz 射頻,使用 BT_AHB 協調器優先權設定避免衝突
- 配對安全:未加密的 BLE 連線可能被嗅探,正式產品應實作 MITM 配對與加密
八、總結
BLE 憑藉其極低功耗、快速連線與彈性的 GATT 模型,已成為物聯網短距無線通訊的主流選擇。ESP32 的雙模藍牙支援讓開發者能夠靈活選擇 Arduino 快速原型或 IDF 深度定製。從本文的廣播/掃描範例到 GATT Server/Client 實作,你現在已經擁有開發完整 BLE 產品的技術基礎。記住:成功的 BLE 應用不僅在於通訊建立,更在於功耗管理、安全加密與射頻共存的最佳化。
文章評論