ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

STM32 SMBus实战:I2C差异、超时复位与PEC寄存器排错

STM32 SMBus实战:I2C差异、超时复位与PEC寄存器排错

前阵子帮人返工一块小板子,主控是 STM32,总线上挂了颗电池管理芯片和一个温度传感器。代码是从另一个项目直接拷过来的标准 I2C 驱动,写寄存器一切正常,读数据却时不时返回 0xFFFF,跑上十几分钟还会整个总线卡死、BUSY 位置起来再也不掉。换芯片、换板子、改地址、换上拉,折腾了整整两天,最后拿逻辑分析仪一测才发现两件事:一是模块自带的 10 kΩ 上拉把上升沿拖到了 900 ns 左右,二是从机在被拉低超过 25 ms 之后自己把内部状态机复位了——这两条都是SMBus的硬性规定,而标准 I2C 代码里根本没有对应的处理。

SMBus 这个东西,本质上是 I2C 的一个"加了规矩的子集":电气参数被钉死、时序窗口被限制、还强制要求 CRC 校验和超时复位。它出现的场景很集中——笔记本电池、服务器电源管理、温度/电压监控、内存条上的 SPD、各种板级管理芯片。这些场合的共同点是"必须能自愈",一条挂在机箱里的总线,不能因为某一个器件抽风就把整个系统拖死,所以 SMBus 才把超时和 PEC 写进了规范。适合读这篇的人大概分三类:手上正好有一颗 SMBus 器件要接 STM32 的;I2C 代码能跑但偶发出错想搞清楚原因的;以及想弄明白 STM32 的 I2C 外设里那几个 SMBUS、SMBTYPE、ENPEC、ENARP 位到底是干什么的。

下面我按"差异分析—寄存器推导—协议层实现—排错—实测验收"的顺序走一遍,代码以 STM32F1 的寄存器操作为主线,其他系列(F0/F3/F4/L4/G0 等)思路完全一致,只是超时寄存器和部分位名有差别,我会在对应位置标出来。

1. SMBus 与 I2C 的四处硬差异:不搞清这些配置必然翻车

很多人以为 SMBus 就是"I2C 加个名字",把 I2C 跑通就相当于支持 SMBus 了。实际调试下来,出问题的几乎全部集中在下面四组差异上,而且这四组差异是互相耦合的:电气参数不对会拖慢边沿,边沿慢了从机就更容易被超时复位,超时复位之后地址不应答,你就开始怀疑地址配错了。

1.1 一张表看清哪些参数被"钉死"了

先把结论摆出来,下面这张表是我自己调试时贴在工位上的那版,只保留了对写代码和画板子有影响的行:

项目I2C 标准模式SMBus 2.0实际影响
时钟频率范围无下限,可直流停住10 kHz ~ 100 kHz不能为了省事把 SCL 调到 1 kHz 去"看清楚波形"
逻辑门限0.3·VDD / 0.7·VDD(比例式)VIL ≤ 0.8 V,VIH ≥ 2.1 V(固定)1.8 V 器件直接接 3.3 V SMBus 有风险
上升/下降沿≤ 1000 ns≤ 300 ns上拉电阻必须重算,10 kΩ 通常不合格
总线超时无规定SCL 低/高电平超过 25~35 ms 视为异常STM32 要开超时检测,软件要有复位链路
数据校验无标准算法PEC,CRC-8,多项式 0x07硬件 ENPEC 或软件算都行,但必须一致
地址应答从机可不应答除"忙"以外必须应答收到 AF 基本可以判定是地址/电气问题
中断线无SMBALERT# + ARA(0x0C)可选,但接上之后能省掉大量轮询

表格里最容易被忽略的是"固定门限"和"300 ns 上升沿"这两条,因为它们不在代码里,在硬件上。我见过太多次把 1.8 V 的传感器直接挂到 3.3 V 总线上,代码层面完全看不出问题,示波器上看波形也挺漂亮,但就是偶发不应答——原因就是 1.8 V 器件的高电平输出达不到 SMBus 要求的 2.1 V VIH。这种情况必须加电平转换,不要指望"上拉拽上去",SMBus 是开漏总线,高电平完全由上拉电阻提供,器件自己不驱动高电平。

1.2 10 kHz 下限:为什么不能用低速"慢慢读"

SMBus 规定 SCL 最低 10 kHz,这个规定乍看莫名其妙,其实和超时机制是配套的:从机要靠"SCL 长时间不动"来判断主机是不是挂了,如果允许主机用 1 kHz 慢慢磨,那 25 ms 的超时窗口就完全失去意义了。所以 10 kHz 下限的真正含义是——总线上任何一个字节都必须在有限时间内传完。

实测中这条限制有两种踩法。第一种是为了兼容长线缆,把 CCR 调大让 SCL 降到 5 kHz,结果某些严格的电池管理芯片直接不应答;第二种是在软件里手动翻转 GPIO 模拟时序(bit-banging),翻转间隔随手写了 200 µs,等效频率 2.5 kHz,同样不合规。真要做软件模拟,也得保证半周期不超过 50 µs,也就是整体不低于 10 kHz。

1.3 25 ms 超时:STM32 各系列的实现差别很大

超时是 SMBus 和 I2C 差别里最"隐蔽"的一条。它的表现不是通信报错,而是从机悄悄把你的连接断掉——主机还在等 ACK,从机已经复位回空闲状态,于是你收到一个 NACK 或者干脆读到全 1。

