1. 项目概述
在USB Type-C和电力传输(PD)生态系统中,固件的现场更新能力是衡量一个产品是否具备长期生命力的关键指标。想象一下,你设计的一款高端笔记本或扩展坞,因为PD协议的一个小版本更新,就需要召回或者返厂升级,这无论对用户还是厂商都是一场灾难。而TPS6598x系列USB PD控制器,通过其内建的I2C从机接口和一套精心设计的ASCII命令集,为嵌入式控制器(EC)提供了一个优雅的“空中升级”解决方案。这个方案的核心,就是让EC扮演一个“快递员”和“指挥官”的双重角色:它首先通过I2C总线,将新的固件二进制文件“搬运”到TPS6598x的内部缓冲区,然后发送一系列特定的“4CC”命令,指挥TPS6598x自己动手,将新固件安全、可靠地写入其外挂的SPI Flash存储器中。
我最近在为一个客户设计基于TPS65982D的100W PD扩展坞时,就深度实践了这套I2C固件更新流程。从最初阅读德州仪器(TI)那篇略显晦涩的应用笔记SLVA783A,到最终在自家EC上稳定跑通整个升级流程,中间踩过的坑、绕过的弯,足够写一篇血泪史。今天,我就把自己从原理理解、代码移植到调试排错的全过程干货分享出来。无论你是在开发笔记本主板EC固件,还是在设计独立的USB PD充电器,只要你的系统中存在一个主控MCU需要管理TPS6598x,这篇文章都能为你提供一个清晰、可落地的参考框架。
2. 核心原理与架构解析
2.1 TPS6598x的存储架构与启动机制
要理解固件更新,必须先摸清TPS6598x的“家底”——它的存储和启动方式。TPS6598x内部是一个RAM-Based的处理器,这意味着它本身没有非易失性存储空间来存放程序代码。它的“操作系统”完全存储在外置的一颗SPI Flash芯片里。上电或复位后,TPS6598x会作为SPI Master,主动从Flash中读取固件映像(Firmware Image)加载到内部RAM中执行。
为了提高可靠性,防止单点故障导致设备“变砖”,TI采用了双区冗余备份的设计。SPI Flash的存储空间被逻辑上划分为两个完全独立的区域:Region 0(低区)和Region 1(高区)。这两个区域存储着完全相同的固件映像。在启动时,TPS6598x的Bootloader会依次校验这两个区域的完整性。其策略通常是:优先尝试启动Region 0,如果校验失败(如CRC错误、Flash读取错误),则自动回退到Region 1。这种设计确保了即使一次固件更新过程意外中断,导致其中一个区域数据损坏,设备依然能从另一个完好的区域正常启动。
那么,我们通过I2C更新时,操作的对象是什么?并不是直接去写整个SPI Flash的物理地址。TPS6598x的固件设计了一套抽象的“Flash操作命令”,我们更新的是逻辑区域。具体来说,我们提供给EC的输入文件是一个名为low-region.bin的二进制文件,它包含了完整的、可执行的固件映像。更新程序的任务,就是把这个low-region.bin文件的数据,同时写入Region 0和Region 1。每个区域的内部又分为两部分:一个4KB的头部(Header,包含ID、版本、配置数据等)和最大64KB的应用代码区。因此,一个区域的总大小最大为68KB。
2.2 I2C通信与4CC命令集:与PD控制器的“对话”协议
嵌入式控制器(EC)作为I2C Master,TPS6598x作为Slave,它们之间的通信不仅仅是简单的寄存器读写,更包含了一套命令-响应机制。这是整个更新流程的灵魂。
TPS6598x预留了两个特殊的I2C寄存器对,用于这种高级命令交互:
- CMD1/DATA1寄存器对:主命令接口,地址为0x08和0x09。
- CMD2/DATA2寄存器对:次命令接口,地址为0x10和0x11。在大多数应用和本例中,我们使用主接口(CMD1/DATA1)。
所谓的“4CC”命令,即Four-Character Command,是一个由4个ASCII字符组成的命令字。例如,擦除Flash区域的命令是FLem(Flash Erase Memory)。这套命令集可以理解为EC向TPS6598x发送的“工作指令”。其执行有严格的时序和步骤要求,任何一步错漏都可能导致命令执行失败。
一个完整的4CC命令执行流程如下:
- 写入输入数据:将命令所需的参数(如要擦除的起始地址、要写入的数据块)通过I2C写入DATA1寄存器。
- 发送命令:将4CC命令的ASCII码(如
F,L,e,m)写入CMD1寄存器。写入动作本身即触发TPS6598x开始执行该命令。 - 轮询命令完成:循环读取CMD1寄存器,直到其值从命令字变为
0x00000000。这表示命令执行完毕。如果读到!CMD(0x21434D44),则说明命令无效或格式错误。 - 读取输出与状态:从DATA1寄存器中读取命令执行的结果或状态码。这一步至关重要,它不仅能获取信息(如
FLrr命令读回的指针值),还能清空DATA1缓冲区,为下一个命令做好准备。
固件更新流程主要用到以下6个核心4CC命令:
FLrr:读取当前活跃的Flash区域指针。用于确定接下来要操作哪个区域。FLem:擦除指定地址开始的若干个Flash扇区。Flash写入前必须先擦除。FLad:设置后续Flash写操作的起始地址。FLwd:向当前地址写入64字节数据块。这是数据搬运的主力命令。FLvy:验证指定区域的Flash内容是否有效(如CRC校验)。GAID:触发TPS6598x硬件复位,使其重新启动并加载新的固件。
2.3 整体更新流程与状态机
将上述存储架构和命令集结合起来,就形成了下图所示的固件更新状态流程。这个流程体现了很强的鲁棒性设计思想,它不是一个简单的“写入-重启”过程,而是包含了多次校验和条件判断。
整个流程可以概括为以下几个阶段:
- 前置检查与准备:EC首先读取TPS6598x的版本号和启动标志寄存器。启动标志(Boot Flags)中的
BootOk位和Region0/Region1位,揭示了设备当前从哪个区域启动、哪个区域是有效的。这决定了后续更新策略(先更新非活跃区,还是两个区都更新)。同时,为了安全,EC会通过修改系统配置寄存器,暂时禁用Type-C端口,防止更新过程中发生意外的PD通信。 - 区域更新循环:这是核心数据写入阶段。针对每个需要更新的区域(Region 0 和 Region 1),执行相同的子流程: a.
FLrr:获取该区域的起始指针。 b.FLem:从该指针处开始,擦除足够容纳新固件(68KB)的Flash扇区(对于TPS65982是17个4KB扇区)。 c.FLad:将写地址设置回区域起始指针。 d.FLwd循环:将low-region.bin文件的数据,以每次64字节的块,循环写入Flash。对于68KB文件,需要循环1088次。 e.FLvy:写入完成后,立即验证该区域数据的有效性。 - 复位与验证:两个区域都成功更新并验证后,EC发送
GAID命令触发TPS6598x硬件复位。复位后,EC再次读取启动标志和版本号,确认设备已从新固件正常启动,且版本号已更新。
这个流程的精妙之处在于其顺序和冗余。通常,它会优先更新当前非活跃的区域。例如,如果设备当前从Region 0启动,则先更新Region 1。这样,即使更新Region 1失败,设备仍能从完好的Region 0启动,系统不会“变砖”。只有Region 1更新并验证成功后,才会去更新Region 0。这种“滚动更新”策略最大程度地保障了更新过程的安全性。
3. 代码实现深度剖析与移植要点
TI的应用笔记提供了基于Tiva-C平台的示例代码,但我们的EC可能是基于ARM Cortex-M、RISC-V或其他内核的MCU。因此,直接拷贝代码是行不通的,关键在于理解其逻辑,然后移植到自己的硬件抽象层上。下面我结合自己的移植经验,拆解几个关键模块。
3.1 硬件抽象层(HAL)接口适配
示例代码中的ReadIICRegister和WriteIICRegister函数,其内部调用了Tiva-C特有的HostCommandSend函数和tI2cMsg结构体。这是我们首要替换的部分。
在你的EC平台上,你需要实现这两个函数,它们是对底层I2C驱动函数的封装。核心是遵循TPS6598x的I2C写协议:每次传输的第一个字节是寄存器地址,第二个字节是数据长度(Length Byte),后面紧跟实际数据。这一点在数据手册中可能不显眼,但却是通信成功的关键。
一个通用的WriteIICRegister函数实现思路如下:
bool WriteIICRegister(uint32_t i2c_bus, uint8_t slave_addr, uint8_t reg_addr, uint8_t data_len, uint8_t* data) { uint8_t write_buffer[MAX_ARG_LENGTH]; // 长度字节+数据 bool error = false; // 构造发送缓冲区: [寄存器地址] [长度N] [数据0] [数据1] ... [数据N-1] write_buffer[0] = reg_addr; write_buffer[1] = data_len; // 长度字节 memcpy(&write_buffer[2], data, data_len); // 调用你的平台I2C主发送函数,发送 data_len + 2 个字节 if (your_i2c_master_transmit(i2c_bus, slave_addr, write_buffer, data_len + 2) != SUCCESS) { error = true; // 这里最好加入重试或超时机制 } return error; }ReadIICRegister函数稍微复杂,它通常需要先写寄存器地址启动传输,再发起读操作。许多MCU的I2C驱动支持“复合传输”(Write followed by Read),这正是我们需要的。
3.2 核心更新函数FWUpdate82()的逻辑拆解
这个函数是更新的总调度中心。它的逻辑清晰体现了之前提到的状态机:
- 读取状态:获取当前固件版本和启动标志。
- 安全准备:禁用Type-C端口。
- 决策更新路径:根据
BootFlags82.Region0和BootFlags82.Region1判断当前活跃区域,决定先更新哪个区域。这是安全性的核心逻辑。 - 调用
RegionUpdate82():执行单个区域的擦除、写入、验证。 - 复位与验收:发送
GAID,等待重启,验证新固件。
这里有一个极易忽略的细节:BootOk标志位的判断。在示例代码中,它是通过if (BootFlags82.BootOk == 1)来判断的。但在TPS65982D的固件中,这个位被重新定义为PatchHeaderErr。所以,如果你在为TPS65982D开发,这里的判断条件需要改为if (!BootFlags82.PatchHeaderErr)。这个差异在TI的文档中以注释形式给出,但一不留神就会导致更新逻辑在82D上无法启动。
3.3 区域更新引擎RegionUpdate82()详解
这个函数负责对一个区域执行完整的“擦-写-验”三部曲。其内部有一个关键的循环:
for (count32bit = 0; count32bit < 1088; count32bit++) { // 1. 准备64字节数据 (从low-region.bin文件读取) // 2. 调用 fourCC_Command(FLwd, data); }循环次数1088是怎么来的?这是针对最大68KB固件映像的计算结果:(65536 + 4096) Bytes / 64 Bytes per write = 1088。但在实际产品中,你的固件可能小于68KB。示例代码为了演示,使用了生成假数据的initCountUpDwn64函数。在实际移植中,你必须将其替换为从真实的low-region.bin文件缓冲区读取数据的逻辑。循环的结束条件,也应该由固定的1088次,改为判断文件数据是否已全部写入。
另一个关键点是扇区擦除数量。FLem命令的输入参数之一是要擦除的4KB扇区数量。对于TPS65982,一个区域是68KB,需要17个扇区。但对于TPS65982D,其应用代码区最大为8KB,因此一个区域总大小为12KB,只需要擦除3个扇区。示例代码中通过修改sectorCount常量来体现这一差异。如果你搞错了这个数字,要么擦不干净导致写入失败,要么擦除了不该擦的冗余数据,后果严重。
3.4 4CC命令执行器fourCC_Command()的稳健性实现
这个函数是驱动TPS6598x执行命令的“遥控器”。其实现必须严格遵守前文提到的四步时序。示例代码中使用了switch-case来组织不同的命令,但所有命令都共享同一套“写数据->写命令->轮询->读状态”的骨架。
这里我分享一个提高稳健性的技巧:为命令轮询(读取CMD寄存器直到清零)增加超时机制。示例代码中使用的是do...while循环,如果TPS6598x因故没有响应,程序会死在这里。你应该加入一个超时计数器。
uint32_t timeout = 0; do { readError = ReadIICRegister(...); // ... 解析event ... timeout++; if(timeout > MAX_POLLING_COUNT) { error = true; UARTprintf("CMD 0x%08x timeout!\n", fourCC); break; } } while(!((event == 0) || (event == nCMD)));此外,在发送FLwd(写数据)命令后,示例代码中有一个DelayInMilliseconds(50)的等待。这个50ms的延时是经验值,用于确保TPS6598x有足够时间将64字节数据从缓冲区编程到Flash中。这个延时不能随意缩短,尤其是在主控MCU时钟频率较高时,确保等待时间充足是避免写操作失败的关键。
4. 从理论到实践:移植与集成实战指南
有了对原理和代码的深入理解,接下来就是动手将其集成到你的EC项目中了。这个过程更像是一个“外科手术”,需要小心地剥离示例代码中与Tiva-C强绑定的部分,换上你自己系统的“器官”。
4.1 工程文件结构与移植步骤
首先,建议在你的EC固件工程中,为TPS6598x FW更新功能建立一个独立的模块或文件夹。可以包含以下文件:
tps6598x_fw_update.c/.h:对应示例中的i2cFwUpdate82.c/.h,包含核心更新逻辑。tps6598x_i2c_handler.c/.h:对应i2cHandlerHostTo82.c/.h,实现底层的I2C读写和4CC命令封装。tps6598x_registers.h:对应hostIF82.h,定义所有用到的寄存器地址、长度、位域结构和4CC命令宏。
移植的具体步骤如下:
- 复制并重命名文件:将TI示例代码中的三个
.c和三个.h文件复制到你的项目目录,并改为你喜欢的命名规范。 - 替换硬件依赖层:这是最关键的一步。找到并重写所有与Tiva-C特定硬件或库相关的函数。主要是
ReadIICRegister、WriteIICRegister以及它们可能调用的底层I2C初始化、发送、接收函数。确保你的I2C驱动支持标准模式(100kHz)和快速模式(400kHz),示例中读操作用100kHz,写操作用400kHz。 - 替换调试输出:将所有的
UARTprintf替换为你项目中的日志输出函数(如LOG_INFO)。如果不需要详细调试信息,可以用宏控制其开关,就像示例中的#ifdef UART_Stream_ON。 - 适配延时函数:将
DelayInMilliseconds替换为你系统的毫秒延时函数。 - 集成文件系统或数据源:移除
initCountUpDwn64这个生成假数据的函数。你需要实现一个函数,能从你的存储介质(如EC内部的Flash、从主机通过HID或SPI传输过来的缓冲区)中,按顺序读取low-region.bin文件的64字节数据块,并传递给FLwd命令。 - 修改主循环调用:将示例
main.c中的FWUpdate82()调用,整合到你EC的主任务或某个事件处理函数中。例如,可以在EC收到主机下发的“开始固件更新”命令后,在一个独立的、具有较低优先级的任务中执行此函数。
4.2 关键配置与参数调整
移植不是简单的复制粘贴,必须根据你使用的具体TPS6598x型号和硬件设计进行调整:
- I2C从机地址:在
tps6598x_registers.h中,DefAddr1和DefAddr2定义了TPS6598x的两个I2C地址(通常是0x38和0x3F)。你需要确认你的硬件原理图中,TPS6598x的I2C_ADDR引脚配置,以确定使用哪个地址作为主通信接口。 - TPS65982D的特殊处理:
- 扇区数:将
sectorCount从17改为3。 - 启动标志判断:将所有判断
BootOk == 1的地方,改为判断PatchHeaderErr == 0。 - 数据结构:根据TI注释,
tBootFlags82结构体的第一个位域定义从BootOk改为了PatchHeaderErr,需同步修改你的结构体定义和相关的打印、判断语句。
- 扇区数:将
- 固件映像来源:这是产品化必须解决的问题。示例代码假设数据已经在EC内存中。实际产品中,新固件
low-region.bin通常由主机(如x86 AP)通过EC-AP间的通信接口(如eSPI、共享内存、HID)下发到EC。你需要在EC端开辟一个足够大的缓冲区(至少68KB)来接收并暂存这个文件,或者在更新时流式读取。
4.3 调试与验证:让更新流程“跑起来”
在第一次尝试运行更新代码前,强烈建议分步调试,而不是一次性跑全流程。
第一阶段:基础通信测试
- 编写一个简单的测试函数,尝试用
ReadIICRegister读取TPS6598x的版本寄存器(0x0F)。如果能正确读出版本号(如0x01.07.06.00),证明I2C底层通信和寄存器读操作是正常的。 - 测试
WriteIICRegister,可以尝试写一个无关紧要的配置寄存器,再读回来验证。
第二阶段:4CC命令测试
- 测试
FLrr命令。这是只读命令,风险最低。发送FLrr命令读取区域指针,看是否能得到0x2000或0x20000这样的预期值。 - 测试
GAID命令。发送GAID命令,观察TPS6598x是否会发生一次复位(可以通过其GPIO输出或电源状态变化来判断)。注意:执行GAID后,I2C通信会暂时中断,直到TPS6598x重启完成。
第三阶段:模拟更新与真实更新
- 首次务必使用模拟数据:在确认
FLwd命令能正常执行前,先使用示例中的假数据循环。将循环次数改为一个很小的值(比如4),并暂时注释掉FLem(擦除)命令。这样做的目的是测试“写”流程的通路,而不会破坏Flash中的原有固件。通过FLvy验证时,预期会失败(因为写的是假数据),但这正好验证了验证流程本身是工作的。 - 进行第一次真实更新:
- 确保你有可靠的固件备份方法。最好能通过编程器直接读取SPI Flash的原始内容备份。
- 准备一个确认可用的
low-region.bin文件(通常来自TI的配置工具)。 - 在EC中,将该bin文件以数组形式编译进去,或通过调试器加载到内存。
- 启用
FLem擦除命令。 - 执行更新流程。建议先只更新一个区域(通过临时修改代码,让
RegionUpdate82只执行一次)。 - 更新完成后,不要立即发送
GAID复位!而是通过FLrr再次读取指针,并通过I2C读取Flash内容(如果支持)进行比对,或者使用TI的Utilities Tool通过I2C读取Flash校验。 - 确认一个区域更新无误后,再恢复代码,进行完整的双区更新测试。
5. 常见问题排查与实战经验总结
即使完全按照指南操作,在实际硬件上第一次运行时,你也大概率会遇到各种问题。下面是我在多个项目中总结出的“故障树”和解决方案。
5.1 I2C通信失败
这是最基础也最常见的问题。
- 症状:
ReadIICRegister或WriteIICRegister总是返回错误,或读取的数据全为0xFF/0x00。 - 排查步骤:
- 查硬件:用示波器或逻辑分析仪抓取I2C总线(SCL, SDA)波形。检查时序是否符合标准,是否有ACK信号。特别注意上拉电阻是否合适(通常4.7kΩ~10kΩ),总线电容是否过大导致边沿过缓。
- 查地址:确认EC程序中使用的I2C从机地址与TPS6598x硬件配置(
I2C_ADDR引脚上拉/下拉)一致。0x38是7位地址,写入时左移一位是0x70。 - 查速率:TPS6598x的I2C支持标准模式(100kHz)和快速模式(400kHz)。确保EC初始化I2C控制器时配置的时钟频率不超过从设备支持的最高速率。示例中读用100k,写用400k,如果你的布线较长或干扰大,可先统一降至100k测试。
- 查协议:确认你的
WriteIICRegister函数发送的序列格式是[寄存器地址] [长度字节] [数据...]。长度字节是TPS6598x I2C协议的特殊要求,很多通用I2C驱动需要特别处理。
5.2 4CC命令无响应或返回!CMD
- 症状:发送4CC命令后,轮询CMD寄存器永远得不到0x00,或者直接返回
!CMD。 - 排查步骤:
- 检查DATA寄存器:在发送CMD前,是否已向DATA寄存器写入了命令所需的正确长度的参数数据?
FLem需要8字节,FLad需要4字节,FLwd需要64字节。数据长度错误是导致!CMD的常见原因。 - 检查缓冲区清零:在发送一个新的4CC命令前,必须先读取DATA寄存器以清空之前的返回数据。如果忘了这一步,DATA寄存器中残留的数据可能会被TPS6598x误认为是下一个命令的输入参数,导致命令解析失败。
- 检查时序与延时:在写入CMD寄存器触发命令后,是否需要一个小的延时(几微秒)再开始轮询?某些MCU的I2C驱动在连续读写时过于紧凑,可能需要在操作间加入
__nop()或短延时。另外,FLwd命令后的50ms延时是否足够?在较慢的Flash芯片上,可能需要延长。 - 检查命令字符:确认你写入CMD寄存器的四个字节的ASCII码完全正确,大小写敏感。
FLem是F,L,e,m,不是F,L,E,M。
- 检查DATA寄存器:在发送CMD前,是否已向DATA寄存器写入了命令所需的正确长度的参数数据?
5.3 固件更新后设备不启动或版本未变
- 症状:更新流程看似成功完成,但发送
GAID复位后,设备无法正常工作,或读取的版本号还是旧的。 - 排查步骤:
- 验证Flash写入:在发送
GAID前,用FLvy命令验证两个区域。如果验证失败,说明写入的数据有问题。问题可能出在:a)low-region.bin文件本身损坏;b) EC从文件到FLwd传输的数据缓冲区发生了错位或字节序问题;c) Flash擦除不彻底。 - 检查启动标志:在更新前和
GAID复位后,都读取并打印详细的启动标志寄存器(0x2D)。关注BootOk、Region0/1Invalid、Region0/1CrcFail、Region0/1FlashErr这些位。它们能明确指出是哪个区域出了问题,以及问题的类型(CRC错误、Flash硬件错误等)。 - 核对区域指针:
FLrr命令读回的指针值是否正确?对于Region 0应该是0x2000,对于Region 1应该是0x20000。如果指针错误,后续的FLad和FLwd操作就会写到错误的Flash地址。 - 确认复位成功:
GAID命令发送后,TPS6598x会经历一个完整的硬件复位序列,耗时可能超过100ms。确保EC在发送GAID后,等待足够长的时间(建议500ms-1s)再尝试通过I2C访问它。在此期间,EC应忽略I2C总线上的NACK错误。 - 电源稳定性:Flash写入和擦除对电源电压有要求。确保在更新过程中,给TPS6598x和SPI Flash的供电稳定、无毛刺。特别是使用电池供电或动态调压的系统,要避免在更新过程中进入低功耗模式。
- 验证Flash写入:在发送
5.4 针对TPS65982D的特定问题
TPS65982D作为带集成Patch存储器的版本,其Flash布局和部分寄存器定义与标准TPS65982不同。
- 最易错点:扇区数量。82D的Region大小是12KB(8KB代码+4KB头),只需要擦除3个扇区。如果错误地设置为17,
FLem命令会擦除远超Region范围的Flash空间,可能破坏其他重要数据(如Patch Bundle),导致设备永久性故障。 - 数据结构对齐:修改
tBootFlags82结构体时,确保位域的排列顺序与你的编译器默认对齐方式一致。最好使用编译器相关的#pragma pack指令确保结构体是紧凑的1字节对齐,避免因对齐问题导致位域映射错乱。 - Patch Bundle:82D的更新流程主要针对Application区域。对于Patch Bundle的更新,TI有另外的流程和命令,不要与本文所述的Application FW更新流程混淆。
5.5 性能与优化建议
- 更新耗时:一次完整的双区更新(写入2*68KB),以每次64字节、50ms延时计算,仅
FLwd写入时间就需要1088 * 2 * 50ms ≈ 109秒,加上擦除、验证、复位等时间,总流程可能接近2分钟。在产品设计中,需要告知用户更新过程耗时较长,并确保在此期间系统供电稳定。 - 流式更新:为了减少EC的内存占用,可以实现流式更新。即EC一边从主机接收
low-region.bin的数据块,一边执行FLwd写入,而不需要在EC端缓存整个68KB文件。 - 错误恢复与断点续传:对于可靠性要求极高的系统,可以设计更复杂的错误恢复机制。例如,在每次
FLwd循环中记录当前写入的偏移量。如果更新过程因断电中断,重新上电后可以先读取Flash中的特定标记,判断上次更新进行到哪一步,然后从断点处继续,而不是从头开始。这需要额外的元数据管理,但能极大提升用户体验。
6. 总结与延伸思考
通过I2C接口为TPS6598x进行固件更新,是一个涉及硬件接口、通信协议、Flash操作和状态机设计的综合性任务。TI提供的示例代码是一个很好的起点,但它更像一个“实验室原型”。要将它转化为产品中可靠的OTA更新功能,你需要像一位严谨的外科医生,在理解其每一处解剖结构的基础上,完成精密的移植手术。
从我个人的经验来看,成功的关键在于分阶段验证和详尽的日志。不要试图一次性让整个流程跑通。从最简单的I2C读版本号开始,逐步测试每个4CC命令,再用假数据测试写入流程,最后才用真数据做全流程更新。在每个关键节点,通过UART或日志系统输出足够多的状态信息(当前步骤、命令返回值、寄存器内容),这样当问题出现时,你才能快速定位。
最后,请务必记住,固件更新功能一旦出厂就无法撤回。在量产前,必须进行海量的、在各种边界条件下的测试:低压测试、高温低温测试、快速连续更新测试、更新过程中模拟断电测试等等。确保你的更新代码像磐石一样稳固,因为它守护的是产品在用户手中的“生命线”。