第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;