TMS320F280015x I2C驱动调试:数字回环、NACK处理与中断系统详解

📅 2026/7/20 13:10:38 👁️ 阅读次数 📝 编程学习
TMS320F280015x I2C驱动调试:数字回环、NACK处理与中断系统详解

1. 项目概述与核心价值

在嵌入式开发,尤其是基于TI C2000系列DSP(如TMS320F280015x)的项目中,I2C总线因其简洁的两线制(SDA和SCL)和灵活的多主多从架构,成为了连接各类传感器、EEPROM、RTC等外设的首选通信协议。然而,在实际调试和产品验证阶段,我们常常会遇到一个棘手的问题:硬件电路尚未就绪,或者物理连接存在不确定性,如何验证我们精心编写的I2C驱动代码逻辑是否正确?又或者,当从设备无响应时,主设备应该如何优雅地处理,而不是傻等或崩溃?这些问题直接关系到项目的开发效率和最终产品的鲁棒性。

今天,我们就来深入探讨TMS320F280015x微控制器中I2C模块的两个高级且极其实用的功能:数字回环模式NACK生成机制,并理清其复杂但至关重要的中断系统。数字回环模式让你在没有外部硬件的情况下,就能完成数据收发流程的自检,是驱动开发初期的“定心丸”。而透彻理解NACK的生成条件与处理方式,则是构建稳定、容错通信系统的关键。最后,我们将拆解I2C的七种基本中断和FIFO中断,让你能根据实际场景选择最有效率的数据交互方式(查询或中断),并编写出清晰、健壮的中断服务程序。

无论你是正在调试第一块C2000板卡的新手,还是希望优化现有通信协议的老手,掌握这三个核心机制,都能让你对I2C的掌控力提升一个档次,从“能用”走向“精通”和“稳定”。

2. 数字回环模式深度解析与实战配置

数字回环模式是I2C模块内置的一种自测试功能。它的核心思想是“内部短路”:将模块的发送端和接收端在芯片内部连接起来,让发送的数据不经过物理的SDA和SCL引脚,而是通过内部数字路径直接环回到接收端。这相当于在芯片内部构建了一个虚拟的、理想的I2C通信链路。

2.1 工作原理与信号路由

当我们将I2C模式寄存器(I2CMDR)中的DLB位(Digital Loopback)置1时,模块内部的信号路径会发生重定向。

关键路径变化如下:

  1. 发送路径:写入发送数据寄存器(I2CDXR)的数据,不再被送到发送移位寄存器(I2CXSR)并通过SDA引脚输出,而是直接通过一个内部反馈路径。
  2. 接收路径:上述内部反馈路径上的数据,在经过一个固定的延迟后,被送入接收移位寄存器(I2CRSR),并最终载入接收数据寄存器(I2CDRR),仿佛数据是从外部总线接收的一样。
  3. 时钟路径:发送时钟(SCL_OUT)同样在内部被用作接收时钟(SCL_IN),实现了时钟同步。
  4. 地址响应:在回环模式下,出现在外部SDA引脚上的地址将是自身的地址(I2COAR寄存器中的值)。但请注意,由于数据不走外部总线,这个地址实际上不会被驱动到引脚上,这只是内部逻辑的一个状态。

文档中给出了一个关键公式:数据从I2CDXR到达I2CDRR所需的延迟周期数n = 8 * (SYSCLK) / (I2C module clock)。这里的SYSCLK是系统时钟频率,I2C module clock是经过预分频器(I2CPSC)分频后得到的模块工作时钟。这个延迟是硬件固定的,在编写测试代码时,我们需要在写入数据后等待足够的周期再去读取,或者更可靠的是,通过查询RRDY(接收就绪)标志位来判断数据是否已就绪。

注意:官方手册明确强调,自由数据格式I2CMDR.FDF = 1)在数字回环模式下不被支持。这意味着回环测试必须使用标准的带地址帧的I2C格式(7位或10位地址)。

