1. BLE协议栈概述与开发环境搭建

1.1 BLE协议栈分层原理

BLE(Bluetooth Low Energy)协议栈采用经典的分层架构设计,将无线通信的复杂过程拆解为多个功能独立的层级。每一层只负责明确定义的职责,并通过标准化的接口为上层提供服务。这种设计使得协议栈的开发、测试、优化和演进可以分别进行,从而大幅提升系统的可维护性和可扩展性。

层级全称核心职责
PHY物理层射频收发、调制解调、跳频
LL链路层连接管理、数据包组装/拆解、重传
HCI主机控制器接口Host 与 Controller 之间的命令/事件传输
L2CAP逻辑链路控制与适配数据分片与重组、协议多路复用
SM安全管理配对、绑定、加密密钥生成
ATT属性协议属性数据的读/写/通知操作
GATT通用属性协议基于 ATT 定义服务与特征值的框架
GAP通用访问协议广播、扫描、连接建立的角色控制

物理层与链路层通常由硬件 Controller 实现,HCI 以上各层在 Host 侧(主处理器)以软件协议栈形式运行。这种软硬分离的结构是 BLE 协议栈最核心的设计模式之一。

分层设计模式意图:将无线通信系统按功能垂直切分,每层对内隐藏实现细节,对外暴露稳定接口。层间耦合度低,允许各层独立选型、升级和调试——例如更换射频芯片只需重写 PHY/LL,而上层协议栈无需改动。
BLE协议栈分层架构 BLE协议栈分层架构(Layered Pattern) PHY 物理层 LL 链路层 HCI 主机控制器接口 L2CAP 逻辑链路控制与适配 SM / ATT / GATT 安全·属性·通用属性 GAP 通用访问协议 应用层(Application) 分层设计模式:每层封装独立功能,通过标准化接口向上层提供服务

1.2 开发环境搭建与工具链安装

本教程以 ESP-IDF v5.x 为开发框架,以 ESP32-C3 为硬件平台。以下步骤在 Ubuntu 22.04 上验证,Windows/macOS 用户请参考 ESP-IDF 官方文档调整路径。

  1. 安装依赖包
sudo apt-get update
sudo apt-get install git wget flex bison gperf python3 python3-pip \
    python3-venv cmake ninja-build ccache libffi-dev libssl-dev dfu-util
  1. 下载 ESP-IDF
mkdir -p ~/esp
cd ~/esp
git clone --recursive https://github.com/espressif/esp-idf.git -b v5.1.2
cd esp-idf
./install.sh esp32c3
  1. 设置环境变量
echo "alias get_idf='. ~/esp/esp-idf/export.sh'" >> ~/.bashrc
source ~/.bashrc
get_idf
检查安装:运行 idf.py --version 应输出类似 "v5.1.2" 的版本号。如果提示命令未找到,请确认 ~/esp/esp-idf/export.sh 已被 source。

1.3 工程模板创建

使用 idf.py create-project 命令快速生成 BLE 工程骨架。

cd ~/esp
idf.py create-project ble_first
cd ble_first

生成的目录结构如下:

ble_first/
├── CMakeLists.txt          # 顶层 CMake 构建配置
├── main/
│   ├── CMakeLists.txt      # 组件 CMake 配置
│   └── ble_first.c         # 用户主程序(入口)
└── sdkconfig               # 项目配置(默认生成)

打开 main/CMakeLists.txt,确保包含 BLE 组件:

idf_component_register(SRCS "ble_first.c"
                    INCLUDE_DIRS "."
                    REQUIRES nvs_flash bt)   # bt 组件启用 BLE 协议栈

1.4 第一个BLE示例项目

实现一个最简单的 BLE 广播设备:启动后持续发送 Beacon 帧,不建立连接。核心代码位于 main/ble_first.c:

#include <stdio.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "nvs_flash.h"
#include "esp_bt.h"
#include "esp_bt_main.h"
#include "esp_ble_gap.h"

static const char *TAG = "BLE_FIRST";

static void gap_event_handler(esp_gap_ble_cb_event_t event,
                              esp_ble_gap_cb_param_t *param)
{
    switch (event) {
    case ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT:
        ESP_LOGI(TAG, "广告数据配置完成,开始广播");
        esp_ble_gap_start_advertising(&adv_params);
        break;
    case ESP_GAP_BLE_ADV_START_COMPLETE_EVT:
        if (param->adv_start_cmpl.status == ESP_BT_STATUS_SUCCESS) {
            ESP_LOGI(TAG, "广播已启动,设备名称:BLE_First");
        } else {
            ESP_LOGE(TAG, "广播启动失败,状态:%d",
                     param->adv_start_cmpl.status);
        }
        break;
    default:
        break;
    }
}

