Linux I2C 调试实录:用寄存器打印揪出 TRISE 配置过小
平台:STM32 + Linux(
i2c-stm32/ 设备树节点)现象:偶发
EIO、从机无 ACK、示波器上 SCL 上升沿"拖尾"结论:CCR 合理,但 TRISE 过小 → SCL 未拉高到有效高电平就读数据
1. 背景:为什么先打寄存器而不是先改代码
在 Linux 下做 I2C 从机或主机驱动时,上层往往只看到:
bash
i2c i2c-1: transfer failed: -5 (EIO)
硬件侧(时钟、滤波、上升时间)和软件侧(消息长度、地址、DMA)都可能出错。
在改驱动逻辑之前,先把 I2C 外设关键寄存器 dump 出来,可以快速区分:
| 类别 | 典型寄存器 | 能回答的问题 |
|---|---|---|
| 时序 | CCR, TRISE, FREQR |
波特率、上升时间是否合规 |
| 状态 | SR1, SR2 |
是否 BUSY、AF、BERR |
| 控制 | CR1, CR2 |
PE、ACK、中断/DMA 是否误开 |
下文寄存器名与数值按 STM32F1 系列 I2C 习惯书写(示例值可替换为你板子上的真实 dump)。
2. 在驱动里加"寄存器快照"打印
在 probe 或一次失败的 xfer 回调里,读 MMIO 并 dev_info 输出(示例):
bash
static void i2c_dump_regs(struct device *dev, void __iomem *base)
{
u32 cr1 = readl(base + 0x00);
u32 cr2 = readl(base + 0x04);
u32 oar1 = readl(base + 0x08);
u32 oar2 = readl(base + 0x0C);
u32 dr = readl(base + 0x10);
u32 sr1 = readl(base + 0x14);
u32 sr2 = readl(base + 0x18);
u32 ccr = readl(base + 0x1C);
u32 trir = readl(base + 0x20); /* TRISE */
u32 fltr = readl(base + 0x24); /* 部分型号有 FLTR */
dev_info(dev,
"I2C regs @fail:\n"
" CR1=0x%08x CR2=0x%08x OAR1=0x%08x OAR2=0x%08x\n"
" DR=0x%08x SR1=0x%08x SR2=0x%08x\n"
" CCR=0x%08x TRISE=0x%08x FLTR=0x%08x\n",
cr1, cr2, oar1, oar2, dr, sr1, sr2, ccr, trir, fltr);
}
一次失败时的示例输出(编造的典型现场):
bash
[ 312.448091] stm32-i2c 40005400.i2c: I2C regs @fail:
[ 312.448120] CR1=0x00000401 CR2=0x00000000 OAR1=0x00000040 OAR2=0x00000000
[ 312.448145] DR=0x000000A3 SR1=0x00000400 SR2=0x00000002
[ 312.448168] CCR=0x00000050 TRISE=0x00000002 FLTR=0x00000000
2.1 如何粗读这些数(示例解读)
| 寄存器 | 示例值 | 粗读 |
|---|---|---|
CR1 |
0x401 |
PE=1,外设已使能 |
SR1 |
0x400 |
AF=1(应答失败),常伴随时序/从机未就绪 |
SR2 |
0x2 |
主机模式、总线忙痕迹 |
CCR |
0x50 |
对应约 100 kHz 量级(与 pclk 计算一致) |
TRISE |
0x2 |
仅 2 个时钟周期的上升时间 → 偏激进 |
注意:
TRISE的单位是 I2C 内核时钟(FREQR 对应的 MHz)周期数,不是纳秒;要和手册公式对照。
3. 总线时序与 TRISE 的关系(示意图)
I2C 规范要求:SCL/SDA 的 上升时间 Tr 有下限(标准模式常见 1000 ns 量级,与负载电容有关)。
MCU 用 TRISE 告诉硬件:"至少等这么久再认为高电平有效"。
3.1 正常:TRISE 足够
bash
SCL ────┐ ┌─────┐ ┌────
│ │ │ │
└─────┘ └─────┘
↑
Tr 满足规范
采样点在高电平稳定区
3.2 异常:TRISE 过小(本文问题)
bash
SCL ────┐ ╱╲ ┌─────
│ ╱ ╲ │
└╱ ╲┘
↑
上升沿还在爬坡
主机已采样 / 进入下一比特
→ 误码、AF、偶发 EIO
3.3 Mermaid:逻辑上的"抢跑读取"
从设备SCL 引脚I2C 硬件状态机从设备SCL 引脚I2C 硬件状态机TRISE 过小:等待计数不足释放 SCL,开始上升沿判定"高电平有效"过早采样 SDA / 发下一时钟实际仍为低或边沿区SR1.AF=1,传输失败
4. 完整调试流程(可按此复现)
上升沿慢/圆角
完全无波形
是
否
用户态 i2cget 失败 / dmesg EIO
示波器看 SCL/SDA
波形异常?
怀疑 Tr / 上拉 / TRISE
查引脚复用/时钟/电源
驱动中加寄存器 dump
对比 CCR 与 TRISE
TRISE 相对 CCR 过小?
按手册重算 TRISE
查 SR1 AF/BERR/仲裁/从机地址
改设备树或 clk 初始化
复测 + 长时间压力测试
通过
步骤 1:复现并保留日志
bash
# 用户态复现
i2cget -y 1 0x50 0x00
# 同时抓内核
dmesg -w | tee /tmp/i2c_fail.log
步骤 2:示波器 / 逻辑分析仪
- 通道 1:SCL,通道 2:SDA
- 触发:下降沿或 I2C 解码器的 NACK
- 重点看 SCL 上升沿 :是否未达到
Vih就进入下一比特
步骤 3:失败瞬间 dump 寄存器
在 stm32_i2c_xfer 返回 -EIO 前调用 i2c_dump_regs()(见第 2 节)。
连续抓 20 次失败,统计 SR1、TRISE 是否稳定。
步骤 4:手算 TRISE(示例)
假设:
PCLK1 = 36 MHz→FREQR = 36(写入值 36)- 目标 Standard Mode 100 kHz
- 手册要求:
TRISE = FREQ(MHz) + 1(最小值,单位 cycles)
对 36 MHz:至少 37(0x25) - 现场 dump 为
TRISE = 2→ 明显低于规范下限
CCR 示例计算(与 dump 一致):
bash
Thigh = CCR * Tpclk1
= 0x50 * (1/36µs) ≈ 2.2 µs → 接近 100kHz 占空比之一半
CCR 看起来"像 100kHz",但 TRISE 没跟上 ,所以问题不在波特率而在 上升时间约束。
步骤 5:修改配置
设备树(示例):
bash
&i2c1 {
clock-frequency = <100000>;
/* 部分 BSP 用自定义属性传递 tririse */
st,i2c-trise-ns = <1000>;
status = "okay";
};
或在 probe 里按手册编程:
bash
/* 36MHz 时:TRISE 至少 37 */
writel(37, base + I2C_TRISE_OFFSET);
改完后再次 dump,期望看到:
bash
CCR=0x00000050 TRISE=0x00000025 /* 0x25 = 37 */
步骤 6:验证
| 项 | 命令/方法 | 通过标准 |
|---|---|---|
| 单次读 | i2cget -y 1 0x50 0x00 |
无 EIO |
| 压力 | for i in $(seq 1 10000); do i2cget -y 1 0x50 0; done |
0 失败 |
| 波形 | 示波器 Tr | 上升沿平顶后再采样 |
| 寄存器 | 失败时 dump | SR1.AF 不再置位 |
5. 根因总结
| 项目 | 说明 |
|---|---|
| 现象 | 偶发 EIO,SR1 中 AF 置位,示波器 SCL 上升沿"未拉满" |
| 误导项 | CCR 计算正确,容易误以为波特率没问题 |
| 根因 | TRISE 寄存器值过小 (示例 0x2),硬件在 SCL 尚未达到有效高电平时继续位时序 |
| 修复 | 按 FREQR 与模式(标准/快速)重算 TRISE,并考虑外部上拉与总线电容 |
6. 经验清单(Checklist)
- 失败路径上 必打
SR1/SR2/CCR/TRISE,不要只打CR1 - CCR 与 TRISE 成对检查,二者对应不同物理约束
- 示波器看 上升沿,不要只看频率
- 修改后做 长时间循环
i2cget/i2cdetect,偶发问题需压力验证 - 文档记录:当次
PCLK、FREQR、目标模式、最终TRISE写入值
7. 附录:与本文对应的"编造"寄存器对照表
| 阶段 | CCR | TRISE | SR1 关键位 | 结果 |
|---|---|---|---|---|
| 调试前 | 0x50 | 0x02 | AF=1 | 偶发失败 |
| 修复后 | 0x50 | 0x25 (37) | 正常 | 稳定通过 |