2.2 实战配置步骤与代码示例

假设我们要测试主模式下的发送与接收功能,系统时钟为100MHz,目标I2C模块时钟为10MHz。

步骤一:模块初始化与时钟配置首先,必须确保在配置期间模块处于复位状态(I2CMDR.IRS = 0)。

// 1. 将I2C模块置于复位状态 I2caRegs.I2CMDR.bit.IRS = 0; // 2. 配置预分频器,产生10MHz模块时钟 (假设输入时钟为100MHz) // module clock = input clock / (IPSC + 1) => 100MHz / (9 + 1) = 10MHz I2caRegs.I2CPSC.all = 9; // 3. 配置时钟高低电平时间,以产生100kHz的标准I2C总线速率(SCL频率) // I2C clock frequency = module clock / (ICCL + d + ICCH + d) // 其中d为调整因子,通常为固定值(例如7)。为简化,假设d=7。 // 目标SCL = 10MHz / (ICCL + 7 + ICCH + 7) = 100kHz // 因此 ICCL + ICCH ≈ 100 - 14 = 86。通常设置为对称,各43。 I2caRegs.I2CCLKL = 43 - 7; // 计算ICCL寄存器的值 I2caRegs.I2CCLKH = 43 - 7; // 计算ICCH寄存器的值 // 4. 配置自身地址(在回环模式下,这个地址会在内部被使用) I2caRegs.I2COAR = 0x50; // 设置自身从机地址为0x50 // 5. 使能数字回环模式,并设置为主模式、发送器 I2caRegs.I2CMDR.all = 0x0000; // 先清空 I2caRegs.I2CMDR.bit.DLB = 1; // 使能数字回环 I2caRegs.I2CMDR.bit.MST = 1; // 主模式 I2caRegs.I2CMDR.bit.TRX = 1; // 发送器模式 I2caRegs.I2CMDR.bit.IRS = 1; // 使能I2C模块(退出复位)

步骤二:执行回环测试配置完成后,我们可以模拟一次完整的发送-接收过程。

Uint16 test_data = 0xAA55; Uint16 received_data = 0; // 1. 设置数据计数(非重复模式) I2caRegs.I2CCNT = 1; // 发送1个字节数据 // 2. 写入从机地址(虽然是回环,但地址帧仍会参与内部逻辑) I2caRegs.I2CSAR = 0x50; // 目标地址,这里设为与自身地址相同 // 3. 产生START条件 I2caRegs.I2CMDR.bit.STT = 1; // 4. 等待寄存器访问就绪(ARDY),表示地址已处理,可以发送数据 while(I2caRegs.I2CSTR.bit.ARDY == 0); // 5. 写入要发送的数据 I2caRegs.I2CDXR = test_data; // 6. 等待发送完成(这里可以等待XRDY或ARDY,取决于模式) // 在非FIFO模式下,等待XRDY表示数据已从DXR转移到XSR,可以写入下一个数据。 // 因为我们只发一个字节,也可以等待ARDY表示整个操作(包括可能的STOP)完成。 while(I2caRegs.I2CSTR.bit.ARDY == 0); // 7. 产生STOP条件(在非重复模式且STP=1时,计数器到0会自动产生,这里我们手动确保) I2caRegs.I2CMDR.bit.STP = 1; while(I2caRegs.I2CSTR.bit.SCD == 0); // 等待STOP条件完成 // 8. 读取回环接收到的数据 // 需要等待接收就绪标志 RRDY while(I2caRegs.I2CSTR.bit.RRDY == 0); received_data = I2caRegs.I2CDRR; // 9. 验证数据 if(received_data == test_data) { // 测试成功!驱动层基本逻辑正确。 } else { // 测试失败,检查时钟配置、延迟等待或寄存器操作顺序。 }

