☰
I2C从模式设计与总线鲁棒性:时钟延展落地与死锁恢复
2026/10/5 1:33:17 网站建设 项目流程

第06讲:从模式设计与总线鲁棒性——时钟延展落地 + 死锁恢复

做 I2C 从机开发的朋友,十有八九都被同一类问题折磨过:总线偶尔挂死、主设备读不到数据、从机明明在跑却响应不了地址。表面上看是时序问题,往深了挖,其实是从模式设计和总线鲁棒性这两个层面没做好。这一篇就把“从模式设计”这个概念拆开讲清楚,重点落在两个实操点上:时钟延展(Clock Stretching)怎么落地到代码里,以及总线真的被锁死之后,死锁恢复有哪些能直接抄的招。内容偏实战,搞嵌入式、写驱动、调 I2C 的小伙伴都会用得上。

1. 从模式设计:先把“从机软件”当工程来做

很多人写 I2C 从机,上来就是堆中断、填寄存器,能通就跑,不通就调。等产品量大了、总线上的设备多了,才发现稳定性经不起推敲。我以前也是这么干的,直到连续两台样机在老化测试时总线锁死,才开始认真琢磨从模式到底该怎么设计。

1.1 从模式不是“从设备”翻译一下就行

I2C 从机的本质是一个状态机。主设备发起通信,从机被地址选中后,要么接收数据,要么发送数据。这个过程不是线性的,任意时刻都可能被 stop 条件打断,也可能被 nack 中止。如果你只在中断里处理“收到一个字节”这个事件,那么寄存器状态、收发缓冲、总线时序之间的边界条件就会变得特别多,一旦某个状态没处理好,后续全是坑。

更好的做法是把从机看成几个独立模块的组合:

  • 地址匹配模块:负责检测总线地址,判断是否“选中”自己。
  • 收发引擎:按状态机处理一个字节一个字节的传输,包括 ACK/NACK 的生成。
  • 寄存器组模块:对外暴露的寄存器地址空间,读写都在这上面进行。
  • 事件分发模块:把“收到数据”“需要发送数据”“总线错误”这些事件转成回调,交给上层逻辑处理。

这样拆分以后,每一块都能单独测试。我以前调试过一块从机芯片,问题出在寄存器地址跨页时读写指针没正确更新,就是因为收发引擎和寄存器组耦合在一起,查了半天才定位到。

1.2 23种设计模式里,嵌入式最值得用的就这几个

提到“设计模式”,很多人第一反应是 Java、C++ 那套面向对象的东西。但实际上嵌入式 C 语言里同样可以用设计模式的思路来组织从机代码。状态模式是最核心的,I2C 从机天然就是一个状态机;观察者模式对应事件回调机制;命令模式适合处理寄存器命令字;适配器模式则用来屏蔽不同硬件平台之间的差异。

拿状态模式举例。I2C 从机的状态可以定义为:IDLE(空闲)、ADDR(地址匹配)、RX(接收)、TX(发送)、STOP(停止)。每个状态下,对 SCL 上升沿/下降沿事件的处理方式完全不同。

typedef enum { I2C_SLAVE_IDLE, I2C_SLAVE_ADDR, I2C_SLAVE_RX, I2C_SLAVE_TX, I2C_SLAVE_STOP } i2c_slave_state_t;

核心逻辑在一个 switch-case 里,每个 case 是独立的状态处理函数。这样改一个状态的行为不会影响其他状态,排查问题的时候也容易加日志。

观察者模式在 I2C 从机里体现为中断回调。很多 MCU 的 I2C 外设都支持事件中断,比如地址匹配中断、数据接收中断、数据发送中断、错误中断。你只需要注册几个回调函数,把底层事件和业务逻辑解耦开,上层不用关心底层寄存器细节。

真正在嵌入式从机固件里常用的,其实也就四五个模式。23种设计模式不是让你全部往上套,而是挑能解决实际问题的用。我见过把简单从机代码写出十几个文件的,接口抽象来抽象去,最后编译出来比原来大了一倍,性能还变差了,这就属于过度设计。

1.3 从模式状态机的关键状态定义与转移条件

设计从机状态机,最主要是定清楚“谁来改变状态”“状态转移的触发条件是什么”。I2C 总线的时序由主设备控制,从机只能被动响应,所以从机的状态转移完全由总线事件驱动。

