尧图精选

FUSB302 PD Sink协议开发实战:状态机设计、代码架构与调试技巧

🕒 发布时间:2026/9/1 5:32:54 📁 来源:尧图网络
简介一份面向嵌入式开发者的USB供电协议PD可运行源码示例围绕FUSB302芯片完整实现PD1.0与PD2.0的初始化、通信流程和电源协商适用于充电器、受电设备及Type-C电源适配器等原型验证可解决在FUSB302上快速集成PD协议的实际难题。代码包含器件初始化、CC引脚状态检测、FIFO缓冲区读写、消息ID管理以及源能力、请求、接受等关键消息的解析与回应并支持通过按键在PPS模式下对输出电压进行1V、100mV和20mV三档精细调节代码层次分明、处理逻辑完整可直接用于快速搭建PD充电方案。压缩包共2个文件以HTML说明文档和可运行的代码工程文件inscode为主整体仅3KB轻量且结构清晰便于复制到实际项目中二次开发。目前已有281人浏览学习适合需要快速集成USB PD快充功能的软硬件工程师也适合想了解Type-C接口电压协商细节的开发者。 FUSB302这颗芯片在PD协议开发圈子里口碑一直不错体积小、外围简单、价格也不算贵最关键的是它把BMC物理层收发都做进内部了MCU只需要通过I2C读写寄存器就能完成PD通信。我前阵子基于FUSB302做了一套PD Sink的示例工程源码可以编译运行的那种今天就把代码架构、状态机设计、实操流程和踩过的坑一起整理出来给正在搞PD协议开发的朋友当个参考。这篇内容适合手里有FUSB302或者类似Type-C控制器比如FUSB307B、TUSB320的朋友看也适合刚接触PD协议、想用单片机把PD跑起来的人。即使你没有FUSB302的板子理解这套代码的思路对你后面看官方协议栈也会有很大帮助。1. FUSB302与PD协议为什么选这个组合1.1 搞懂FUSB302这颗芯片到底帮你干了什么FUSB302是安森美推出的一款USB Type-C控制器支持DFP主机/电源提供方、UFP设备/电源接收方和DRP双角色端口三种模式。PD协议最复杂的部分其实是物理层BMC编码、5-bit 4B/5B转换、CRC校验、GoodCRC的硬件自动应答这些如果全靠单片机软件实现不仅占资源时序也容易出问题。FUSB302把这一大块硬件化之后MCU的压力就小很多。具体来说FUSB302内部集成了BMC收发器、SOP/SOP/SOP报文过滤、CRC硬件计算和GoodCRC自动回复功能。你要做的就是通过I2C往FIFO里写发送数据、从FIFO里读接收数据然后把状态机跑好。说通俗一点FUSB302就是把“电话线那头的调制解调器”给做好了你要做的是学会“怎么打电话、怎么说标准化的话”。I2C从机地址默认是0x227位地址内部寄存器不多核心几个是DeviceID0x01、Power0x03、Control0x06、Status0x0A/0x0B、FIFO0x43、Interrupt0x0C-0x0E等。我做的示例代码就是围绕这几个寄存器展开的。1.2 PD协议里的几个关键概念必须先理清楚PD协议按报文内容划分最常用的是控制消息Control Message和数据消息Data Message。控制消息是15位CRC后的固定32位消息头数据消息则带扩展数据。在我们做Sink时打交道最多的是下面这几条Source Capabilities0x80由Source端比如充电器广播包含PDO列表告诉你可以提供哪些电压电流组合。Request0x82Sink端发出你要向Source申请哪个PDO以及具体电流。Accept0x83Source回复表示接受你的请求。PS_RDY0x86Source电源切换完成此时才开始正式输出电压。GoodCRC0x01接收方收到消息后回ACKFUSB302硬件自动处理不需要软件干预。PD消息的基础结构是“消息头 数据 CRC”在FUSB302内部你往FIFO写的时候只需要写消息头和数据部分CRC由芯片自动计算。SOP类型选择也是通过寄存器控制比如普通SOP用于Source/Sink之间的通信SOP和SOP是跟线缆通信的日常开发用不到那么多。1.3 为什么不用带MCU的PD方案偏要FUSB302单片机市面上像LDR6282这类芯片内置了协议栈外部MCU只需要通过串口发命令就行开发确实快。但FUSB302的方案给了你完全的控制权能让你真正理解PD协议同时灵活性高比如你想自定义一些PD策略动态调整请求电流、根据温度降功率等内置协议栈不方便改FUSB302就随便折腾。我打个比方LDR6282就像是一辆自动挡汽车你踩油门就走FUSB302就是手动挡你不仅要会踩油门还得会换挡。刚开始麻烦但上手之后你能精准控制每个细节。而且FUSB302这颗料也在不少量产的快充触发线、PD诱骗取电模块里用学习价值很高。2. 示例代码整体架构与核心状态机设计2.1 代码文件怎么划分模块怎么组织我这份示例代码按功能拆成四个文件加上一个主程序入口|-- fusb302.h / fusb302.c // 芯片驱动层I2C读写、寄存器操作、FIFO收发 |-- pd_protocol.h / pd_protocol.c // PD协议层消息解析、状态机、PDO处理 |-- pd_sink.h / pd_sink.c // 策略层当前请求的PDO编号、请求电流 |-- main.c // 业务层初始化、主循环、回调处理这样分层的核心逻辑是驱动层管硬件、协议层管状态、策略层管决策、业务层管交互。如果以后换芯片只需要重写fusb302.c上面的协议层和策略层都能保留如果从Sink改成Source也只需要改协议层的状态机分支。2.2 PD状态机的几个核心状态Sink侧的PD状态机我用了一个典型的状态枚举typedef enum { PD_STATE_ATTACHED_WAIT, // 检测到CC连接等待通信 PD_STATE_GET_SOURCE_CAP, // 等待Source发送能力报文 PD_STATE_NEGOTIATE, // 发送Request请求所需功率 PD_STATE_TRANSITION, // 等待Source切换电压 PD_STATE_READY, // 电源稳定正常运行 PD_STATE_SEND_SOFT_RESET, // 异常复位 PD_STATE_ERROR // 错误状态 } pd_state_t;实际运行中90%的时间都在PD_STATE_READY里待着。状态机跳转的触发条件有两个一个是收到FUSB302中断有消息进来另一个是超时定时器等待回复超时。2.3 状态机为什么不用RTOS裸机足够我见过不少人在单片机上跑PD协议时喜欢上RTOS其实PD通信是严格的事件驱动模型消息到来由中断通知处理完就休眠等待。裸机主循环完全可以胜任还省了任务同步的麻烦。我的工程里就是用一个全局标志位记录当前状态机状态主循环里不断查询是否有中断标志有就进入事件处理流程。这个方案实测在48MHz的STM32F103上跑都没有任何压力。有一个点要注意状态机执行过程中不能有阻塞式延迟。比如你发送Request之后要等Accept如果用HAL_Delay(500)这种写法在这500ms内FUSB302收到消息触发的中断会被挂起轻则超时重传重则Source侧认为通信异常直接断开。正确做法是用一个5ms周期的定时器递增超时计数在超时时间内每次主循环轮询检查是否收到了新消息。3. 从初始化到PD握手完整实操流程与关键代码3.1 初始化FUSB302寄存器配置这一步很关键上电之后第一步是复位芯片然后配置关键寄存器。下面这段是我在工程里实际用的初始化代码做了简化说明void fusb302_init(void) { // 1. 复位芯片 i2c_write_reg(FUSB302_REG_RESET, 0x01); // SW_RESET ms_delay(10); // 2. 进入Sink模式UFP使能BCLK和测量 i2c_write_reg(FUSB302_REG_POWER, 0x01); // 使能接收器 // 3. 配置Control寄存器 // - 自动GoodCRCEN_GOODCRC1 // - 使能SOP类型接收SOP1, SOP0, SOP0 // - 发送模式SOP i2c_write_reg(FUSB302_REG_CONTROL0, 0x44); // 自动GoodCRC 使能SOP i2c_write_reg(FUSB302_REG_CONTROL1, 0x00); // 禁止SOP/SOP i2c_write_reg(FUSB302_REG_CONTROL3, 0x02); // 发送SOP类型 // 4. 使能我们需要的中断源 // 重点I_TOGDONE发送完成、I_RETRYFAIL重试失败、I_SOP_RX收到SOP i2c_write_reg(FUSB302_REG_MASK0, 0x83); // 5. 清空FIFO fusb302_flush_fifo(); // 6. 开启CC检测上拉Sink模式内部5.1k下拉在芯片内部 // 外部电路用电阻也可以取决于硬件设计 i2c_write_reg(FUSB302_REG_SWITCHES0, 0x02); }其中CONTROL0的0x44是EN_GOODCRCINT_MASK的合理组合让FUSB302在收到消息后自动回GoodCRC并且在收到SOP时给MCU产生中断。这一步要是配错了可能会出现“消息能发出去但收不到回复”的诡异问题后面排查时很头疼。3.2 发送Request消息往FIFO写数据的姿势有讲究PD 3.0的消息头结构是16位数据部分最多28字节。发送消息时先写消息头再写数据最后FUSB302自动算CRC并发送。这里给出发送一条Request消息的核心实现void pd_send_request(uint8_t pdo_num, uint16_t current_ma) { uint8_t buf[8]; // 消息头(2字节) Request Data Object(4字节) // 消息头消息类型Request(0x82)消息长度1方向0端口号0数据角色0 uint16_t header (0x01 12) | (PD_MSG_REQUEST 8) | 0x00; buf[0] header 0xFF; buf[1] (header 8) 0xFF; // Request Data Object // bit[28:26] PDO编号bit[25:20] 电流(单位10mA) uint32_t rdo ((pdo_num 0x07) 28) | ((current_ma / 10) 20); buf[2] rdo 0xFF; buf[3] (rdo 8) 0xFF; buf[4] (rdo 16) 0xFF; buf[5] (rdo 24) 0xFF; fusb302_write_fifo(buf, 6); }注意写FIFO之前一定要先通过SWITCHES0寄存器确认发送器的SOP类型同时要确保上一包消息已经发送完成通过I_TOGDONE中断标志或轮询STATUS1寄存器不然FIFO写进去的顺序会乱掉。3.3 完整PD握手流程用600ms完成一次协商从检测到CC连接到进入稳定供电整个协商流程大概是这样的FUSB302检测到CC上有电压Source端通过Rp电阻上拉触发VBUS_OK或者CC_CNG中断MCU收到后进入ATTACHED_WAIT状态。Source端芯片会在连接后约100ms内发送Source Capabilities消息FUSB302收到后产生I_SOP_RX中断。MCU从FIFO读回这条消息解析出PDO数量与各PDO的电压电流范围。MCU根据预设策略选一个合适的PDO发送Request消息。Source收到后回复AcceptFUSB302硬件的自动GoodCRC会在物理层直接应答MCU在逻辑层收到Accept消息会产生新的I_SOP_RX中断。Source开始调整电压完成后发PS_RDY消息。MCU收到PS_RDY后等待100ms让VBUS稳定然后切到READY状态。之后就是每30~40ms收发一次维持消息或者根据需求动态调整请求。从我的示波器抓取的数据来看在无重传的正常情况下从CC连接成功到PS_RDY收到整个协商时长在80ms~150ms之间。如果Source端延迟响应或者有重传最长能到400ms这也是USB-IF规范中允许的范围。3.4 不要忽视I2C速度与FIFO读取的时序配合FUSB302支持最高1MHz的I2C时钟但我实测在400kHz下最稳。I2C速度太快在长线缆或者布局不佳的板子上容易出现数据错位尤其是读取FIFO时一个字节错位后面解析整包就废了。读取FIFO的代码我建议每读完一个字节就检查一次STATUS1寄存器中的RX_EMPTY位确认FIFO里还有数据再继续读。不要一次性阻塞读否则可能多读一个字节把下一包消息的头给吃掉。uint8_t fusb302_read_fifo(void) { // 从FIFO读一字节 uint8_t data; i2c_read_reg(FUSB302_REG_FIFO, data); // 检查FIFO是否已空 uint8_t status; i2c_read_reg(FUSB302_REG_STATUS1, status); if (status 0x01) { // RX_EMPTY1 // FIFO空了最后清一次中断标志 i2c_write_reg(FUSB302_REG_INTERRUPT0, 0x00); fusb302_flush_fifo(); } return data; }4. 常见问题排查与调试技巧实录4.1 这个问题速查表我踩过的基本都在里面了调试PD协议的过程中我整理了一份问题对照表几乎每个都实际踩过现象可能原因解决办法收不到Source CapabilitiesCC下拉电阻没接好或者Type-C检测模式配置错误检查SWITCHES0设置确认UFP模式下拉使能一直收到GoodCRC但等不到AcceptRequest消息的RDO格式错误PDO编号越界用逻辑分析仪抓包核对消息头与RDO字段Source回复拒绝reject请求电压电流超出Source能力范围解析PDO时打印全部电压对照选择合适档位偶发性收包失败CRC错误多I2C时序不稳定或FIFO读取时序不正确I2C降速到400kHz检查接收函数非阻塞逻辑软复位后协商失败Soft Reset后Source不会主动重发Source Capabilities在软复位后主动发送Get Source Capabilities消息PS_RDY之后电压没变化硬件VBUS通路开关比如MOS管没有打开检查VBUS相关GPIO配置确认在PS_RDY后拉高4.2 没有USB协议分析仪用逻辑分析仪也能凑合抓包USB-IF的PD分析仪动辄几千块个人开发者不一定舍得买。我的做法是用一个24MHz采样率的逻辑分析仪直接量FUSb302的CC引脚Source端到FUSB302的CC线然后用PulseView的UART解码器按BMC格式解码。BMC信号的波特率其实没有标准的UART那么好解但PD协议规定BMC信号速率是300kbpsFast BPSK是1Mbps你可以在PulseView里把CC引脚配置成UART RX波特率设置为300000数据位8无校验停止位1。这样抓出来的数据虽然带很多噪声但能看清楚消息头是什么、CRC是不是一致。经验如果逻辑分析仪解码出来的字节全是0xFF或者0x00大概率是你接错线了CC1和CC2要选对Source的输出方向要确认清楚。4.3 三个容易忽略的细节写代码时多留个心眼第一GoodCRC的硬件自动应答并非总是开启的。在刚复位之后FUSB302的CONTROL0寄存器默认可能没有设置EN_GOODCRC如果这时候Source发来第一条消息你不回GoodCRCSource会认为链路不通导致后续所有协商都无法进行。初始化时一定要确认这个位是置1的。第二Source Capabilities消息里可能会有多个PDO电压不是单调递增的比如PPS可编程电源的PDO是一个电压范围。如果你的策略只是“选第一个5V档”那没问题但如果要做PPS调压需要解析APDO的可调电压区间再用RDO里的相关字段去请求。这部分逻辑我已经在pd_sink.c里做了示例策略上默认选最大功率档位。第三PD协议在空闲时有个维持机制Source在10秒内没有收到Sink的任何消息会自动关闭输出。如果不想一直发送维持消息可以在进入READY后通过“DPM请求Get Status”或“发送Hard Reset”之外的方式保持连接但最稳妥的做法还是定期发一个维持消息比如每4秒发一次。我在示例代码里加了一个2秒周期的定时器进入READY后自动发送维持消息实测连续跑24小时没有掉线。4.4 软复位流程这一块很多参考代码都没写清楚当协议层出现连续超时或者CRC重试失败最优雅的恢复手段就是发起Soft Reset。具体操作是发送一条Soft Reset控制消息消息类型0x0D然后把状态机切回GET_SOURCE_CAP等待Source重新发能力报文。这里有个细节发起Soft Reset之后FUSB302的FIFO里可能残留之前的消息如果不清空等新消息到来时会跟旧数据混在一起导致报文解析错位。所以Soft Reset状态入口处我特意加了fusb302_flush_fifo()和重新使能接收器这两步。这些看起来是小地方但真正出问题时能省你半天调试时间。在代码里我还预留了PD_DEBUG_ENABLE开关开启后通过串口打印每个状态迁移和每一条收发消息的十六进制内容。调试新硬件的时候建议默认打开这个开关等量产版本再关掉。做PD这个项目最有意思的地方在于你看着自己写的代码跟充电器完成了一次功率协商然后再看到电压从5V稳稳切到20V那种成就感很难用语言形容。这套示例代码我后续还会继续维护打算加上PPS模式的支持和更完整的错误恢复策略。如果你也在调FUSB302或者其它Type-C控制器遇到问题可以在评论区留下具体现象和寄存器配置我们互相验证一下。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联 返回资讯列表 →