实操心得与避坑指南:

  • 时机至关重要:在使能数字回环(DLB=1)前,务必确保模块处于复位(IRS=0)状态。在IRS=1运行时动态切换DLB可能导致不可预知的行为。
  • 耐心等待标志位:I2C操作是异步的。ARDYRRDYSCD等状态位是你的好朋友。在关键操作后(如写STT、写DXR、写STP),一定要查询相应的状态位,确保前一个动作已完成,再进行下一步。盲目操作是导致通信失败最常见的原因之一。
  • 理解“虚假”的地址:在回环模式下,虽然地址帧在内部处理,但外部SDA线理论上会出现自身地址。如果你的硬件上SDA/SCL接了其他设备,要小心回环测试可能产生的意外影响。最安全的做法是在测试时,确保I2C总线上的其他设备处于未上电或高阻态。
  • 时钟配置是基础:即使数据不走外部总线,模块时钟(I2CPSC)和SCL分频(I2CCLKL/H)的配置也必须正确。不正确的时钟配置可能导致内部状态机紊乱,回环测试失败。

3. NACK生成机制:原理、策略与错误处理

NACK(Not Acknowledge)是I2C协议中接收方向发送方反馈“未确认”的信号。在第九个时钟周期(应答位)将SDA线拉高,即表示NACK。合理利用NACK是设计鲁棒性I2C通信的关键。

3.1 NACK的生成条件与配置方法

根据文档,I2C模块作为接收方(无论是主接收还是从接收)时,可以通过多种方式触发NACK的发送。下表总结了不同模式下的NACK生成选项:

I2C模块工作模式NACK生成选项操作说明与时机
从接收模式1. 允许溢出条件当接收移位寄存器满(RSFULL=1)时,模块会自动发送NACK。
2. 复位模块I2CMDR.IRS清零,模块复位,会中止当前传输。
3. 设置NACKMOD位你打算接收的最后一个数据位的上升沿之前,将I2CMDR.NACKMOD置1。
主接收模式 (重复模式 RM=1)1. 生成STOP条件设置I2CMDR.STP=1,模块在完成当前字节传输后产生STOP,并可能在之前发送NACK。
2. 复位模块I2CMDR.IRS清零。
3. 设置NACKMOD位在最后一个预期数据位的上升沿前,将I2CMDR.NACKMOD置1。
主接收模式 (非重复模式 RM=0)1. 计数器归零强制STOP如果STP=1,内部数据计数器(I2CCNT)减到0会自动产生STOP条件,并在最后一个字节后发送NACK。
2. 手动设置STOP如果STP=0,将其置1以生成STOP条件。
3. 复位模块I2CMDR.IRS清零。
4. 设置NACKMOD位在最后一个预期数据位的上升沿前,将I2CMDR.NACKMOD置1。

