I2C总线恢复期间错误位处理与重试 - 第2节

2. I2C 总线状态机与数据传输流程归纳总结

做 I2C 驱动十几年了,我最大的体会是:理解状态机,比死记时序参数更重要。 你想想看,总线上一旦出问题,你连当前处在哪个阶段都说不清,那还谈什么恢复重试?

这一节我们就彻底把 I2C 的“脉搏”摸清楚。从状态机开始,再到数据怎么流动,最后归纳一张我自己的总结表格——嗯,这张表当年救过我不少次。

2.1 I2C 总线状态机

I2C 总线其实没那么多花哨的状态。主器件内部的有限状态机,核心就那么六七种。我习惯把它们分成两类:控制状态和传输状态。

  • 空闲(IDLE):SCL 和 SDA 都释放为高电平。这是起点,也是终点。
  • 起始(START):SCL 高时,SDA 从高变低。主器件宣告“我要开始说话了”。
  • 重复起始(RESTART):传输中途再拉一次起始条件,用于改变方向或切换从机。
  • 数据发送/接收(DATA):每字节 8 位,高位在前。主器件负责提供时钟。
  • 应答(ACK):第 9 个时钟,接收方拉低 SDA 表示“收到”。
  • 非应答(NACK):第 9 个时钟,SDA 保持高,表示“不想要”或“结束”。
  • 停止(STOP):SCL 高时,SDA 从低变高。总线回到空闲。

避坑重点: 我曾经在项目中遇到一个坑——从机在应答位之后没有释放 SDA,导致主器件无法发出停止条件。那时候我才意识到:状态机里,每个状态之间的切换条件都必须严格检查,尤其是应答后的总线释放。

下面这张状态图是我自己画的精简版,覆盖了最常见的完整传输路径。你仔细看箭头上的条件,尤其是 ACK/NACK 的分支——很多 I2C 锁死就发生在这里。

I2C 总线状态机(完整传输链路) 空闲 (IDLE) 起始 (START) 数据发送 (DATA) ACK NACK 停止 (STOP) 继续数据 或停止 释放总线 说明 ACK → 继续或停止 NACK → 只有停止 重复起始可从ACK出发 不允许从NACK直接发

我的小技巧: 在调试时,我会在代码里用一个枚举变量记录当前状态机 step。每次时钟跳变都检查 step 是否合法——这样一旦总线卡住,我能立刻定位到是哪个阶段出了问题,而不是盲目测波形。

2.2 数据传输流程——主从握手详解

好,状态机清楚了,我们再看数据怎么流动。一个典型的 I2C 传输,说白了就是主器件和从器件之间的一场“一问一答”。

  1. 主器件发出起始条件,总线上所有从机都知道要开始了。
  2. 主器件发送 7 位从机地址 + 读写位(第 8 位)。高 7 位是地址,最低位 0 表示写,1 表示读。
  3. 被寻址的从机在第 9 个时钟拉低 SDA 应答。其他从机不理睬。
  4. 主器件开始发送或接收数据字节。每次 8 位,之后等待应答。
  5. 如果是从机应答了 ACK,可以继续下一个字节;如果是 NACK,主器件必须停止或者发重复起始。
  6. 全部传输完成,主器件发出停止条件,总线释放。

2.3 关键细节:应答与仲裁

这里我要重点提两个容易被忽略的点。

警告: 我曾经遇到过从机在应答位之后应该释放 SDA 却没有释放的情况(硬件 bug),导致主器件拉 SDA 为低想发停止,结果 SDA 一直被从机拉着不上去——总线直接锁死。后来我们的重试机制里加了一步:发现异常后先发 9 个时钟脉冲“清场”,再复位从机。

仲裁呢?I2C 允许多主器件,但我们做恢复重试时,仲裁会导致数据冲突。我的建议是:恢复期间,软件层面确保只有主控器主动释放总线,不要同时多个主器件抢线。 否则重试时序会乱套。

2.4 归纳总结——一张实战表

