STM32 SMBus通信配置:从I2C差异到PEC与故障排查

发布时间:2026/11/4 5:50:39
STM32 SMBus通信配置:从I2C差异到PEC与故障排查 近两年做电池管理、服务器电源监控、热插拔板卡的项目越来越多SMBus 这个在消费电子里不太起眼的协议被反复提到。很多人第一反应是“不就是 I2C 吗改个波特率就行”真上手才发现设备不回 ACK、从机隔三差五被主机判定超时、PEC 校验永远错一位。SMBus 是挂在 I2C 电气层上的一层严格规则集STM32 的 I2C 外设虽然标着支持 SMBus 模式但默认配置几乎不会满足它的硬指标。这篇内容围绕 STM32 上的 SMBus 通信配置展开从电气参数算起到寄存器位的含义、主机与从机两侧的落地代码、PEC 的实现与验证最后把我在调试现场反复遇到的故障现象整理成速查表。适合已经会用 I2C 驱动外设、准备接智能电池或电源管理类设备的朋友也适合刚接触 STM32 外设、想把这套流程完整走一遍的新手。文中代码以常见的中容量型号为基准寄存器名和 HAL 接口在不同系列上会有出入关键差异我会逐条标出来。1. 先厘清 SMBus 与 I2C 的差异别急着写代码1.1 六处硬指标差异每一条都能让通信失败SMBus 沿用了 I2C 的两线结构和地址格式但规范里塞进了一堆强制约束不满足就属于“不合规设备”对方芯片完全可以不搭理你。我按踩坑频率从高到低列一下。总线速率上限被锁死在 100kHz。I2C 有标准、快速、快速加强多个档位SMBus 规范里最高就是 100kHz最低可以到 10kHz。有些电源管理芯片内部滤波很重实际只能跑 50kHz 甚至更低。第一次接触时我把时钟配到 400kHz逻辑分析仪上波形漂亮得很从机一个 ACK 都不给后来翻手册才发现它对输入滤波有最低脉宽要求。超时是强制的不是可选项。SMBus 规定 SCL 保持低电平超过 25ms 到 35ms 之间主从双方都有权把总线判为超时并复位状态机从而解锁一条被拉死的总线。这条规则的意义很大I2C 遇到从机死机把 SDA 拉低主机只能靠软件硬复位或手动打九个时钟SMBus 直接用超时机制兜底。PEC 校验包。SMBus 事务在数据后面可以带一个 CRC-8 字节多项式是 x⁸x²x1对应 0x07初值 0x00覆盖范围是地址字节含读写位加上所有数据字节最后那个 PEC 字节自己不算进去。这是和裸 I2C 最本质的差别之一数据完整性由协议层保证而不是靠上层重试。电气阈值固定。I2C 的 VIH/VIL 跟 VDD 挂钩SMBus 直接写死 VIH 最小值 2.1V、VIL 最大值 0.8V跟电源电压无关。这意味着 1.8V 供电的 MCU 直接接 SMBus 设备逻辑高可能压根到不了 2.1V必须做电平转换。数据保持时间 tHD:DAT 要求 300ns。SCL 下降沿之后SDA 上的数据必须再保持 300ns 才能变化。这一条专门坑 MCU 做从机发送的场景因为普通 I2C 从机发完一个 bit 会立刻换下一位速度稍快就违反保持时间。STM32 的 I2C 外设在标准模式下大多能过但如果你把时钟拉到 100kHz 又打开了模拟滤波器之外的加速配置就得拿示波器实测了。地址分配有约定。SMBus 规范保留了几个固定地址Alert Response Address 是 0x0C默认设备地址是 0x61主机地址是 0x08。这几个地址不能随便占用从机侧做地址规划时必须避开。提示PEC 是“可以启用”而非“永远必须”具体看对方设备手册里事务描述有没有标 PEC 字段。混用会直接导致字节错位收到一堆看似随机的数据。1.2 STM32 各系列对 SMBus 的支持程度不一样STM32 的 I2C 外设版本分好几代SMBus 相关的能力差别不小选型和移植时得心里有数。第一代以常见的 F1、F2、F4 早期型号为代表。它的 I2C_CR1 里有 SMBUS 位和 SMBTYPE 位能切到 SMBus 模式I2C_SR1 里有 SMBALERT 和 TIMEOUT 标志PEC 由 ENPEC 位控制硬件会算 CRC 并在出错时置 PECERR。这一代的外设比较“笨”没有独立的超时寄存器超时检测是模式内建的时间窗口固定只能靠软件配合。第二代比如 L4、G0、G4 这些I2C 外设重写过增加了 TIMEOUTR 寄存器超时时间可以按自己的需求配置 A、B 两套门限还支持独立的 SMBus 超时使能位。这一代做从机侧更省心配置也更灵活代价是寄存器名字全变了HAL 里的结构体成员也不同。还有一类是低端小容量型号I2C 功能裁剪得比较厉害SMBus 模式不一定齐全选型时一定要把参考手册里 I2C 章节翻到 SMBus 小节确认 SMBUS、SMBTYPE、PEC 这几个位都在。我个人的经验是做主机去访问别人的 SMBus 设备哪一代都能凑合反正主机侧的超时和 PEC 都可以用软件模拟做从机被别人的主机访问尽量选第二代外设硬件超时和时钟延展的处理更从容。1.3 什么场景该上 SMBus什么场景别折腾判断标准其实很简单看对面那颗芯片的数据手册。智能电池模块、电源监控芯片、热插拔控制器、部分温度传感器和 EEPROM手册里如果明确写了“SMBus 兼容”并且列出了事务类型Read Word、Block Read 之类那你最好老老实实按 SMBus 走因为它内部的状态机大概率是按规范实现的你用裸 I2C 发同样的字节序列很可能在 PEC 或者超时环节被拒。反过来如果对面只是一颗普通 I2C 传感器手册里没提 SMBus那就别硬套。打开 SMBus 模式会引入严格的超时约束某些慢速器件在转换期间拉长时钟直接触发 TIMEOUT 中断反而制造麻烦。还有一类情况值得注意总线上挂了多颗设备其中一颗是标准 SMBus 器件其余是普通 I2C 器件。这时候不必把整个总线切成 SMBus 模式STM32 的 SMBus 模式本质是外设行为约束主机侧完全可以只针对特定地址启用 PEC、其余事务按普通 I2C 走。前提是你得自己管好 PEC 计算和超时兜底不能指望硬件。2. 硬件层先把坑填平上拉、ALERT 与电气参数2.1 上拉电阻的两次计算一次都不能省上拉电阻选错是 SMBus 调试里最常见的“隐形故障”。阻值太大上升沿爬不上去波形变成圆角从机判不出高电平阻值太小灌电流超标从机在拉低时 VOL 抬高同样识别错误。计算分两步。第一步算上限由上升时间决定。公式是 tr 0.8473 × Rp × Cb其中 Cb 是总线总电容包含走线、引脚和器件输入电容一般按 100pF 到 200pF 估。SMBus 标准模式下 tr 最大 1000ns但为了留裕量我通常按 300ns 到 500ns 反推。取 Cb 200pF、tr 1000ns得到 Rp ≈ 5.9kΩ也就是上拉不能大于这个值。取 4.7kΩ 时 tr 约 800ns落在合规区间内。第二步算下限由灌电流能力决定。公式是 Rp(min) (VDD − VOL) / IOL。SMBus 规范要求器件在 VOL ≤ 0.4V 时至少能灌 350µA按 3.3V 电源算Rp(min) ≈ (3.3 − 0.4) / 350µA ≈ 8.3kΩ。这个数是“规范允许的最大下限”实际 STM32 的 GPIO 开漏输出能灌 3mA 以上按 IOL 3mA 算出来 Rp(min) ≈ 970Ω。两个下限取更严格的实际可选区间大概在 1kΩ 到 5.9kΩ 之间4.7kΩ 是兼顾功耗和速度的常用值。参数取值依据总线电容 Cb100~200pF走线加器件引脚估算上升时间 tr≤1000nsSMBus 标准模式上限上拉上限 Rp(max)约 5.9kΩtr 0.8473·Rp·Cb 反推上拉下限 Rp(min)约 0.97kΩ按 IOL 3mA 计算工程取值4.7kΩ兼顾边沿与功耗如果总线上挂了五六颗设备Cb 可能超过 400pF这时候 4.7kΩ 就偏大了要么降到 2.2kΩ要么用有源上拉或者总线缓冲器。我遇到过一块板子为了省电把上拉放到 10kΩ单机测试没问题一插上第二块从机板就间歇性 NACK换成 3.3kΩ 后彻底稳定。2.2 ALERT 线为什么必须单独拉一根SMBus 的告警机制是这样的任何从机发现异常电量低、温度超限、电压越界把 SMBALERT# 这根开漏线拉低。主机检测到之后用 Alert Response Address 0x0C 发起一次读事务所有拉低告警线的从机同时参与仲裁地址最小的那个赢得总线把自己的 7 位地址作为数据回给主机。主机拿到地址后再针对这个地址去读具体状态从机在响应后释放告警线。如果还有别的从机在告警线仍然是低的主机重复这个过程。这套机制要求 ALERT 线是独立的一根不能和 SDA、SCL 复用也不能用普通推挽输出驱动。STM32 侧接这根线的 GPIO 要配成开漏输出加上拉或者干脆配成浮空输入加外部上拉用外部中断检测下降沿。需要说明的是STM32 的 I2C 外设本身并没有一根专门的 SMBALERT 输入引脚部分型号在特定封装上把某个引脚复用成 SMBALERT但主流用法还是外接普通 GPIO。I2C_SR1 里的 SMBALERT 标志位反映的是外设内部对该信号的采样结果实际工程里更可靠的做法是用 GPIO 中断加软件状态机处理把 I2C 外设的 ALERT 相关中断当作辅助。注意多从机共享 ALERT 线时从机释放告警线的时机必须严格按规范来——收到地址匹配并成功返回地址后立即释放。如果软件延迟释放主机会误判为还有其他设备在告警陷入反复轮询 0x0C 的死循环。2.3 电源域和电平兼容别等烧了才想起来SMBus 的固定逻辑阈值带来的问题是3.3V 的 STM32 和 5V 的 SMBus 器件直连高电平方向没问题3.3V 大于 2.1V但 5V 器件输出 5V 高电平打进 STM32 引脚如果这个引脚不是 5V 容忍的长期工作会损伤输入级。反过来1.8V 供电的 MCU 输出高电平只有 1.8V达不到 2.1V 的下限从机根本认不出高电平。处理方式有两种。简单粗暴的是选 5V 容忍引脚STM32 大部分型号的 I2C 引脚标着 FTFive-volt Tolerant数据手册的引脚定义表里能查到。稳妥一点的是加双向电平转换器件选那种专门为开漏总线设计的注意转换器本身不能引入过大的串联电阻否则会叠加到上升时间上。另外SMBus 设备很多是从系统待机电源取电的主控断电时从机还在工作此时 STM32 的引脚处于高阻总线上的上拉会把 SCL、SDA 拉到高电平看起来像空闲状态实际从机可能正在等待一次事务的中间字节。这种场景下建议加总线隔离或者让从机侧有超时复位机制。3. 寄存器级配置把 SMBus 模式真正打开3.1 时钟、GPIO 与复用配置的先后顺序配置顺序错了会出现“寄存器写进去但外设不工作”的怪现象。正确顺序是先开 GPIO 和 I2C 的时钟再配 GPIO 为复用开漏最后才动 I2C 寄存器。以常见的中容量型号为例I2C1 默认落在 PB6SCL和 PB7SDA上__HAL_RCC_GPIOB_CLK_ENABLE(); __HAL_RCC_I2C1_CLK_ENABLE(); GPIO_InitTypeDef gpio {0}; gpio.Pin GPIO_PIN_6 | GPIO_PIN_7; gpio.Mode GPIO_MODE_AF_OD; /* 必须是复用开漏 */ gpio.Pull GPIO_PULLUP; /* 内部弱上拉外部仍要接 4.7k */ gpio.Speed GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(GPIOB, gpio);这里有两个容易忽略的点。第一模式必须是GPIO_MODE_AF_OD配成推挽输出的话两个器件同时驱动总线会直接短路。第二内部上拉只有几十 kΩ只能防止引脚悬空不能替代外部 4.7kΩ 上拉别指望省掉外部电阻。如果用的是重映射引脚或者第二路 I2C别忘了开 AFIO 时钟并调用重映射函数。有些型号的 I2C 引脚和调试口、晶振脚冲突画板子的时候就要避开。3.2 CCR 和 TRISE 的数值手算一遍才踏实时钟配置的核心是 I2C_CCR 和 I2C_TRISE 两个寄存器HAL 里的ClockSpeed参数最终也是换算成这两个值写进去的。搞明白换算过程遇到非标准时钟或者需要压到 50kHz 时才不会抓瞎。标准模式100kHz 及以下下CCR 的计算公式是CCR fPCLK1 / (2 × fSCL)假设 I2C1 挂在 APB1 上PCLK1 36MHz目标 100kHzCCR 36 000 000 / (2 × 100 000) 180180 换算成十六进制是 0xB4小于 12 位上限 0xFFF合法。如果目标降到 50kHzCCR 360同样合法。这里有个约束标准模式下 CCR 最小不能小于 4否则外设行为未定义。TRISE 寄存器填的是“最大允许上升时间对应的时钟周期数加一”。标准模式下 SCL 上升时间上限 1000nsTRISE (1000ns / Tpclk1) 1 fPCLK1(MHz) × 1 136MHz 时 TRISE 37。这个值本质是告诉外设从释放 SCL 到采样高电平最多等这么多个时钟周期。填小了会误判总线忙填大了会拖慢整个时序。PCLK1目标速率CCRTRISE36MHz100kHz180 (0xB4)3736MHz50kHz360 (0x168)378MHz100kHz40 (0x28)972MHz100kHz360 (0x168)73写 CCR 和 TRISE 之前必须先写 CR2 的 FREQ 字段告诉外设 APB 时钟频率是多少 MHz。FREQ 范围是 2 到 50超范围直接写不进去。这个顺序错了CCR 和 TRISE 的计算基准就是错的。3.3 SMBUS 和 SMBTYPE 两个位含义别搞反I2C_CR1 里有两个位专门管 SMBusbit1 是 SMBUSbit3 是 SMBTYPE。SMBUS 1 表示把外设切到 SMBus 模式此时外设会启用固定逻辑阈值采样、超时检测、PEC 相关的行为。SMBTYPE 决定当前外设扮演什么角色置 0 是从机Device置 1 是主机Host。这个角色只影响外设对特定地址和报文的响应逻辑不影响你手动发起传输。配置代码大概是这样/* 先确保外设处于关闭状态 */ I2C1-CR1 ~I2C_CR1_PE; /* 使能 SMBus 模式 */ I2C1-CR1 | I2C_CR1_SMBUS; /* 选择角色0 Device1 Host */ #if defined(SMBUS_ROLE_HOST) I2C1-CR1 | I2C_CR1_SMBTYPE; #else I2C1-CR1 ~I2C_CR1_SMBTYPE; #endif /* 需要硬件 PEC 时再打开 */ I2C1-CR1 | I2C_CR1_ENPEC; I2C1-CR1 | I2C_CR1_PE;HAL 库里对应的是宏__HAL_I2C_ENABLE_SMBUS()和__HAL_I2C_DISABLE_SMBUS()但这两个宏在不同系列的头文件里名字和实现都有出入有些版本只操作 SMBUS 位不管 SMBTYPE。稳妥的做法是查一下当前工程用的 HAL 头文件实在找不到就直接写寄存器反正就两个位。有个细节值得说切到 SMBus 模式后SCL 和 SDA 的输入滤波参数会自动调整某些型号还会禁用或改变模拟滤波器的行为。这意味着你在 I2C 模式下调好的时序裕量切到 SMBus 模式后可能不够用需要重新用示波器看波形。3.4 从机地址寄存器的保留位写错就永远收不到呼叫做从机的时候要往 OAR1 写自己的地址这个寄存器有个坑bit14 是保留位某些系列要求必须写 1写 0 的话地址寄存器直接不生效。STM32F1 系列的手册里明确写了“该位必须保持为 1”。地址宽度由 bit15ADDMODE决定0 表示 7 位地址1 表示 10 位地址。SMBus 只用 7 位地址所以这一位保持 0。/* 7 位地址 0x2C注意 bit14 必须置 1 */ I2C1-OAR1 (0x2C 1) | (1U 14);地址的计算要特别注意SMBus 规范里说的地址是 7 位而 HAL 和大部分代码里传的是左移一位后的 8 位形式。0x2C 左移一位是 0x58再或上读写位。如果你在 OAR1 里直接写 0x2C 而不是 0x58实际匹配的地址就变成了 0x16主机叫 0x2C 永远叫不醒。双地址功能OAR2在 SMBus 场景下基本用不到除非你要同时响应默认地址 0x61 和自己的业务地址。真要用的话记得 OAR2 的使能位和地址掩码位一起配。4. 主机侧实操一次带 PEC 的 SMBus 写事务4.1 事务拆解先把字节序和结构定死SMBus 定义了十来种事务类型实际项目里 80% 的场景只用三种Send Byte 用来发单条命令Write Word 用来写 16 位参数Block Read 用来读电池信息这类超过 2 字节的数据。以 Write Byte 为例完整报文是起始 → 地址W → 命令码 → 数据 → PEC → 停止四个字节的内容分别是地址字节7 位地址左移一位最低位 0 表示写、命令码、数据、PEC。PEC 覆盖前三个字节。Read Word 稍微复杂报文是起始 → 地址W → 命令码 → 重复起始 → 地址R → 数据低字节 → 数据高字节 → PEC → 停止注意这里有两个关键点。第一中间必须是重复起始Sr不能是停止再起始否则从机会认为事务结束内部指针复位。第二多字节数据是小端序低字节在前。我见过不少人按大端拼数据读出来的电压值总是大得离谱。事务类型报文结构常见用途Send Byte地址W命令触发动作、清状态Write Byte地址W命令数据PEC写 8 位寄存器Write Word地址W命令低字节高字节PEC写阈值、保护点Read Word地址W命令Sr地址R低高PEC读电压、电流、温度Block Read地址W命令Sr地址R长度数据…PEC读电池型号、厂商信息Block Read 还有一个限制长度字节最大 32超过就是从机违规。读的时候主机要先收长度字节再按长度收数据最后收 PEC收完最后一个字节要回 NACK 再发停止。4.2 PEC 的计算以及一个能对答案的例子PEC 用的是 CRC-8多项式 0x07初值 0x00不反转输入输出不做最终异或。逐位实现的代码如下static uint8_t crc8_update(uint8_t crc, uint8_t data) { crc ^ data; for (int i 0; i 8; i) { if (crc 0x80) crc (uint8_t)((crc 1) ^ 0x07); else crc (uint8_t)(crc 1); } return crc; } static uint8_t smbus_pec(uint8_t addr_rw, const uint8_t *buf, uint32_t len) { uint8_t crc 0x00; crc crc8_update(crc, addr_rw); for (uint32_t i 0; i len; i) crc crc8_update(crc, buf[i]); return crc; }拿一个具体例子对答案从机地址 0x16执行 Write Byte命令码 0x08数据 0x00。地址字节是 0x16 1 0x2C。参与计算的三字节是 0x2C、0x08、0x00算出来的 PEC 应该是 0x11。你可以拿这段代码跑一遍验证如果对不上八成是初值写成了 0xFF 或者把地址字节右移了。提示很多设备的 PEC 只在写事务或者特定命令上启用读事务反而不带。这种混搭设计在智能电池类设备上很常见。别想当然地认为“开了 SMBus 就必须全程带 PEC”一切以设备手册的报文图为准。4.3 读事务的重复起始用 Mem_Read 比手动拼更省事HAL 的HAL_I2C_Master_Transmit和HAL_I2C_Master_Receive之间会插入一个停止条件这在 SMBus 的读事务里是致命的。解决办法是用HAL_I2C_Mem_Read它内部会发出重复起始把“写命令码”和“读数据”串成一个完整事务。/* 读 2 字节数据 1 字节 PEC */ uint8_t rx[3]; HAL_StatusTypeDef st HAL_I2C_Mem_Read(hi2c1, addr 1, cmd, I2C_MEMADD_SIZE_8BIT, rx, 3, 50); if (st HAL_OK) { uint16_t value (uint16_t)rx[0] | ((uint16_t)rx[1] 8); uint8_t pec rx[2]; }这里有个必须实测确认的细节使能 ENPEC 后硬件在接收时会自己算 CRC并把最后一个字节和计算值比较不一致就置 PECERR。不同系列对这个最后一字节的处理位置不完全一样有的会把它放进接收缓冲有的直接丢弃并在状态位里体现。上面这段代码按“PEC 在 buffer 最后一个字节”来处理如果你的型号不是这样读回来的数据会整体偏移一位表现为数值莫名其妙地翻倍或者溢出。调试阶段最稳的做法是先用逻辑分析仪抓一次完整波形数清楚总线上到底有几个字节再决定 buffer 开多大、PEC 从哪一位取。4.4 超时兜底和总线恢复这层代码必须有SMBus 的超时是双向的从机可以把主机判超时主机也可以把从机判超时。STM32 在 SMBus 模式下触发 TIMEOUT 后外设状态机需要复位才能继续通信否则后续所有传输都卡在 BUSY 状态。我的处理模板是三层第一层每次传输设一个软件超时比如 50ms。HAL 的阻塞式接口自带超时参数别图省事传HAL_MAX_DELAY那样一旦从机不响应整个主循环就挂死了。第二层检测到 TIMEOUT 或 BERR 后按标准流程复位先读 SR1 再读 SR2 清除标志然后清 PE 位、重新配置 CCR 和 TRISE、再置 PE 位。注意复位会丢失当前事务的所有状态该重试的要在上层重试。第三层如果 BUSY 位一直为 1说明某颗从机把 SDA 拉死了。这时候走经典的总线解锁流程把 SCL 配成普通 GPIO 推挽输出手动打 9 个时钟脉冲每个脉冲之间延时几微秒让从机把剩余字节吐完然后发一个停止条件最后重新把引脚切回复用开漏。static void i2c_bus_recover(void) { /* 1. 关外设SCL/SDA 切成普通 GPIO 推挽 */ I2C1-CR1 ~I2C_CR1_PE; gpio_set_output_pushpull(SCL_PIN); gpio_set_output_pushpull(SDA_PIN); gpio_write(SDA_PIN, 1); /* 2. 打 9 个时钟 */ for (int i 0; i 9; i) { gpio_write(SCL_PIN, 0); delay_us(5); gpio_write(SCL_PIN, 1); delay_us(5); } /* 3. 制造停止条件SDA 在 SCL 高时上升 */ gpio_write(SDA_PIN, 0); delay_us(5); gpio_write(SCL_PIN, 1); delay_us(5); gpio_write(SDA_PIN, 1); delay_us(5); /* 4. 恢复复用开漏并重新初始化外设 */ gpio_set_af_opendrain(SCL_PIN); gpio_set_af_opendrain(SDA_PIN); MX_I2C1_Init(); }这套恢复逻辑我放在每个项目里触发次数不多但每次都能救命。有次现场设备连续运行三天后通信中断远程日志里全是 BUSY就是靠这段代码自动恢复的。5. 从机侧实操用 STM32 模拟一个 SMBus Device5.1 状态机怎么划决定了代码乱不乱把 STM32 当从机被别人的主机访问难度比做主机高一个量级因为节奏完全由对方控制你只能被动响应中断。我的经验是先把状态机画清楚再写代码否则中断里那一堆 if-else 很快就会变成屎山。一个典型的 SMBus 从机事务最多经历这几个状态空闲、收到地址匹配判断是写还是读、接收命令码、接收数据可能多字节、发送数据可能多字节、PEC 校验、结束。中断服务函数里就是靠 SR1 和 SR2 的标志位在状态之间跳转。用 HAL 的话HAL_I2C_Slave_Receive_IT这类接口能帮你处理一部分但它对 SMBus 特有事件PEC 错误、超时、ALERT的支持有限很多标志还得自己清。我的做法是 HAL 只用来初始化中断处理全部自己写直接读 SR1、SR2、DR逻辑最清晰。5.2 中断里的处理顺序错一步就锁死中断服务函数里的判断顺序非常关键因为多个标志可能同时置位。我总结的顺序是先处理错误类标志AF、BERR、ARLO、OVR、PECERR、TIMEOUT这类标志如果不清后续通信全部受影响。特别注意 AF应答失败标志从机模式下如果主机发了地址但你没使能 ACK或者地址不匹配AF 会置位必须读 SR1 再写 CR1 清除。再处理 ADDR 标志。ADDR 置位表示地址匹配成功此时要读一次 SR1 和 SR2 来清除该标志同时从 SR2 的 TRA 位判断方向TRA 0 表示主机要写数据给你接收TRA 1 表示主机要读数据发送。这个方向判断决定了后续走哪条分支。然后处理 RxNE 和 TxE。接收时每个字节到了读 DR发送时 DR 空了就写下一个字节。多字节事务要自己维护一个索引别指望硬件帮你数。最后处理 STOPF。收到停止条件说明事务结束把状态机复位触发上层解析。void I2C1_EV_IRQHandler(void) { uint32_t sr1 I2C1-SR1; uint32_t sr2 I2C1-SR2; if (sr1 I2C_SR1_ADDR) { /* 读 SR1 和 SR2 已清除 ADDR */ if (sr2 I2C_SR2_TRA) { /* 主机要读准备第一个待发字节 */ smbus_slave_on_read_request(); } else { /* 主机要写复位接收索引 */ smbus_slave_on_write_start(); } } if (sr1 I2C_SR1_RxNE) { uint8_t b I2C1-DR; smbus_slave_on_byte(b); } if (sr1 I2C_SR1_TxE) { I2C1-DR smbus_slave_next_tx_byte(); } if (sr1 I2C_SR1_STOPF) { /* 读 SR1 后写 CR1 清除 STOPF */ I2C1-CR1 | 0; smbus_slave_on_stop(); } }注意清除 STOPF 的方法是“读 SR1 然后写 CR1”写什么值不重要关键是有这个写动作。只读不写STOPF 永远清不掉中断会反复进入。5.3 ALERT 响应和 ARA别把地址发反了从机侧实现 ALERT 功能核心是两件事一是在检测到本地异常时把 ALERT 引脚拉低二是在收到 ARA 地址 0x0C 的读请求时把自己的 7 位地址作为数据字节发出去然后释放 ALERT 线。这里有个容易搞错的地方发出去的地址是 7 位原始值不是左移一位的 8 位形式。假设你的从机地址是 0x2C那么在 ARA 事务里要发 0x2C不是 0x58。发错了主机会认为这是个无效响应反复轮询。另一个细节是优先级。如果总线上有两颗从机同时告警地址小的赢得仲裁这是靠 SDA 线上的线与特性自然实现的——发送 0 的器件会把发送 1 的器件压下去。所以从机在 ARA 响应时不需要做任何特殊处理硬件自动完成。但软件上要保证输了仲裁的那颗从机它的 ALERT 线仍然是低的主机处理完第一颗之后还能继续轮询到它。STM32 的 I2C 外设对 ARA 地址的识别可以靠 ENARP 位使能也可以完全软件判断——在 ADDR 中断里比对收到的地址是不是 0x0C。我倾向于软件判断逻辑更直观也方便加日志。6. 调试实录常见故障与排查速查表6.1 六个高频现象按这个顺序查调试 SMBus 的过程中故障现象翻来覆去就那么几种我把它们和对应的排查方向整理成了一张表。现象最可能的原因排查动作完全无响应波形正常地址计算错误、未开 ACK用扫描代码逐个地址试探确认 CR1 的 ACK 位偶发 NACK上拉偏大、总线电容超标示波器测上升沿超过 1µs 就降阻值PECERR 频繁置位覆盖范围算错、初值不对手工算一遍 PEC和逻辑分析仪抓的字节对比TIMEOUT 反复触发时钟配置过快、从机转换慢降到 50kHz 甚至 10kHz 观察BUSY 位一直为 1SDA 被拉死执行总线恢复流程打 9 个时钟读回数据错位一位PEC 字节处理位置不对抓波形数总字节数调整 buffer 长度这几种里最隐蔽的是“读回数据错位一位”。现象是数值看起来有规律但对不上比如电压读出来是实际值的 256 倍本质是字节序反了或者所有值都偏移了半个字节那是 PEC 被当成了数据的一部分。遇到这种情况别改代码先抓波形把总线上实际出现的每一个字节记下来和你的 buffer 逐字节对问题立刻现形。6.2 工具用对效率差十倍逻辑分析仪是必需品不是可选项。百来块的那种八通道版本就够用采样率设到 10MHz 以上解码器选 I2C把地址、读写位、数据、ACK 全解出来。遇到时序问题时把采样率提到 50MHz看边沿细节。示波器主要负责三件事看上升沿时间是否超标、看 SCL 和 SDA 的时序关系特别是停止条件、看电源纹波是否耦合到总线上。我用下来最有用的是双通道同时看 SCL 和 SDA触发设在 SCL 下降沿能清楚看到数据保持时间够不够。软件层面写一个总线扫描函数从 0x08 扫到 0x77每个地址发一次空写看谁回 ACK。这比翻手册猜地址快得多。扫描时记得跳过保留地址而且扫描本身会触发某些设备的告警最好在系统空闲时做。6.3 几条踩出来的经验第一条

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询