Linux I2C 调试实录:用寄存器打印揪出 TRISE 配置过小

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 次失败,统计 SR1TRISE 是否稳定。

步骤 4:手算 TRISE(示例)

假设:

  • PCLK1 = 36 MHzFREQR = 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. 根因总结

项目 说明
现象 偶发 EIOSR1AF 置位,示波器 SCL 上升沿"未拉满"
误导项 CCR 计算正确,容易误以为波特率没问题
根因 TRISE 寄存器值过小 (示例 0x2),硬件在 SCL 尚未达到有效高电平时继续位时序
修复 FREQR 与模式(标准/快速)重算 TRISE,并考虑外部上拉与总线电容

6. 经验清单(Checklist)

  • 失败路径上 必打 SR1/SR2/CCR/TRISE,不要只打 CR1
  • CCR 与 TRISE 成对检查,二者对应不同物理约束
  • 示波器看 上升沿,不要只看频率
  • 修改后做 长时间循环 i2cget/i2cdetect,偶发问题需压力验证
  • 文档记录:当次 PCLKFREQR、目标模式、最终 TRISE 写入值

7. 附录:与本文对应的"编造"寄存器对照表

阶段 CCR TRISE SR1 关键位 结果
调试前 0x50 0x02 AF=1 偶发失败
修复后 0x50 0x25 (37) 正常 稳定通过
相关推荐
脆皮炸鸡7556 小时前
进程信号~信号的产生
linux·服务器·开发语言·经验分享·笔记·学习方法
Emtronix英创6 小时前
RK3568 CAN驱动测试及使用说明
linux·arm开发·rk3568·全国产主板
vortex56 小时前
CentOS 系包管理器完全指南:从 dnf 到 rpm
linux·运维·centos
SZ放sai哑滋7 小时前
工控机刷Linux、Qt教程
linux·运维·服务器
MY_TEUCK7 小时前
【2026最新Linux本地部署Ollama】Ollama Linux 安装全流程(含离线 / 开机自启 / 远程访问)
linux·运维·服务器
无限进步_7 小时前
【Linux】软件包管理器:Linux 的“应用商店”
linux·运维·服务器
z202305087 小时前
RDMA之infiniband专用网络 LID 和GID 的作用
linux·服务器·网络
陈陈CHENCHEN8 小时前
【Linux】Rsync + Inotify 实时文件同步案例
linux·运维·服务器
charlie1145141918 小时前
嵌入式Linux嵌入式Linux驱动开发:设备树驱动改造——从硬编码到设备树的实战之旅
linux·运维·驱动开发