AM62L I2C排空机制详解:解决FIFO阈值不整除的数据传输难题

1. 项目概述与核心价值

在嵌入式开发中,I2C总线是连接传感器、EEPROM、RTC等外设的“血管”。我们通常关注地址、时序和速率,但一个更隐蔽、却直接影响通信稳定性的问题常常被忽略:当一次传输的数据量,无法被FIFO的触发阈值整除时,最后那“零头”数据怎么办?如果处理不当,轻则数据丢失,重则触发硬件错误,导致整个通信链路挂起。这正是AM62L Sitara™处理器中I2C模块的“排空机制”所要解决的核心痛点。

想象一下,你有一个深度为8的RX FIFO,并设置当FIFO中有4个数据(RXTRSH=3)时,才触发中断或DMA请求来批量读取。现在你需要接收13个字节的数据。前12个字节(3个完整的阈值周期)可以顺利处理,但最后那1个字节,它永远无法凑够4个来触发阈值中断。如果没有排空机制,这个字节就会永远“卡”在FIFO里,DMA控制器可能会因为尝试读取不存在的第4个数据而报错,系统只能干等着超时。

AM62L的I2C排空机制,就是为解决这个“零头”问题而生的。它通过在传输结束时,检测FIFO中剩余数据量是否低于阈值,并主动产生一个“排空中断”(RDR或XDR),来通知CPU:“伙计,还有点儿‘尾货’,快来手动处理一下。” 这个机制的价值在于,它将数据传输的完整性从“必须对齐阈值”的硬性约束中解放出来,允许我们进行任意长度的传输,同时保证了DMA等高效传输方式在边界情况下的可靠性。对于需要频繁、不定长读取传感器数据,或与复杂I2C设备通信的嵌入式应用来说,理解和正确配置排空机制,是从“功能实现”到“稳定可靠”的关键一步。

2. I2C排空机制深度解析

2.1 排空机制的工作原理:从“阈值触发”到“零头处理”

要理解排空,首先要理解AM62L I2C模块的标准工作流程。其核心是阈值触发机制。无论是接收(RX)还是发送(TX),你都需要在I2C_BUF寄存器中设置一个阈值(Threshold)。

  • 接收阈值 (RXTRSH): 设置在I2C_BUF[13:8]位域。当RX FIFO中的数据量大于或等于(RXTRSH + 1)时,硬件会置位RRDY(接收就绪)状态位。如果使能了中断(RRDY_IE),则会触发中断;如果使能了DMA,则会发出DMA请求。此时,主机(LH)或DMA控制器应当一次性读取(RXTRSH + 1)个数据。
  • 发送阈值 (TXTRSH): 设置在I2C_BUF[5:0]位域。当TX FIFO中的空闲位置大于或等于(TXTRSH + 1)时,硬件会置位XRDY(发送就绪)状态位。同理,这会触发中断或DMA请求,主机或DMA应当一次性写入(TXTRSH + 1)个数据。

这种批处理方式效率很高。但问题来了:如果总传输长度N不是(Threshold + 1)的整数倍呢?设阈值为3(即触发深度为4),传输长度N=10。那么前8个数据(2个完整周期)由阈值机制处理,最后2个数据(10 mod 4 = 2)就成为了“零头”。

排空机制正是在这个“零头”场景下激活的。它的工作逻辑如下:

  1. 条件检测:在传输即将结束时(例如,接收到STOP信号,或发送计数器DCOUNT减到0),硬件会检查FIFO状态。
  2. 状态判断
    • 接收模式:检查RX FIFO中是否还有数据,且数据量小于(RXTRSH + 1)。如果是,则置位RDR(接收排空就绪)状态位。
    • 发送模式:检查是否还有数据需要写入TX FIFO才能完成传输,且剩余待写入量小于(TXTRSH + 1)。如果是,则置位XDR(发送排空就绪)状态位。
  3. 中断触发:如果使能了对应的排空中断使能位(RDR_IEXDR_IE),则会产生中断。
  4. 信息提供:同时,I2C_BUFSTAT寄存器中的RXSTAT(接收状态)或TXSTAT(发送状态)位域会更新,其值精确等于FIFO中剩余的、需要处理的数据字节数。
  5. 主机响应:主机在中断服务程序(ISR)或轮询中,读取RXSTAT/TXSTAT的值(假设为M),然后执行M次精确的数据读取或写入操作,从而将FIFO“排空”,完成整个传输。

