☰
FUSB302 PD Sink协议开发实战:状态机设计、代码架构与调试技巧
2026/10/9 20:55:15 网站建设 项目流程

简介:一份面向嵌入式开发者的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从机地址默认是0x22(7位地址),内部寄存器不多,核心几个是:DeviceID(0x01)、Power(0x03)、Control(0x06)、Status(0x0A/0x0B)、FIFO(0x43)、Interrupt(0x0C-0x0E)等。我做的示例代码就是围绕这几个寄存器展开的。

1.2 PD协议里的几个关键概念,必须先理清楚

PD协议按报文内容划分,最常用的是控制消息(Control Message)和数据消息(Data Message)。控制消息是15位CRC后的固定32位消息头,数据消息则带扩展数据。在我们做Sink时打交道最多的是下面这几条:

  • Source Capabilities(0x80):由Source端(比如充电器)广播,包含PDO列表,告诉你可以提供哪些电压电流组合。
  • Request(0x82):Sink端发出,你要向Source申请哪个PDO以及具体电流。
  • Accept(0x83):Source回复,表示接受你的请求。
  • PS_RDY(0x86):Source电源切换完成,此时才开始正式输出电压。
  • GoodCRC(0x01):接收方收到消息后回ACK,FUSB302硬件自动处理,不需要软件干预。

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寄存器 // - 自动GoodCRC:EN_GOODCRC=1 // - 使能SOP类型接收:SOP=1, SOP'=0, SOP''=0 // - 发送模式: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_GOODCRC+INT_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连接,到进入稳定供电,整个协商流程大概是这样的:

  1. FUSB302检测到CC上有电压(Source端通过Rp电阻上拉),触发VBUS_OK或者CC_CNG中断,MCU收到后进入ATTACHED_WAIT状态。
  2. Source端芯片会在连接后约100ms内发送Source Capabilities消息,FUSB302收到后产生I_SOP_RX中断。
  3. MCU从FIFO读回这条消息,解析出PDO数量与各PDO的电压电流范围。
  4. MCU根据预设策略选一个合适的PDO,发送Request消息。
  5. Source收到后回复Accept,FUSB302硬件的自动GoodCRC会在物理层直接应答,MCU在逻辑层收到Accept消息(会产生新的I_SOP_RX中断)。
  6. Source开始调整电压,完成后发PS_RDY消息。MCU收到PS_RDY后,等待100ms让VBUS稳定,然后切到READY状态。
  7. 之后就是每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_EMPTY=1 // 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信号速率是300kbps(Fast BPSK是1Mbps),你可以在PulseView里把CC引脚配置成UART RX,波特率设置为300000,数据位8,无校验,停止位1。这样抓出来的数据虽然带很多噪声,但能看清楚消息头是什么、CRC是不是一致。

经验:如果逻辑分析仪解码出来的字节全是0xFF或者0x00,大概率是你接错线了,CC1和CC2要选对,Source的输出方向要确认清楚。

4.3 三个容易忽略的细节,写代码时多留个心眼

第一,GoodCRC的硬件自动应答并非总是开启的。在刚复位之后,FUSB302的CONTROL0寄存器默认可能没有设置EN_GOODCRC,如果这时候Source发来第一条消息,你不回GoodCRC,Source会认为链路不通,导致后续所有协商都无法进行。初始化时一定要确认这个位是置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控制器,遇到问题可以在评论区留下具体现象和寄存器配置,我们互相验证一下。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询