4、BLE OTA升级流程:从设备发现、连接、固件传输到校验与激活的完整步骤

BLE OTA(Over-The-Air)升级是nRF系列芯片产品生命周期管理中至关重要的一环。它允许开发者通过无线方式修复固件漏洞、添加新功能或更新配置参数,而无需物理接触设备。一个完整的BLE OTA升级流程并非简单的“发文件”,而是一个涉及连接管理、数据分片、流量控制、完整性校验和系统激活的复杂状态机。本章将深入拆解从设备发现到固件激活的每一个关键步骤,帮助开发者构建稳定、可靠且可回滚的升级链路。

阶段一:发现与连接 阶段二:参数协商 阶段三:固件传输 阶段四:校验与激活 广播/扫描 设备发现 连接建立 Connection Interval 服务发现 DFU Service UUID MTU协商 ATT_MTU 读取DFU版本 固件兼容性检查 设置PRN Packet Receipt Notification 选择升级目标 App/Bootloader/SoftDevice 接收对象创建 Init Packet + Firmware 数据分片 20字节/包 (默认) 写入Control Point 启动传输命令 循环写入Data Write Without Response 流控与重传 PRN机制/超时重发 CRC校验 固件完整性验证 写入校验命令 Verify Command 激活固件 Activate & Reset 回滚检查 Bank0/Bank1切换 状态机:IDLE → DISCOVERY → DFU_START → RECEIVING → VERIFY → ACTIVATE → RESET

上图展示了BLE OTA升级的四个核心阶段及其状态流转。整个流程从设备发现开始,经过参数协商、固件传输,最终到达校验与激活。每个阶段都有其特定的协议操作和失败处理机制,任何一步的疏忽都可能导致升级失败甚至设备变砖。下面我们将逐一深入每个阶段的实现细节。

4.1 设备发现与连接建立:广播、扫描与连接参数

核心原理:BLE OTA升级的第一步是让目标设备(Peripheral)进入可发现状态,同时让升级工具(Central,如手机App或PC工具)能够扫描到它。nRF设备通常通过广播特定UUID来标识其DFU服务。在nRF5 SDK中,DFU服务的广播包通常包含0xFE59或自定义的128-bit UUID。连接建立后,Central需要执行服务发现(Service Discovery)来找到DFU Service的句柄,并协商MTU(Maximum Transmission Unit)大小。

操作流程:

  1. 广播配置:设备进入DFU模式后,广播间隔建议设置为20ms-30ms(快速广播),以加快发现速度。广播数据中必须包含DFU Service UUID。
  2. 扫描与过滤:Central扫描到设备后,通过匹配UUID过滤出目标设备。建议同时读取设备的设备名称或MAC地址进行二次确认。
  3. 连接参数协商:连接成功后,Central应请求较短的Connection Interval(如7.5ms-15ms)和较短的Slave Latency(0),以提高数据传输速率。nRF设备默认的连接参数可能偏向低功耗,不适合高速传输。
  4. MTU协商:通过GATT Exchange MTU操作,将MTU从默认的23字节提升到247字节(nRF52832/52840支持)。这能显著减少分包数量,例如一个100KB的固件,使用20字节MTU需要5000包,而使用244字节MTU仅需410包。
经验提醒:在nRF Connect SDK(NCS)中,连接参数更新请求应在DFU开始前完成。如果连接参数未协商成功,传输速度会非常慢。建议在DFU Service的on_connect回调中主动发起连接参数更新请求,并等待CONNECTION_UPDATED事件确认后再开始传输。
参数默认值OTA推荐值说明
Connection Interval30-50ms7.5-15ms越小传输越快,但功耗增加
Slave Latency40必须为0,否则从机可跳过连接事件
Supervision Timeout4000ms2000ms需大于连接间隔的2倍
ATT_MTU23247需双方协商,nRF52系列最大支持247

4.2 固件传输机制:分片、写入与流控

核心原理:固件传输阶段的核心是可靠地将固件二进制数据分片写入到设备的DFU Data特征值中。nRF的DFU协议定义了两种写入模式:Write with Response(写入后等待设备确认)和Write without Response(写入后不等待确认)。为了提升速度,通常使用Write Without Response,但必须配合流控机制防止数据溢出。

关键机制:

  • PRN(Packet Receipt Notification):设备每收到N个数据包后,会向Central发送一个通知,告知已接收的包数量。Central收到通知后继续发送下一批数据。PRN值通常设置为10-20,太小则频繁等待通知降低速度,太大则可能因缓冲区溢出导致丢包。
  • 数据包格式:每个Data特征值写入的数据包前两个字节为包的序列号(16-bit),随后是固件数据。序列号用于检测丢包和乱序。
  • 超时重传:如果Central在发送完一批数据后,在超时时间(如500ms)内未收到PRN通知,则必须重发该批次数据。
// 伪代码示例:发送固件数据
uint16_t seq = 0;
for (uint32_t offset = 0; offset < firmware_size; offset += chunk_size) {
    uint8_t pkt[chunk_size + 2];
    pkt[0] = seq & 0xFF;
    pkt[1] = (seq >> 8) & 0xFF;
    memcpy(&pkt[2], &firmware[offset], chunk_size);
    ble_gatts_hvx(conn_handle, data_handle, pkt, sizeof(pkt));
    seq++;
    if (seq % prn == 0) {
        // 等待PRN通知,带超时
        wait_for_prn(timeout_ms);
    }
}
风险提示:不要将PRN设置为0(即禁用通知)。虽然理论上可以连续发送所有数据,但一旦发生丢包,Central无法得知设备实际接收到的位置,只能从头重传,代价极高。此外,如果设备端Flash写入速度跟不上接收速度,会导致内部缓冲区溢出,表现为设备无响应或返回错误码。