注意:排空机制默认是关闭的。它仅在你明确使能了RDR_IEXDR_IE中断,传输长度不是阈值的整数倍时才会生效。如果传输长度恰好是阈值的整数倍,则不会触发排空中断,完全由标准的RRDY/XRDY机制处理。

2.2 关键寄存器详解与配置要点

排空机制的实现依赖于几个核心寄存器,理解它们的每一位都至关重要。

1. I2C_BUF (Buffer Configuration Register) - 阈值设置这是排空机制的“标尺”。配置错误会导致排空机制无法按预期工作。

// 假设我们希望当RX FIFO中有4个数据时触发中断/DMA,当TX FIFO有4个空位时触发。 // 则:RXTRSH = 4 - 1 = 3, TXTRSH = 4 - 1 = 3 #define RX_THRESHOLD 3 // 对应触发深度为4 #define TX_THRESHOLD 3 // 配置I2C_BUF寄存器 // RXTRSH占据位[13:8], TXTRSH占据位[5:0] uint32_t bufConfig = (RX_THRESHOLD << 8) | TX_THRESHOLD; HWREG(I2Cx_BASE + I2C_BUF) = bufConfig;

配置心得:阈值的设置需要权衡。设置太小(如1),中断/请求过于频繁,CPU开销大;设置太大,数据延迟增加。通常根据单次传输的典型数据量和系统实时性要求来定。对于AM62L,FIFO深度是固定的(需查数据手册,通常为8或16),阈值必须小于FIFO深度。

2. I2C_BUFSTAT (Buffer Status Register) - 状态读取这是排空阶段的“导航仪”。当RDRXDR中断发生时,必须读取此寄存器以获取剩余数据量。

// 在RDR中断服务程序中 uint32_t bufStatus = HWREG(I2Cx_BASE + I2C_BUFSTAT); uint8_t remainingRxBytes = (bufStatus >> 8) & 0x3F; // 提取RXSTAT[13:8] // 在XDR中断服务程序中 uint8_t remainingTxSpace = bufStatus & 0x3F; // 提取TXSTAT[5:0]

关键点RXSTATTXSTAT的值是动态的,仅在排空场景下为我们提供准确的剩余量。在标准阈值中断下,它们通常等于你设置的阈值。

3. I2C_IRQENABLE_SET / I2C_IRQSTATUS_RAW - 中断控制这是排空机制的“开关”和“指示灯”。

// 使能接收排空中断(RDR)和发送排空中断(XDR) HWREG(I2Cx_BASE + I2C_IRQENABLE_SET) = (1 << 13) | (1 << 14); // 置位RDR_IE和XDR_IE位 // 在中断服务程序中,读取原始中断状态以判断来源 uint32_t irqStatus = HWREG(I2Cx_BASE + I2C_IRQSTATUS_RAW); if (irqStatus & (1 << 13)) { // 处理RDR排空中断 // ... 读取I2C_BUFSTAT.RXSTAT并读取剩余数据 } if (irqStatus & (1 << 14)) { // 处理XDR排空中断 // ... 读取I2C_BUFSTAT.TXSTAT并写入剩余数据 } // 清除中断标志:向I2C_IRQSTATUS寄存器的对应位写1 HWREG(I2Cx_BASE + I2C_IRQSTATUS) = (1 << 13) | (1 << 14);

避坑指南:务必区分I2C_IRQSTATUS_RAW(只读,反映原始状态)和I2C_IRQSTATUS(写1清除)。清除中断标志的标准做法是向I2C_IRQSTATUS的对应位写1,而不是直接操作IRQSTATUS_RAW

