3. 寄存器详解(上):控制寄存器 CR1、CR2,状态寄存器 SR1、SR2
好,咱们直接进入正题。I2C 外设能不能听话,全看这几个寄存器怎么配。今天先啃掉四个最核心的:CR1、CR2、SR1、SR2。说白了,CR 是发号施令的,SR 是汇报状态的。搞懂它们,驱动就写好了一半。
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,不然从机以为你还要继续收。
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 读数据。我一般在中断服务函数里就盯着这两个位。
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 时要注意顺序。
3.5 知识结构图:四个寄存器的协作关系
下面这张图展示了 CR1、CR2、SR1、SR2 在 I2C 通信中的角色和交互方式。你可以看到,控制寄存器负责“发令”,状态寄存器负责“汇报”,而数据寄存器 DR 是它们之间的桥梁。
3.6 实战要点总结
- CR1 的 PE 位是总开关——先使能,再配其他。复位后默认是 0。
- CR2 的 FREQ 必须写对——与实际 APB 时钟一致,否则时序全乱。
- SR1 的状态位是驱动核心——SB、ADDR、TxE、RxNE 这四个位,中断服务函数里天天见。
- 清标志位有固定顺序——ADDR 位必须“读 SR1 再读 SR2”,顺序不能错。
- SR2 的 BUSY 位用来防冲突——多主机环境或重试逻辑中必用。