单片机 - STM32软件模拟IIC通信中delay_us的使用规律全解析(含口诀与源码详解)

STM32软件模拟IIC通信中delay_us的使用规律全解析(含口诀与源码详解)

本文关键词:STM32、IIC、软件模拟、delay_us、时序控制、应答机制、位操作

一、写在前面:为什么我们需要自己"模拟"IIC?

STM32大多数型号都自带硬件I2C模块,那么为什么还需要我们手动"模拟"IIC总线呢?

以下几种情况,是你迟早会碰到的"不得不软模拟"的场景:

  1. 多个 I2C 器件地址冲突,而 STM32 的硬件 I2C 模块有限
  2. 硬件 I2C 某些功能限制(如不支持10位地址)
  3. 调试需求:我们希望直接掌控SCL/SDA线,方便逻辑分析仪抓波形
  4. 部分低端芯片无I2C模块,纯GPIO只能靠自己模拟

这时候,我们就需要用两个GPIO口(SCL 和 SDA)模拟整个通信过程。

而为了让这种"假装"的IIC让从设备"信以为真",我们必须精确地模拟它的时序特征。

二、IIC协议核心时序图解

图示来自官方IIC协议文档,建议打印出来贴在桌子上

从图中我们可以看到:

  • 起始条件(START):SCL高电平期间,SDA由高变低
  • 停止条件(STOP):SCL高电平期间,SDA由低变高
  • 数据发送/接收:数据在SCL低电平时准备,在SCL上升沿被采样
  • ACK应答:第9个时钟周期,从机在SDA线上拉低表示"我收到了"

结论:

IIC的核心时序规则是:"数据在线上必须在SCL上升沿前稳定"

这也是我们使用 delay_us() 来保证"稳定"、"采样"、"响应"的根本原因。

三、delay_us() 的使用原则总结

1. 时钟切换前后,需要适当延时

  • 避免数据/时钟突变太快,造成信号抖动、误采样

2. SDA 在 SCL 低电平期间变换,之后再延时使其稳定

  • 用于确保"数据稳定后再采样"的条件

3. SCL 上升后延时,留时间给接收方采样

  • 从设备一般在SCL上升沿采样SDA

4. 用于构造起始/停止条件的"边沿切换"

四、口诀记忆:助你不背协议也能写出稳定代码

"SCL拉低先准备,SCL拉高等稳定。"

这句口诀几乎可以涵盖所有 delay_us 出现的关键位置。

解释:

  • SCL拉低先准备

    无论是发送数据、起始条件、发送ACK,都必须先拉低SCL,才能改变SDA的电平。

  • SCL拉高等稳定

    发送完数据之后,要拉高SCL让从机读取,但不能立刻切换,要等几微秒确保从机确实采样完成。

五、delay_us() 的使用场景归纳

序号 场景 示例 推荐延时时间
1 设置 SDA 后拉高 SCL(发送/接收) IIC_SDA_H; delay_us(3); IIC_SCL_H; 3~5 us
2 SCL 拉高后等待采样完成 IIC_SCL_H; delay_us(5); 5 us
3 起始条件 SDA 下降沿 SCL 高电平期间 SDA 拉低 5 us
4 停止条件 SDA 上升沿 SCL 高电平期间 SDA 拉高 5 us
5 主机接收应答信号前等待 IIC_SCL_H; delay_us(5); 5 us
6 SDA 设置为输入状态前 IIC_SDA_H; delay_us(3); 2~3 us

说明:以上延时值以 STM32F4@72MHz 为基础,适配绝大多数 IIC 设备。

六、详细示例:发送一个字节并等待ACK(带注释)

