机盒通信的主链路是: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;

应答包需要重新设置发送帧头、左右耳方向、数据长度和数据区,并在协议规定的时间内发出。关机、复位等命令必须先应答,再改变系统状态。

机盒通信的核心链路可以固定为:收包 → 组帧 → 校验 → 寻址 → 分发 → 执行业务 → 应答。