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

9.1 KiB
Raw Permalink Blame History

蓝牙连接方案与流程

本文档说明车机端(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 的逻辑即可。