因digitalwrite开销大(常超1–2 µs),无法满足i2c快速模式下scl高低电平各≥0.6 µs及起始/停止边沿的严格时序要求,易致从机不响应或ack丢失。

为什么不能直接用 digitalWrite 模拟 I2C 时序
因为 digitalWrite 在多数 Arduino 兼容平台(如 ESP32、STM32 HAL、甚至部分 AVR)上开销大,执行一次常超 1–2 µs,而标准 I2C 快速模式(400 kbps)要求 SCL 高/低电平各不低于 0.6 µs;更别说起始/停止条件对边沿时序的严格要求(如起始:SCL 高时 SDA 从高→低)。盲目用 digitalWrite 很容易导致从机不响应或 ACK 丢失。
实操建议:
- 改用寄存器直写(如 AVR 的
PORTB、STM32 的GPIOx->BSRR),把单次电平翻转压到 1 个机器周期(≈62.5 ns @ 16 MHz) - 禁用中断(
noInterrupts()或__disable_irq())防止时序被干扰,操作完再恢复 - 用示波器抓 SCL/SDA 实际波形,重点看起始条件中 SDA 下降沿是否发生在 SCL 高电平期间且保持稳定
如何手动构造 START 和 STOP 条件
I2C 的 START 是 SCL 为高时 SDA 由高→低;STOP 是 SCL 为高时 SDA 由低→高。这两个动作必须在 SCL 保持高电平的“窗口”内完成,否则会被识别为数据位。
实操建议:
- 先拉高 SCL(寄存器置位),延时 ≥ 4 µs(留出余量),再拉低 SDA
- START 后需等待 ≥ 4.7 µs 才能发第一个数据位(满足 tHD;STA)
- STOP 前必须确保 SCL 已拉高、SDA 处于低电平;先抬高 SDA,延时 ≥ 4 µs,再拉低 SCL
- 避免用
delayMicroseconds(1)—— 它在不同平台实现差异大,有的会关中断、有的不准;优先用空循环(如for(volatile int i=0; i)做微调
读写一字节时怎么处理 ACK/NACK 时序
主设备发完 8 位后,必须释放 SDA(设为输入上拉),然后在第 9 个 SCL 高电平期间采样 SDA——若为低,表示从机 ACK;若为高(或浮空),即 NACK。关键点在于:SCL 第 9 次拉高后,必须等 ≥ 4 µs 再读 SDA(满足 tVD;DAT),且 SCL 拉低前要留够 tLOW(≥1.3 µs)。
实操建议:
- 发送第 8 位后,立刻将 SDA 引脚设为输入(如 AVR 的
DDRB &= ~(1),并确保外部有上拉电阻(通常 4.7 kΩ) - SCL 拉高后,用
for(volatile int i=0; i 延时(根据主频校准),再读 <code>PINB & (1 - 若读到高电平,说明 NACK,应立即发 STOP 并返回错误;不要继续发后续字节
- 接收字节时,主机在每个 SCL 高电平中期采样 SDA(避开边沿抖动),不是一拉高就立刻读
ESP32 / STM32 上用 GPIO 矩阵模拟时要注意什么
这些芯片的 GPIO 配置寄存器更新有延迟(尤其 STM32 的 GPIOx->MODER 切换方向),且某些引脚存在硬件复用冲突(如 ESP32 的 GPIO34–39 默认仅输入)。直接切输入/输出模式可能引入不可控的几微秒毛刺。
实操建议:
- ESP32 推荐用
gpio_set_direction()+gpio_set_level(),但务必在初始化阶段调用gpio_hold_dis()关闭保持功能,否则切换方向时电平可能锁死 - STM32 若用 HAL,别用
HAL_GPIO_WritePin(),改用HAL_GPIO_TogglePin()配合__DSB()内存屏障保证顺序 - 始终为 SDA/SCL 加 4.7 kΩ 上拉(接 VCC),且两条线尽量等长、远离干扰源(如电机驱动、SWD 调试线)
- 跑 100 kbps 可以容忍一定误差,但一旦升到 400 kbps,必须用示波器逐段验证:START、每 bit 中点、ACK 采样点、STOP —— 任何一段超时都会失败
真正难的不是写出能通信的代码,而是让波形每一处都落在 I2C 规范的 timing diagram 阴影区里。哪怕只有一处超出 tSU;STA 或 tHD;DAT,设备就可能间歇性失联——这种问题不会报错,只会让你反复烧录、抓耳挠腮。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











