4. I2C起始与停止条件生成:手动控制I2C_CR1的START/STOP位
好,咱们进入实战环节。上一章我们把寄存器映射关系理清了,这一章直接上手——用逻辑分析仪看波形。
I2C通信里,起始条件和停止条件是最基本的两个动作。说白了,就像打电话时的“拨号”和“挂断”。没有这两个信号,从机根本不知道你什么时候开始说话、什么时候说完。
4.1 起始条件与停止条件的硬件行为
先看协议层面的定义:
- 起始条件(S):SCL高电平时,SDA从高电平切换到低电平
- 停止条件(P):SCL高电平时,SDA从低电平切换到高电平
这两个时序是I2C总线仲裁的基础。所有从机都在监测这个边沿。
关键点:起始和停止条件必须由主机产生。STM32作为主机时,我们通过操作I2C_CR1寄存器的START位和STOP位来触发。
我个人习惯把这两个位叫做“扳机位”——你写1,硬件就自动帮你把时序拉出来。你不需要手动去翻转GPIO。
4.2 I2C_CR1寄存器中的START和STOP位
打开参考手册,找到I2C_CR1寄存器。咱们只看第8位和第9位:
| 位 | 名称 | 功能描述 |
|---|---|---|
| 8 | START | 写1产生起始条件;传输开始后硬件自动清0 |
| 9 | STOP | 写1产生停止条件;传输结束后硬件自动清0 |
注意:这两个位都是写1有效,硬件在完成对应动作后会自己清0。你不需要手动去清。
我曾经踩过的坑:在START位还没被硬件清0之前,又去写STOP位。结果总线状态乱掉,从机直接卡死。正确的做法是:每次操作前先检查SB(起始位标志)或MSL(主从模式标志),确认当前状态再动手。
4.3 手动控制流程:三步走
咱们的目标很简单:用代码手动产生一个起始条件,等一会儿,再产生一个停止条件。然后用逻辑分析仪抓波形。
流程如下:
- 使能I2C外设:设置I2C_CR1的PE位为1
- 产生起始条件:设置I2C_CR1的START位为1
- 等待起始完成:检查I2C_SR1的SB位是否为1
- 产生停止条件:设置I2C_CR1的STOP位为1
- 等待停止完成:检查I2C_SR1的STOPF位(或者直接延时)
嗯,这里要注意:停止条件完成后,硬件会置位STOPF标志。但如果你不关心这个标志,加个短延时也能凑合。不过我个人建议还是查标志,更可靠。
4.4 代码实现:纯寄存器操作
下面这段代码,我直接操作寄存器,不调用任何HAL库函数。这样你能看到最底层发生了什么。
// 假设I2C1已经使能了时钟,GPIO也已经配置为复用开漏
// 这里只展示起始和停止的控制部分
void I2C_GenerateStart(void)
{
// 设置START位为1
I2C1->CR1 |= I2C_CR1_START;
// 等待SB位置1,表示起始条件已发出
while(!(I2C1->SR1 & I2C_SR1_SB));
}
void I2C_GenerateStop(void)
{
// 设置STOP位为1
I2C1->CR1 |= I2C_CR1_STOP;
// 等待STOPF位置1,表示停止条件已发出
// 注意:STOPF位在SR1中,但需要先读SR1再读SR2才能清掉
while(!(I2C1->SR1 & I2C_SR1_STOPF));
// 读SR2清标志(实际项目中需要)
uint16_t tmp = I2C1->SR2;
(void)tmp; // 防止编译器警告
}
int main(void)
{
// ... 初始化代码省略 ...
// 第一步:使能I2C1
I2C1->CR1 |= I2C_CR1_PE;
// 第二步:产生起始条件
I2C_GenerateStart();
// 延时一段时间,让逻辑分析仪能看清
for(volatile uint32_t i = 0; i < 100000; i++);
// 第三步:产生停止条件
I2C_GenerateStop();
while(1);
}
小技巧:调试时可以在产生起始和停止之间加个GPIO翻转,用示波器同时抓GPIO和I2C总线,方便定位时序问题。
4.5 逻辑分析仪捕获波形实战
代码烧进去之后,把逻辑分析仪的通道0接SCL,通道1接SDA,共地。设置采样率至少4MHz(I2C标准模式100kHz,4倍采样够用)。
触发条件设为SDA下降沿(起始条件)。然后点击开始捕获,复位板子。
你会看到类似这样的波形:
你会看到:起始条件处,SCL高电平时SDA下降;停止条件处,SCL高电平时SDA上升。中间那段SCL在翻转、SDA在变化,那是数据段——咱们下一章再细聊。
4.6 常见问题与避坑指南
- 波形出不来? 先检查GPIO配置是不是复用开漏,有没有外部上拉电阻。我遇到过好几次,板子上忘了焊上拉,总线一直低电平。
- 起始条件重复产生? 检查代码里是不是循环调用了GenerateStart。START位写一次就够了,硬件清0之前别重复写。
- 停止条件后总线被拉低? 可能是从机还在响应。检查从机地址或时钟延展问题。
我曾经遇到一个诡异的问题:起始条件发出后,SB标志一直不置1。查了半天,发现是I2C外设时钟没使能。记住:操作任何外设寄存器之前,先确认RCC里对应的时钟位已经打开。
4.7 本章小结
这一章我们做了三件事:
- 理解了起始条件和停止条件的协议定义
- 学会了操作I2C_CR1的START和STOP位
- 用逻辑分析仪亲眼看到了波形