单片机 - 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 项目实战。

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

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


(完)

相关推荐
果壳science33 分钟前
张祥前实验如何获取正电荷
笔记·科技·嵌入式硬件·硬件工程·制造
南山电子nscn37 分钟前
中科芯CETC汽车电池管理系统BMS进口芯片国产型号替代指南
单片机·汽车·bms
沉淀的.晴天1 小时前
can总线
stm32
桀人1 小时前
认识AT24C02
stm32·单片机·嵌入式硬件
国科安芯3 小时前
星间激光通信终端CANFD控制网络的时延确定性与辐射可靠性
网络·嵌入式硬件·信息与通信·canfd·低轨卫星·商业航天·抗辐射
一直C3 小时前
嵌入式 Linux 驱动开发学习笔记:内核定时器、platform 总线与 I²C 驱动
linux·驱动开发·嵌入式硬件·学习·imx6ull
传感器与混合集成电路3 小时前
150MHz主频、8路DAC与6路PWM:JLH221360伺服控制SIP解析
嵌入式硬件
comyccccc3 小时前
STM32PID控制板设计全解析学习笔记
单片机·嵌入式硬件
碧海银沙音频科技研究院4 小时前
实现炬芯ATS308X的usb cdc 串口IAP 固件升级功能实现
人工智能·嵌入式硬件·深度学习·算法
恒锐丰科技林技术员5 小时前
EG1160 高压半桥驱动芯片:高压功率变换的实用驱动方案
经验分享·嵌入式硬件·硬件工程