实际项目里几个容易出问题的状态转移点:

  • 地址匹配后没有正确进入收发状态:有些外设在检测到地址匹配后,还要等第一个数据位才真正进入收发状态,如果此时主设备立即发 stop,从机可能卡在中间状态。
  • 接收完最后一个字节后的 NACK 处理:主设备读从机数据时,读完最后一个字节会发 NACK,从机收到 NACK 后应该回到 IDLE 状态。如果这个转移漏了,下一次通信就会错位。
  • 错误状态的重置:总线错误(比如仲裁丢失、超时)发生后,从机要能把状态机强制复位到 IDLE,不能等下一次地址匹配才恢复。

我建议写代码的时候,给每个状态加一个入口函数和退出函数。入口函数负责初始化该状态下的硬件寄存器,退出函数负责清理。这个习惯帮我避免过好几次“忘了清标志位”的低级错误。

2. 总线鲁棒性:从电气到协议的层层加固

总线鲁棒性和从模式设计是两个层面的事,但实际调板子的时候它们互相影响。**鲁棒性差的从机,会把总线上的小问题放大成死锁;鲁棒性好的从机,即使主设备发错时序,也能扛住不死。**这一节聊我在项目里验证过的几个加固手段。

2.1 总线鲁棒性威胁:主设备异常、从机跑飞、噪声干扰

常见的 I2C 总线异常,来源无非这么几类:

  • 主设备端异常:主控芯片 I2C 外设的 bug、驱动代码 bug、主设备看门狗复位导致通信中途终止。
  • 从机端异常:从机固件跑飞、中断卡死、从机被复位但 SCL/SDA 处于输出状态。
  • 电气噪声干扰:总线走线过长、上拉电阻选得不对、电源纹波大,导致信号沿变差,从机误判起始/停止条件。

我之前遇到过一种很隐蔽的情况:主设备在传输过程中因为紧急任务抢占了 CPU,I2C 中断被延迟了好几个毫秒。SCL 停留在低电平超过从机的超时阈值,从机判断总线异常,主动释放 SCL。但主设备恢复过来之后,不知道这个情况,继续按原有时序操作,两边就对不上了。这种问题靠硬件很难查,因为逻辑分析仪抓到的时序看起来是完整的。

2.2 软件防呆:超时检测与状态不变量检查

软件层面的鲁棒性设计,核心思路是**“不要无条件信任总线状态”**。哪怕总线时序看起来正常,代码层面也要有兜底机制。

我最常用的两个手段:

  • 字节级超时检测:从机每接收到一个字节,就启动一个硬件定时器,超时时间设为 10ms 左右。如果超过 10ms 下一个字节没来,判定总线异常,强制复位收发状态机。这个 10ms 不是什么标准值,而是基于 I2C 典型通信频率反推的。标准模式 100kHz 下一个字节约 90us,快速模式 400kHz 下约 22.5us,10ms 已经足够宽松,不会误判正常通信。
  • 状态不变量检查:在每个状态入口和出口,校验 SCL/SDA 的实际电平是否符合预期。比如 RX 状态下,理论上 SCL 应该是高电平(等待主设备发送下一个时钟),如果此时读到 SCL 为低,说明从机可能正被时钟延展,或者主设备出了异常,需要特殊处理。