下面这张表是我这些年调 I2C 时反复用到的手册。每次写代码前扫一眼,能避免八九成的低级错误。

阶段 SCL 行为 SDA 行为 主器件责任 从器件责任
空闲 高 高(上拉) 释放 SCL & SDA 释放 SDA
起始 高→保持高 高→低 拉低 SDA 检测起始
地址发送 产生 8 个脉冲 按位输出 输出地址+读写位 匹配地址
应答位 产生第 9 脉冲 输入/输出 释放 SDA 拉低则为 ACK
数据写 产生 8 个脉冲 主输出 输出数据 采样数据
数据读 产生 8 个脉冲 从输出 采样数据 输出数据
停止 高→保持高 低→高 释放 SDA 检测停止

避坑指南: 我曾经在写驱动时忽略了“读操作后主器件必须发 NACK 来结束”,结果从机一直输出下一字节,总线乱套。记住:读最后一个字节之前,主器件必须预先把应答位设置为 NACK,然后在第 9 个时钟不拉低 SDA。

2.5 为什么状态机对重试这么关键?

我见过不少工程师,一遇到 I2C 超时就盲目发停止条件。其实更高效的做法是:先检查当前总线状态(空闲、起始中、数据传输中?),然后“对症下药”。

  • 如果总线空闲(SCL & SDA 都高),直接重置状态机,重发起始。
  • 如果总线卡在某个字节中途(SCL 为低且 SDA 不确定),先发 9 个时钟脉冲让从机释放 SDA,再发停止。
  • 如果从机死锁(应答位后 SDA 被拉低不放),需要强制拉高 SDA 时钟,甚至复位硬件。

说白了,没有状态机这把尺子,你测再多波形也找不到根因。而这张状态图 + 上面的表格,就是我每次写重试逻辑时摆在手边的“挂图”。

嗯,这一节的内容就这些。记住:状态机是骨架,数据流是血肉,重试是医生。 下回遇到 I2C 异常,先问问自己——现在总线在哪个状态?

2. I2C 总线状态机与数据传输流程归纳总结

做 I2C 驱动十几年了,我最大的体会是:理解状态机,比死记时序参数更重要。 你想想看,总线上一旦出问题,你连当前处在哪个阶段都说不清,那还谈什么恢复重试?

这一节我们就彻底把 I2C 的“脉搏”摸清楚。从状态机开始,再到数据怎么流动,最后归纳一张我自己的总结表格——嗯,这张表当年救过我不少次。

2.1 I2C 总线状态机

I2C 总线其实没那么多花哨的状态。主器件内部的有限状态机,核心就那么六七种。我习惯把它们分成两类:控制状态和传输状态。

  • 空闲(IDLE):SCL 和 SDA 都释放为高电平。这是起点,也是终点。
  • 起始(START):SCL 高时,SDA 从高变低。主器件宣告“我要开始说话了”。
  • 重复起始(RESTART):传输中途再拉一次起始条件,用于改变方向或切换从机。
  • 数据发送/接收(DATA):每字节 8 位,高位在前。主器件负责提供时钟。
  • 应答(ACK):第 9 个时钟,接收方拉低 SDA 表示“收到”。
  • 非应答(NACK):第 9 个时钟,SDA 保持高,表示“不想要”或“结束”。
  • 停止(STOP):SCL 高时,SDA 从低变高。总线回到空闲。

避坑重点: 我曾经在项目中遇到一个坑——从机在应答位之后没有释放 SDA,导致主器件无法发出停止条件。那时候我才意识到:状态机里,每个状态之间的切换条件都必须严格检查,尤其是应答后的总线释放。

下面这张状态图是我自己画的精简版,覆盖了最常见的完整传输路径。你仔细看箭头上的条件,尤其是 ACK/NACK 的分支——很多 I2C 锁死就发生在这里。