STM32 各系列在这块的硬件支持并不一样:

  • F1 系列:I2C 外设没有可编程的超时寄存器,SMBus 模式下由硬件做固定窗口的超时检测,超时后在 SR1 里置 TIMEOUT 标志(配合 ITERREN 可以进中断)。也就是说你只能"知道"超时了,不能改窗口大小。
  • F0/F3/F4/L4/G0 等:有独立的I2C_TIMEOUTR寄存器,可以分别使能"总线空闲超时"和"SCL 低电平超时",并自行设定计数值。

TIMEOUTR的计数单位是 2048 个外设时钟周期,公式是:

t_timeout = (TIMEOUT + 1) × 2048 × T_pclk

以 F4 的 I2C1 为例,APB1 = 42 MHz,T_pclk = 23.8 ns,那么 2048 × 23.8 ns ≈ 48.8 µs,想得到 25 ms 的超时窗口:

TIMEOUT = 25 ms / 48.8 µs - 1 ≈ 511

填 0x1FF 就差不多了。填完之后记得把TIMEOUTEN(总线超时使能)置 1,需要检测 SCL 被拉死的情况还要打开TEXTEN(扩展超时使能)。

注意:超时检测只能"报告",不能"修复"。真正的自愈要靠软件——检测到 TIMEOUT 之后,必须走一次总线恢复流程(本文第 3.4 节),否则下一次通信还是照样失败。

1.4 PEC:不是可选装饰,是协议的一部分

PEC(Packet Error Code)就是一个 CRC-8,x^8 + x^2 + x + 1,即多项式 0x07,初值 0x00,MSB 优先,不做反转也不做最终异或。关键在于计算范围:从起始条件之后的第一个地址字节开始,一直到最后一个数据字节,全部参与计算,包括地址字节本身,也包括读操作里那个"重复起始 + 地址+R"。

这一点是新手最容易算错的地方。举个具体例子,读一个字节(Read Byte)时,参与 PEC 计算的字节序列是:

[addr<<1 | 0] → [命令字] → [addr<<1 | 1] → [数据字节]

而不是只有数据字节。我第一次实现的时候只把数据丢进 CRC,对了几十次都对不上,后来把地址字节补进去立刻就对了。

2. 从时钟树到寄存器:STM32 侧配置的完整推导

搞清楚差异之后,配置就变成了一道算术题。这一节把 CCR、TRISE、CR1 里那几个位逐个拆开,给出可以直接抄的数字,并且说明为什么这么算。

2.1 CCR 与 TRISE 的算法和一组现成数字

STM32 标准模式下DUTY位为 0 时,高电平和低电平时间是相等的,都等于CCR × T_pclk,所以:

CCR = f_pclk / (2 × f_SCL)

TRISE寄存器填的是"允许的最大上升沿时间",单位是外设时钟周期:

TRISE = t_r_max / T_pclk + 1

I2C 标准模式允许 1000 ns 的上升沿,所以 ST 的 HAL 库统一按FREQ + 1来填;但 SMBus 要求 300 ns,必须自己覆盖掉。常用的几组值我列在下面,可以直接查:

PCLK1目标 SCLCCRTRISE(SMBus,300 ns)
8 MHz100 kHz403
36 MHz100 kHz18011
42 MHz100 kHz21013
48 MHz100 kHz24015
36 MHz50 kHz36011

顺带说一句上拉电阻怎么算,因为这事和TRISE是一体的。SMBus 的 300 ns 上升沿上限,配合总线电容Cb,可以反推出电阻上限:

Rp ≤ t_r / (0.8473 × Cb)

假设一条短板上总线电容 100 pF:

Rp ≤ 300 ns / (0.8473 × 100 pF) ≈ 3.5 kΩ

下限则由 VOL 决定,3.3 V 供电、VOL 最大 0.4 V、灌电流 3 mA:

Rp ≥ (3.3 - 0.4) / 3 mA ≈ 970 Ω

所以 3.3 V 系统、100 pF 负载下,1.8 kΩ ~ 2.2 kΩ是比较舒服的取值。而市面上大量传感器模块自带的 10 kΩ 上拉,按上面的公式算下来上升沿约 850 ns,已经超出 SMBus 上限近三倍——这就是我开头那块板子出问题的直接原因。把模块上的上拉拆掉或者并联一个 2.2 kΩ,故障率立刻降下来。

2.2 CR1 里那几个位到底该摆成什么状态

以 F1 的I2C_CR1为例,和 SMBus 相关的有五位,它们的含义和推荐值如下:

位名称作用主机读从机时的推荐值
bit1SMBUS切换到 SMBus 模式,启用电平门限与超时检测1
bit3SMBTYPE0 = Device,1 = Host0
bit4ENARP使能 SMBALERT 引脚与 ARP需要中断线时置 1
bit5ENPEC使能硬件 PEC 计算用软件 PEC 时置 0
bit7NOSTRETCH从机模式禁止时钟拉伸主机模式无影响,保持 0

SMBTYPE这一位特别容易搞反。很多人看到"主机"两个字就想置 1,其实它控制的是作为从机被寻址时的地址匹配行为——置 1 之后,接口会对 SMBus Host 地址(0x08)作出应答,配合 SR2 里的 SMBHOST 标志,用于 Host Notify 协议。当你只是"用 STM32 去读写一颗电池芯片"时,要的是 Device 模式,这一位保持 0。

ENPEC我也建议先关掉,用软件算。原因不是硬件算得不对,而是硬件 PEC 的计算范围取决于接口实际发出和收到的每一个字节,一旦中间插入 DMA、或者传输被中断打断重启,PECR里的值就对不上了,排查起来比软件算法麻烦得多。软件 PEC 每字节一次 CRC 更新,100 kHz 下最多几十微

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进