核心机制解读:

  • NACKMOD:这是最直接、最可控的NACK生成方式。你必须在目标数据位的最后一个时钟上升沿之前将其置位。硬件在发送完该NACK后会自动清除此位。这常用于接收指定长度数据后,主动终止传输。
  • 溢出条件(RSFULL:当CPU来不及读取I2CDRR,而新的数据已从I2CRSR移入时,RSFULL置1,模块会发送NACK。这是一种“背压”机制,告诉发送方“我忙不过来了,请暂停”。这是一个重要的错误状态,需要在中断或查询中处理。
  • STOP条件:在主接收模式下,STOP条件的产生通常伴随着对最后一个字节的NACK(除非是重复模式下的特殊操作)。这是标准流程的一部分。

3.2 NACK发生后的模块行为与处理流程

当模块发送了一个NACK后,硬件会自动执行以下动作:

  1. 清除STP位I2CMDR寄存器中的STP位被清零。
  2. 保持SCL低电平:时钟线被拉低,总线进入等待状态。
  3. 置位NACK状态位:状态寄存器I2CSTR中的NACK位被置1。

作为主设备(发送方)时,处理从设备发来的NACK:这是更常见的情况。当你作为主设备发送地址或数据后,从设备回复了NACK,通常意味着:

  • 从设备地址错误。
  • 从设备忙或故障。
  • 写入操作到只读寄存器或非法地址。

此时,I2CSTR.NACK位会被置位。你的处理流程应该是:

// 假设主设备发送数据后 I2caRegs.I2CDXR = data_to_send; // ... 等待传输完成或使用中断 // 检查是否收到NACK if(I2caRegs.I2CSTR.bit.NACK == 1) { // 1. 清除NACK标志(写1清除) I2caRegs.I2CSTR.bit.NACK = 1; // 2. 产生STOP条件,释放总线(如果之前没设置) I2caRegs.I2CMDR.bit.STP = 1; while(I2caRegs.I2CSTR.bit.SCD == 0); // 3. 进行错误处理:重试、记录日志、切换备用设备等 handle_i2c_nack_error(); // 4. 在重试前,可能需要重新初始化或确保模块回到空闲状态 // 简单的做法是:置位IRS=0,再置位IRS=1,并重新配置参数(如果需要)。 }

避坑要点:

  • 及时清除标志位NACKARDYRRDY等标志位有些是写1清除(W1C),有些是读I2CISRC清除,有些是自动清除。务必查阅手册。对于NACK,手册明确是写1清除。不清除旧标志会导致误判。
  • 总线恢复:发生NACK错误后,总线可能处于异常状态(SCL被拉低)。最稳妥的恢复方式是:先产生一个STOP条件(如果可能),然后复位I2C模块IRS=0IRS=1),最后重新初始化并开始新的传输序列。
  • 超时机制:在等待ARDYRRDY等状态时,一定要加入超时判断。如果从设备彻底死机不回复ACK或NACK,主设备会永远等待。一个简单的计数器超时跳出循环,能防止整个系统卡死。
#define I2C_TIMEOUT 10000 Uint32 timeout = 0; while((I2caRegs.I2CSTR.bit.ARDY == 0) && (timeout < I2C_TIMEOUT)) { timeout++; } if(timeout >= I2C_TIMEOUT) { // 超时处理:复位总线,报告错误 }

4. I2C中断系统全解析与编程模型

轮询(Polling)方式简单,但效率低下,CPU大量时间浪费在等待状态位上。中断方式则能解放CPU,让它在等待I2C传输时去处理其他任务。TMS320F280015x的I2C模块提供了丰富的中断源,可分为两大类:基本I2C中断FIFO中断

4.1 基本I2C中断请求与优先级管理

模块可以产生7种基本中断,它们通过一个仲裁器复用到一个CPU中断线(例如I2CINT1A)。每个中断都有对应的标志位(在I2CSTR中)和使能位(在I2CIER中)。

中断源详解:

  1. ARBLINT(仲裁丢失)优先级最高。当多主竞争总线,本模块失去仲裁时触发。此时模块会自动从主模式切换到从接收模式。
  2. NACKINT(无应答):作为主发送器时,未收到从机的应答信号时触发。对应I2CSTR.NACK位。
  3. ARDYINT(寄存器访问就绪):当模块已处理完先前编程的地址、数据和命令值,寄存器可被访问时触发。在主模式下,用于指示可以写入下一个数据或命令。对应I2CSTR.ARDY位。
  4. RRDYINT(接收就绪):接收数据寄存器(I2CDRR)有新数据可读时触发。在FIFO模式下应禁用,改用FIFO接收中断。对应I2CSTR.RRDY位。
  5. XRDYINT(发送就绪):发送数据寄存器(I2CDXR)为空,可写入新数据时触发。在FIFO模式下应禁用,改用FIFO发送中断。对应I2CSTR.XRDY位。
  6. SCDINT(停止条件检测):总线上检测到STOP条件时触发。对应I2CSTR.SCD位。
  7. AASINT(被寻址为从机)优先级最低。当模块被总线上的其他主设备寻址时触发。对应I2CSTR.AAS位。