void i2c_slave_state_check(i2c_slave_state_t current_state) { uint8_t scl_level = gpio_read_pin(SCL_PIN); if (current_state == I2C_SLAVE_RX && scl_level == 0) { // SCL被拉低,可能正被从机时钟延展,记录调试信息 i2c_debug_log("SCL low during RX state"); } }

这段代码看起来简单,但在现场排查问题时价值极大。你在实验室复现不了的偶发死锁,现场设备上通过日志就能看到总线卡在哪个状态、SCL/SDA 是什么电平。

2.3 电气层面的鲁棒性:上拉电阻与总线隔离

软件怎么加固都替代不了基本的电气设计。I2C 是开漏结构,SCL 和 SDA 必须接上拉电阻到 VDD。上拉电阻选多大,直接影响总线抗干扰能力和信号边沿。

上拉电阻的计算公式不复杂,核心是满足上升时间的要求。以 400kHz 快速模式为例,I2C 规范要求上升时间不超过 300ns,总线等效电容按典型值 100pF 估算,用公式Rpu = T_rise / (C_bus * ln(2))计算,大约得到300ns / (100pF * 0.693) ≈ 4.3kΩ。实际项目里我一般选 2.2kΩ 到 4.7kΩ 之间,既能满足上升时间,又不会让低电平灌电流太大。

总线电平转换也要留意。如果主设备是 1.8V,从机是 3.3V,直接连 SCL/SDA 肯定不行。常见方案是使用双向电平转换芯片,比如 PCA9306,或者用分立 MOS 管搭建转换电路。这里有个细节:转换电路的两侧上拉电阻都要接,而且要保证一侧为高电平时另一侧能正确识别。很多人只接了一侧上拉,结果高电平斜率变差,距离稍远就误码。

还有一个我比较少看到有人提的点:总线上的电容组不要加太大滤波电容。有人为了防干扰,在 SCL/SDA 上各加一个 100nF 电容接地,结果上升时间直接翻车。I2C 是边沿触发协议,加电容等于自废武功。真要滤波,用串联小电阻 33Ω 左右,搭配 RC 滤毛刺就行。

3. 时钟延展落地:让慢速从机“按住总线等一等”

时钟延展是 I2C 协议里非常有特色的一个机制,也是从模式设计里最容易被忽视的部分。很多人知道这个名词,但不知道它到底怎么影响总线时序、代码里怎么实现。这一节完整拆开讲。

3.1 时钟延展的物理机制:SCL 被从机拉低

正常情况下,SCL 时钟由主设备产生。但 I2C 协议允许从机在需要时拉低 SCL,强迫主设备等待。这个过程就叫时钟延展。它的物理机制很简单:SCL 是开漏结构,主设备释放 SCL 为高之后,如果从机也把 SCL 驱动为低,那么总线上的 SCL 就是低电平。主设备检测到 SCL 为低,就暂停时钟产生,一直等 SCL 变高再继续。

这个机制的意义在于,从机处理数据需要时间。比如从机接收完一个字节,需要把数据搬进缓冲区,更新寄存器状态,如果这些操作还没做完,下一个字节的时钟来了也处理不了。从机通过拉低 SCL,给自己争取处理时间,主机等多久取决于从机什么时候释放。

类比一下就是:你跟别人对话,别人说一句你还没来得及消化,你举手示意“等一等”,对方就停下来等你示意继续再说。没有这个机制,对话就只能按对方的节奏走,你跟不上就只能丢信息。

3.2 不同速率模式下的时序要求与延展窗口

I2C 协议定义了多个速率模式,常见的有标准模式 100kHz、快速模式 400kHz、快速+模式 1MHz。时钟延展在每个模式下的规则是一致的,但时间窗口不一样。

速率模式最低 SCL 频率/周期字节传输典型时间从机可用延展时间(推荐)
标准模式 100kHz周期 10us约 90us建议小于 1ms
快速模式 400kHz周期 2.5us约 22.5us建议小于 250us
快速+模式 1MHz周期 1us约 9us建议小于 100us

延展时间越长,主设备占用总线的时间越久,总线上其他设备可能被饿死。所以从机的延展窗口在满足处理时间的前提下越短越好。

从机什么时候可以延展?两个典型位置:一是地址匹配阶段,从机确认是自己的地址后,可以拉低 SCL,争取时间准备收发缓冲区;二是数据传输阶段,每接收或发送一个字节后,从机在 ACK 位的位置拉低 SCL,争取时间处理这一字节。有些从机在发送完数据后也要延展,用于准备下一个字节,这在 EEPROM 这类存储设备上很常见。

3.3 落地实现:硬件 I2C 外设与 GPIO 模拟两种方案

时钟延展的落地实现分两种情况:用 MCU 内置的硬件 I2C 外设,或者用 GPIO 模拟。

硬件 I2C 外设的情况下,多数 MCU 支持自动时钟延展。比如 STM32 的 I2C 外设,I2C_SLOW 模式或时钟延展使能位配置后,从机在接收数据的过程中如果软件还没有读走数据寄存器,硬件会自动拉低 SCL。这个行为是自动的,你不需要手动控制 SCL,但要注意配置正确的中断优先级。如果从机中断被更高优先级的中断阻塞,SCL 会被一直拉低,主设备就一直等。这种情况要特别小心,不能让长时间中断阻塞 I2C 中断。我见过一个项目,从机的中断优先级没配好,一个外部中断处理函数跑了 50ms,期间 I2C 中断完全被阻塞,SCL 被拉低到从机看门狗超时,总线锁死。

GPIO 模拟从机时,时钟延展就要自己实现了。基本思路是:检测到 SCL 下降沿后,从机主动把 SCL 引脚输出为低电平,保持一定时间后再释放为高。

void i2c_slave_clock_stretch(void) { // 拉低SCL,让主设备等待 gpio_set_pin(SCL_PIN, 0); // 执行需要时间的数据处理 process_received_byte(); // 处理完成,释放SCL,让主设备继续产生时钟 gpio_set_pin_mode(SCL_PIN, GPIO_MODE_INPUT_PULLUP); }

注意释放 SCL 时不能直接输出高电平,因为 SCL 是开漏结构,必须切回输入模式(或者输出模式但输出高电平,取决于硬件设计)。如果 MCU 的 GPIO 不支持真正的开漏输出,释放 SCL 时切回输入模式带上拉才是正确的。

还有一种情况:GPIO 模拟从机接收数据时,要确认识别 SCL 的边沿。通常用外部中断捕获 SCL 下降沿,在中断里读取 SDA,然后进入字节处理流程。每一步处理完成后再释放 SCL。这种模式下,整个 I2C 时序的每一步都由软件驱动,任何一步卡住,总线就卡住,所以超时保护必须做得非常严密。

3.4 时钟延展与 SMBus 超时:一个容易忽略的兼容性问题

时钟延展在 I2C 协议里没有严格的超时限制,即使从机把 SCL 拉低 100ms,纯 I2C 主设备也会一直等下去。但SMBus(系统管理总线)规范明确规定了 35ms 的超时阈值。如果你的从机同时需要兼容 SMBus 主设备,时钟延展超过 35ms,主设备就会判定通信失败。

这意味着从机的延展时间必须做两次裁剪:

  • 一次是考虑总线占用时间,越短越好;
  • 一次是考虑 SMBus 兼容性,必须小于 35ms。

落地时可以在从机软件里做一个延展时长检查:每次拉低 SCL 后启动定时器,超过 25ms 还没处理完,强制执行完当前操作并释放 SCL。25ms 留了 10ms 余量,给 SMBus 主设备的 35ms 超时留出安全裕量。

我在一个双模设备(同时支持 I2C 和 SMBus)上吃过亏,从机延展时间做到 40ms,I2C 主设备下一切正常,换上 SMBus 主控后通信频繁超时。查了半天才发现是 SMBus 规范限制。这个细节如果不是对两种协议都比较熟,真的很难想到。

4. 死锁恢复:总线被“锁死”后的最后一根稻草

总线锁死这个现象,做 I2C 开发的基本都碰到过。逻辑分析仪抓到的波形要么是 SCL 停在低电平不动,要么是 SDA 被钳在低电平,主设备一直等 ACK,整个系统像卡死一样。这一节专门讲死锁是怎么产生的,以及恢复手段有哪些。

4.1 死锁的三类典型成因

第一类是从机时钟延展未释放。从机在拉低 SCL 期间,如果自身代码跑飞、死循环或者看门狗复位,SCL 就被永久拉低。主设备等待延展结束,一直等不到,总线就锁死了。

第二类是停止条件丢失。主设备发送 STOP 条件时,SDA 要从低变高,而此时 SCL 必须是高。如果因为时序问题,SDA 拉高的动作发生在 SCL 为低的时候,或者 SCL 被从机拉低,STOP 条件就发不出去。总线状态停留在半传输状态,下一次通信的起始条件无法正确对齐。

第三类是总线电平冲突。主设备在发送起始条件时,SDA 从高变低,但此时总线上某个从机还占着 SDA 输出低电平,就会导致 SDA 一直是低,起始条件无法被其他设备识别。

这三类死锁的共同点是:总线上的某个信号线被拉低后没人释放。恢复思路也就围绕“强制释放”和“让所有设备重新对齐”展开。

4.2 经典恢复法:9 个时钟脉冲

业界最常见的总线恢复方法是 9 个时钟脉冲法,出自 SMBus 规范的恢复策略:主设备切换 GPIO 模式,接管 SCL 和 SDA,手动发送 9 个时钟脉冲。原理是,如果总线上有从机正处于半字节传输状态,9 个脉冲可以让它接收完当前字节并释放总线。

void i2c_bus_recover(void) { // 1. 将SCL和SDA配置为输出,初始都为高 gpio_set_mode(SCL_PIN, GPIO_MODE_OUTPUT); gpio_set_mode(SDA_PIN, GPIO_MODE_OUTPUT); gpio_write_pin(SCL_PIN, 1); gpio_write_pin(SDA_PIN, 1); delay_us(1); // 2. 发送9个时钟脉冲 for (int i = 0; i < 9; i++) { gpio_write_pin(SCL_PIN, 0); delay_us(5); gpio_write_pin(SCL_PIN, 1); delay_us(5); } // 3. 发送STOP条件 gpio_write_pin(SDA_PIN, 0); delay_us(1); gpio_write_pin(SCL_PIN, 0); delay_us(1); gpio_write_pin(SCL_PIN, 1); delay_us(1); gpio_write_pin(SDA_PIN, 1); delay_us(1); // 4. 恢复为正常I2C模式 gpio_set_mode(SCL_PIN, GPIO_MODE_I2C); gpio_set_mode(SDA_PIN, GPIO_MODE_I2C); }

为什么是 9 个脉冲而不是 8 个或者 10 个?I2C 协议中一个字节是 8 位数据加 1 位 ACK,一共 9 个时钟周期。如果从机正处于半字节接收状态,8 个时钟让它收完数据,第 9 个时钟对应 ACK 位,这个位置从机要释放 SDA 以便主设备发送 ACK。所以 9 个脉冲刚好能把从机从一个不完整的字节传输中“赶”出来,回到可以识别下一次起始条件的空闲状态。

实际操作中有一个前提:如果 SCL 被从机拉低,主设备根本发不出这 9 个脉冲。因为 SCL 被拉低时,主设备往 SCL 上写高电平是写不上去的。这种情况下,必须先把 SCL 引脚从“开漏输出”临时切换为“推挽输出”,强制把 SCL 拉高,然后才能产生脉冲。这也解释了为什么恢复函数第一步要把 GPIO 模式切换为输出模式并且直接写 1——就是为了强制释放 SCL。

恢复完成后,从机侧还需要做一件配合工作:检测到超过一定时间没有通信,自动复位自身 I2C 状态机到 IDLE。这样即使主设备发了 9 个脉冲,从机也能正确识别新的起始条件,两边重新对齐。

4.3 软件看门狗与总线状态自恢复:从机侧不能只靠主机

死锁恢复不能只依赖主设备。从机如果具备独立的总线监控能力,恢复效率会高很多。我在从机上做了一个 10ms 的总线空闲检查:任何一个 I2C 事件(起始、停止、数据)都会刷新定时器。如果 10ms 内没有任何总线活动,判定总线异常,从机自动复位 I2C 外设和状态机。

实现上很简单:

void i2c_slave_watchdog_update(void) { last_bus_activity = get_tick_ms(); } void i2c_slave_watchdog_check(void) { if (get_tick_ms() - last_bus_activity > 10) { // 总线空闲超过10ms,主动复位I2C状态机 i2c_slave_force_reset(); } }

这个机制在正常通信下不会误触发,因为 I2C 通信是连续的,字节间隔远小于 10ms。在死锁场景下,总线确实没有活动了,从机主动复位就能在下一次地址匹配时重新参与通信。

还有一个设计细节:从机复位后,寄存器状态要恢复到默认值,不能保留死锁前的中间状态。比如一个 16 位寄存器正在被主设备分两次写入,第一次写入了高字节,第二次还没写低字节就死锁了,复位后寄存器应该是“未写入”状态,而不是“高字节已写入”状态。否则恢复后主设备再对该寄存器读写,数据就对不上了。

4.4 防止死锁的设计经验:延展窗口、中断优先级与 NACK 处理

死锁最好的处理方式,是让它根本不发生。基于我踩过的坑,总结几个预防经验。

第一,时钟延展窗口要短,且不能在中断里做耗时操作。从机需要拉低 SCL 争取时间,但争取来的时间应该只做“数据搬运、状态更新”这种必要操作,不能在中断里做协议栈处理、日志打印、Flash 擦写。这些都是耗时大户,会无限拉长延展时间,增加死锁概率。我在一个项目里就因为从机中断里做了 Flash 写入,导致 SCL 延展超过 100ms,主设备看门狗超时,两边互相等,系统直接假死。

第二,I2C 中断优先级要足够高。前面提到过,I2C 中断被阻塞,SCL 就会被长时间拉低。一般情况下,I2C 中断优先级应该高于普通外设中断,但可以低于系统节拍之类更紧急的中断。关键是“不能被长耗时中断阻塞”,这点每个项目要根据实际中断负载评估。

第三,正确处理从机 NACK 后的状态。主设备发 NACK 表示“我收完了”,从机收到 NACK 后,应该立即释放 SDA,回到 IDLE。有些从机芯片在 NACK 后 SDA 释放不及时,再接下一个 start 条件时就会产生毛刺。代码上务必要在 NACK 状态退出时把 SDA 切回输入模式。

5. 常见问题排查与避坑实录

最后整理一份问题速查表,都是我在实际项目中遇到的典型问题,症状可能五花八门,根因往往集中在几个点上。

症状可能原因排查方法解决方案
SCL 保持低电平,总线锁死从机时钟延展未释放,或 SCL 被从机开漏输出拉低用示波器量 SCL 电平;检查从机代码是否卡在延展流程执行 9 脉冲恢复;检查从机延展超时机制
SDA 保持低电平,主设备一直等 ACK从机未释放 SDA,或主设备 STOP 条件缺失量 SDA 电平;抓起始/停止条件波形执行 9 脉冲恢复;检查从机 NACK 后 SDA 释放逻辑
总线偶发死锁,复位后恢复正常从机接收到非法字节,状态机错乱加状态日志,记录死锁前是哪个状态增加非法字节检测,强制状态复位
快速模式 400kHz 下偶尔误码上拉电阻选得过大,上升沿太慢用示波器量上升时间换更小的上拉电阻,比如 2.2kΩ
SMBus 主设备连接后频繁超时从机时钟延展超过 35ms,触发 SMBus 超时检查延展期间 SCL 低电平持续时间缩短延展时间,增加 SMBus 超时兼容逻辑
从机复位后总线即锁死从机复位瞬间 SCL/SDA 处于输出状态,与总线冲突从机复位代码不释放 GPIO,或默认配置错误复位初始化时先将 SCL/SDA 配置为高阻输入

再分享一个排查工具的心得:逻辑分析仪是 I2C 调试的第一生产力。普通示波器可以看波形,但分析协议帧很费劲。逻辑分析仪能直接解码 I2C 协议,按帧显示地址、数据、ACK、停止条件。调试死锁问题时,先把捕获深度调到足够大,触发条件设为 STOP 条件,就能抓全整个通信过程,死锁发生在哪一帧一目了然。

最后说个实战案例。之前做一个传感器从机,主设备每隔 100ms 读取一次数据。产品在小批量测试时发现偶发死锁,概率约千分之一。加日志后发现从机在一个特定寄存器地址被读取时,正好赶上内置 ADC 转换结束中断。中断函数里做了均值计算,耗时约 3ms,期间 I2C 中断被阻塞。从机硬件外设因为数据寄存器没及时读取,自动拉低 SCL 延展。3ms 虽然不长,但那个应用场景下主设备连续发送了两次读请求,第一次延展还没结束第二次就来了,总线状态直接错乱。解决办法是:把 ADC 均值计算放到主循环里做,中断内只置标志位,同时给 I2C 中断提升优先级。改完后跑了两周,没有再复现。

6. 写在最后的个人体会

做从机设计这些年,我最深的感觉是:I2C 从机的问题,80% 出在状态管理上,而不是时序参数上。时序参数错了,波形一眼就能看出来;状态管理错了,波形看起来一切正常,数据却悄悄丢一两个字节,或者偶尔死锁一次,特别难查。

时钟延展这个功能,用好了是从机的“减速缓冲”,用不好就是“死锁加速器”。关键不是知道怎么拉低 SCL,而是想清楚什么时候拉低、拉低多久、超时了怎么退出。每一个延展窗口都要有超时保护,每一个状态转移都要考虑异常路径,这样才是真正把总线鲁棒性做进去了。

最后再分享一个排查技巧:如果死锁只在特定操作序列下出现,比如先写寄存器 A 再读寄存器 B,那大概率是状态机在连续两个传输之间没有正确复位。可以用逻辑分析仪把两次传输的波形同时抓下来对比,重点看第二次的起始条件是否和第一次的停止条件对齐。这个位置很容易出现毛刺或者状态残留,也是死锁的最高发区域。调试多了你会发现,总线协议不怕慢,就怕乱。状态机管住了,总线自然就稳了。

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

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

立即咨询