0x6A Logbook

0x6A Logbook
Shi6a的筆記本
  1. 首頁
  2. 程式開發
  3. 正文

BLE(藍牙低功耗)通訊協定完整教學:從原理到 ESP32 實作

2026 年 6 月 1 日 11點熱度 0人點贊 0條評論

前言

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 自訂服務與特徵值

BLE vs Classic BT

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

BLE Key Parameters

圖 5:BLE 核心參數一覽 — 2.4 GHz ISM 頻段、40 個頻道、1/2 Mbps 傳輸速率

二、BLE 協定棧架構

BLE Stack Architecture

圖 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 三個為主要廣播頻道。

BLE Advertising

圖 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 連線與資料交換

BLE Connection Events

圖 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 服務模型

BLE GATT Hierarchy

圖 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 應用不僅在於通訊建立,更在於功耗管理、安全加密與射頻共存的最佳化。

標籤: ESP32 工業通訊
最後更新:2026 年 6 月 1 日

shi6a

這個人很懶,什麼都沒留下

點贊
< 上一篇
下一篇 >

文章評論

razz evil exclaim smile redface biggrin eek confused idea lol mad twisted rolleyes wink cool arrow neutral cry mrgreen drooling persevering
取消回覆

COPYRIGHT © 2026 0x6A Logbook. ALL RIGHTS RESERVED.

Theme Kratos Made By Seaton Jiang