c 复制代码
// 发送一个字节,返回从机是否应答(0表示ACK,1表示NACK)
uint8_t IIC_SendByte(uint8_t Byte)
{
    uint8_t i, ack;

    for(i = 0; i < 8; i++)
    {
        // 步骤1:拉低SCL准备设置数据
        IIC_SCL_L;
        delay_us(3); // 等待数据线准备时间

        // 步骤2:根据当前bit设置SDA
        if(Byte & (0x80 >> i)) // 从高位到低位依次发送
        {
            IIC_SDA_H;
        }
        else
        {
            IIC_SDA_L;
        }

        delay_us(2); // 设置数据后稍等,确保数据稳定

        // 步骤3:拉高SCL,让从机采样数据
        IIC_SCL_H;
        delay_us(5); // 等待从机采样

        // 步骤4:循环下一位
    }

    // 步骤5:接收从机应答
    ack = IIC_RecvAck();

    // 步骤6:拉低SCL,为下次传输做准备
    IIC_SCL_L;

    return ack;
}

七、错误案例分析(不加delay或加错)

错误1:不加delay_us()

c 复制代码
IIC_SDA_H;
IIC_SCL_H; // 没有delay,直接拉高,可能采样失败

分析:SDA刚设置,SCL就拉高,接收端可能采样到错误数据,导致通信失败。

错误2:SDA在SCL高电平时变动

c 复制代码
IIC_SCL_H;
IIC_SDA_L; // 协议明令禁止此种操作(除非是起始/停止条件)

分析:违反IIC规范,会被从机误判为START或STOP,整个通信中断。

八、实战建议:如何灵活调整延时

建议使用宏定义统一管理延时

c 复制代码
#define IIC_DELAY_SHORT delay_us(3)
#define IIC_DELAY_LONG  delay_us(5)

好处:

  • 可以方便地调整通信速率(例如调到400kHz时,统一换成delay_us(1))
  • 修改延时只需要改一处,项目维护更轻松

九、进阶技巧:是否能不加delay_us?

可以,但前提是:

  1. 使用硬件I2C(由I2C控制器保证时序)
  2. 利用定时器精确控制输出波形(一般不推荐)
  3. 使用DMA+事件同步(复杂程度显著上升)

在大多数场景下,手动加 delay_us() 是最经济、最直观、最容易调试的做法。

十、总结:掌握IIC模拟的时序本质

本文系统总结了 delay_us() 在软件模拟IIC中的具体作用、使用场景、推荐时间、口诀记忆、详细代码,以及错误分析与调试建议。

请记住:

  • 模拟 IIC 不是盲目地 "拉高拉低",而是严格执行一套时间策略
  • delay_us() 并不浪费资源,而是让时序"像真的一样"
  • 稳定优先于速度,特别是 EEPROM、OLED、MPU6050 等外设对时序非常敏感

十一、推荐阅读与参考

  • 《STM32F4参考手册》:GPIO操作与时钟频率部分
  • IIC协议规范(NXP官方文档)
  • EEPROM AT24C02 数据手册
  • 《嵌入式C语言实战:驱动篇》

如果你对 EEPROM 读写、MPU6050 姿态读取、OLED 显示等感兴趣,后续我还会更新一整套基于 STM32 的 IIC 项目实战。

欢迎关注、收藏、点赞支持!

有任何问题,也欢迎评论区留言讨论。让我们共同成长!


(完)

相关推荐
想搞嵌入式的小白16 分钟前
STM32的串口通信
stm32·单片机·嵌入式硬件
、我是男生。1 小时前
STM32和树莓派的分工
stm32·单片机·嵌入式硬件
Wangshanjie_982 小时前
【STM32】启动流程
stm32
Java小白,一起学习3 小时前
ESP32开发入门
单片机·物联网·iot
物联网嵌入式小冉学长5 小时前
12.UDP客户端
网络·单片机·网络协议·udp·嵌入式
Naiva6 小时前
【电力电子】基于STM32F103C8T6单片机双极性SPWM逆变(软件篇)(二)
stm32·单片机·嵌入式硬件·逆变器·spwm
Python小老六6 小时前
单片机测ntc热敏电阻的几种方法
单片机·嵌入式硬件
武汉芯源半导体6 小时前
基于CW32L010单片机的扫振一体电动牙刷应用方案
单片机·嵌入式硬件
菜菜why6 小时前
MSPM0G3507学习笔记(二) 超便捷配置led与按键
笔记·单片机·电赛·msp430