Files
P4-USB-transfer/蓝牙连接方案与流程.md
zhengxinandCursor 851e347cf5 初始提交卡包 USB 工程
纳入固件源码、屏幕组件、nand_xfer 工具和文档。忽略本机构建产物、sdkconfig,以及含本机路径的依赖锁。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-13 21:54:50 +08:00

215 lines
9.1 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 蓝牙连接方案与流程
本文档说明车机端(ESP32)蓝牙是如何建立连接、以及连接后的处理流程。基于 NimBLE 协议栈,作为 GATT 从机(Server),等待手机 APP 连接。
---
## 一、整体架构
- 协议栈:NimBLE(ESP-IDF 组件)
- 角色:BLE 从机(Peripheral / Server)
- 设备名:ESP32(宏 DEVICE_NAME)
- 主服务 UUID:0xFFFF
- 连接方式:可连接广播,手机端发起连接
---
## 二、系统启动与 BLE 初始化顺序
```
app_main()
├── 事件组创建 (system_event_group)
├── LCD / LVGL / SPIFFS / 动画任务 等初始化
└── ble_main() // 蓝牙入口
├── NVS 初始化、表情包存储加载
├── nimble_port_init() // NimBLE 端口初始化
├── ble_gatts_count_cfg(gatt_svr_svcs) // 统计 GATT 服务
├── ble_gatts_add_svcs(gatt_svr_svcs) // 注册 GATT 服务与特征
├── nimble_host_config() // 配置主机(reset_cb, sync_cb)
├── nimble_port_freertos_init(nimble_host_task) // 启动 NimBLE 主机任务
├── xTaskCreate(notification_task) // 通知发送任务
└── xTaskCreate(sentinel_task) // 哨兵模式任务
```
说明:GATT 服务在 `ble_main()` 里完成注册,NimBLE 主机在独立任务中运行;主机同步完成后会触发 `sync_cb`,再开始广播。
---
## 三、GATT 服务与特征定义
在 `ble.c` 中通过 `gatt_svr_svcs[]` 定义:
| 类型 | UUID | 说明 | 权限 / 属性 |
|----------|--------|--------------------------|-------------|
| 主服务 | 0xFFFF | 自定义主服务 | - |
| 特征 TX | 0xFF01 | 可读可写,APP 写、车机收 | READ, WRITE |
| 特征 RX | 0xFF02 | 只读,APP 读 | READ |
| 特征 NOTIFY | 0xFF03 | 通知,车机主动推给 APP | READ, NOTIFY, WRITE + CCCD(0x2902) |
- APP 下发指令:写 **TX (0xFF01)**,车机在 `gatt_svr_chr_access` 里收到后调用 `ble_data_ansy()` 解析协议。
- 车机上报数据:通过 **NOTIFY (0xFF03)** 发送协议帧;只有 APP 对 NOTIFY 特征的 CCCD 写入 0x0001 后,车机才能成功发通知。
---
## 四、广播与连接建立流程
### 4.1 主机同步完成后(ble_app_on_sync)
1. 推断本机地址类型:`ble_hs_id_infer_auto()`
2. 设置设备名:`ble_svc_gap_device_name_set("ESP32")`
3. 开始广播:`ble_app_advertise()`
### 4.2 广播内容(ble_app_advertise)
- 广播数据(Advertising Data):
- 设备名:ESP32(完整名)
- 标志:可发现、不支持 BR/EDR
- 16bit UUID:0xFFFF(完整列表)
- 扫描响应(Scan Response):发射功率(自动)
- 广播参数:
- 可连接、可发现(通用)
- 使用快速广播间隔(BLE_GAP_ADV_FAST_INTERVAL1)
- 信道:0x07(3 个广播信道)
- 超时:永久(BLE_HS_FOREVER)
- 连接与广播事件通过回调:`ble_gap_event()` 处理
### 4.3 连接建立(BLE_GAP_EVENT_CONNECT)
当手机 APP 发起连接且 status == 0 时:
1. 置 `is_connected = true`
2. 保存 `conn_handle = event->connect.conn_handle`
3. 置系统事件:`set_system_event(BLE_CONNECTED_BIT)`
4. 可选:`ble_gap_conn_find()` 打印连接间隔/延迟/超时
若 status != 0(连接失败),则重新调用 `ble_app_advertise()` 继续广播。
---
## 五、连接后的业务处理流程
### 5.1 事件传递链
```
BLE 连接成功 (ble_gap_event)
→ set_system_event(BLE_CONNECTED_BIT)
main_task (main.c) 中 wait_system_event(..., BLE_CONNECTED_BIT, ...)
→ 收到 BLE_CONNECTED_BIT
→ xTaskNotify(notification_task_handle, BLE_SEND_VERSION, eSetBits)
→ xTaskNotify(notification_task_handle, BLE_SEND_EMOJI_USING, eSetBits)
```
即:连接成功只置位事件位;由主任务统一检测后,通过任务通知让“通知任务”发送“首次连接”的数据。
### 5.2 通知任务(notification_task)对 BLE_SEND_VERSION 的处理
当收到 BLE_SEND_VERSION 时,依次发送(协议帧,见《蓝牙指令测试文档》):
1. **get_system_ok 帧(2.1A)**:系统就绪
2. **brightness 帧(2.2A)**:当前亮度(默认 50,0–100)
3. **version 帧(2.3A)**:固件版本号
发送前会置 `notify_enabled = 1`,以便通过 `send_protocol_frame()` 走 NOTIFY 特征下发。若 APP 尚未订阅 NOTIFY,底层可能发送失败或 APP 收不到,通常建议 APP 在连接后尽快订阅 NOTIFY 再请求或等待上述三条。
### 5.3 APP 主动请求系统信息(get_sys_info 0x0010)
- APP 通过 **TX (0xFF01)** 下发 get_sys_info 指令。
- 车机在 `ble_data_ansy.c` 中解析到 `get_sys_info` 后,同样通过:
- `xTaskNotify(notification_task_handle, BLE_SEND_VERSION, eSetBits)`
- 通知任务再次发送上述 2.1A / 2.2A / 2.3A 三帧,实现“按需上报”版本与亮度。
### 5.4 订阅通知(BLE_GAP_EVENT_SUBSCRIBE)
- APP 对 NOTIFY 特征的 CCCD(0x2902) 写入 0x0001 时,会触发 BLE_GAP_EVENT_SUBSCRIBE。
- 代码中根据 `attr_handle == notify_char_handle` 更新:`notify_enabled = (cur_notify || cur_indicate)`。
- 此后所有通过 `send_protocol_frame()` 的下发(版本、亮度、OTA 进度、WiFi 状态等)才会真正通过 NOTIFY 推送到 APP。
---
## 六、连接断开与重连
### 6.1 断开(BLE_GAP_EVENT_DISCONNECT)
1. 置 `is_connected = false`、`notify_enabled = false`、`conn_handle = 0`
2. 根据 `event->disconnect.reason` 打日志(如认证失败、超时、用户断开等)
3. 再次调用 `ble_app_advertise()` 开始广播,等待下次连接
### 6.2 广播结束(BLE_GAP_EVENT_ADV_COMPLETE)
若当前未连接(`!is_connected`),会再次调用 `ble_app_advertise()`,保证持续可被发现、可连接。
---
## 七、流程简图(连接与首次上报)
```
[ 车机 ] [ 手机 APP ]
ble_main()
→ 注册 GATT (0xFFFF, TX/RX/NOTIFY)
→ 启动 NimBLE 主机任务
→ 创建 notification_task / sentinel_task
ble_app_on_sync()
→ 设置设备名 "ESP32"
→ ble_app_advertise() // 开始广播
------------ 手机扫描并发起连接 ------------
BLE_GAP_EVENT_CONNECT (status=0)
→ is_connected = true, conn_handle 保存
→ set_system_event(BLE_CONNECTED_BIT)
------------ 主任务事件循环 ------------
main_task: wait_system_event() 得到 BLE_CONNECTED_BIT
→ xTaskNotify(BLE_SEND_VERSION)
→ xTaskNotify(BLE_SEND_EMOJI_USING)
notification_task: 收到 BLE_SEND_VERSION
→ notify_enabled = 1
→ send get_system_ok 帧
→ send brightness 帧
→ send version 帧
(通过 NOTIFY 特征 0xFF03 发送,需 APP 已订阅 CCCD)
------------ 之后 ------------
APP 写 TX(0xFF01) 下发指令
→ gatt_svr_chr_access(W RITE) → ble_data_ansy()
→ 解析协议并执行(表情/亮度/日期/OTA/哨兵等)
车机需要上报时(OTA 进度、WiFi 状态等)
→ notification_task 收到对应 Notify 位
→ send_xxx_frame() → send_protocol_frame() → NOTIFY(0xFF03)
```
---
## 八、关键代码位置(便于对照)
| 流程环节 | 文件 | 函数/位置说明 |
|--------------------|-------------|----------------|
| BLE 入口 | main.c | app_main() 中调用 ble_main() |
| BLE 初始化与 GATT 注册 | ble.c | ble_main() |
| 主机同步与开始广播 | ble.c | ble_app_on_sync() → ble_app_advertise() |
| 广播参数与启动 | ble.c | ble_app_advertise(),ble_gap_adv_start(..., ble_gap_event, ...) |
| 连接/断开/订阅事件 | ble.c | ble_gap_event(),case BLE_GAP_EVENT_* |
| 连接成功置位事件 | ble.c | BLE_GAP_EVENT_CONNECT 内 set_system_event(BLE_CONNECTED_BIT) |
| 主任务处理连接事件 | main.c | main_task() 中 event_bits & BLE_CONNECTED_BIT → xTaskNotify(BLE_SEND_VERSION / BLE_SEND_EMOJI_USING) |
| 首次版本/亮度上报 | ble.c | notification_task() 中 bits & BLE_SEND_VERSION |
| APP 写 TX 收数据 | ble.c | gatt_svr_chr_access(),BLE_CHAR_UUID_TX 的 WRITE → ble_data_ansy() |
| get_sys_info 触发上报 | ble_data_ansy.c | get_sys_info 分支中 xTaskNotify(..., BLE_SEND_VERSION) |
| CCCD 订阅更新 | ble.c | gatt_svr_chr_access() 写 CCCD(0x2902),以及 BLE_GAP_EVENT_SUBSCRIBE 中更新 notify_enabled |
---
## 九、小结
- 车机作为 BLE 从机,上电后完成 NimBLE 与 GATT 注册,在主机同步完成后开始可连接广播。
- 手机连接后,通过事件位 BLE_CONNECTED_BIT 驱动主任务通知 notification_task,发送版本、亮度、get_system_ok;APP 也可通过 get_sys_info 指令再次请求同一套上报。
- 所有车机→APP 的协议帧均通过 NOTIFY 特征(0xFF03) 发送,依赖 APP 订阅 CCCD;APP→车机指令通过写 TX 特征(0xFF01) 下发,由 ble_data_ansy 解析并执行。
如需调整“连接后何时发版本/亮度”或“是否在订阅后再发”,只需修改 main_task 对 BLE_CONNECTED_BIT 的处理,或增加在 BLE_GAP_EVENT_SUBSCRIBE 中触发 BLE_SEND_VERSION 的逻辑即可。