中断处理流程:

  1. 中断发生:某个使能的事件发生,I2CSTR中对应标志位置位。
  2. 中断请求:如果I2CIER中对应使能位为1,则该中断请求被送至仲裁器。
  3. 仲裁与响应:仲裁器根据优先级选择最高优先级的中断,将其编码写入中断源寄存器I2CISRC,并向CPU发出中断请求。
  4. CPU响应:CPU跳转到中断服务程序(ISR)。
  5. 源判断:ISR首先读取I2CISRC寄存器。这个读取操作会自动清除I2CSTR中对应中断的标志位(除了ARDYRRDYXRDY,这三个需要手动写1清除)
  6. 分支处理:根据I2CISRC.INTCODE的值,跳转到对应的处理子程序。
  7. 清除中断:对于需要���动清除的标志位(如ARDY),在ISR中完成处理后,应写1清除。
  8. 返回:中断返回。

示例:基于中断的主设备发送代码框架

// 初始化部分:配置I2C参数,开启中断 void I2CA_Init(void) { EALLOW; // 配置GPIO为I2C功能(���) // 配置PIE,将I2CINT1A映射到对应向量表(略) EDIS; I2caRegs.I2CMDR.bit.IRS = 0; // 复位模块 // ... 配置时钟分频、自身地址等 I2caRegs.I2CMDR.bit.IRS = 1; // 使能模块 // 使能所需的中断(例如发送就绪和NACK中断) I2caRegs.I2CIER.bit.XRDY = 1; // 使能发送就绪中断 I2caRegs.I2CIER.bit.NACK = 1; // 使能NACK中断 // 注意:在FIFO模式下,不应使能XRDY和RRDY中断 // 使能PIE组和CPU总中断(略) } // 中断服务程序 __interrupt void I2CA_ISR(void) { Uint16 int_source = I2caRegs.I2CISRC.bit.INTCODE; switch(int_source) { case 0x1: // ARBLINT: 仲裁丢失 I2caRegs.I2CSTR.bit.ARBL = 1; // 写1清除标志 // 处理仲裁丢失,通常重试发送 break; case 0x2: // NACKINT: 无应答 I2caRegs.I2CSTR.bit.NACK = 1; // 写1清除标志 I2caRegs.I2CMDR.bit.STP = 1; // 产生STOP // 设置错误标志,主循环中处理重试或报错 i2c_error_flag = NACK_ERROR; break; case 0x3: // ARDYINT: 寄存器就绪 // ARDY标志不会因读I2CISRC而清除,需手动清除 I2caRegs.I2CSTR.bit.ARDY = 1; // 在非重复模式的主发送中,ARDY表示上一个数据/命令已处理完。 // 可以在此处准备并写入下一个数据到I2CDXR。 if(tx_data_index < tx_data_length) { I2caRegs.I2CDXR = tx_buffer[tx_data_index++]; } else { // 数据发送完毕,可以准备结束传输 I2caRegs.I2CMDR.bit.STP = 1; } break; case 0x4: // RRDYINT: 接收就绪 (非FIFO模式) I2caRegs.I2CSTR.bit.RRDY = 1; rx_buffer[rx_data_index++] = I2caRegs.I2CDRR; break; case 0x5: // XRDYINT: 发送就绪 (非FIFO模式) // XRDY标志不会因读I2CISRC而清除,且通常在此模式下由写I2CDXR操作清除。 // 可以在此写入下一个数据 if(tx_data_index < tx_data_length) { I2caRegs.I2CDXR = tx_buffer[tx_data_index++]; } break; // ... 处理其他中断源 default: break; } // 清除PIE组中断标志,并返回 PieCtrlRegs.PIEACK.all = PIEACK_GROUP8; // 假设I2C在GROUP8 }

4.2 FIFO中断与高效数据传输

