📚 I2C 技术系列 · 持续更新中
本系列从 OD 开漏物理层一路打到 Linux i2c子系统,把"两根线"背后那套电气/协议/仲裁/恢复/驱动全拧成一根线。
👉 想系统追更,戳这里查看完整目录:
本篇定位:协议是软件视角,控制器是硬件实现。本篇讲 I2C 控制器 IP 内部长什么样------状态机、移位寄存器、控制/状态/数据寄存器,以及 STM32 / DesignWare / RISC-V SoC 的差异。读完你能看懂任何 I2C 控制器的寄存器手册,知道每一步硬件在干啥。
懂控制器状态机才能调死锁你调 I2C,控制器卡在某状态(BUSY 一直 1),不知怎么死的。看状态机图,知道它卡在"等 ACK"还是"等 STOP",才能对症下药。寄存器手册 + 状态机是 I2C 驱动工程师的内功。
目录
- [一、控制器 IP 总框图](#一、控制器 IP 总框图)
- 二、核心组成模块
-
- [2.1 分频器](#2.1 分频器)
- [2.2 移位寄存器](#2.2 移位寄存器)
- [2.3 过滤与同步](#2.3 过滤与同步)
- [2.4 中断逻辑](#2.4 中断逻辑)
- 三、状态机详解
-
- [3.1 主模式状态机(简化)](#3.1 主模式状态机(简化))
- [3.2 关键状态](#3.2 关键状态)
- [3.3 卡死状态定位](#3.3 卡死状态定位)
- 四、关键寄存器
-
- [4.1 通用寄存器组(概念)](#4.1 通用寄存器组(概念))
- [4.2 STM32F1 I2C 寄存器(实例)](#4.2 STM32F1 I2C 寄存器(实例))
- [4.3 Synopsys DesignWare 寄存器(实例)](#4.3 Synopsys DesignWare 寄存器(实例))
- [4.4 关键状态位对照](#4.4 关键状态位对照)
- [五、典型 IP 对照](#五、典型 IP 对照)
-
- [5.1 STM32 I2C](#5.1 STM32 I2C)
- [5.2 Synopsys DesignWare](#5.2 Synopsys DesignWare)
- [5.3 NXP LPC](#5.3 NXP LPC)
- [5.4 RISC-V SoC(芯来/Andes/平头哥)](#5.4 RISC-V SoC(芯来/Andes/平头哥))
- [六、DMA 触发与 FIFO](#六、DMA 触发与 FIFO)
-
- [6.1 FIFO](#6.1 FIFO)
- [6.2 DMA 触发](#6.2 DMA 触发)
- [6.3 FIFO + DMA 的工作流](#6.3 FIFO + DMA 的工作流)
- [七、关键问题 Q&A](#七、关键问题 Q&A)
-
- [Q1:控制器卡 BUSY,怎么定位?](#Q1:控制器卡 BUSY,怎么定位?)
- [Q2:DesignWare 的 IC_TX_ABRT_SOURCE 怎么用?](#Q2:DesignWare 的 IC_TX_ABRT_SOURCE 怎么用?)
- [Q3:STM32 I2C 状态机为什么"难用"?](#Q3:STM32 I2C 状态机为什么"难用"?)
- Q4:滤波长度怎么配?
- [Q5:FIFO 没数据时发什么?](#Q5:FIFO 没数据时发什么?)
- Q6:从机模式状态机和主机有何不同?
- 八、实战案例
-
- [案例 1:DesignWare I2C 卡 BUSY,读 ABRT_SOURCE 定位](#案例 1:DesignWare I2C 卡 BUSY,读 ABRT_SOURCE 定位)
- [案例 2:STM32F1 I2C 事件顺序错卡死](#案例 2:STM32F1 I2C 事件顺序错卡死)
- [案例 3:FIFO + DMA 读 BMP280,首字节丢](#案例 3:FIFO + DMA 读 BMP280,首字节丢)
- [案例 4:RISC-V SoC I2C 拉伸超时未配,挂死](#案例 4:RISC-V SoC I2C 拉伸超时未配,挂死)
- 九、本篇小结与速查
- 延伸阅读
- 参考资料
一、控制器 IP 总框图
典型 I2C 控制器 IP 内部:
┌─────────────────────────────────────────┐
│ I2C 控制器 IP │
│ │
APB/ │ ┌──────────┐ ┌──────────┐ │
AHB ←──┼─→│ 寄存器组 │←→│ 控制逻辑 │ │
总线 │ │ CON/STAT │ │ (状态机) │ │
│ │ DAT/ADDR │ └────┬─────┘ │
│ └──────────┘ │ │
│ ↓ │
│ ┌──────────┐ ┌──────────┐ │
│ │ 分频器 │←→│ 移位寄存器│ │
│ │(SCL生成)│ │ (SDA收发)│ │
│ └────┬─────┘ └────┬─────┘ │
│ │ │ │
│ ┌────┴─────┐ ┌────┴─────┐ │
│ │ 中断逻辑 │ │ 过滤/同步│ │
│ │ DMA 接口 │ │ (去毛刺) │ │
│ └──────────┘ └────┬─────┘ │
└──────────────────────┼──────────────────┘
│
SDA / SCL(OD 输出)
- 寄存器组:软件接口,配置/读状态/读写数据
- 控制逻辑(状态机):协议实现核心
- 分频器:从系统时钟分频出 SCL
- 移位寄存器:SDA 串行↔并行转换
- 过滤/同步:SDA/SCL 输入去毛刺、跨时钟域同步
- 中断/DMA:事件通知、批量传输
二、核心组成模块
2.1 分频器
PCLK(系统时钟) → 分频器 → SCL(目标频率)
分频系数 = PCLK / (fSCL × 2)(基本)
实际:HCNT/LCNT 分别配 tHIGH/tLOW
- 配 HCNT(高电平计数)、LCNT(低电平计数)
- 【I2C 技术系列 06】I2C 速度模式与时序参数讲过分频计算
2.2 移位寄存器
SDA 串行 → [移位寄存器 8bit] → 并行数据(读)
并行数据(写) → [移位寄存器 8bit] → SDA 串行
时钟:SCL 边沿移位
方向:发(MSB先出)/收(MSB先进)
- 发送:写 DAT 寄存器 → 移位到 SDA
- 接收:SDA 移位 → 读 DAT 寄存器
2.3 过滤与同步
SDA/SCL 外部进来,先过:
外部 SDA/SCL → 去毛刺(数字滤波) → 同步(打2拍) → 内部使用
- 去毛刺:滤除 < 50ns 的尖峰(可配滤波长度)
- 同步:跨时钟域(PCLK ≠ SCL 域)打 2 拍防亚稳态
!note 嵌入式视角:滤波长度可配置是关键
你在强干扰环境(汽车/工业),I2C 上易耦合毛刺,控制器滤波长度要配大(滤掉 ns 级尖峰)。但配太大可能滤掉合法短脉冲。滤波长度 = 抗噪 vs 速度的权衡,功能安全场景必配。
2.4 中断逻辑
事件 → 置状态位 → 若使能则触发中断:
事件:
START 发出 / STOP 发出
TX_EMPTY(发送寄存器空,可写下一字节)
RX_FULL(收到一字节,可读)
ACK/NACK 收到
ARB_LOST(仲裁丢失)
TIMEOUT(超时)
NACK(从机不应答)
...
- 每个事件对应一个状态位 + 中断使能位
- 软件按事件分支处理
三、状态机详解
3.1 主模式状态机(简化)
┌─────────┐
│ IDLE │←─────────────────────┐
└────┬────┘ │
│ 软件发起 START │
↓ │
┌─────────┐ 发完地址 ┌────────┴────┐
│ START │────────────→│ ADDR+RW 发送 │
└─────────┘ └──────┬──────┘
│ 收 ACK
┌──────────────┴──────────────┐
↓ ↓
┌────────────┐ ┌────────────┐
│ 主发送(写)│ │ 主接收(读)│
│ TX 数据 │ │ RX 数据 │
└─────┬──────┘ └─────┬──────┘
│ 发完+ACK/NACK │ 收完+ACK/NACK
↓ ↓
┌────────────┐ ┌────────────┐
│ 发 STOP │ │ 发 STOP │
└─────┬──────┘ └─────┬──────┘
└─────────────┬────────────────┘
└──→ IDLE
3.2 关键状态
| 状态 | 含义 | 卡住原因 |
|---|---|---|
| IDLE | 空闲 | --- |
| START | 发 START | SDA/SCL 被拉低(总线忙) |
| ADDR | 发地址等 ACK | 从机不应答(地址错/器件不在) |
| TX | 发数据等 ACK | 从机不应答 / 拉伸 |
| RX | 收数据发 ACK/NACK | 从机不驱动 SDA / 拉伸 |
| STOP | 发 STOP | SDA 被从机拉低(没收 NACK) |
| ARB_LOST | 仲裁丢失 | 多主冲突(瞬时) |
3.3 卡死状态定位
控制器卡死,看状态寄存器:
STAT = ?
BUSY=1, TIP=0(传输完成位)=1 → 卡在传输中
NACK=1 → 从机不应答
ARB_LOST=1 → 仲裁丢
RX_FULL=1 → 数据没收走
BUSY=1 长时间 → 总线挂死(拉伸/从机拉低)
- BUSY=1 一直不消:总线挂死,要恢复(10 篇)
- TIP=1 一直不消:传输卡在中间,可能拉伸超时
四、关键寄存器
4.1 通用寄存器组(概念)
| 寄存器 | 含义 |
|---|---|
| CON | 控制(使能/主从/速度/中断使能) |
| STAT | 状态(BUSY/TIP/NACK/ARB_LOST/RX_FULL/TX_EMPTY) |
| DAT | 数据(写发/读收) |
| ADDR | 从机地址(7bit) + RW |
| BAUD/HCNT/LCNT | 分频(高/低电平计数) |
| INTEN | 中断使能 |
| INTSTAT | 中断状态(原始)/ INTCLR(清除) |
| FIFO_CTRL | FIFO 配置(若有) |
| DMA | DMA 配置(若有) |
| TIMEOUT | 拉伸/总线超时 |
| FLT | 滤波长度 |
4.2 STM32F1 I2C 寄存器(实例)
| 寄存器 | 含义 |
|---|---|
| CR1 | 控制1(PE/START/STOP/ACK/POS) |
| CR2 | 控制2(频率/中断使能/DMA) |
| DR | 数据寄存器 |
| SR1 | 状态1(SB/ADDR/TxE/RxNE/BTF) |
| SR2 | 状态2(BUSY/MSL/TRA/GENCALL) |
| OAR1/OAR2 | 自身地址(从模式) |
| CCR | 时钟分频 |
| TRISE | 上升时间 |
STM32F1 I2C 状态机复杂(事件多),开发体验差,F4/F7/H7 改进了。
4.3 Synopsys DesignWare 寄存器(实例)
| 寄存器 | 含义 |
|---|---|
| IC_CON | 控制(速度/主从/重启) |
| IC_TAR | 目标地址(主模式从机地址) |
| IC_SAR | 自身地址(从模式) |
| IC_DATA_CMD | 数据/命令(写发/读请求) |
| IC_SS_SCL_HCNT/LCNT | 标准模式分频 |
| IC_FS_SCL_HCNT/LCNT | 快速模式分频 |
| IC_HS_SCL_HCNT/LCNT | 高速模式分频 |
| IC_INTR_STAT/MASK | 中断状态/屏蔽 |
| IC_RAW_INTR_STAT | 原始中断状态 |
| IC_STATUS | 状态(BUSY/TFNF/TFE/RFNE) |
| IC_TX_ABRT_SOURCE | 中止原因(ARB_LOST/NACK...) |
| IC_ENABLE | 使能 |
| IC_CLR_* | 清除各类中断 |
DesignWare IP 是 Linux 最常见 I2C 控制器(ARM/RISC-V SoC 多用)。
4.4 关键状态位对照
| 功能 | STM32 | DesignWare |
|---|---|---|
| 总线忙 | SR2.BUSY | IC_STATUS.ACTIVITY |
| 发送空 | SR1.TxE | IC_STATUS.TFE |
| 接收满 | SR1.RxNE | IC_STATUS.RFNE |
| START 发出 | SR1.SB | IC_INTR.START_DET |
| 地址发出 | SR1.ADDR | IC_INTR.RD_REQ + ADDR |
| NACK | SR1.AF | IC_TX_ABRT_SOURCE.ABRT_7B_ADDR_NOACK |
| 仲裁丢失 | SR1.BERR/ARLO | IC_TX_ABRT_SOURCE.ARB_LOST |
| STOP 发出 | --- | IC_INTR.STOP_DET |
五、典型 IP 对照
5.1 STM32 I2C
- 型号差异:F1(复杂事件)/F4(改进)/H7(独立时钟 + 更灵活)
- 特点:状态机事件多,开发繁琐;H7 改善
- 优势:ST 生态完善,HAL/LL 库
5.2 Synopsys DesignWare
- ARM/RISC-V SoC 常用(RK/RISC-V 厂商多授权)
- 特点:寄存器清晰,FIFO + DMA 完整,Linux 主力支持
- 优势:Linux i2c-designware 驱动成熟
5.3 NXP LPC
- LPC 系列 I2C 简洁
- 特点:状态寄存器位少,易用
5.4 RISC-V SoC(芯来/Andes/平头哥)
- 多数集成 DesignWare 或自研 IP
- 配 HCNT/LCNT 分频,中断驱动
六、DMA 触发与 FIFO
6.1 FIFO
- 现代控制器多有 TX/RX FIFO(8~32 字节)
- TX FIFO:软件一次写多字节,硬件逐字节发
- RX FIFO:硬件逐字节收,软件批量读
- 优势:减少中断次数(CPU 不用每字节进出)
6.2 DMA 触发
- TX FIFO 半空 → 触发 DMA,DMA 从内存搬数据到 TX FIFO
- RX FIFO 半满 → 触发 DMA,DMA 搬数据到内存
- 阈值可配(1/4、1/2、3/4)
6.3 FIFO + DMA 的工作流
软件:配置 DMA(源/目的/长度)+ I2C(地址+读/写长度)→ 启动
硬件:
写:DMA 从内存 → TX FIFO → 移位 → SDA
读:SDA → 移位 → RX FIFO → DMA → 内存
完成中断:DMA 传输完 + I2C STOP
软件:处理完成
- 大块传输(EEPROM 多字节/图像缓冲)用 DMA
- 小数据(寄存器读写)用中断/轮询
七、关键问题 Q&A
Q1:控制器卡 BUSY,怎么定位?
A:
- 读 STAT,BUSY=1 是真忙还是假忙
- 看 TIP(传输中)/NACK/ARB_LOST 位
- 示波器看 SDA/SCL:有没有动?停在什么电平?
- 若 SCL 被从机拉低(拉伸)→ 拉伸超时 + 恢复(10 篇)
- 若 SDA 被从机拉低 → 9 时钟恢复(10 篇)
- 软件复位控制器 → 重新初始化
Q2:DesignWare 的 IC_TX_ABRT_SOURCE 怎么用?
A:传输中止时,读 IC_TX_ABRT_SOURCE 寄存器,各位表示中止原因:
- ABRT_7B_ADDR_NOACK:7bit 地址无应答
- ABRT_10ADDR1_NOACK:10bit 第一字节无应答
- ABRT_10ADDR2_NOACK:10bit 第二字节无应答
- ABRT_TXDATA_NOACK:数据无应答
- ABRT_GCALL_NOACK:通用广播无应答
- ARB_LOST:仲裁丢失
- ABRT_SBYTE_NORSTART:单字节无重启
- ABRT_HS_NORSTART:Hs 无重启
读后自动清除(读 IC_CLR_TX_ABRT)。
Q3:STM32 I2C 状态机为什么"难用"?
A :STM32F1 I2C 状态机事件多(SB/ADDR/ADDR10/TxE/RxNE/BTF/STOPF 等),每个事件要软件清标志,顺序错就卡死。F4/H7 改进(寄存器重组 + 独立时钟)。用 HAL/LL 库封装好,别直接撸寄存器。
Q4:滤波长度怎么配?
A:
- 标准/快速模式:配 50ns 左右(滤毛刺)
- 强干扰:配 100~200ns
- 高速模式:配小(< 30ns),否则滤掉合法信号
- DesignWare:IC_FS_SPKLEN 寄存器
- STM32:无显式配置(内部固定)
Q5:FIFO 没数据时发什么?
A:TX FIFO 空,控制器等。若配置了"TX FIFO 空时发 STOP",则自动 STOP;否则挂起等软件填。多数 IP 默认挂起,软件要保证 FIFO 不空(用 DMA 或及时中断填)。
Q6:从机模式状态机和主机有何不同?
A:从机:
- 等主机 START + 地址匹配
- 匹配后,主机发 RW 决定从机收(写)或发(读)
- 从机按 SCL 节拍移位
- 从机可拉伸 SCL 暂停
- 从机发 ACK/NACK
从机驱动用于:把 MCU 当 I2C 从机给主控读(如传感器模拟、调试桥)。
八、实战案例
案例 1:DesignWare I2C 卡 BUSY,读 ABRT_SOURCE 定位
现象:RK3399 I2C 读传感器,偶发卡死,BUSY=1。
排查:
- 读 IC_TX_ABRT_SOURCE = 0x40(ARB_LOST)
- 但系统是单主,不该有仲裁
- 查波形:SCL 上有毛刺,被误识别为其他主信号
- 增大滤波长度(IC_FS_SPKLEN),问题消失
案例 2:STM32F1 I2C 事件顺序错卡死
现象:STM32F1 写 EEPROM,读完 SR1 没立刻读 SR2(清 ADDR),卡住。
分析:STM32F1 I2C ADDR 标志要"读 SR1 再读 SR2"清除,顺序错就卡。
解决:严格按手册事件序列,或用 HAL 库。
案例 3:FIFO + DMA 读 BMP280,首字节丢
现象:DMA 读 BMP280 6 字节,第一字节错。
分析:DMA 启动时序错,IC_DATA_CMD 没先发读请求就启动 DMA,首字节没触发。
解决:先写 IC_DATA_CMD(读请求 N 次),再启动 DMA。
案例 4:RISC-V SoC I2C 拉伸超时未配,挂死
现象:N300 平台 I2C 读 EEPROM,写后立刻读,偶发挂死。
分析:EEPROM 写周期 5ms,拉伸 SCL,控制器未配拉伸超时,等到完成;但某次 EEPROM 异常拉伸超 1s,控制器无限等。
解决:配 IC_TMO(拉伸超时),超时触发中断 + 恢复。
九、本篇小结与速查
核心要点
- 控制器 IP = 寄存器组 + 控制逻辑(状态机)+ 分频器 + 移位寄存器 + 过滤同步 + 中断/DMA
- 状态机:IDLE→START→ADDR→TX/RX→STOP→IDLE
- 关键寄存器:CON(控制)/STAT(状态)/DAT(数据)/ADDR(地址)/HCNT-LCNT(分频)/INTR(中断)/ABRT_SOURCE(中止原因)
- 状态位:BUSY/TIP/TX_EMPTY/RX_FULL/NACK/ARB_LOST/START_DET/STOP_DET
- DesignWare IP 最常见(ARM/RISC-V),Linux i2c-designware 驱动成熟
- STM32F1 状态机繁琐,用 HAL/LL 库
- FIFO + DMA:大块传输减中断
- 滤波长度:抗噪 vs 速度权衡
- 卡 BUSY:看 ABRT_SOURCE + 波形定位
DesignWare 关键寄存器速查
| 寄存器 | 作用 |
|---|---|
| IC_CON | 配速度/主从/重启 |
| IC_TAR | 主模式目标地址 |
| IC_DATA_CMD | 数据/读请求(bit8=cmd) |
| IC_INTR_STAT | 中断状态 |
| IC_TX_ABRT_SOURCE | 中止原因 |
| IC_STATUS | BUSY/TFE/RFNE |
| IC_CLR_* | 清中断 |
| IC_*_SCL_HCNT/LCNT | 分频 |
速查表
| 想干啥 | 看哪个寄存器/位 |
|---|---|
| 看忙不忙 | STAT.BUSY / IC_STATUS.ACTIVITY |
| 看传输中 | STAT.TIP |
| 看从机应答 | STAT.NACK / ABRT.7B_ADDR_NOACK |
| 看仲裁 | STAT.ARB_LOST / ABRT.ARB_LOST |
| 看中断原因 | INTR_STAT / TX_ABRT_SOURCE |
| 配速度 | HCNT/LCNT + CON 速度位 |
| 发数据 | DAT / IC_DATA_CMD |
| 收数据 | DAT / IC_DATA_CMD(读请求) |
| 清中断 | IC_CLR_* / 写 1 清 |
| 配 DMA | DMA 寄存器 + FIFO 阈值 |
延伸阅读
- \[04-I2C协议与时序详解] ------ 状态机对应协议事件
- \[06-I2C速度模式与时序参数] ------ 分频寄存器计算
- \[09-I2C传输模式-轮询-中断-DMA] ------ 寄存器的三种使用模式
- \[10-I2C错误处理与总线恢复] ------ 卡 BUSY 怎么恢复
- \[11-I2C Linux驱动框架] ------ Linux 下寄存器封装
参考资料
- Synopsys DesignWare I2C Databook
- STM32 RM0008(I2C 章节)
- Linux drivers/i2c/busses/i2c-designware.c
- NXP LPC I2C User Manual
\[00-I2C技术系列总目录] · 上一篇:\[07-I2C寻址与协议扩展] · 下一篇:\[09-I2C传输模式-轮询-中断-DMA]
✨ 感谢你的阅读 ✨
技术之路漫漫,分享是为了更好地交流。
如果本文的内容对你有启发,希望能得到你的支持:
👍 点赞 | ⭐ 收藏 | 👀 关注
如果你在调试过程中遇到了其他问题,
欢迎在 评论区 💬 留言,我们一起探讨。
关注我,一起交流底层开发的那些事儿!🚀