4. I2C_CNT (Data Count Register) - 传输长度排空机制与DCOUNT寄存器紧密相关。硬件内部会计算DCOUNT mod (Threshold + 1)来判断是否需要排空。因此,在控制器模式下发起传输前,必须正确设置DCOUNT

// 设置本次传输的总数据长度为13字节 HWREG(I2Cx_BASE + I2C_CNT) = 13;

2.3 排空机制与DMA、AERR的关联

排空机制的一个核心设计目标,就是避免DMA访问错误(AERR)

  • AERR是什么?访问错误中断。当你在RX FIFO为空时尝试读取I2C_DATA寄存器,或在TX FIFO已满时尝试写入I2C_DATA寄存器,AERR位就会被置位。
  • DMA模式下的风险:假设你设置DMA传输长度为4(与阈值匹配),但最后一次传输实际只剩1个数据在FIFO里。DMA控制器会试图读取4次,后3次读取将面对空的FIFO,从而连续触发AERR。
  • 排空机制的救赎:当排空中断(RDR/XDR)触发时,它明确告知主机:“这是最后一次数据搬运了,数量是RXSTAT/TXSTAT,不是完整的阈值。” 在DMA模式下,这意味着你需要在排空中断里,重新配置DMA的传输长度为剩余字节数,然后启动这次特殊的DMA传输。在非DMA(轮询或中断)模式下,你只需要手动进行相应次数的读写操作。

因此,一个健壮的I2C驱动,必须处理以下中断:RRDY/XRDY(阈值触发)、RDR/XDR(排空触发)、AERR(错误处理)。排空机制是预防AERR的重要手段,而非补救措施。

3. AM62L I2C排空编程实践

理论清晰后,我们进入实战环节。以下将以AM62L的I2C控制器模式为例,展示一个包含排空处理的完整驱动流程。我们假设使用轮询(Polling)方式,以便更清晰地展示状态判断流程。

3.1 初始化与配置

在使能I2C控制器之前,必须完成一系列关键配置。这不仅是排空的基础,也是整个I2C通信的基石。

// 1. 使能模块时钟(此处为伪代码,具体取决于AM62L的时钟控制系统) enable_i2c_module_clock(I2C0); // 2. 配置预分频器,获得约12MHz的内部采样时钟 // 假设系统功能时钟SYS_CLK为200MHz,需要分频到~12MHz: 200/12 ≈ 16.67 // I2C_PSC = (SYS_CLK / 内部采样时钟) - 1 uint16_t psc_value = (200000000 / 12000000) - 1; // 计算结果约为15 HWREG(I2Cx_BASE + I2C_PSC) = psc_value & 0xFF; // 3. 配置标准模式(100kbps或400kbps)的SCL高低电平时间 // 内部采样时钟周期 t_clk = 1/12MHz ≈ 83.33ns // 对于100kbps,SCL周期为10us。需要计算高电平和低电平各占多少t_clk。 // 假设目标为100kbps,且高低电平时间相等(占空比50%): // SCLH = SCLL = (10us / 2) / 83.33ns ≈ 60 uint8_t sclh_scll_value = 60; HWREG(I2Cx_BASE + I2C_SCLL) = sclh_scll_value; HWREG(I2Cx_BASE + I2C_SCLH) = sclh_scll_value; // 4. (可选)配置高速模式(HS mode)参数,此处略过 // 5. 配置自身地址(OA寄存器),假设为0x50 HWREG(I2Cx_BASE + I2C_OA) = 0x50; // 6. 【排空关键配置】设置FIFO阈值。假设FIFO深度为8,我们设置阈值为3(即4个数据触发) #define FIFO_TRIGGER_DEPTH 4 #define TRSH_VALUE (FIFO_TRIGGER_DEPTH - 1) // 值为3 uint32_t bufConfig = (TRSH_VALUE << 8) | TRSH_VALUE; // RXTRSH=3, TXTRSH=3 HWREG(I2Cx_BASE + I2C_BUF) = bufConfig; // 7. 使能I2C控制器模块(将其带出复位状态) uint32_t conReg = HWREG(I2Cx_BASE + I2C_CON); conReg |= (1 << 15); // 设置I2C_EN位为1 HWREG(I2Cx_BASE + I2C_CON) = conReg;

