☰
【嵌入式外设精学】Day13|I2C 进阶:ACK/NACK、失败与总线恢复
2026/10/2 8:29:42 网站建设 项目流程

【嵌入式外设精学】Day13|I2C 进阶:ACK/NACK、失败与总线恢复

今天解决:没应答怎么办?SDA 卡在低电平怎么救?


目录

  1. 问题 2. 原理 3. 代码 4. 误区 5. 自测 6. 答案

1. 问题

  • 读写偶发失败
  • 拔线一次后整个 I2C 就挂了

2. 原理

2.1 ACK / NACK

每传 1 字节后的第 9 个时钟:

谁含义
从机 ACK(低)收到了
主机读完 NACK(高)再不读了,准备结束

2.2 常见失败

现象可能原因
地址就没应答地址错(注意 7 位 vs 含 R/W 的写法)、没接好、没上拉
传一半 NACK从机忙、数据不对
SDA 一直为低从机当时正在发数据被强行打断,卡住总线

2.3 总线恢复

从机把 SDA 拉死时,主机可以:

  1. 转成 GPIO,手动发 9 个 SCL 脉冲(逼它吐出剩下的数据)
  2. 再发一个 STOP
  3. 重新初始化 I2C 外设

3. 代码示例

#include"stm32f4xx.h"#include<stdbool.h>/* 简化:手动 SCL 时钟 + 恢复(脚位按板子改) */staticvoidscl_pulse(void){GPIO_SetBits(GPIOB,GPIO_Pin_6);for(volatileinti=0;i<50;i++){}GPIO_ResetBits(GPIOB,GPIO_Pin_6);for(volatileinti=0;i<50;i++){}}voidi2c_bus_recover(void){inti;/* 假设已把 SDA/SCL 切到 GPIO 开漏输出 */for(i=0;i<9;i++){scl_pulse();}/* 再造一个 STOP:SCL 高时 SDA 上升(示意) */GPIO_ResetBits(GPIOB,GPIO_Pin_7);for(volatileintk=0;k<50;k++){}GPIO_SetBits(GPIOB,GPIO_Pin_6);for(volatileintk=0;k<50;k++){}GPIO_SetBits(GPIOB,GPIO_Pin_7);/* STOP */}

带超时的“等待应答”思路:

boolwait_flag_timeout(volatileuint32_t*flag,uint32_tms_max){uint32_tn=ms_max*1000u;while(n--){if(*flag){returntrue;}}returnfalse;/* 超时,走恢复/报错 */}

4. 常见误区

误区正解
读完还发 ACK应 NACK 再 STOP
失败就 while 死等必须超时
恢复只重新 init 不拉时钟从机可能仍占着 SDA
地址 8 位当 7 位用写法混用会找不到设备

5. 自测

  1. 主机读多字节,最后一字节后发什么?
  2. SDA 卡低时恢复步骤?

6. 答案

  1. NACK,然后 STOP。
  2. 9 个 SCL 脉冲 + STOP,再初始化 I2C,必要时复位从机电源。

作者:shuifanyu
标签:I2CACK总线恢复

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询