void app_main(void)
{
    // 1. 初始化 NVS
    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);

    // 2. 初始化 BLE 协议栈
    esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
    ESP_ERROR_CHECK(esp_bt_controller_init(&bt_cfg));
    ESP_ERROR_CHECK(esp_bt_controller_enable(ESP_BT_MODE_BTDM));
    ESP_ERROR_CHECK(esp_bluedroid_init());
    ESP_ERROR_CHECK(esp_bluedroid_enable());

    // 3. 注册 GAP 回调
    ESP_ERROR_CHECK(esp_ble_gap_register_callback(gap_event_handler));

    // 4. 配置广播参数
    esp_ble_adv_params_t adv_params = {
        .adv_int_min        = 0x100,      // 约 80ms
        .adv_int_max        = 0x200,      // 约 160ms
        .adv_type           = ADV_TYPE_IND,
        .own_addr_type      = BLE_ADDR_TYPE_PUBLIC,
        .channel_map        = ADV_CHNL_ALL,
        .adv_filter_policy  = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY,
    };

    // 5. 配置广播数据(包含设备名称)
    esp_ble_adv_data_t adv_data = {
        .set_scan_rsp       = false,
        .include_name       = true,
        .include_txpower    = true,
        .appearance         = 0x0080,     // 通用键鼠
        .manufacturer_len   = 0,
        .p_manufacturer_data= NULL,
        .service_data_len   = 0,
        .p_service_data     = NULL,
        .service_uuid_len   = 0,
        .p_service_uuid     = NULL,
        .flag               = 0x06,
    };
    ESP_ERROR_CHECK(esp_ble_gap_config_adv_data(&adv_data));

    ESP_LOGI(TAG, "BLE 初始化完成,等待广播...");
}

1.5 配置与编译

  1. 配置项目:运行 idf.py menuconfig,在 "Component config → Bluetooth" 下确认 "Bluetooth" 已启用,并选择 "Bluedroid + BLE"。
  2. 编译烧录:
idf.py set-target esp32c3
idf.py build
idf.py -p /dev/ttyACM0 flash monitor
注意:如果使用 Windows,串口端口通常为 COM3 或更高编号,请根据设备管理器调整 -p 参数。首次烧录前需按住开发板上的 BOOT 键再上电。

1.6 调试日志分析与部署验证

成功启动后,串口监视器应输出类似以下的日志:

I (318) BLE_FIRST: BLE 初始化完成,等待广播...
I (348) BLE_FIRST: 广告数据配置完成,开始广播
I (378) BLE_FIRST: 广播已启动,设备名称:BLE_First

验证步骤:

  1. 打开手机 App nRF Connect( Nordic 官方工具)或其它 BLE 扫描器。
  2. 点击 "SCAN" 按钮,列表中将出现名为 BLE_First 的设备。
  3. 点击设备可查看广播数据详情,应包含 Tx Power 和 "Generic Keyboard" 外观值。
验证项期望结果
扫描到设备名BLE_First
广播类型可连接非定向广播(ADV_IND)
Tx Power 存在是(-3 ~ +3 dBm 范围内)
设备外观Generic Keyboard (0x0080)

1.7 易错点与优化建议

常见错误
  • NVS 未初始化:协议栈依赖 NVS 存储配对密钥,忘记调用 nvs_flash_init() 会导致 ESP_ERR_NO_MEM 错误。
  • 缺少 bt 组件依赖:在 main/CMakeLists.txt 的 REQUIRES 中漏写 bt,编译时会出现链接错误。
  • 广播参数不合理:adv_int_min 设置过小(< 0x20)会导致射频拥塞,被协议栈拒绝启动。
  • 串口权限不足:Linux 下未将用户加入 dialout 组,导致无法打开 /dev/ttyACM0。
优化建议
  • 降低广播功耗:将 adv_int_min 从 0x100 提升至 0x400(约 320ms),空闲场景电流可降低 40%。
  • 使用非易失存储:通过 NVS 保存设备名称和外观等配置,避免硬编码,便于后期 OTA 更新。
  • 启用协议栈调试日志:在 menuconfig 中启用 "Bluetooth → Bluedroid → Enable BT debug log",可输出 HCI 帧和 L2CAP 交互,加速问题定位。
  • 合理选择地址类型:默认使用公共地址(PUBLIC),若需要隐私保护可切换为随机地址(RANDOM),防止设备被长期跟踪。