3.2 控制器发送模式(含排空处理)流程实现

我们实现一个发送N字节数据的函数,它需要处理标准的XRDY中断和排空XDR中断。

/** * @brief 通过I2C控制器模式发送数据(轮询方式) * @param slaveAddr 从设备地址 * @param data 待发送数据指针 * @param len 待发送数据长度(字节) * @return 0成功,其他为错误码 */ int i2c_controller_transmit(uint16_t slaveAddr, uint8_t *data, uint32_t len) { // 0. 等待总线空闲 while (HWREG(I2Cx_BASE + I2C_IRQSTATUS_RAW) & (1 << 12)) { // 检查BB位 // 可加入超时机制 } // 1. 初始化控制器:配置为主机、发送模式 uint32_t conReg = 0; conReg |= (1 << 15); // I2C_EN = 1 conReg |= (1 << 10); // MST = 1, 控制器模式 conReg |= (1 << 9); // TRX = 1, 发送模式 // 根据需求配置其他位,如10位地址模式等 HWREG(I2Cx_BASE + I2C_CON) = conReg; // 2. 配置目标从机地址和传输数据长度 HWREG(I2Cx_BASE + I2C_SA) = slaveAddr & 0x3FF; // 10位地址支持 HWREG(I2Cx_BASE + I2C_CNT) = len; // 关键:设置总长度,用于内部计算排空 // 3. 【排空关键配置】使能发送排空中断(XDR) HWREG(I2Cx_BASE + I2C_IRQENABLE_SET) = (1 << 14); // 置位XDR_IE // 4. 启动传输:产生START条件 conReg = HWREG(I2Cx_BASE + I2C_CON); conReg |= (1 << 0); // 设置STT位为1 HWREG(I2Cx_BASE + I2C_CON) = conReg; uint32_t dataIndex = 0; uint32_t threshold = FIFO_TRIGGER_DEPTH; // 4 // 5. 主循环:处理数据发送 while (dataIndex < len) { uint32_t irqStatus = HWREG(I2Cx_BASE + I2C_IRQSTATUS_RAW); // 检查错误中断 if (irqStatus & (1 << 1)) { // NACK // 处理无应答错误 clear_interrupt(1); return -1; } if (irqStatus & (1 << 0)) { // AL // 处理仲裁丢失 clear_interrupt(0); return -2; } if (irqStatus & (1 << 7)) { // AERR // 发生了访问错误,这通常意味着编程逻辑有误,比如在FIFO满时写 clear_interrupt(7); return -3; } // 情况A: 标准阈值触发 - XRDY if (irqStatus & (1 << 4)) { // 计算本次能写入的最大数据量 uint32_t bytesToWrite = len - dataIndex; if (bytesToWrite > threshold) { bytesToWrite = threshold; } // 向TX FIFO写入bytesToWrite个数据 for (uint32_t i = 0; i < bytesToWrite; i++) { HWREG(I2Cx_BASE + I2C_DATA) = data[dataIndex++]; } clear_interrupt(4); // 清除XRDY中断标志 } // 情况B: 排空触发 - XDR 【核心排空处理逻辑】 if (irqStatus & (1 << 14)) { // 1. 读取TXSTAT,获取TX FIFO中剩余的空闲位置(即还需要写入多少字节) uint32_t bufStat = HWREG(I2Cx_BASE + I2C_BUFSTAT); uint32_t remainingSpace = bufStat & 0x3F; // 提取TXSTAT[5:0] // 2. 将剩余的数据写入FIFO for (uint32_t i = 0; i < remainingSpace && dataIndex < len; i++) { HWREG(I2Cx_BASE + I2C_DATA) = data[dataIndex++]; } // 3. 清除XDR中断标志 clear_interrupt(14); // 4. 排空处理后,理论上dataIndex应等于len,循环结束 // 可以在此处设置STP位产生STOP条件,或等待其他逻辑 } // 情况C: 寄存器访问就绪 - ARDY (可选,用于检查状态) if (irqStatus & (1 << 2)) { clear_interrupt(2); } } // 6. 所有数据已放入FIFO,等待传输完成并产生STOP // 设置STP位,产生STOP条件 uint32_t conRegFinal = HWREG(I2Cx_BASE + I2C_CON); conRegFinal |= (1 << 1); // 设置STP位为1 HWREG(I2Cx_BASE + I2C_CON) = conRegFinal; // 等待STOP完成(例如,等待ARDY中断或总线空闲) while (!(HWREG(I2Cx_BASE + I2C_IRQSTATUS_RAW) & (1 << 2))) { // 等待ARDY // 超时处理 } clear_interrupt(2); // 7. 禁用排空中断(可选,根据应用设计) HWREG(I2Cx_BASE + I2C_IRQENABLE_CLR) = (1 << 14); return 0; // 成功 } // 清除中断标志的辅助函数 void clear_interrupt(uint8_t bitPos) { HWREG(I2Cx_BASE + I2C_IRQSTATUS) = (1 << bitPos); }

代码逻辑解读: 这个函数的核心循环不断检查中断状态。大部分数据通过XRDY(阈值触发)批量写入。当剩余数据量不足一个阈值(4字节)时,硬件会触发XDR中断。此时,我们不再写入threshold个数据,而是读取TXSTAT,获得精确的剩余待写入量(例如,最后1个或2个字节),进行精确写入,从而优雅地完成整个传输。

3.3 控制器接收模式(含排空处理)流程实现

接收模式是排空机制更常见的应用场景,因为从传感器读取的数据长度往往不是固定的。

/** * @brief 通过I2C控制器模式接收数据(轮询方式) * @param slaveAddr 从设备地址 * @param buffer 接收数据缓冲区 * @param len 期望接收的数据长度(字节) * @return 实际接收到的字节数,或错误码 */ int i2c_controller_receive(uint16_t slaveAddr, uint8_t *buffer, uint32_t len) { // 0. 等待总线空闲 while (HWREG(I2Cx_BASE + I2C_IRQSTATUS_RAW) & (1 << 12)) { // 超时处理 } // 1. 初始化控制器:配置为主机、接收模式 uint32_t conReg = 0; conReg |= (1 << 15); // I2C_EN conReg |= (1 << 10); // MST conReg |= (0 << 9); // TRX = 0, 接收模式 HWREG(I2Cx_BASE + I2C_CON) = conReg; // 2. 配置从机地址和数据长度 HWREG(I2Cx_BASE + I2C_SA) = slaveAddr; HWREG(I2Cx_BASE + I2C_CNT) = len; // 3. 【排空关键配置】使能接收排空中断(RDR) HWREG(I2Cx_BASE + I2C_IRQENABLE_SET) = (1 << 13); // 置位RDR_IE // 4. 启动传输 conReg = HWREG(I2Cx_BASE + I2C_CON); conReg |= (1 << 0); // STT HWREG(I2Cx_BASE + I2C_CON) = conReg; uint32_t dataIndex = 0; uint32_t threshold = FIFO_TRIGGER_DEPTH; // 4 // 5. 主循环:处理数据接收 while (dataIndex < len) { uint32_t irqStatus = HWREG(I2Cx_BASE + I2C_IRQSTATUS_RAW); // 错误处理(同上,略) if (irqStatus & ((1<<1)|(1<<0)|(1<<7))) { // ... 错误处理与清理 return -1; } // 情况A: 标准阈值触发 - RRDY if (irqStatus & (1 << 3)) { // 一次性读取threshold个数据 for (uint32_t i = 0; i < threshold && dataIndex < len; i++) { buffer[dataIndex++] = HWREG(I2Cx_BASE + I2C_DATA); } clear_interrupt(3); // 清除RRDY } // 情况B: 排空触发 - RDR 【核心排空处理逻辑】 if (irqStatus & (1 << 13)) { // 1. 读取RXSTAT,获取RX FIFO中剩余的数据字节数 uint32_t bufStat = HWREG(I2Cx_BASE + I2C_BUFSTAT); uint32_t remainingData = (bufStat >> 8) & 0x3F; // 提取RXSTAT[13:8] // 2. 将剩余的数据从FIFO中读出 for (uint32_t i = 0; i < remainingData && dataIndex < len; i++) { buffer[dataIndex++] = HWREG(I2Cx_BASE + I2C_DATA); } // 3. 清除RDR中断标志 clear_interrupt(13); // 4. 排空处理后,接收完成 // 注意:RDR中断意味着传输已结束(STOP已收到),FIFO中只剩这点“零头” } // 情况C: 寄存器访问就绪 - ARDY if (irqStatus & (1 << 2)) { clear_interrupt(2); } } // 6. 产生STOP条件(对于接收,主机需要在读取最后一个字节后发送NACK和STOP) // 在AM62L中,当DCOUNT减为0时,硬件可能会自动处理。这里我们显式设置STP。 // 更严谨的做法是等待传输完成(ARDY)后再发STOP。 while (!(HWREG(I2Cx_BASE + I2C_IRQSTATUS_RAW) & (1 << 2))) { // 等待最后一次ARDY,表示计数结束 } clear_interrupt(2); conReg = HWREG(I2Cx_BASE + I2C_CON); conReg |= (1 << 1); // STP HWREG(I2Cx_BASE + I2C_CON) = conReg; // 7. 等待STOP完成 while (!(HWREG(I2Cx_BASE + I2C_IRQSTATUS_RAW) & (1 << 2))) { } clear_interrupt(2); HWREG(I2Cx_BASE + I2C_IRQENABLE_CLR) = (1 << 13); // 禁用RDR_IE return dataIndex; // 返回实际接收的字节数 }

实操心得:在接收模式下,RDR中断的触发时机非常关键。它不仅仅在传输长度非阈值整数倍时触发,更重要的是,它标志着总线上的传输已经结束(例如,主机已发送STOP位)。因此,在RDR中断处理程序中读取完剩余数据后,本次接收事务实质上已经完成,后续主要是清理和总线控制权释放的工作。

4. 排空机制的高级应用与故障排查

4.1 与DMA控制器协同工作

在追求极致效率的场景下,我们会使用DMA来搬运I2C数据。排空机制与DMA的配合是难点,也是体现其价值的地方。

配置流程:

  1. 初始化DMA:为I2C的XRDY/RRDY请求配置DMA通道,设置初始传输长度为一个阈值周期(例如4字节)。
  2. 使能排空中断:使能XDR_IERDR_IE,但将排空中断映射到DMA。
  3. DMA传输循环:DMA根据XRDY/RRDY请求自动搬运数据。
  4. 排空中断处理:当排空中断发生时,意味着DMA配置的传输长度(阈值)与实际剩余数据量不匹配。
    • 在ISR中:读取TXSTAT/RXSTAT,获得精确的剩余字节数M
    • 重新配置DMA:停止当前DMA通道,将其传输长度重新配置为M,然后重新启动DMA。
    • 单次触发:这次DMA传输完成后,整个I2C传输也随之结束。

关键点:你需要确保DMA控制器支持在传输过程中动态改变传输长度(Transfer Size)。许多DMA控制器(如AM62L的EDMA)支持通过链接机制或手动更新参数来实现这一点。排空中断ISR的执行必须非常快,以免影响总线时序。

4.2 常见问题与排查技巧实录

在实际调试中,排空机制相关的问题往往比较隐蔽。下面是一个常见问题速查表:

问题现象可能原因排查步骤与解决方案
根本未触发排空中断1. 排空中断未使能 (RDR_IE/XDR_IE=0)。
2. 传输长度恰好是(Threshold+1)的整数倍。
3. 传输未正常结束(如被NACK终止)。
1. 检查I2C_IRQENABLE_SET寄存器。
2. 确认传输长度DCOUNT。计算DCOUNT % (Threshold+1)是否不为0。
3. 检查NACK中断是否被触发。确保从设备地址和通信正常。
排空中断触发,但RXSTAT/TXSTAT为01. 读取状态寄存器时机不对,数据已被其他机制(如DMA)取走。
2. 中断标志清除过早。
1. 在排空中断ISR的最开头立即读取I2C_BUFSTAT
2. 确保先读取状态、处理数据,最后才清除RDR/XDR中断标志。
触发AERR(访问错误)中断1. 在排空场景下,仍按阈值数量访问FIFO。
2. DMA传输长度未在排空时调整,导致对空FIFO读或满FIFO写。
1. 检查排空中断处理逻辑,必须使用RXSTAT/TXSTAT的值作为访问次数。
2. 在DMA模式下,必须在排空ISR中修正DMA传输长度。检查DMA配置代码。
数据丢失或重复1.RDR/XDRRRDY/XRDY中断处理逻辑冲突或重复计数。
2. 在排空中断后,又错误处理了一次阈值中断。
1. 理清状态机:RRDY/XRDY用于“批量搬运”,RDR/XDR用于“收尾”。两者是互斥的。
2. 在排空处理后,应确保全局数据索引已等于总长度,并退出数据搬运循环。
总线锁死或SCL被拉低发生了接收溢出(ROVR)或发送欠载(XUDF),且未正确处理。1. 检查ROVRXUDF中断状态。使能并处理这些错误中断。
2. 接收溢出通常因主机读取太慢;发送欠载因主机写入太慢。优化代码或使用DMA。
3. 发生错误后,可能需要软件复位I2C模块或重新初始化。

深度避坑技巧

  • 状态机设计:将I2C数据传输视为一个状态机。RRDY/XRDY是“批量状态”,RDR/XDR是“结束状态”。设计清晰的switch-case或标志位来区分,避免逻辑交叉。
  • 调试利器:寄存器快照:在关键中断入口处(如RDRISR开头),将I2C_CNTI2C_BUFSTATI2C_IRQSTATUS_RAW等寄存器值打印或保存下来。这能帮你清晰看到触发排空时的精确上下文。
  • 模拟边界条件:主动测试边界情况。如果你的阈值是4,就专门测试传输长度为1、2、3、4、5、7、8、9字节的情况。观察中断触发顺序和数据完整性。
  • 关于I2C_CNT的妙用:手册提到,主机可以不读BUFSTAT,而是通过内部计算DCOUNT mod (Threshold+1)来获得剩余数量。这意味着,如果你在传输过程中持续更新一个本地的剩余字节计数器(初始值为DCOUNT,每处理一个阈值就减去Threshold+1),那么在排空中断时,这个本地计数器的值就等于需要处理的剩余字节数。这为某些不允许频繁读寄存器的超低功耗场景提供了另一种思路。

4.3 系统测试模式下的排空验证

AM62L的I2C模块提供了系统测试模式(System Test Mode),这对于在不连接外部物理设备的情况下验证排空逻辑极其有用,尤其是在开发初期。

如何进入测试模式:

  1. I2C_SYSTEST[15] ST_EN位设置为1。
  2. I2C_SYSTEST[13:12] TMODE设置为11(回环模式+SCL/SDA IO控制)。

在回环模式下验证排空:在控制器发送模式下,写入I2C_DATA的数据会通过内部路径回环到接收FIFO。你可以:

  1. 设置一个非阈值整数倍的传输长度(如13字节)。
  2. 使能XDR_IERDR_IE(虽然回环,但机制仍在)。
  3. 启动传输并观察中断触发情况。理论上,你会先看到XRDY中断(对应批量发送),然后是XDR中断(发送排空),接着由于数据回环,会看到RRDY中断(批量接收),最后是RDR中断(接收排空)。
  4. RDR中断中读取RXSTAT,并读取剩余数据,与发送的数据进行对比,可以完美验证整个排空数据路径的正确性。

这是一种非常安全的自检方法,能让你在硬件连接前就对排空逻辑充满信心。