对于批量数据传输,频繁的中断(每个字节一次)会造成巨大的CPU开销。FIFO(先入先出)缓冲区正是为了解决这个问题。TMS320F280015x的I2C模块提供了16级深度的发送和接收FIFO。

FIFO中断配置要点:

  1. 使能FIFO模式:通过设置I2CFFTX寄存器的I2CFFEN位为1来使能FIFO功能。
  2. 复位FIFO指针:在使能FIFO操作前,通过TXFFRSTRXFFRST位复位对应的FIFO。
  3. 设置中断水位TXFFILRXFFIL分别设置发送和接收FIFO的中断触发阈值。
    • 发送FIFO:当FIFO中的数据量小于或等于TXFFIL设定的值时,触发发送中断(TXFFINT),提示CPU需要填充更多数据。
    • 接收FIFO:当FIFO中的数据量大于或等于RXFFIL设定的值时,触发接收中断(RXFFINT),提示CPU可以读取一批数据。
  4. 使能FIFO中断:通过TXFFIENARXFFIENA位使能中断。注意,一旦使能FIFO模式,就必须禁用基本的XRDYINTRRDYINT中断
  5. 中断服务程序:FIFO中断是独立的(I2CINT2A)。在ISR中,需要读取I2CFFTX.TXFFINTI2CFFRX.RXFFINT来判断中断源,并通过写1到TXFFINTCLRRXFFINTCLR来清除中断标志。

FIFO模式发送示例流程:

// 初始化FIFO I2caRegs.I2CFFTX.all = 0x0000; // 先禁用 I2caRegs.I2CFFTX.bit.TXFFRST = 0; // 复位发送FIFO I2caRegs.I2CFFTX.bit.TXFFIL = 4; // 设置水位:当TX FIFO中数据<=4时触发中断 I2caRegs.I2CFFTX.bit.TXFFIENA = 1; // 使能发送FIFO中断 I2caRegs.I2CFFTX.bit.I2CFFEN = 1; // 使能I2C FIFO模式 I2caRegs.I2CFFTX.bit.TXFFRST = 1; // 使能发送FIFO操作 // 在FIFO发送中断服务程序中 __interrupt void I2CA_FIFO_TX_ISR(void) { if(I2caRegs.I2CFFTX.bit.TXFFINT == 1) { // 清除中断标志 I2caRegs.I2CFFTX.bit.TXFFINTCLR = 1; // 检查FIFO状态,填充数据直到FIFO满或数据发送完 while((I2caRegs.I2CFFTX.bit.TXFFST < 16) && (tx_index < tx_total)) { I2caRegs.I2CDXR = tx_buffer[tx_index++]; // 写I2CDXR即压入FIFO } } // ... 清除PIE中断标志 }

关于FCM(Forward Compatibility Mode)模式:文档中提到了I2CEMDR寄存器的FCM位。当FCM=1时,会改变XRDY的行为。在传统模式下,只要I2CDXR的数据被转移到I2CXSRXRDY就置位。这可能导致在传输被异常中止(如仲裁丢失、NACK)时,I2CDXR中残留的“陈旧”数据在下次传输时被错误发送。FCM=1模式下,XRDY(及其中断)仅在总线事务真正需要数据时(如收到地址ACK后)才置位,避免了陈旧数据问题。在需要高可靠性的应用中,建议启用此模式。

5. 常见问题排查与调试技巧实录

在实际开发中,I2C通信失败是家常便饭。以下是我总结的一些常见问题及其排查思路,很多都是“踩坑”后得来的经验。

