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 手动控制流程:三步走

咱们的目标很简单:用代码手动产生一个起始条件,等一会儿,再产生一个停止条件。然后用逻辑分析仪抓波形。

流程如下:

  1. 使能I2C外设:设置I2C_CR1的PE位为1
  2. 产生起始条件:设置I2C_CR1的START位为1
  3. 等待起始完成:检查I2C_SR1的SB位是否为1
  4. 产生停止条件:设置I2C_CR1的STOP位为1
  5. 等待停止完成:检查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下降沿(起始条件)。然后点击开始捕获,复位板子。

你会看到类似这样的波形:

I2C起始与停止条件波形 SCL SDA 起始条件 SDA↓ SCL高 停止条件 SDA↑ SCL高 中间区域为数据/地址传输(本章暂不关注)

你会看到:起始条件处,SCL高电平时SDA下降;停止条件处,SCL高电平时SDA上升。中间那段SCL在翻转、SDA在变化,那是数据段——咱们下一章再细聊。

4.6 常见问题与避坑指南

  • 波形出不来? 先检查GPIO配置是不是复用开漏,有没有外部上拉电阻。我遇到过好几次,板子上忘了焊上拉,总线一直低电平。
  • 起始条件重复产生? 检查代码里是不是循环调用了GenerateStart。START位写一次就够了,硬件清0之前别重复写。
  • 停止条件后总线被拉低? 可能是从机还在响应。检查从机地址或时钟延展问题。

我曾经遇到一个诡异的问题:起始条件发出后,SB标志一直不置1。查了半天,发现是I2C外设时钟没使能。记住:操作任何外设寄存器之前,先确认RCC里对应的时钟位已经打开。

4.7 本章小结

这一章我们做了三件事:

  • 理解了起始条件和停止条件的协议定义
  • 学会了操作I2C_CR1的START和STOP位
  • 用逻辑分析仪亲眼看到了波形