ESP32 BLE 数据接收流程

第1层:射频接收 + 硬件中断

ESP32 芯片内部集成了蓝牙射频收发器和基带控制器,独立于主 CPU 运行。

手机发送 BLE 数据包(2.4GHz 射频信号)
  → 天线接收模拟信号
    → 射频前端(LNA + 混频器 + ADC)转换为数字基带信号
      → 基带控制器(硬件状态机)执行:
         - 解调、解码、CRC 校验
         - 解析 BLE Link Layer 包头
         - 识别:这是一个已连接设备发来的数据包
      → 基带控制器通过 DMA 将有效载荷写入内存 buffer
      → 触发硬件中断(蓝牙控制器中断线,连接到 ESP32 中断矩阵)

第2层:CPU 进入 ISR(中断服务程序)

硬件中断触发后,CPU 暂停当前任务,跳转到蓝牙驱动注册的 ISR:

CPU 检测到蓝牙控制器中断
  → 暂停当前执行的 FreeRTOS 任务,保存上下文
    → 跳转到蓝牙驱动的 ISR 入口
      → ISR 内部操作(必须极快,通常微秒级):
         ① 读取中断状态寄存器,确认中断来源
         ② 清除中断标志位(否则会反复触发)
         ③ 将接收数据的指针和长度封装为结构体
         ④ 通过 xQueueSendFromISR() 或 xSemaphoreGiveFromISR()
            将结构体投递到 Bluedroid 驱动层的事件队列
         ⑤ 若有高优先级任务被唤醒,触发 taskYIELD() 进行任务切换
      → ISR 结束,FreeRTOS 恢复任务调度

关键点: ISR 中不执行协议解析,只负责"搬运数据 + 通知上层",避免长时间占用 CPU。


第3层:Bluedroid 任务被唤醒

esp_bluedroid_init() 初始化时创建了一个 FreeRTOS 任务运行 Bluedroid 协议栈。该任务平时阻塞在事件队列上:

// Bluedroid 内部任务(简化示意)
void bluedroid_task(void *arg) {
    while (1) {
        xQueueReceive(ble_event_queue, &event, portMAX_DELAY); // 阻塞等待
        process_event(event);
    }
}

当 ISR 向队列投递数据后:

xQueueReceive() 返回(任务被唤醒)
  → 从队列中取出数据包
    → 进入 HCI 层处理

第4层:协议栈逐层解析

Bluedroid 采用分层架构,数据从底层逐层向上解析:

HCI 层(Host Controller Interface)

→ 解析 HCI 包头,识别为 ACL 数据(异步无连接,BLE 数据通过此通道传输)
→ 去掉 HCI 封装,将 payload 上交 L2CAP 层

L2CAP 层(Logical Link Control and Adaptation Protocol)

→ 解析 L2CAP 头,提取 channel ID
→ BLE 中固定使用 0x0004(ATT 通道)
→ 去掉 L2CAP 封装,将 payload 上交 ATT 层

ATT 层(Attribute Protocol)

→ 解析 ATT 包头,识别操作码(Opcode):
   - 0x12 → Write Request(需要响应)
   - 0x52 → Write Command(不需要响应)
→ 提取关键字段:
   • attribute handle:写入的目标特征值
   • value:写入的数据内容
→ 根据 handle 查找本地 GATT 属性表(即代码中的 gatt_db)
→ 检查权限(是否有 ESP_GATT_PERM_WRITE)
→ 将数据写入对应的 attribute value

第5层:产生事件 + 调用用户回调

ATT 层处理完成后,Bluedroid 构造事件并调用用户注册的回调:

事件数据结构

event = ESP_GATTS_WRITE_EVT;
param->write.handle   = 写入的特征值 handle
param->write.value    = 写入的数据指针
param->write.len      = 数据长度
param->write.conn_id  = 连接 ID
param->write.need_rsp = 是否需要响应(Write Request 为 true)

回调调用链

Bluedroid 构造事件 ESP_GATTS_WRITE_EVT
  │
  ▼
调用 esp_ble_gatts_register_callback() 注册的回调
  → gatts_event_handler()           // bt.c:381
    │
    │  内部逻辑:
    │  - 注册事件(ESP_GATTS_REG_EVT)时保存 gatts_if
    │  - 遍历 heart_rate_profile_tab[],匹配 gatts_if
    │  - 调用对应 profile 的回调函数
    │
    ▼
  gatts_profile_event_handler()     // bt.c:244
    │
    │  switch(event)
    │
    ▼
  case ESP_GATTS_WRITE_EVT:         // bt.c:282
    → param->write.value  = 手机发来的数据
    → param->write.len    = 数据长度

完整流程图

手机发送 BLE 数据
  │
  ▼
[射频信号] ──天线──▶ LNA → ADC → 数字基带信号
  │
  ▼
[基带控制器 (硬件)]    解调 + CRC + 解析 Link Layer
  │
  ▼
[DMA 写入 buffer]  →  触发硬件中断
  │
  ▼
[ISR (微秒级)]      读寄存器 → 清中断 → 投递队列
  │
  ▼
[Bluedroid Task]    队列阻塞解除,被唤醒
  │
  ▼
[HCI 层]            解析 HCI 头,提取 ACL 数据
  │
  ▼
[L2CAP 层]          解析 L2CAP 头,提取 ATT channel 数据
  │
  ▼
[ATT 层]            识别 Write Opcode,查属性表,校验权限,写入值
  │
  ▼
[构造事件]          ESP_GATTS_WRITE_EVT + param
  │
  ▼
gatts_event_handler()              ← bt.c:381  注册于 bt_init() 第457行
  │
  ▼
gatts_profile_event_handler()      ← bt.c:244
  │
  ▼
case ESP_GATTS_WRITE_EVT:          ← bt.c:282
  → param->write.value  就是手机发来的数据
  → param->write.len    是数据长度

补充:GATT 属性表

代码中的 gatt_db(bt.c:151)定义了 GATT 服务端暴露给手机的三个特征值,这就是 BLE 中数据交互的"端点":

Service (UUID: 0x00FF)
  ├── Characteristic A (UUID: 0xFF01)  可读 + 可写 + 可通知
  ├── Characteristic B (UUID: 0xFF02)  只读
  └── Characteristic C (UUID: 0xFF03)  只写

其中 Characteristic A 和 C 具有 ESP_GATT_PERM_WRITE 权限,手机对它们执行写操作时都会触发 ESP_GATTS_WRITE_EVT

可通过 param->write.handle 区分写入目标:

case ESP_GATTS_WRITE_EVT:
    if (param->write.handle == heart_rate_handle_table[IDX_CHAR_VAL_A]) {
        // 写入的是特征值 A (0xFF01)
    } else if (param->write.handle == heart_rate_handle_table[IDX_CHAR_VAL_C]) {
        // 写入的是特征值 C (0xFF03)
    } else if (param->write.handle == heart_rate_handle_table[IDX_CHAR_CFG_A]) {
        // 写入的是 CCCD(客户端开启/关闭 notify 通知)
    }
    break;

添加新评论