3. 寄存器详解(上):控制寄存器 CR1、CR2,状态寄存器 SR1、SR2

好,咱们直接进入正题。I2C 外设能不能听话,全看这几个寄存器怎么配。今天先啃掉四个最核心的:CR1、CR2、SR1、SR2。说白了,CR 是发号施令的,SR 是汇报状态的。搞懂它们,驱动就写好了一半。

核心观点: 操作 I2C 寄存器,本质上就是“写控制字 → 查状态位 → 做动作”的循环。别想着一次配完就完事,中断模式下状态位的变化才是关键。

3.1 控制寄存器 CR1:I2C 外设的“总开关”

CR1 是 16 位的,但实际用到的位不多。我习惯把它分成三组来看:使能位、中断使能位、以及协议配置位。

位号 名称 作用 我常用的值
15 SWRST 软件复位 I2C 外设 0(正常)
10 ACK 应答使能 1(使能应答)
9 STOP 产生停止条件 由硬件自动清
8 START 产生起始条件 由硬件自动清
6 PE 外设使能 1(使能)
1 ITEVTEN 事件中断使能 1(中断模式)
0 ITBUFEN 缓冲区中断使能 1(中断模式)

PE 位(位 6)——这是总闸。不置 1,其他位配得再漂亮也没用。我记得刚学 STM32 时,有次调了半天 I2C 没反应,最后发现是 PE 位忘了写。嗯,这种低级错误犯一次就够了。

START 和 STOP 位(位 8、9)——这两个位是“一次性”的。你写 1 进去,硬件就开始干活,干完自动清 0。所以千万别在循环里反复写它们,除非你想让总线乱掉。

ACK 位(位 10)——主机模式下,这个位决定收到数据后是否回 ACK。如果你做的是主机接收,记得在接收最后一个字节前把它清 0,不然从机以为你还要继续收。

我的习惯: 初始化时先把 CR1 全部清零,再按需置位。这样能避免上电后某些位处于不确定状态。

3.2 控制寄存器 CR2:时钟与 DMA 配置

CR2 主要干两件事:设置 I2C 时钟频率,以及配置 DMA 传输。16 位寄存器里,低 6 位是 FREQ[5:0],用来写 APB 时钟频率(单位 MHz)。

举个例子,如果你的 APB1 时钟是 36MHz,那 FREQ 就写 36。为什么?因为 I2C 硬件需要知道总线时钟,才能正确生成 SCL 的时序。写错了,通信速率就不对。

我个人建议:FREQ 的值一定要和实际 APB 时钟严格对应。之前有个项目,同事把 36MHz 写成了 40,结果 I2C 在高速模式下老是丢数据。查了两天才找到原因。

位号 名称 作用 说明
5:0 FREQ[5:0] APB 时钟频率 2MHz ~ 50MHz,写实际值
11 ITERREN 错误中断使能 建议开启
12 DMAEN DMA 请求使能 大数据量传输时用
14 LAST DMA 最后传输标志 配合 DMA 使用

CR2 里还有 ITERREN(位 11),这是错误中断使能。我强烈建议你在调试阶段把它打开。一旦 I2C 出现仲裁丢失、应答失败之类的问题,能立刻进中断抓住现场。

3.3 状态寄存器 SR1:通信过程的“仪表盘”

SR1 是 16 位的,但只用到了低 10 位。每个位都代表一个事件或错误状态。你想想看,I2C 通信过程中,硬件会不断更新这些位,你的代码就是通过读它们来判断下一步该做什么。

位号 名称 含义 触发条件
7 TxE 发送缓冲区空 DR 寄存器已空,可写新数据
6 RxNE 接收缓冲区非空 DR 寄存器有数据待读
5 SB 起始条件已发送 主机模式下 START 位完成
4 ADDR 地址已发送/匹配 主机发送地址后,或从机地址匹配
3 BTF 字节传输完成 移位寄存器和 DR 都空
2 AF 应答失败 未收到 ACK
1 ARLO 仲裁丢失 多主机冲突
0 BERR 总线错误 起始/停止条件位置错误

SB 位(位 5)——当你写 START 位后,硬件发送起始条件成功,SB 就会置 1。这时候你应该去写从机地址。注意:SB 位需要用软件清 0,方法是先读 SR1,再写 DR。