4.3 校验与激活:从临时存储到固件切换

核心原理:nRF的DFU设计采用双Bank机制(Bank0和Bank1)。Bank0存放当前运行的固件,Bank1作为临时接收区。固件传输完成后,设备执行校验(Verify),确认Bank1中的固件完整无误,然后执行激活(Activate)操作,将Bank1的内容复制到Bank0(或交换Bank指针),最后复位重启。

操作流程:

  1. 写入Init Packet:在传输固件数据之前,必须先发送Init Packet,其中包含固件大小、CRC、SDK版本、设备类型等信息。设备会校验这些信息是否与当前硬件匹配。
  2. 发送校验命令:固件数据全部写入后,Central向Control Point特征值写入Verify命令(0x04)。设备开始对Bank1中的数据进行CRC校验。
  3. 接收校验结果:校验完成后,设备通过Notification返回结果。成功返回Success,失败返回错误码(如CRC Error)。
  4. 发送激活命令:校验成功后,Central写入Activate命令(0x05)。设备执行Bank切换,将新固件设置为启动目标。
  5. 系统复位:激活完成后,设备自动复位,Bootloader根据Bank标志位启动新固件。
关键细节:在nRF5 SDK中,激活操作并非立即生效。设备会先标记Bank1为“待激活”,然后执行软复位。Bootloader在启动时检查该标志,决定是从Bank0还是Bank1启动。如果新固件启动失败(如看门狗超时),Bootloader会自动回滚到Bank0,这是nRF DFU的天然保护机制。

4.4 实战案例:一次完整的OTA升级排障过程

背景:某物联网项目使用nRF52832作为主控,固件大小为120KB。开发者在测试OTA升级时,发现升级成功率仅约60%,失败时设备无响应,需要重新上电才能恢复。

初始现象与数据:

  • 使用手机App(nRF Connect)作为Central,连接正常,MTU协商为247。
  • 传输过程中,App显示发送速度约8KB/s,但经常在传输到30%-50%时卡住,随后超时断开。
  • 设备端日志显示,在卡住前收到了大量BLE_GATTS_EVT_WRITE事件,但Flash写入操作频繁返回NRF_ERROR_BUSY。

分析判断过程:

  1. 初步怀疑是Flash写入速度瓶颈:nRF52832内部Flash页擦除时间约20ms,写入一个页(4KB)约需要50ms。如果数据接收速度超过Flash写入速度,缓冲区会溢出。
  2. 检查PRN设置:发现App端PRN设置为50,即每50包(约1KB)才通知一次。在MTU=247时,50包约12KB数据,而设备端缓冲区仅4KB,导致溢出。
  3. 进一步分析:设备端SDK的DFU实现中,默认的缓冲区大小为4KB。当接收数据超过缓冲区容量时,SDK会暂停接收,但此时Central仍在发送,导致丢包。

实际操作步骤:

  1. 将PRN值从50调整为10(即每10包通知一次,约2.4KB数据)。
  2. 在设备端DFU初始化中,将DFU_RX_BUF_SIZE从默认的4KB增加到8KB(需确认RAM足够)。
  3. 在Central端增加发送间隔控制:每发送20包后延时2ms,给设备Flash写入留出时间。

结果对比:调整后,升级成功率提升至99%以上,传输速度稳定在12KB/s(受限于Flash写入速度)。

风险与代价:增加缓冲区会占用更多RAM,在资源受限的设备上需谨慎。延时发送会降低理论吞吐量,但换取了稳定性。

复盘经验:BLE OTA的瓶颈往往不在无线链路,而在设备端的Flash写入速度。设计时需根据Flash页大小、擦除时间和缓冲区大小,反推合理的PRN值和发送节奏。

4.5 常见误区与总结

误区一:忽略连接参数更新。很多开发者直接使用默认连接参数进行OTA,导致传输速度极慢。正确做法是在DFU开始前主动请求短连接间隔。

误区二:MTU协商失败后仍继续传输。如果MTU协商失败(如对端不支持),应回退到20字节分包模式,而不是直接报错。但需注意,20字节模式下传输时间会成倍增加。

误区三:将PRN设置过大或过小。PRN过大会导致缓冲区溢出,过小则频繁等待通知,降低效率。建议根据MTU和缓冲区大小计算:PRN = 缓冲区大小 / (MTU-3),并留出20%余量。

误区四:忽略Init Packet的校验。Init Packet中的固件大小、CRC和兼容性信息是设备端拒绝错误固件的第一道防线。如果Init Packet错误,设备会直接拒绝接收固件,而不是等到传输完成后再报错。

总结:一个稳定的BLE OTA流程需要精细的工程调优。建议开发者在设计阶段就明确:1)目标设备的Flash写入速度;2)可用的RAM缓冲区大小;3)预期的传输时间。基于这三个参数,计算出最优的MTU、PRN和发送节奏。同时,务必在固件中实现看门狗和Bank回滚机制,确保升级失败时设备不会变砖。