5.1 通信完全无响应,SCL/SDA线一直为高

  • 检查清单
    1. 物理连接:万用表检查SCL、SDA对地、对电源是否短路或开路?上拉电阻是否焊接(通常4.7kΩ-10kΩ)?电压是否正常?
    2. 引脚复用配置:C2000的GPIO功能是复用的。确认你是否正确配置了GPxMUXGPxGMUX寄存器,将对应引脚设置为I2C功能。这是新手最常犯的错误之一。使用TI的SysConfig工具可以直观配置并生成代码。
    3. 模块使能:你设置I2CMDR.IRS = 1了吗?模块必须在非复位状态下才能工作。
    4. 时钟配置I2CPSCI2CCLKLI2CCLKH计算是否正确?用示波器测量SCL引脚,看是否有波形?频率是否符合预期?如果SCL没有时钟输出,问题一定在时钟配置或主/从模式设置(MST位)上。

5.2 能抓到起始信号,但地址帧后无应答(NACK)

  • 排查思路
    1. 从机地址:7位地址还是10位地址?I2CMDR.XA位设置对吗?I2CSAR寄存器写入的地址是否正确(注意7位地址左移一位后,最低位是R/W位)?从机设备的实际地址是多少?(很多传感器地址可通过引脚选择)
    2. 从机电源与就绪:从设备上电了吗?有些传感器(如某些IMU)上电后需要几毫秒的初始化时间才能响应I2C。
    3. 时序问题:SCL频率是否过快?尝试降低到100kHz甚至10kHz试试。从设备可能无法支持高速模式。
    4. 总线竞争:是否有其他主设备或故障设备将总线拉低?可以尝试断开所有从设备,用数字回环模式自检主设备是否正常。

5.3 能收到ACK,但数据帧错误或丢失

  • 深度排查
    1. 数据位宽I2CMDR.BC位设置是否正确?你打算发送/接收8位数据,但对方设备是7位?这会导致错位。
    2. FIFO与中断冲突:你是否同时使能了FIFO模式和基本的XRDYINT/RRDYINT这是禁止的。确保在FIFO模式下只使用FIFO中断。
    3. 中断标志清除:你是否正确清除了中断标志?ARDYRRDYXRDY需要写1清除,而NACKARBL等会在读I2CISRC后自动清除。标志未清除会导致中断持续触发或状态误判。
    4. 状态查询顺序:你的代码逻辑是否严格遵循“等待ARDY -> 写数据 -> 等待XRDY/中断 -> ...”的顺序?在非FIFO模式下,不等待ARDY就写STTDXR是常见错误。
    5. STP位处理:在非重复模式下,你设置STP=1了吗?如果没有,主设备在发送完I2CCNT指定的字节数后不会产生STOP条件,总线会一直处于忙状态(BB=1),阻塞后续操作。务必在传输序列的最后确保STOP条件被生成,并等待SCD位置位。

5.4 使用调试器(如JTAG)时通信异常

  • 经验之谈
    • FREE位:当你在调试时设置断点,CPU暂停,但I2C模块可能还在驱动总线。如果此时SCL被拉低,总线会被锁死。将I2CMDR.FREE位设置为1,可以让I2C模块在调试器暂停时继续运行,避免锁死总线。
    • 实时监控:利用CCS的寄存器查看器和内存查看器,实时观察I2CSTR状态位、I2CDRR/I2CDXR数据寄存器的变化,是定位问题的利器。
    • 逻辑分析仪是终极武器:投资一个哪怕是最简单的逻辑分析仪(如Saleae)。将其连接到SCL和SDA线上,可以直观地看到起始、地址、数据、ACK/NACK、停止等每一个波形,绝大部分协议层问题都能一目了然。

最后,分享一个我个人调试I2C的“笨”办法,但非常有效:编写一个最简单的、轮询方式的、只发送一个字节数据的测试函数。先确保这个最基本的功能能在数字回环模式下通过,然后再在真实硬件上通过。接着,逐步增加功能:接收一个字节、发送多个字节、使用中断、使用FIFO。每增加一个功能就测试一次,这样能最快地定位问题出现在哪个环节。I2C协议本身不复杂,但和硬件、时序、配置紧密相关,耐心和细致的排查是成功的关键。