STM32软件模拟IIC通信中delay_us的使用规律全解析(含口诀与源码详解)
本文关键词:STM32、IIC、软件模拟、delay_us、时序控制、应答机制、位操作
一、写在前面:为什么我们需要自己"模拟"IIC?
STM32大多数型号都自带硬件I2C模块,那么为什么还需要我们手动"模拟"IIC总线呢?
以下几种情况,是你迟早会碰到的"不得不软模拟"的场景:
- 多个 I2C 器件地址冲突,而 STM32 的硬件 I2C 模块有限
- 硬件 I2C 某些功能限制(如不支持10位地址)
- 调试需求:我们希望直接掌控SCL/SDA线,方便逻辑分析仪抓波形
- 部分低端芯片无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?
可以,但前提是:
- 使用硬件I2C(由I2C控制器保证时序)
- 利用定时器精确控制输出波形(一般不推荐)
- 使用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 项目实战。
欢迎关注、收藏、点赞支持!
有任何问题,也欢迎评论区留言讨论。让我们共同成长!
(完)