ADDR 位(位 4)——这个位很有意思。主机模式下,它表示地址字节已经发送完毕并收到了 ACK。从机模式下,它表示收到了匹配的地址。清 0 的方式是读 SR1 再读 SR2。嗯,这里有个坑:必须先读 SR1,再读 SR2,顺序反了清不掉。

TxE 和 RxNE(位 7、6)——这两个是中断模式下最常用的位。TxE 为 1 表示可以写数据到 DR,RxNE 为 1 表示可以从 DR 读数据。我一般在中断服务函数里就盯着这两个位。

避坑指南: 我曾经在写从机发送程序时,忽略了 BTF 位(位 3)。结果数据发到一半,新的数据又写进去了,导致数据错位。BTF 位表示“字节传输真正完成了”,而不仅仅是缓冲区空了。如果你做大数据量传输,一定要检查 BTF。

3.4 状态寄存器 SR2:补充信息与从机状态

SR2 不像 SR1 那么常用,但有几个位在特定场景下非常关键。尤其是做从机设备时,SR2 几乎是必读的。

位号 名称 含义 使用场景
7 PEC 数据包错误校验 SMBus 模式
6 F/S 主从模式标志 0=从机,1=主机
5 DUALF 双地址标志 使能双地址时
4 GENCALL 广播呼叫标志 收到 0x00 地址
1 MSL 主从模式位 0=从机,1=主机
0 BUSY 总线忙标志 检测总线是否被占用

BUSY 位(位 0)——这个位我几乎每次写 I2C 驱动都会用。在发起通信前,先读一下 BUSY 位,如果为 1 就等待或重试。你想想看,如果总线上有其他主机在通信,你强行发 START,不就乱套了吗?

MSL 位(位 1)——用来判断当前设备是主机还是从机。在从机模式下,如果收到 STOP 条件,MSL 会清 0。我曾在调试从机接收时,用这个位来判断主机是否结束了传输。

SR2 还有一个特殊用法:清除 ADDR 位时必须读它。前面说了,清 ADDR 位要“读 SR1 再读 SR2”。这个操作会同时把 SR2 的某些位也更新,所以读 SR2 时要注意顺序。

小技巧: 如果你用中断模式,可以在 ADDR 中断里读一次 SR2,把从机地址和方向信息都抓出来。这样后续的数据处理就能知道自己是收还是发。

3.5 知识结构图:四个寄存器的协作关系

下面这张图展示了 CR1、CR2、SR1、SR2 在 I2C 通信中的角色和交互方式。你可以看到,控制寄存器负责“发令”,状态寄存器负责“汇报”,而数据寄存器 DR 是它们之间的桥梁。

I2C 寄存器协作关系图 控制寄存器组 CR1 PE, START, STOP, ACK CR2 FREQ, ITERREN, DMAEN 数据寄存器 DR 发送/接收数据缓冲区 状态寄存器组 SR1 SB, ADDR, TxE, RxNE SR2 BUSY, MSL, DUALF 写数据 状态更新 中断信号 / 状态反馈 工作流程 1. 配置 CR1/CR2 → 使能外设和中断 2. 写 START 位 → 硬件发送起始条件 → SR1.SB 置位 3. 写从机地址到 DR → 硬件发送 → SR1.ADDR 置位

3.6 实战要点总结

  • CR1 的 PE 位是总开关——先使能,再配其他。复位后默认是 0。
  • CR2 的 FREQ 必须写对——与实际 APB 时钟一致,否则时序全乱。
  • SR1 的状态位是驱动核心——SB、ADDR、TxE、RxNE 这四个位,中断服务函数里天天见。
  • 清标志位有固定顺序——ADDR 位必须“读 SR1 再读 SR2”,顺序不能错。
  • SR2 的 BUSY 位用来防冲突——多主机环境或重试逻辑中必用。
曾经踩过的坑: 我在一个产品中用了 I2C 中断模式,但忘了使能 CR1 的 ITEVTEN 和 ITBUFEN。结果中断从来不进,CPU 轮询 SR1 发现状态位也不动。查了半天才发现是中断使能位没开。嗯,这种“配了外设但没开中断”的错误,估计每个嵌入式工程师都经历过。