4、寄存器详解(下):数据寄存器DR、时钟控制寄存器CCR、TRISE寄存器
好,咱们接着聊。上一节我们把I2C的状态寄存器(SR1/SR2)和控制寄存器(CR1/CR2)捋了一遍。说实话,那些标志位和使能位是I2C通信的“交通规则”,你懂了它们,就知道什么时候该踩刹车、什么时候该加油门。
但光有交通规则还不够,你还得知道“方向盘”和“仪表盘”怎么用。这一节要讲的三个寄存器——数据寄存器DR、时钟控制寄存器CCR、TRISE寄存器,就是真正让你操控I2C总线、收发数据的关键。
我个人习惯把这三个寄存器称为“I2C三件套”:DR管数据,CCR管速度,TRISE管波形。缺一个,你的I2C就跑不顺。
4.1 数据寄存器DR:I2C的“收发室”
DR寄存器,全名Data Register,地址偏移0x10。它是个16位寄存器,但实际只用了低8位(位[7:0])。
说白了,它就是I2C外设的“收发室”。你要发送的数据,写进DR;你收到的数据,从DR读出来。硬件自动帮你完成移位、并串转换这些脏活累活。
核心要点:DR寄存器在发送和接收模式下,行为是不一样的。别搞混了。
4.1.1 发送模式下的DR
当你往DR里写数据时,硬件会做两件事:
- 把数据从DR拷贝到移位寄存器
- 置位TXE(发送数据寄存器空)标志位
TXE置位意味着:DR已经空了,你可以写下一个字节了。但注意——数据不一定已经发送出去了!它可能还在移位寄存器里,正一位一位地往SDA线上送呢。
我曾经犯过一个低级错误:连续往DR里写数据,没等TXE标志位变高就写下一个。结果呢?数据覆盖了,丢包。后来我养成一个习惯:每次写DR之前,先检查TXE是否为1。这个习惯救了我很多次。
4.1.2 接收模式下的DR
接收时,DR的行为刚好反过来。当移位寄存器收完一个字节,硬件会自动把它拷贝到DR,然后置位RXNE(接收数据寄存器非空)标志位。
RXNE置位意味着:DR里有数据等你取走。你得在下一个字节到来之前把它读出来,否则新数据会覆盖旧数据——这叫“溢出错误”(OVR)。
注意:溢出错误一旦发生,SR1寄存器的OVR位会被置1。如果你不处理,I2C外设会一直卡住。我见过有人调试了两天,最后发现是忘了读DR导致OVR一直挂着。
4.1.3 DR的“双缓冲”机制
你可能会问:为什么DR和移位寄存器要分开?
答案是:为了效率。DR和移位寄存器构成了一个双缓冲结构。你在DR里准备下一个字节的同时,硬件正在移位寄存器里发送/接收当前字节。两者并行工作,不浪费一个时钟周期。
我画个图帮你理解:
实战技巧:在中断服务函数里读写DR时,记得先读SR1再读/写DR。因为硬件在访问DR时会自动清除某些标志位(比如TXE、RXNE),顺序搞反了会导致标志位状态不对。
4.2 时钟控制寄存器CCR:I2C的“油门”
CCR,全名Clock Control Register,地址偏移0x1C。这个寄存器决定了I2C的通信速率——也就是SCL线的时钟频率。
你想想看,I2C总线上的所有设备都靠SCL时钟同步。如果CCR配错了,要么通信太快从机跟不上,要么太慢浪费带宽。我见过最离谱的案例:有人把CCR配成了0,结果SCL直接不跑了,总线死在那。
4.2.1 CCR的位结构
| 位域 | 名称 | 功能说明 |
|---|---|---|
| 位[15:12] | F/S | 模式选择:0=标准模式(100kHz),1=快速模式(400kHz) |
| 位[11] | DUTY | 快速模式占空比:0=T_low=2×CCR, T_high=CCR;1=T_low=9×CCR, T_high=2×CCR |
| 位[5:0] | CCR[5:0] | 时钟分频系数,决定SCL频率 |
嗯,这里要注意:CCR的计算公式取决于你用的是标准模式还是快速模式。
4.2.2 标准模式(100kHz)
标准模式下,SCL频率的计算公式很简单:
SCL频率 = APB时钟频率 / (2 × (CCR + 1))
举个例子,如果你的APB1时钟是36MHz,想要100kHz的SCL:
100000 = 36000000 / (2 × (CCR + 1))
CCR + 1 = 36000000 / 200000 = 180
CCR = 179
所以CCR配成179(0xB3)就行了。
4.2.3 快速模式(400kHz)
快速模式下,公式稍微复杂一点,因为引入了DUTY位:
- DUTY=0(占空比2:1):SCL频率 = APB时钟频率 / (3 × (CCR + 1))
- DUTY=1(占空比9:2):SCL频率 = APB时钟频率 / (25 × (CCR + 1))
我个人习惯用DUTY=0,因为计算简单,而且2:1的占空比在大多数场景下够用了。还是36MHz的APB1,配400kHz:
400000 = 36000000 / (3 × (CCR + 1))
CCR + 1 = 36000000 / 1200000 = 30
CCR = 29
注意:快速模式下,CCR的最小值是1(不能为0)。另外,APB时钟频率不能太低——如果APB低于2MHz,你就算把CCR配成1,也跑不到400kHz。硬件有极限,别硬来。
4.2.4 我踩过的坑:CCR与APB时钟的关系
有一次我做项目,把APB1时钟从36MHz改到了8MHz(为了省电),结果I2C突然不工作了。查了半天,发现CCR没跟着改,SCL频率掉到了十几kHz,从机直接超时了。
所以我的建议是:把CCR的计算封装成一个宏或函数,每次改系统时钟后自动重新计算。别手算,别硬编码。
#define I2C_CCR_CALC(apb_clk, target_freq, mode) \
((mode == I2C_MODE_STANDARD) ? \
((apb_clk) / (2 * (target_freq)) - 1) : \
((apb_clk) / (3 * (target_freq)) - 1))
4.3 TRISE寄存器:I2C的“波形整形器”
TRISE寄存器,全名Rise Time Register,地址偏移0x20。这个寄存器很多人会忽略,但它其实很重要。
为什么需要TRISE?因为I2C总线上的信号不是理想方波——从低电平跳到高电平需要时间,这个时间叫“上升时间”(Rise Time)。如果SCL的上升时间太长,从机可能误判时钟边沿。
TRISE的作用就是告诉I2C外设:SCL线的最大上升时间是多少。硬件会根据这个值来调整内部采样点,确保在上升沿期间不误采样。
3.3.1 TRISE的计算
TRISE的值很简单:
TRISE = (最大上升时间 / APB时钟周期) + 1
I2C规范里规定了最大上升时间:
- 标准模式(100kHz):1000ns
- 快速模式(400kHz):300ns
还是36MHz的APB1(周期≈27.8ns):
- 标准模式:TRISE = 1000 / 27.8 + 1 ≈ 36 + 1 = 37(0x25)
- 快速模式:TRISE = 300 / 27.8 + 1 ≈ 10 + 1 = 11(0x0B)
经验值:如果你用的总线不长(比如板内通信,线长<10cm),实际上升时间通常比规范值小。但TRISE配大一点没关系——它只是让硬件更“宽容”,不会影响功能。我一般直接按规范最大值配,省事。
3.3.2 TRISE配错会怎样?
TRISE配小了,硬件会在上升沿期间误认为SCL已经稳定,提前采样,导致数据错位。TRISE配大了,只是稍微降低了一点抗干扰能力,但功能正常。
所以我的原则是:TRISE宁大勿小。
4.4 三个寄存器的配置顺序
这三个寄存器在初始化时,配置顺序是有讲究的。我总结了一个“三步走”:
- 先配CCR:确定通信速率,这是基础
- 再配TRISE:根据CCR选择的模式,计算上升时间
- 最后配CR1的PE位:使能I2C外设,开始工作
为什么这个顺序?因为CCR和TRISE必须在I2C外设使能之前配好。如果你先使能了I2C,再改CCR,硬件可能产生意外的时钟脉冲,把总线搞乱。
初始化代码骨架:
// 1. 配置CCR(标准模式,100kHz)
I2C1->CCR = 0xB3; // 假设APB1=36MHz
// 2. 配置TRISE
I2C1->TRISE = 0x25; // 标准模式,1000ns上升时间
// 3. 使能I2C外设
I2C1->CR1 |= I2C_CR1_PE;
4.5 小结
这一节的内容就这些。DR、CCR、TRISE这三个寄存器,是I2C驱动开发中最常用的三个寄存器。你写驱动时,80%的时间都在跟它们打交道。
DR是数据通道,记住“先查标志再读写”就行。CCR是速度控制器,算好分频系数别超限。TRISE是波形保障,宁大勿小。