TPS6598x USB PD控制器I2C固件更新原理与移植实战

发布时间:2026/7/24 3:16:04
TPS6598x USB PD控制器I2C固件更新原理与移植实战 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 MasterTPS6598x作为Slave它们之间的通信不仅仅是简单的寄存器读写更包含了一套命令-响应机制。这是整个更新流程的灵魂。TPS6598x预留了两个特殊的I2C寄存器对用于这种高级命令交互CMD1/DATA1寄存器对主命令接口地址为0x08和0x09。CMD2/DATA2寄存器对次命令接口地址为0x10和0x11。在大多数应用和本例中我们使用主接口CMD1/DATA1。所谓的“4CC”命令即Four-Character Command是一个由4个ASCII字符组成的命令字。例如擦除Flash区域的命令是FLemFlash Erase Memory。这套命令集可以理解为EC向TPS6598x发送的“工作指令”。其执行有严格的时序和步骤要求任何一步错漏都可能导致命令执行失败。一个完整的4CC命令执行流程如下写入输入数据将命令所需的参数如要擦除的起始地址、要写入的数据块通过I2C写入DATA1寄存器。发送命令将4CC命令的ASCII码如F,L,e,m写入CMD1寄存器。写入动作本身即触发TPS6598x开始执行该命令。轮询命令完成循环读取CMD1寄存器直到其值从命令字变为0x00000000。这表示命令执行完毕。如果读到!CMD0x21434D44则说明命令无效或格式错误。读取输出与状态从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。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的供电稳定、无毛刺。特别是使用电池供电或动态调压的系统要避免在更新过程中进入低功耗模式。5.4 针对TPS65982D的特定问题TPS65982D作为带集成Patch存储器的版本其Flash布局和部分寄存器定义与标准TPS65982不同。最易错点扇区数量。82D的Region大小是12KB8KB代码4KB头只需要擦除3个扇区。如果错误地设置为17FLem命令会擦除远超Region范围的Flash空间可能破坏其他重要数据如Patch Bundle导致设备永久性故障。数据结构对齐修改tBootFlags82结构体时确保位域的排列顺序与你的编译器默认对齐方式一致。最好使用编译器相关的#pragma pack指令确保结构体是紧凑的1字节对齐避免因对齐问题导致位域映射错乱。Patch Bundle82D的更新流程主要针对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或日志系统输出足够多的状态信息当前步骤、命令返回值、寄存器内容这样当问题出现时你才能快速定位。最后请务必记住固件更新功能一旦出厂就无法撤回。在量产前必须进行海量的、在各种边界条件下的测试低压测试、高温低温测试、快速连续更新测试、更新过程中模拟断电测试等等。确保你的更新代码像磐石一样稳固因为它守护的是产品在用户手中的“生命线”。