机盒通信的主链路是:UART 收包、协议解析、校验与寻址、命令分发、业务执行、应答返回。这篇记录我负责该模块时使用的应用层调用链。
一、UART 收包与协议解析
%%{init: {"flowchart": {"htmlLabels": true, "wrappingWidth": 1000, "rankSpacing": 30}, "themeVariables": {"lineColor": "#71829a"}}}%%
flowchart LR
N0["充电盒发送数据"]
N1["UART 中断接收字节<br/>并写入环形缓冲区"]
N2["协议状态机<br/>拼接完整数据包"]
N3["应用任务<br/>从缓冲区取包并校验"]
N0 --> N1
N1 --> N2
N2 --> N3
class N0,N1,N2,N3 blue;
classDef blue fill:#e8f2ff,stroke:#7ba4d8,color:#23456b,stroke-width:1.5px;
classDef green fill:#e7f4ec,stroke:#78aa8b,color:#24563a,stroke-width:1.5px;
classDef amber fill:#fff3df,stroke:#d5a25b,color:#72501e,stroke-width:1.5px;UART 中断侧负责收字节和快速识别数据包:
%%{init: {"flowchart": {"htmlLabels": true, "wrappingWidth": 1000, "rankSpacing": 30}, "themeVariables": {"lineColor": "#71829a"}}}%%
flowchart TB
subgraph R0["① 中断与接收回调"]
direction LR
R0N0["uart1_isr()"]
R0N1["uart1_rx_done_cb()"]
R0N0 --> R0N1
end
class R0N0,R0N1 blue;
style R0 fill:#f1f6fd,stroke:#b8cfe8,color:#23456b
subgraph R1["② 解析与接收处理"]
direction LR
R1N0["bsp_uart_packet_parse()"]
R1N1["charge_box_packet_recv()"]
R1N0 --> R1N1
end
class R1N0,R1N1 green;
style R1 fill:#f0f8f3,stroke:#b9d7c4,color:#24563a
R0 --> R1
classDef blue fill:#e8f2ff,stroke:#7ba4d8,color:#23456b,stroke-width:1.5px;
classDef green fill:#e7f4ec,stroke:#78aa8b,color:#24563a,stroke-width:1.5px;
classDef amber fill:#fff3df,stroke:#d5a25b,color:#72501e,stroke-width:1.5px;中断将字节写入环形缓冲区,快速解析一次以记录收包时间及开盖、心跳等标志。charge_box_ssw_process(charge_sta) 再从缓冲区取出字节,重新组包、校验并处理命令。
协议包包含以下字段:
| 字段 | 作用 |
|---|---|
| 帧头 | 判断包起点和数据方向 |
| 校验值 | 检查传输数据 |
| 左右耳方向 | 指定左耳、右耳或双耳 |
| 命令 | 区分开盖、关盖、电量查询、配对等操作 |
| 数据长度 | 指定有效数据长度 |
| 数据区 | 携带电量、版本和配对信息 |
帧头、方向或长度无效时,解析器丢弃当前包并重新等待帧头。数据包接收完整后,再计算校验值。
二、充电与非充电两条处理路径
%%{init: {"flowchart": {"htmlLabels": true, "wrappingWidth": 1000, "rankSpacing": 30}, "themeVariables": {"lineColor": "#71829a"}}}%%
flowchart TB
subgraph R0["充电流程:① 进入处理"]
direction LR
R0N0["charge_box_charge_on_process()"]
R0N1["charge_box_ssw_process(1)"]
R0N0 --> R0N1
end
class R0N0,R0N1 blue;
style R0 fill:#f1f6fd,stroke:#b8cfe8,color:#23456b
subgraph R1["充电流程:② 校验并分发"]
direction LR
R1N0["charge_box_analysis_packet_for_charge()"]
R1N1["ux_box_packet_proc_charge()"]
R1N0 --> R1N1
end
class R1N0,R1N1 blue;
style R1 fill:#f1f6fd,stroke:#b8cfe8,color:#23456b
R0 --> R1
classDef blue fill:#e8f2ff,stroke:#7ba4d8,color:#23456b,stroke-width:1.5px;
classDef green fill:#e7f4ec,stroke:#78aa8b,color:#24563a,stroke-width:1.5px;
classDef amber fill:#fff3df,stroke:#d5a25b,color:#72501e,stroke-width:1.5px;%%{init: {"flowchart": {"htmlLabels": true, "wrappingWidth": 1000, "rankSpacing": 30}, "themeVariables": {"lineColor": "#71829a"}}}%%
flowchart TB
subgraph R0["非充电流程:① 进入处理"]
direction LR
R0N0["charge_box_process()<br/>或蓝牙流程 / 低功耗流程"]
R0N1["charge_box_ssw_process(0)"]
R0N0 --> R0N1
end
class R0N0,R0N1 green;
style R0 fill:#f0f8f3,stroke:#b9d7c4,color:#24563a
subgraph R1["非充电流程:② 校验并分发"]
direction LR
R1N0["charge_box_analysis_packet()"]
R1N1["ux_box_packet_proc()"]
R1N2["ux_box_commu_pkt_proc()"]
R1N0 --> R1N1 --> R1N2
end
class R1N0,R1N1,R1N2 green;
style R1 fill:#f0f8f3,stroke:#b9d7c4,color:#24563a
R0 --> R1
classDef blue fill:#e8f2ff,stroke:#7ba4d8,color:#23456b,stroke-width:1.5px;
classDef green fill:#e7f4ec,stroke:#78aa8b,color:#24563a,stroke-width:1.5px;
classDef amber fill:#fff3df,stroke:#d5a25b,color:#72501e,stroke-width:1.5px;两条路径共用 UART 缓冲区、协议解析和校验,区别在有效数据包进入哪个业务分发函数。
两个分发入口都会检查左右耳方向。命令指定左耳时右耳不处理,指定右耳时左耳不处理,指定双耳时两边都可以处理。
三、主要命令落到哪些业务
| 机盒命令 | 耳机侧处理 |
|---|---|
| 开盖 | 更新盒子电量和版本、切换开盖状态、取消关盖退出计时、发起回连 |
| 关盖 | 返回电量、更新关盖状态、停止相关业务、准备退出蓝牙 |
| 电量查询 | 更新盒子电量,返回耳机电量和连接状态 |
| 手机配对 | 返回本机地址和当前配对状态 |
| 清除配对 | 清理手机连接记录并返回执行结果 |
| TWS 组队 | 交换左右耳信息,更新对耳关系 |
| 关机或复位 | 先返回应答,再执行关机或复位 |
主从切换、手机回连和灯效不是机盒协议解析本身,而是开关盖、入盒等命令触发的后续业务。
四、以开盖命令看完整链路
%%{init: {"flowchart": {"htmlLabels": true, "wrappingWidth": 1000, "rankSpacing": 30}, "themeVariables": {"lineColor": "#71829a"}}}%%
flowchart TB
subgraph R0["① 接收并更新盒状态"]
direction LR
R0N0["收到开盖数据包"]
R0N1["校验数据<br/>并确认目标耳机"]
R0N2["读取盒子电量和版本"]
R0N3["更新开盖、入盒状态"]
R0N0 --> R0N1 --> R0N2 --> R0N3
end
class R0N0,R0N1,R0N2,R0N3 blue;
style R0 fill:#f1f6fd,stroke:#b8cfe8,color:#23456b
subgraph R1["② 恢复业务并应答"]
direction LR
R1N0["清除未执行的关盖退出计时"]
R1N1["恢复 TWS 或手机回连"]
R1N2["填入耳机电量<br/>配对和 TWS 状态"]
R1N3["重新计算校验<br/>并返回应答"]
R1N0 --> R1N1 --> R1N2 --> R1N3
end
class R1N0,R1N1,R1N2,R1N3 green;
style R1 fill:#f0f8f3,stroke:#b9d7c4,color:#24563a
R0 --> R1
classDef blue fill:#e8f2ff,stroke:#7ba4d8,color:#23456b,stroke-width:1.5px;
classDef green fill:#e7f4ec,stroke:#78aa8b,color:#24563a,stroke-width:1.5px;
classDef amber fill:#fff3df,stroke:#d5a25b,color:#72501e,stroke-width:1.5px;应答包需要重新设置发送帧头、左右耳方向、数据长度和数据区,并在协议规定的时间内发出。关机、复位等命令必须先应答,再改变系统状态。
机盒通信的核心链路可以固定为:收包 → 组帧 → 校验 → 寻址 → 分发 → 执行业务 → 应答。