蓝牙 OTA 升级断连、升级失败底层链路问题定位 OTAOver-The-Air升级是 TWS 耳机的高频事故现场——升级到一半断连、升级后变砖、双耳只能升一只。这些问题 60% 不是 OTA 代码的 bug而是底层链路在长时间大数据传输下暴露的问题。这篇拆解 OTA 失败的链路层根因和定位方法。TL;DROTA 失败的三类现象断连、超时、数据错误。每类根因不同。断连多因链路质量差导致 connection timeout 或 watchdog 超时。查 RSSI、连接参数、watchdog 配置。超时多因吞吐不足。查 ATT MTU、连接间隔、加密开销。数据错误多因 CRC 校验实现错误或 Flash 写入异常。查 CRC 算法和 Flash 操作。OTA 过程中禁用其他业务A2DP/HFP避免链路负载过重。目录OTA 升级的链路特点失败现象分类断连问题定位超时问题定位数据错误问题定位TWS 双耳 OTA 特殊问题防砖设计1. OTA 升级的链路特点1.1 和正常业务的区别OTA 升级和日常使用音乐/通话完全不同维度日常业务OTA 升级持续时间几分钟到几小时5-30 分钟全程高负载数据量实时流几百 kbps几 MB 固件全量传输链路要求偶尔丢包可接受一个包都不能丢要重传功耗间歇持续高功耗发热失败影响断了重连可能变砖OTA 是对蓝牙链路最严苛的考验——长时间、大数据、不能丢、还要保证 Flash 写入正确。1.2 OTA 的数据流BLE/BREDR手机 APP固件传输RAM 缓冲CRC 校验Flash 写入校验重启链路层的问题主要发生在固件传输阶段——长时间大数据传输链路质量波动会累积暴露。1.3 OTA 协议栈OTA 通常用以下协议之一协议通道特点BLE GATTBLE ATT主流兼容性好BLE L2CAP CoCBLE L2CAP高吞吐RFCOMM/SPPBREDR老方案自定义 HCI 透传BREDR/BLE厂商私有BLE GATT 最常见但吞吐最低。本文以 BLE GATT 为例。2. 失败现象分类2.1 三类现象现象表现根因方向断连升级到一半连接断开链路质量、watchdog、参数超时升级进度极慢最终超时吞吐不足、重传多数据错误升级完成但校验失败CRC、Flash 写入2.2 失败时间分布0-10%建立连接阶段配置问题10-50%传输中段链路质量问题50-90%传输后段Flash 写入慢导致超时90-100%校验重启阶段Flash 数据错误中段失败最常见——链路质量问题在长时间传输下暴露。3. 断连问题定位3.1 断连的直接原因BLE 断连由HCI_Disconnection_Complete事件报告原因码Reason含义常见根因0x08Connection Timeout链路质量差长时间收不到包0x13Remote User Terminated对端主动断开0x16Connection Terminated By Local Host本端主动断开0x22Connection TimeoutLSTO 超时0x3BInstant Passed参数更新失败3.2 Connection Timeout (0x08)最常见。BLE 的 Supervision Timeout 到期——在 timeout 时间内没收到任何包。排查# 查 Supervision Timeout 配置hci_dump|grep-isupervision# 典型配置# ConnInterval 20ms# ConnLatency 0# SupervisionTimeout 2s (2000ms)如果 SupervisionTimeout 太短如 500ms链路短暂波动就会断。OTA 期间建议设到 4-6 秒// OTA 期间增大 SupervisionTimeoutble_gap_update_connection_params(conn_handle,{.min_interval15,// 20ms.max_interval15,// 20ms.latency0,.supervision_timeout600,// 6 秒 (单位 10ms)});根因RSSI 低距离远或遮挡包丢失多干扰Wi-Fi 或其他蓝牙设备CPU 忙Flash 写入时 CPU 占用高来不及处理 BLE 协议栈Crystal 漂移长时间工作后晶振频偏累积3.3 Watchdog 超时很多设备有看门狗BLE 协议栈必须定期喂狗。OTA 期间如果 Flash 写入阻塞了协议栈看门狗复位导致断连——实际是设备重启。排查看设备日志是否有 watchdog reset 记录测 Flash 写入耗时是否超过 watchdog 超时解决// Flash 写入时拆分喂狗voidflash_write_with_watchdog(uint32_taddr,uint8_t*data,size_tlen){constsize_tchunk256;// 每次写 256 字节for(size_ti0;ilen;ichunk){flash_write(addri,datai,min(chunk,len-i));watchdog_feed();// 每写一块喂狗}}3.4 Flash 写入阻塞Flash 写入是原子操作写入期间 CPU 不能取指执行某些芯片。如果一次写 4KB可能阻塞 50-100ms——BLE 协议栈在这期间无法响应可能断连。解决拆分写入每次写 256 字节或更小间隙处理 BLE 事件双缓冲一边收数据到 RAM另一边写 Flash交替进行降低写入频率攒够一定量再写减少写入次数// 双缓冲示例uint8_tbuf[2][4096];intactive_buf0;intbuf_offset0;voidon_ota_data(uint8_t*data,size_tlen){memcpy(buf[active_buf]buf_offset,data,len);buf_offsetlen;if(buf_offset4096){// 触发 Flash 写入非阻塞用 DMA 或中断flash_write_async(buf[active_buf],4096);active_buf1-active_buf;// 切换缓冲buf_offset0;}}3.5 热问题OTA 持续高功耗设备发热。某些芯片高温下 RF 性能下降导致链路质量变差。TWS 耳机体积小散热差这个问题更明显。排查测 OTA 期间设备温度对比常温和高温下的链路质量解决OTA 时降低发射功率缩短距离配合分段升级每段间休息散热改善 PCB 散热设计4. 超时问题定位4.1 吞吐瓶颈BLE OTA 的典型吞吐配置理论吞吐实际吞吐BLE 4.0, MTU23, Interval100ms~8 kbps5-7 kbpsBLE 4.2, MTU185, Interval30ms~150 kbps80-120 kbpsBLE 5.0, 2M PHY, MTU251, Interval15ms~500 kbps200-300 kbps升一个 2MB 固件BLE 4.0约 40 分钟太久BLE 4.2约 3-5 分钟可接受BLE 5.0约 1-2 分钟理想4.2 提升 MTUMTUMaximum Transmission Unit决定每个包能装多少数据ATT 包 ATT Header (3B) ATT Value (MTU - 3)MTU23 时每个 ATT 包只能装 20 字节 OTA 数据——效率极低。优化// OTA 开始时协商大 MTUble_att_request_mtu_exchange(conn_handle,247);// 请求 MTU 247// 等待 MTU 交换完成voidon_mtu_exchanged(uint16_tmtu){ota_mtumtu;// 每包能装 mtu - 3 字节 OTA 数据}4.3 连接间隔ConnInterval 影响每秒能发多少包ConnInterval每秒连接事件每秒数据量MTU247100ms102.4 KB/s30ms338 KB/s15ms6616 KB/s7.5ms13332 KB/sOTA 期间用小 Interval 提升吞吐// OTA 期间调小 ConnIntervalble_gap_update_connection_params(conn_handle,{.min_interval6,// 7.5ms (单位 1.25ms).max_interval6,// 7.5ms.latency0,.supervision_timeout600,});4.4 数据重传包丢失导致重传重传多吞吐下降。查重传率# 看重传统计btmon|grep-iretransmit重传率 10% 时吞吐明显下降。优化方向改善 RF 环境减少干扰缩短距离用 1M PHY 替代 2M PHY2M 灵敏度差弱信号下重传更多4.5 加密开销OTA 通常走加密连接LE Secure Connections。加密的加解密有 CPU 开销低端芯片可能成为瓶颈。优化用硬件加密加速多数芯片支持如果安全要求不高可以不加密不推荐5. 数据错误问题定位5.1 CRC 校验失败OTA 传输完成后对收到的固件做 CRC 校验。校验失败说明数据在传输或写入过程中出错。排查在传输层加 CRC每包 CRC在 Flash 写入后读回校验对比 RAM 中的数据和 Flash 中的数据// 分层校验voidon_ota_packet(uint8_t*data,size_tlen,uint16_tcrc){// 第一层包 CRCif(crc16(data,len)!crc){send_nack();// 要求重传return;}send_ack();buffer_data(data,len);}voidon_ota_complete(){// 第二层整包 CRCif(crc32(firmware_buf,firmware_size)!expected_crc32){send_ota_error(ERR_CRC_MISMATCH);return;}// 第三层Flash 读回校验for(inti0;ifirmware_size;i4096){uint8_treadback[4096];flash_read(OTA_ADDRi,readback,4096);if(memcmp(readback,firmware_bufi,4096)!0){send_ota_error(ERR_FLASH_VERIFY);return;}}}5.2 Flash 写入错误Flash 写入失败的常见原因原因表现解决写入前未擦除写入失败或数据错先擦后写跨页写入部分写入成功按页对齐写入写入超时看门狗复位拆分写入Flash 寿命到写入失败换 Flash电压不足写入异常检查电源排查// Flash 写入要检查返回值flash_err_terrflash_write(addr,data,len);if(err!FLASH_OK){LOG_E(Flash write failed: addr0x%x, err%d,addr,err);// 重试或上报错误}5.3 数据顺序错误如果 OTA 协议支持乱序传输类似 TCP 的选择性 ACK接收端要正确重组。顺序错误会导致固件错乱。排查给每个包加序号接收端检查序号连续性乱序时缓存等缺失包6. TWS 双耳 OTA 特殊问题6.1 双耳同步升级TWS 有两只耳朵固件要同步升级。两种方案方案1分别升级手机分别连主耳和从耳各自升级。优点简单缺点慢且可能出现版本不一致一只升级成功另一只失败方案2主耳转发手机只连主耳主耳收固件后转发给从耳。优点版本一致缺点复杂转发链路质量问题6.2 主从转发 OTA 的链路问题主耳同时维护两条链路手机↔主耳收固件、主耳↔从耳转发固件。两条链路共享 RF调度复杂。问题链路切换主耳在两条链路间切换切换延迟影响吞吐转发延迟主耳收完一个包才能转发串行传输慢双链路质量任一链路差都会拖慢整体优化用流水线主耳收到包 N 时转发包 N-1并行处理动态调整两条链路的时间片比例双链路独立 CRC避免一个链路的错误影响另一个6.3 主从切换导致 OTA 失败如果 OTA 过程中发生主从切换如主耳电量低正在进行的 OTA 会中断。解决OTA 前检查电量低电量禁止 OTAOTA 期间禁用主从切换支持断点续传——切换后从断点继续6.4 双耳版本不一致升级后一只耳朵是新版本另一只是旧版本无法配对。解决双耳都升级完成后才重启重启后校验双方版本一致才配对不一致时回滚到旧版本7. 防砖设计7.1 双区升级用两个固件区A 区和 B 区。升级时写 B 区启动时校验 B 区OK 则切换到 B 区失败则继续用 A 区。Flash 布局 Bootloader: 0x0000-0x3FFF A 区固件: 0x4000-0x3FFFF (当前运行) B 区固件: 0x40000-0x7FFFF (升级写入) 配置区: 0x80000-0x80FFF 升级流程 1. 运行 A 区接收固件写 B 区 2. 写完校验 B 区 CRC 3. 标记启动区为 B 4. 重启 → Bootloader 校验 B 区 → OK 则跳转 B 区 5. 失败则回退 A 区7.2 Bootloader 校验Bootloader 必须简单可靠不能出错。它的职责选择启动区读标志校验固件 CRC校验失败回退另一区跳转固件// Bootloader 伪代码voidbootloader_main(){intactiveread_active_partition();if(verify_firmware(active)){jump_to_firmware(active);}else{// 回退另一区intbackup1-active;if(verify_firmware(backup)){write_active_partition(backup);jump_to_firmware(backup);}else{// 两区都坏进恢复模式enter_recovery_mode();}}}7.3 恢复模式如果两区都坏要有恢复模式通过 USB/UART 接口升级有线通过 BLE 强制升级最小化 BLE 固件只支持 OTA7.4 断点续传支持断点续传断连后重连从断点继续不用从头开始// 断点续传协议voidon_ota_start(){uint32_toffsetread_ota_progress();// 读上次进度send_ota_resume_request(offset);// 请求从 offset 继续}voidon_ota_data(uint32_toffset,uint8_t*data,size_tlen){flash_write(OTA_ADDRoffset,data,len);write_ota_progress(offsetlen);// 保存进度}7.5 防掉电升级过程中掉电是最常见的变砖原因。防护升级前检查电量电量 30% 禁止升级写 Flash 原子操作写完一页才更新进度避免半写状态电容储能检测到掉电时用电容储能完成当前 Flash 写入