3、数字电源通信协议:PMBus协议基础、I2C通信、SMBus协议、PMBus命令集
说到数字电源,通信协议是绕不开的坎儿。你想想看,电源芯片怎么知道要输出3.3V还是1.8V?怎么告诉你它现在温度过高了?全靠这根通信线。
我刚开始接触数字电源时,总觉得协议这东西太抽象。后来在项目里调了几个月,才摸清楚门道。说白了,PMBus、I2C、SMBus这三者的关系,就像普通话、汉语拼音和方言——有继承,有扩展,各有各的用处。
一句话总结:PMBus 是电源管理的“普通话”,I2C 是物理层的“电线”,SMBus 是 I2C 的“加强版方言”。
3.1 I2C 通信——物理层的“两根线”
I2C 是飞利浦(现在的恩智浦)在80年代搞出来的。就两根线:SCL(时钟)和 SDA(数据)。一根线上能挂几十个设备,靠地址区分。
我记得第一次用示波器抓 I2C 波形时,看到那串高低电平,完全不知道在说什么。后来师傅告诉我:你就盯着 SCL,时钟来了就看 SDA 是高是低,就这么简单。
I2C 的基本传输单元是“字节”:
- 起始条件:SCL 高电平时,SDA 从高变低
- 每个字节 8 位,后面跟一个 ACK/NACK 位
- 停止条件:SCL 高电平时,SDA 从低变高
- 标准模式 100kHz,快速模式 400kHz
我的经验:调试 I2C 时,先看起始条件和停止条件对不对。很多通信失败,都是因为这两个条件没满足。我曾经花了一整天,最后发现是上拉电阻焊错了——4.7kΩ 换成了 47kΩ,信号根本拉不起来。
3.2 SMBus 协议——I2C 的“工业加强版”
SMBus 是 Intel 在 1995 年搞出来的,基于 I2C 但加了不少规矩。说白了,I2C 太自由了,工业场合需要更严格的约束。
SMBus 和 I2C 的核心区别:
| 特性 | I2C | SMBus |
|---|---|---|
| 时钟频率 | 100kHz / 400kHz / 1MHz+ | 10kHz ~ 100kHz |
| 超时机制 | 无 | 35ms 超时(必须) |
| 最小时钟低电平 | 无要求 | 4.7μs |
| 地址类型 | 7位 / 10位 | 7位(保留地址更多) |
| 协议包格式 | 自由 | 固定格式(SMBus 2.0) |
嗯,这里要注意。SMBus 的超时机制特别重要。如果从设备卡住了,35ms 后主设备会自动释放总线。I2C 没有这个机制,一旦死锁就只能硬件复位。
我在一个项目中就吃过这个亏。用 I2C 去读一个电源模块,结果模块偶尔会不响应 ACK,总线就卡死了。后来换成 SMBus 模式,加了超时处理,问题就解决了。
SMBus 常用的几种传输协议:
- 快速命令(Quick Command):只发送地址和 R/W 位,用于简单的开关控制
- 发送字节(Send Byte):主设备发一个字节给从设备
- 接收字节(Receive Byte):主设备从从设备读一个字节
- 写字节/字(Write Byte/Word):先发命令码,再发数据
- 读字节/字(Read Byte/Word):先发命令码,再重复起始条件读数据
- 块读写(Block Read/Write):一次传输最多 32 字节
避坑指南:我曾经在块读取时没注意长度字节,结果读回来的数据全是乱的。SMBus 块读的第一个字节是数据长度,后面才是有效数据。这个长度不包括它自己,也不包括 PEC(包错误校验)。
3.3 PMBus 协议——电源管理的“标准语言”
PMBus 是 2005 年由 PMBus 联盟推出的,专门为电源管理设计。它建立在 SMBus 之上,定义了一套标准的命令集。
你想想看,如果没有 PMBus,每个电源芯片厂商都用自己的命令,那工程师得学多少种协议?PMBus 统一了这些,让不同厂家的电源模块可以互相替换。
PMBus 的协议分层:
- 物理层:基于 SMBus(兼容 I2C)
- 传输层:SMBus 协议包格式
- 命令层:PMBus 标准命令集 + 厂商扩展命令
3.4 PMBus 命令集——你需要的都在这里
PMBus 命令集分为几大类。我挑最常用的给你讲,这些在项目里天天见。
核心命令分类:
| 类别 | 命令码 | 功能 | 读写 |
|---|---|---|---|
| 输出电压 | 0x21 | VOUT_COMMAND | R/W |
| 输出电压 | 0x22 | VOUT_TRIM | R/W |
| 输出电流 | 0x8B | IOUT_OC_FAULT_LIMIT | R/W |
| 温度 | 0x8D | OT_FAULT_LIMIT | R/W |
| 状态 | 0x79 | STATUS_WORD | R |
| 状态 | 0x7A | STATUS_VOUT | R |
| 状态 | 0x7B | STATUS_IOUT | R |
| 状态 | 0x7C | STATUS_TEMPERATURE | R |
| 配置 | 0x20 | VOUT_MODE | R/W |
| 配置 | 0x01 | OPERATION | R/W |
| 配置 | 0x02 | ON_OFF_CONFIG | R/W |
实际操作示例:设置输出电压为 3.3V
假设电源模块的 VOUT_MODE 设置为线性模式,指数为 -13。那么 3.3V 对应的数据怎么算?
// 线性模式公式:Vout = 数据值 × 2^指数
// 指数 = -13,Vout = 3.3V
// 数据值 = Vout / 2^(-13) = 3.3 × 8192 = 27033.6
// 取整为 27034 = 0x699A
// I2C/SMBus 写序列(地址 0x58):
// 起始条件
// 发送地址 0xB0(0x58 << 1 | 0 写)
// 等待 ACK
// 发送命令码 0x21(VOUT_COMMAND)
// 等待 ACK
// 发送数据低字节 0x9A
// 等待 ACK
// 发送数据高字节 0x69
// 等待 ACK
// 停止条件
我的习惯:写 PMBus 命令时,先读一下 VOUT_MODE(0x20),确认指数值。不同厂商的默认指数可能不一样。我遇到过一块模块指数是 -12,另一块是 -13,用同一个数据写进去,输出电压差了整整一倍。
状态读取——故障排查的第一步:
当电源出问题时,第一个要读的就是 STATUS_WORD(0x79)。它返回两个字节,包含了主要的故障信息。
// STATUS_WORD 位定义(高字节):
// Bit 15: VOUT 过压/欠压故障
// Bit 14: IOUT 过流故障
// Bit 13: 输入电压故障
// Bit 12: 温度故障
// Bit 11: 通信故障
// Bit 10: 保留
// Bit 9: 厂商自定义
// Bit 8: 其他故障
// 读取示例(地址 0x58):
// 起始条件
// 发送地址 0xB0(写)
// 发送命令码 0x79
// 重复起始条件
// 发送地址 0xB1(读)
// 读取低字节(第一个字节)
// 发送 ACK
// 读取高字节(第二个字节)
// 发送 NACK
// 停止条件
我一般会先读 STATUS_WORD,如果发现某个位被置位,再读对应的详细状态寄存器。比如 VOUT 故障就读 STATUS_VOUT(0x7A),IOUT 故障就读 STATUS_IOUT(0x7B)。这样一层层查下去,问题基本都能定位。
注意:PMBus 的状态寄存器是“读后清零”的。也就是说,你读一次之后,故障位就自动清掉了。如果你在循环里反复读,可能只看到第一次的结果。我建议在故障发生时只读一次,记录结果,然后再做后续处理。
3.5 实际部署中的通信调试要点
说了这么多理论,来点实战的。我在现场调试 PMBus 时,遇到过各种奇奇怪怪的问题。
常见问题及排查方法:
- 通信超时:检查 SCL 和 SDA 的上拉电阻,一般 4.7kΩ 到 10kΩ 之间。线缆太长时,要降低速率。
- 地址冲突:PMBus 地址通常是 7 位,但有些模块支持多个地址。用地址扫描程序扫一遍,看哪些地址有响应。
- 命令无响应:先确认模块是否处于使能状态。有些模块在关闭状态下不响应命令。
- 数据格式错误:PMBus 支持线性、直接和 VID 三种数据格式。读 VOUT_MODE 确认当前格式。
- PEC 校验错误:如果启用了 PEC,每个传输都要计算 CRC-8。计算多项式是 x^8 + x^2 + x + 1(0x07)。
我的调试工具清单:
- 示波器(至少 2 通道,抓 SCL 和 SDA)
- I2C/SMBus 分析仪(推荐 Total Phase 的 Aardvark 或 Beagle)
- PMBus 命令发送工具(自己写脚本或用厂商提供的 GUI)
- 万用表(测上拉电阻电压,确认总线空闲状态)
嗯,最后说一句。PMBus 协议本身不复杂,复杂的是实际系统中的各种边界情况。多动手,多抓波形,慢慢就熟了。我刚开始时也是对着 datasheet 一行一行地读,后来调得多了,闭着眼睛都能写出读写函数。