I2C 总线状态机(完整传输链路) 空闲 (IDLE) 起始 (START) 数据发送 (DATA) ACK NACK 停止 (STOP) 继续数据 或停止 释放总线 说明 ACK → 继续或停止 NACK → 只有停止 重复起始可从ACK出发 不允许从NACK直接发

我的小技巧: 在调试时,我会在代码里用一个枚举变量记录当前状态机 step。每次时钟跳变都检查 step 是否合法——这样一旦总线卡住,我能立刻定位到是哪个阶段出了问题,而不是盲目测波形。

2.2 数据传输流程——主从握手详解

好,状态机清楚了,我们再看数据怎么流动。一个典型的 I2C 传输,说白了就是主器件和从器件之间的一场“一问一答”。

  1. 主器件发出起始条件,总线上所有从机都知道要开始了。
  2. 主器件发送 7 位从机地址 + 读写位(第 8 位)。高 7 位是地址,最低位 0 表示写,1 表示读。
  3. 被寻址的从机在第 9 个时钟拉低 SDA 应答。其他从机不理睬。
  4. 主器件开始发送或接收数据字节。每次 8 位,之后等待应答。
  5. 如果是从机应答了 ACK,可以继续下一个字节;如果是 NACK,主器件必须停止或者发重复起始。
  6. 全部传输完成,主器件发出停止条件,总线释放。

2.3 关键细节:应答与仲裁

这里我要重点提两个容易被忽略的点。

警告: 我曾经遇到过从机在应答位之后应该释放 SDA 却没有释放的情况(硬件 bug),导致主器件拉 SDA 为低想发停止,结果 SDA 一直被从机拉着不上去——总线直接锁死。后来我们的重试机制里加了一步:发现异常后先发 9 个时钟脉冲“清场”,再复位从机。

仲裁呢?I2C 允许多主器件,但我们做恢复重试时,仲裁会导致数据冲突。我的建议是:恢复期间,软件层面确保只有主控器主动释放总线,不要同时多个主器件抢线。 否则重试时序会乱套。

2.4 归纳总结——一张实战表

下面这张表是我这些年调 I2C 时反复用到的手册。每次写代码前扫一眼,能避免八九成的低级错误。

阶段 SCL 行为 SDA 行为 主器件责任 从器件责任
空闲 高 高(上拉) 释放 SCL & SDA 释放 SDA
起始 高→保持高 高→低 拉低 SDA 检测起始
地址发送 产生 8 个脉冲 按位输出 输出地址+读写位 匹配地址
应答位 产生第 9 脉冲 输入/输出 释放 SDA 拉低则为 ACK
数据写 产生 8 个脉冲 主输出 输出数据 采样数据
数据读 产生 8 个脉冲 从输出 采样数据 输出数据
停止 高→保持高 低→高 释放 SDA 检测停止

避坑指南: 我曾经在写驱动时忽略了“读操作后主器件必须发 NACK 来结束”,结果从机一直输出下一字节,总线乱套。记住:读最后一个字节之前,主器件必须预先把应答位设置为 NACK,然后在第 9 个时钟不拉低 SDA。

2.5 为什么状态机对重试这么关键?

我见过不少工程师,一遇到 I2C 超时就盲目发停止条件。其实更高效的做法是:先检查当前总线状态(空闲、起始中、数据传输中?),然后“对症下药”。

  • 如果总线空闲(SCL & SDA 都高),直接重置状态机,重发起始。
  • 如果总线卡在某个字节中途(SCL 为低且 SDA 不确定),先发 9 个时钟脉冲让从机释放 SDA,再发停止。
  • 如果从机死锁(应答位后 SDA 被拉低不放),需要强制拉高 SDA 时钟,甚至复位硬件。

说白了,没有状态机这把尺子,你测再多波形也找不到根因。而这张状态图 + 上面的表格,就是我每次写重试逻辑时摆在手边的“挂图”。

嗯,这一节的内容就这些。记住:状态机是骨架,数据流是血肉,重试是医生。 下回遇到 I2C 异常,先问问自己——现在总线在哪个状态?