1. 从一次通信失败说起为什么DSP的I2C总让人头疼如果你正在用TI的TMS320F2803x系列DSP做项目并且需要和外部传感器、EEPROM或者RTC时钟芯片打交道那你大概率绕不开I2C总线。我刚开始接触2803x的I2C模块时也以为它会像GPIO或者PWM那样“开箱即用”结果却被现实狠狠教育了一番。最典型的场景就是代码写好了逻辑看起来也没问题但用示波器一抓波形要么是起始信号S没出来要么是设备地址Slave Address发出去后收不到应答ACK总线直接卡死SCL线被拉低程序陷入超时等待。这其实不是你的代码写错了而是DSP的I2C模块和常见的单片机比如STM32在设计和用法上有一些根本性的不同。2803x的I2C模块更偏向于一种“状态机驱动”的模式它把整个通信过程拆解成了非常细粒度的步骤需要你手动去查询状态、清除标志位、然后写入下一个动作。它不像某些库函数那样一个I2C_Write()函数就帮你搞定从起始到停止的所有事情。这种设计带来了极高的灵活性但也对开发者的理解深度提出了要求。如果你只是照搬网上STM32的I2C例程十有八九会失败。所以这篇内容我们就来彻底拆解2803x的I2C模块。我们不只讲怎么配置寄存器让总线跑起来更要深入它的状态机理解每一个状态转移的条件以及在实际调试中如何根据示波器波形和寄存器状态快速定位问题。你会发现一旦掌握了它的“脾气”2803x的I2C会变得非常可靠和强大。2. 核心差异状态机驱动的I2C vs 库函数封装的I2C在开始写代码之前我们必须先建立正确的认知模型。很多人调不通根源在于用错了模型。2.1 常见的“黑盒”模型及其局限许多单片机尤其是使用HAL库或标准外设库的ARM Cortex-M芯片将I2C操作封装成了高级API。例如你想往EEPROM的0x00地址写一个字节可能只需要调用三个函数I2C_Start()I2C_WriteByte(DEVICE_ADDR | WRITE)并检查ACKI2C_WriteByte(0x00)并检查ACKI2C_WriteByte(data)I2C_Stop()在这个过程中开发者不需要关心底层硬件具体在哪个时钟周期发出了SDA信号只需要关心“发送”这个动作是否成功。库函数内部帮你处理了所有状态标志和时序。这种模型简单易用但一旦出现问题比如ACK失败调试起来就像个黑盒你很难知道具体卡在哪一步。2.2 2803x的“白盒”状态机模型2803x的I2C模块则完全不同。它有一个非常清晰的状态寄存器I2CSTR和对应的状态机。整个通信过程你必须像操作一台精密的机床手动推动状态机前进。它的基本工作流程是这样的你通过写数据寄存器I2CDXR或配置寄存器来“下达指令”告诉模块下一步要做什么比如发送起始条件。模块执行这个指令同时内部状态机跳转。模块完成动作后会在I2CSTR寄存器中设置一个特定的状态标志位比如XSMT标志变为0表示发送移位寄存器空了ARDY标志变为1表示地址寄存器就绪。你必须主动查询这些标志位确认上一步动作已完成。然后你必须手动清除这个标志位通过向特定比特位写1为下一个状态做准备。最后你才能进行下一步操作比如写入从机地址。如果跳过第4和第5步直接进行下一步操作状态机就会混乱导致通信失败。这就是为什么“抄代码”容易失败的原因——例程里那些while(!I2C_IsBusBusy())或while(!I2C_GetStatusFlag())的语句以及后面紧跟着的I2C_ClearStatusFlag()都不是可有可无的而是状态机运转的核心逻辑。理解了这个模型我们再去看数据手册里的状态转移图就不会觉得是天书了。它其实是在告诉你“当你处在状态A并且完成了动作X标志位Y会置起你清掉Y后才能写入命令Z进入状态B。”3. 硬件初始化与配置奠定稳定的通信基础在操作状态机之前我们需要先把硬件舞台搭建好。2803x的I2C初始化主要涉及引脚、时钟和模块基本参数。3.1 引脚功能复用与上拉电阻配置2803x的I2C引脚通常是GPIO的多功能复用引脚。以常见的GPIO32SDA和GPIO33SCL为例你需要将它们配置为外设功能而不是普通的数字IO。// 假设使用GPIO32和GPIO33作为I2C引脚 EALLOW; // 允许写入受保护的寄存器 GpioCtrlRegs.GPBMUX2.bit.GPIO32 1; // 将GPIO32配置为外设功能I2CA_SDA GpioCtrlRegs.GPBMUX2.bit.GPIO33 1; // 将GPIO33配置为外设功能I2CA_SCL // 通常I2C引脚需要配置为开漏输出但2803x的GPIO模块在复用为I2C时硬件会自动处理。 // 更关键的是需要禁用这些引脚的上拉电阻因为I2C总线需要外部上拉。 GpioCtrlRegs.GPBPUD.bit.GPIO32 1; // 禁用内部上拉 GpioCtrlRegs.GPBPUD.bit.GPIO33 1; // 禁用内部上拉 EDIS; // 禁止写入受保护的寄存器注意这里有一个极易忽略的坑。有些开发板为了省事可能使能了GPIO的内部上拉电阻。如果内部上拉和外部上拉同时存在会导致总线上拉过强影响上升沿速度在高速模式400kHz下可能造成时序问题。最稳妥的做法是始终禁用I2C引脚的内部上拉依靠可靠的外部上拉电阻。典型的上拉电阻值在3.3V系统下为4.7kΩ或10kΩ具体取决于总线电容和通信速度。3.2 模块时钟与通信速率计算I2C模块的时钟来源于系统时钟SYSCLKOUT经过一个预分频器。通信速率100kbps标准模式或400kbps快速模式由I2CPSC和I2CCLKL/I2CCLKH寄存器共同决定。计算过程比想象中要绕一点因为涉及一个预分频和一个主时钟分频IPSC分频I2CPSC寄存器用于产生模块工作的基础时钟I2C_CLK。公式为I2C_CLK SYSCLKOUT / (I2CPSC 1)。这个I2C_CLK必须介于7-12 MHz之间根据数据手册以保证模块正常工作。通常我们取一个中间值例如SYSCLKOUT60MHz设置I2CPSC 5则I2C_CLK 60 / (51) 10 MHz。主时钟分频I2CCLKL和I2CCLKH寄存器用来对I2C_CLK进行分频以产生最终的SCL时钟。SCL的低电平周期由I2CCLKL决定高电平周期由I2CCLKH决定。它们都是I2C_CLK周期数的整数倍。SCL低电平时间 (I2CCLKL 7) * (1 / I2C_CLK)SCL高电平时间 (I2CCLKH 5) * (1 / I2C_CLK)整个SCL周期 低电平时间 高电平时间通信速率 1 / SCL周期例如我们要配置100kHz标准模式I2C_CLK10MHz目标周期 T 1/100kHz 10 us。我们需要分配高低电平时间。通常为了占空比接近50%设低电平时间 ~5us高电平时间 ~5us。计算I2CCLKL5us (I2CCLKL 7) * 0.1usI2CCLKL 7 50I2CCLKL 43计算I2CCLKH5us (I2CCLKH 5) * 0.1usI2CCLKH 5 50I2CCLKH 45验证周期 (437455)0.1us 1000.1us 10us速率100kHz。初始化代码如下void I2CA_Init(void) { // 1. 使能I2C模块时钟在PCLKCR0寄存器中 EALLOW; SysCtrlRegs.PCLKCR0.bit.I2CAENCLK 1; // 使能I2C-A模块时钟 EDIS; // 2. 初始化I2C模块为宿主/主模式 I2caRegs.I2CMDR.all 0x0000; // 先复位MDR寄存器 I2caRegs.I2CMDR.bit.IRS 0; // 先将模块复位IRS0 // 3. 配置时钟预分频产生合适的I2C_CLK // 假设SYSCLKOUT 60MHz目标I2C_CLK 10MHz I2caRegs.I2CPSC.all 5; // I2C_CLK 60/(51)10MHz // 4. 配置SCL时钟分频产生100kHz I2caRegs.I2CCLKL 43; // SCL低电平时间 I2caRegs.I2CCLKH 45; // SCL高电平时间 // 5. 配置其他参数自由数据格式模式FDF0即7位地址模式主模式 I2caRegs.I2CMDR.all 0x0000; // 清空 I2caRegs.I2CMDR.bit.BC 0; // 字节数在自由模式下忽略 I2caRegs.I2CMDR.bit.FDF 0; // 非自由数据格式使用7位地址 I2caRegs.I2CMDR.bit.STT 0; // 起始条件位由软件置位 I2caRegs.I2CMDR.bit.TRX 1; // 传输方向1主发送0主接收 I2caRegs.I2CMDR.bit.MST 1; // 主模式 I2caRegs.I2CMDR.bit.IRS 1; // 使能模块退出复位 // 6. 配置超时寄存器防止总线死锁 I2caRegs.I2CTOA.all 0xFFFF; // 设置一个较大的超时值 // 7. 清除所有可能挂起的标志位 I2caRegs.I2CSTR.all 0xFFFF; // 向标志位写1清除它们 }这段初始化代码的关键是IRS位。你必须先将其清零复位模块在配置完所有参数后再置1使能模块。在模块禁用时配置参数是安全的。4. 主发送模式实战以写入EEPROM为例理论讲完了我们用一个最经典的例子——向24C02 EEPROM写入一个字节——来串联整个状态机的操作流程。假设设备地址是0x507位地址左移一位后写地址为0xA0。我们的目标是向EEPROM的0x00地址写入数据0x55。4.1 操作流程与状态机推演一次完整的“主发送”写操作包含以下步骤每一步都对应着状态机的变迁和标志位的操作产生起始条件S动作将I2CMDR寄存器的STT位设为1。状态变化模块检测到总线空闲后会在SCL为高时拉低SDA产生起始条件。完成后状态寄存器I2CSTR中的ARDY地址就绪标志位会置1。关键操作必须等待ARDY置1然后写1清除它才能进行下一步。发送从机地址写位SLAW动作将7位地址和写位0组合成的字节0xA0写入发送数据寄存器I2CDXR。状态变化模块会自动将这个字节发出。发送完成后会等待从机的应答ACK。如果收到ACKI2CSTR中的ARDY标志会再次置1表示地址阶段完成。如果没收到ACKNACK则NACK标志会置1通信失败。关键操作等待ARDY再次置1然后清除它。同时必须检查NACK标志是否为0。发送内存地址Word Address动作将EEPROM的内部地址0x00写入I2CDXR。状态变化模块发送这个字节等待ACK。完成后I2CSTR中的ARDY标志置1对于数据字节实际上是XRDY发送就绪标志更准确但ARDY在非重复起始条件下也会响应。关键操作等待ARDY置1清除它。发送要写入的数据Data动作将数据0x55写入I2CDXR。状态变化同上发送字节等待ACK标志位置起。关键操作等待ARDY或XRDY置1清除它。产生停止条件P动作将I2CMDR寄存器的STP位设为1。状态变化模块在完成最后一个字节的传输后会产生停止条件SCL为高时SDA由低变高。完成后I2CSTR中的ARDY标志会置1。关键操作等待ARDY置1清除它。至此一次完整的写操作结束。4.2 代码实现与逐行解析下面是根据上述流程编写的函数我加入了大量的注释解释了每一步“为什么”要这么做。#define I2C_EEPROM_ADDR_W 0xA0 // 7位地址0x50左移一位写操作位为0 #define I2C_TIMEOUT 10000 // 超时计数防止死循环 Uint16 I2CA_WriteByte(Uint16 slaveAddr, Uint16 memAddr, Uint16 data) { volatile Uint16 i; Uint16 timeout I2C_TIMEOUT; // 步骤1发送起始条件 I2caRegs.I2CMDR.bit.STT 1; // 置位STT命令模块产生起始条件 // 等待地址就绪标志ARDY置位表示起始条件已成功发出 while((I2caRegs.I2CSTR.bit.ARDY 0) (timeout--)); if(timeout 0) return 0; // 超时返回错误 I2caRegs.I2CSTR.bit.ARDY 1; // **关键** 写1清除ARDY标志为下一步做准备 timeout I2C_TIMEOUT; // 步骤2发送从机地址写 I2caRegs.I2CDXR slaveAddr; // 写入从机地址写位 // 等待ARDY再次置位表示地址字节已发送并期望收到ACK while((I2caRegs.I2CSTR.bit.ARDY 0) (timeout--)); if(timeout 0) return 0; // 清除ARDY前先检查是否收到NACK无应答。这是排查设备未连接或地址错误的关键。 if(I2caRegs.I2CSTR.bit.NACK 1) { // 如果收到NACK需要清除NACK标志并发送停止条件来释放总线 I2caRegs.I2CSTR.bit.NACK 1; I2caRegs.I2CMDR.bit.STP 1; return 0; // 返回错误 } I2caRegs.I2CSTR.bit.ARDY 1; // 清除ARDY标志 timeout I2C_TIMEOUT; // 步骤3发送内存地址EEPROM内部地址 I2caRegs.I2CDXR memAddr; while((I2caRegs.I2CSTR.bit.ARDY 0) (timeout--)); if(timeout 0) return 0; I2caRegs.I2CSTR.bit.ARDY 1; timeout I2C_TIMEOUT; // 步骤4发送数据 I2caRegs.I2CDXR data; while((I2caRegs.I2CSTR.bit.ARDY 0) (timeout--)); if(timeout 0) return 0; I2caRegs.I2CSTR.bit.ARDY 1; // 步骤5发送停止条件 I2caRegs.I2CMDR.bit.STP 1; // 置位STP命令模块产生停止条件 // 等待停止条件完成 while((I2caRegs.I2CSTR.bit.ARDY 0) (timeout--)); if(timeout 0) return 0; I2caRegs.I2CSTR.bit.ARDY 1; // 额外步骤等待总线空闲。STP位不会自动清零但停止条件发出后BB总线忙标志应变0。 timeout I2C_TIMEOUT; while((I2caRegs.I2CSTR.bit.BB 1) (timeout--)); // 等待总线空闲 if(timeout 0) return 0; return 1; // 写入成功 } // 调用示例 void main(void) { // ... 系统初始化I2C初始化 ... if(I2CA_WriteByte(I2C_EEPROM_ADDR_W, 0x00, 0x55)) { // 写入成功处理 } else { // 写入失败处理 } }4.3 关键点与避坑指南标志位清除的玄机I2CSTR寄存器中的标志位如ARDY,NACK是通过写1来清除的而不是写0。这是一个常见误区。I2caRegs.I2CSTR.bit.ARDY 1;这行代码的意思就是“清除ARDY中断标志”。超时处理必不可少所有while等待循环必须搭配超时机制。否则一旦总线故障如从机掉线、SCL被意外拉低程序将永远卡死。NACK处理在发送地址后检查NACK标志至关重要。如果从机不存在或地址错误模块会收到NACK。你的代码必须能检测到这种情况并主动发送停止条件 (STP1) 来释放总线否则总线会一直处于忙状态。STP位的特殊性STP位在你写入1后模块会在当前传输完成后自动产生停止条件然后硬件会自动清零STP位。所以你不需要像清除ARDY那样去清除STP。总线忙BB标志在停止条件发出后虽然ARDY置位了但SDA/SCL线恢复到高电平还需要一点时间。通过检查BB标志是否为0可以更可靠地确认总线已完全释放便于发起下一次传输。5. 主接收模式与重复起始条件读取EEPROM数据写操作相对简单因为方向一致。读操作则复杂一些因为它涉及方向改变必须使用“重复起始条件”Repeated Start Sr。流程是先发送写地址告诉从机要读哪个内存地址然后不停止总线直接发送一个重复起始条件接着发送读地址最后读取数据。目标从EEPROM的0x00地址读取一个字节。5.1 读操作流程解析发送起始条件S。发送从机地址写位SLAW 0xA0。告诉EEPROM“我要写操作”。发送内存地址0x00。告诉EEPROM“我要写入/操作这个地址”。注意这里我们并不真的写数据只是把内部地址指针定位过去。发送重复起始条件Sr。这是与写操作最大的不同。我们不发送停止条件而是再次发送起始条件。在2803x中这通过再次置位STT位来实现而此时STP位为0且总线处于忙状态。发送从机地址读位SLAR 0xA1。告诉EEPROM“现在我要读数据了”。切换为主接收模式并读取数据。将I2CMDR.bit.TRX位改为0主接收然后从I2CDRR数据接收寄存器读取数据。注意在接收最后一个字节前需要配置为发送NACK非应答和停止条件。发送停止条件P。5.2 代码实现理解状态机的方向切换Uint16 I2CA_ReadByte(Uint16 slaveAddr, Uint16 memAddr, Uint16 *data) { volatile Uint16 i; Uint16 timeout I2C_TIMEOUT; // --- 第一部分发送设备地址和内存地址伪写操作--- I2caRegs.I2CMDR.bit.STT 1; // 起始条件 while((I2caRegs.I2CSTR.bit.ARDY 0) (timeout--)); if(timeout0) return 0; I2caRegs.I2CSTR.bit.ARDY 1; timeout I2C_TIMEOUT; I2caRegs.I2CDXR slaveAddr 0xFFFE; // 确保最低位是0写操作 while((I2caRegs.I2CSTR.bit.ARDY 0) (timeout--)); if(timeout0) return 0; if(I2caRegs.I2CSTR.bit.NACK) { I2caRegs.I2CSTR.bit.NACK1; I2caRegs.I2CMDR.bit.STP1; return 0;} I2caRegs.I2CSTR.bit.ARDY 1; timeout I2C_TIMEOUT; I2caRegs.I2CDXR memAddr; // 发送要读取的内存地址 while((I2caRegs.I2CSTR.bit.ARDY 0) (timeout--)); if(timeout0) return 0; I2caRegs.I2CSTR.bit.ARDY 1; timeout I2C_TIMEOUT; // --- 第二部分重复起始切换为读操作 --- I2caRegs.I2CMDR.bit.STT 1; // **关键** 在总线忙时再次置位STT产生的是重复起始条件(Sr) while((I2caRegs.I2CSTR.bit.ARDY 0) (timeout--)); if(timeout0) return 0; I2caRegs.I2CSTR.bit.ARDY 1; timeout I2C_TIMEOUT; // 发送读地址并准备切换为接收模式 I2caRegs.I2CMDR.bit.TRX 0; // **关键** 先将传输方向改为接收 I2caRegs.I2CDXR slaveAddr | 0x0001; // 发送从机地址读位(1) while((I2caRegs.I2CSTR.bit.ARDY 0) (timeout--)); if(timeout0) return 0; if(I2caRegs.I2CSTR.bit.NACK) { I2caRegs.I2CSTR.bit.NACK1; I2caRegs.I2CMDR.bit.STP1; return 0;} I2caRegs.I2CSTR.bit.ARDY 1; timeout I2C_TIMEOUT; // --- 第三部分接收数据 --- // 对于单字节读取我们希望在接收数据后发送NACK和停止条件 // 可以通过配置MDR寄存器一次性完成 // 在发送读地址前或后配置MDR寄存器为主接收、发送NACK、发送停止条件 // 这里我们在发送读地址后重新配置MDR I2caRegs.I2CMDR.all 0x2C20; // 解析MST1(主), TRX0(收), STT0, STP1, NACK1, IRS1 // STP1: 接收完成后自动发停止条件 // NACK1: 在接收数据字节后发送NACK因为只读一个字节 // 注意这里STT是0因为重复起始条件已经在前面发出了。 // 等待接收数据就绪标志 RRDY while((I2caRegs.I2CSTR.bit.RRDY 0) (timeout--)); if(timeout0) return 0; *data I2caRegs.I2CDRR; // 读取数据 I2caRegs.I2CSTR.bit.RRDY 1; // 清除接收就绪标志 // 等待停止条件完成总线空闲 while((I2caRegs.I2CSTR.bit.BB 1) (timeout--)); if(timeout0) return 0; return 1; }5.3 重复起始与NACK/STP的联合配置这是读操作中最精妙也最容易出错的地方。对于单字节读取NACK因为只读一个字节主机在接收到这个字节后需要回复一个NACK非应答信号告诉从机“不要再发数据了”。STP接收完成后主机需要产生停止条件来结束本次传输。在2803x中我们可以通过I2CMDR寄存器中的NACK和STP位让硬件自动完成这些操作。如上面代码所示在接收数据前将I2CMDR配置为NACK1和STP1。这样当硬件接收到一个字节后会自动在SDA上产生NACK信号紧接着产生停止条件。这极大地简化了软件流程。重要提示NACK和STP位是“命令位”它们的作用是指示模块在本次传输的最后一个字节后执行相应动作。因此它们必须在接收最后一个字节之前就配置好。对于多字节读取只有在接收倒数第二个字节后才需要将NACK和STP位置1以在接收最后一个字节后结束传输。6. 调试实战当通信失败时我们该如何排查即使代码逻辑正确在实际硬件调试中I2C通信仍然可能失败。这时示波器或逻辑分析仪和寄存器状态是你最好的朋友。6.1 使用示波器抓取波形将示波器的两个通道分别连接到SDA和SCL线设置为单次触发触发条件为SCL下降沿或SDA下降沿起始条件。执行一次I2C操作抓取完整的波形。对照检查以下关键点起始条件SSCL高电平期间SDA是否有从高到低的跳变幅度和电压是否正常3.3V地址字节起始条件后第一个8位数据7位地址1位读写是否正确波形是否干净没有明显的毛刺或振铃应答位ACK每个字节包括地址和数据后的第9个时钟周期SDA是否被从机拉低ACK如果为高NACK说明从机未应答。数据字节发送或接收的数据位是否正确重复起始条件Sr在读操作中在停止条件之前是否有第二个起始条件SCL高SDA从高到低停止条件PSCL高电平期间SDA是否有从低到高的跳变时钟频率测量SCL周期计算频率是否与你配置的100kHz或400kHz相符上升/下降时间SDA和SCL的上升沿是否陡峭缓慢的上升沿可能是上拉电阻过大或总线电容过大的表现。6.2 通过寄存器状态诊断问题如果波形异常或者程序卡在某个等待循环就需要查询I2CSTR状态寄存器。程序卡在while(!I2CSTR.bit.ARDY)检查BB总线忙位。如果BB0说明总线根本就没启动可能是起始条件发送失败。检查STT位是否已置位SCL/SDA引脚配置是否正确外部上拉电阻是否接好。如果BB1但ARDY一直不来可能是总线被锁定了。检查SCL线是否被意外拉低比如某个从机故障。这时可以尝试通过软件复位I2C模块IRS0再IRS1或者尝试发送停止条件 (STP1) 来强制释放总线。程序因NACK标志失败这是最常见的问题。首先用示波器确认发送的7位地址是否正确。确认从机设备是否已上电电源电压是否正常。确认从机设备的地址引脚如A0, A1, A2电平配置是否与代码中地址匹配。总线上是否有多个地址相同的设备导致冲突SDA/SCL的上拉电阻是否合适电阻太大导致上升沿太慢也可能导致从机采样失败。数据错误检查I2CDXR写入的数据和I2CDRR读出的数据。在发送端检查XRDY发送就绪标志确保数据已移入移位寄存器。在接收端检查RRDY接收就绪标志确保数据已从移位寄存器移到I2CDRR。6.3 软件复位与总线恢复当总线死锁SCL被拉低时需要强制恢复。2803x提供了非标准的总线清除流程。将I2CMDR寄存器中的BC位设置为1总线清除模式STP位设置为1。将I2CMDR寄存器中的IRS位清零模块复位。将 SCL 和 SDA GPIO 暂时配置为数字输出并循环产生时钟脉冲先拉低再拉高SCL同时检测SDA状态直到SDA被释放为高。恢复GPIO的I2C功能重新初始化I2C模块IRS1。这是一个底层的硬件恢复操作代码略复杂但作为开发者需要知道有这么一个“救命”的后手。7. 中断驱动与FIFO使用提升效率的高级玩法轮询方式简单但占用CPU。对于频繁或大数据量的I2C操作使用中断和FIFO是更好的选择。7.1 中断配置2803x的I2C模块有丰富的中断事件最常用的是ARDY寄存器访问就绪和RRDY/XRDY接收/发送就绪。我们可以让这些事件触发CPU中断在中断服务程序ISR中处理数据。// 初始化中断 EALLOW; PieVectTable.I2CINT1A I2CA_ISR; // 将I2C-A的中断服务函数地址填入向量表 EDIS; I2caRegs.I2CIER.all 0x0000; // 先禁用所有中断 I2caRegs.I2CIER.bit.ARDY 1; // 使能ARDY中断 I2caRegs.I2CIER.bit.RRDY 1; // 使能接收就绪中断 // I2caRegs.I2CIER.bit.XRDY 1; // 使能发送就绪中断根据需要 PieCtrlRegs.PIEIER8.bit.INTx1 1; // 使能PIE组8的第1个中断I2CINT1A IER | M_INT8; // 使能CPU第8组中断 EINT; // 全局开中断 // 在I2C发送或接收函数中启动传输后CPU就可以去做别的事了。 // 状态机推进和数据处理将在ISR中完成。中断服务程序需要做三件事判断中断源读取I2CISRC寄存器。执行相应的操作如读取I2CDRR或写入I2CDXR。清除中断标志I2CSTR中的标志位和PIE应答位。7.2 FIFO功能的使用2803x的I2C模块内置了发送和接收FIFO深度各为4字节。启用FIFO可以缓冲数据减少中断频率。// 启用并配置FIFO I2caRegs.I2CFFTX.all 0x0000; // 先禁用发送FIFO I2caRegs.I2CFFRX.all 0x0000; // 先禁用接收FIFO I2caRegs.I2CFFTX.bit.I2CFFEN 1; // 使能I2C FIFO模式 I2caRegs.I2CFFTX.bit.TXFFIL 0; // 发送FIFO中断级别当FIFO为空时产生中断 I2caRegs.I2CFFTX.bit.TXFFINTCLR 1; // 清除发送FIFO中断标志 I2caRegs.I2CFFTX.bit.TXFFIEN 1; // 使能发送FIFO中断 I2caRegs.I2CFFRX.bit.RXFFIL 3; // 接收FIFO中断级别当FIFO中有4个或更多数据时产生中断 I2caRegs.I2CFFRX.bit.RXFFINTCLR 1; // 清除接收FIFO中断标志 I2caRegs.I2CFFRX.bit.RXFFIEN 1; // 使能接收FIFO中断启用FIFO后数据的读写就变成了对I2CDXR和I2CDRR寄存器的连续访问硬件会自动管理FIFO指针。例如在发送多字节时你可以一次性向I2CDXR写入多个字节直到FIFO满硬件会按顺序发送。当发送FIFO空时会产生中断你可以在中断中继续填充数据。这特别适合与DMA配合进行大批量数据传输。从轮询到中断再到FIFO这是一个逐步优化CPU占用率和程序效率的过程。对于简单的单字节读写轮询足够对于连续的数据流中断FIFO是更专业的选择。理解状态机是基础掌握了它你就能驾驭2803x I2C模块的所有高级功能。