1. 项目概述与核心价值在嵌入式开发中I2C总线是连接传感器、EEPROM、RTC等外设的“血管”。我们通常关注地址、时序和速率但一个更隐蔽、却直接影响通信稳定性的问题常常被忽略当一次传输的数据量无法被FIFO的触发阈值整除时最后那“零头”数据怎么办如果处理不当轻则数据丢失重则触发硬件错误导致整个通信链路挂起。这正是AM62L Sitara™处理器中I2C模块的“排空机制”所要解决的核心痛点。想象一下你有一个深度为8的RX FIFO并设置当FIFO中有4个数据RXTRSH3时才触发中断或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传输长度N10。那么前8个数据2个完整周期由阈值机制处理最后2个数据10 mod 4 2就成为了“零头”。排空机制正是在这个“零头”场景下激活的。它的工作逻辑如下条件检测在传输即将结束时例如接收到STOP信号或发送计数器DCOUNT减到0硬件会检查FIFO状态。状态判断接收模式检查RX FIFO中是否还有数据且数据量小于(RXTRSH 1)。如果是则置位RDR接收排空就绪状态位。发送模式检查是否还有数据需要写入TX FIFO才能完成传输且剩余待写入量小于(TXTRSH 1)。如果是则置位XDR发送排空就绪状态位。中断触发如果使能了对应的排空中断使能位RDR_IE或XDR_IE则会产生中断。信息提供同时I2C_BUFSTAT寄存器中的RXSTAT接收状态或TXSTAT发送状态位域会更新其值精确等于FIFO中剩余的、需要处理的数据字节数。主机响应主机在中断服务程序ISR或轮询中读取RXSTAT/TXSTAT的值假设为M然后执行M次精确的数据读取或写入操作从而将FIFO“排空”完成整个传输。注意排空机制默认是关闭的。它仅在你明确使能了RDR_IE或XDR_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开销大设置太大数据延迟增加。通常根据单次传输的典型数据量和系统实时性要求来定。对于AM62LFIFO深度是固定的需查数据手册通常为8或16阈值必须小于FIFO深度。2. I2C_BUFSTAT (Buffer Status Register) - 状态读取这是排空阶段的“导航仪”。当RDR或XDR中断发生时必须读取此寄存器以获取剩余数据量。// 在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]关键点RXSTAT和TXSTAT的值是动态的仅在排空场景下为我们提供准确的剩余量。在标准阈值中断下它们通常等于你设置的阈值。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 // 对于100kbpsSCL周期为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; // RXTRSH3, TXTRSH3 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 ((11)|(10)|(17))) { // ... 错误处理与清理 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的配合是难点也是体现其价值的地方。配置流程初始化DMA为I2C的XRDY/RRDY请求配置DMA通道设置初始传输长度为一个阈值周期例如4字节。使能排空中断使能XDR_IE或RDR_IE但不将排空中断映射到DMA。DMA传输循环DMA根据XRDY/RRDY请求自动搬运数据。排空中断处理当排空中断发生时意味着DMA配置的传输长度阈值与实际剩余数据量不匹配。在ISR中读取TXSTAT/RXSTAT获得精确的剩余字节数M。重新配置DMA停止当前DMA通道将其传输长度重新配置为M然后重新启动DMA。单次触发这次DMA传输完成后整个I2C传输也随之结束。关键点你需要确保DMA控制器支持在传输过程中动态改变传输长度Transfer Size。许多DMA控制器如AM62L的EDMA支持通过链接机制或手动更新参数来实现这一点。排空中断ISR的执行必须非常快以免影响总线时序。4.2 常见问题与排查技巧实录在实际调试中排空机制相关的问题往往比较隐蔽。下面是一个常见问题速查表问题现象可能原因排查步骤与解决方案根本未触发排空中断1. 排空中断未使能 (RDR_IE/XDR_IE0)。2. 传输长度恰好是(Threshold1)的整数倍。3. 传输未正常结束如被NACK终止。1. 检查I2C_IRQENABLE_SET寄存器。2. 确认传输长度DCOUNT。计算DCOUNT % (Threshold1)是否不为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/XDR和RRDY/XRDY中断处理逻辑冲突或重复计数。2. 在排空中断后又错误处理了一次阈值中断。1. 理清状态机RRDY/XRDY用于“批量搬运”RDR/XDR用于“收尾”。两者是互斥的。2. 在排空处理后应确保全局数据索引已等于总长度并退出数据搬运循环。总线锁死或SCL被拉低发生了接收溢出ROVR或发送欠载XUDF且未正确处理。1. 检查ROVR和XUDF中断状态。使能并处理这些错误中断。2. 接收溢出通常因主机读取太慢发送欠载因主机写入太慢。优化代码或使用DMA。3. 发生错误后可能需要软件复位I2C模块或重新初始化。深度避坑技巧状态机设计将I2C数据传输视为一个状态机。RRDY/XRDY是“批量状态”RDR/XDR是“结束状态”。设计清晰的switch-case或标志位来区分避免逻辑交叉。调试利器寄存器快照在关键中断入口处如RDRISR开头将I2C_CNT、I2C_BUFSTAT、I2C_IRQSTATUS_RAW等寄存器值打印或保存下来。这能帮你清晰看到触发排空时的精确上下文。模拟边界条件主动测试边界情况。如果你的阈值是4就专门测试传输长度为1、2、3、4、5、7、8、9字节的情况。观察中断触发顺序和数据完整性。关于I2C_CNT的妙用手册提到主机可以不读BUFSTAT而是通过内部计算DCOUNT mod (Threshold1)来获得剩余数量。这意味着如果你在传输过程中持续更新一个本地的剩余字节计数器初始值为DCOUNT每处理一个阈值就减去Threshold1那么在排空中断时这个本地计数器的值就等于需要处理的剩余字节数。这为某些不允许频繁读寄存器的超低功耗场景提供了另一种思路。4.3 系统测试模式下的排空验证AM62L的I2C模块提供了系统测试模式System Test Mode这对于在不连接外部物理设备的情况下验证排空逻辑极其有用尤其是在开发初期。如何进入测试模式将I2C_SYSTEST[15] ST_EN位设置为1。将I2C_SYSTEST[13:12] TMODE设置为11回环模式SCL/SDA IO控制。在回环模式下验证排空在控制器发送模式下写入I2C_DATA的数据会通过内部路径回环到接收FIFO。你可以设置一个非阈值整数倍的传输长度如13字节。使能XDR_IE和RDR_IE虽然回环但机制仍在。启动传输并观察中断触发情况。理论上你会先看到XRDY中断对应批量发送然后是XDR中断发送排空接着由于数据回环会看到RRDY中断批量接收最后是RDR中断接收排空。在RDR中断中读取RXSTAT并读取剩余数据与发送的数据进行对比可以完美验证整个排空数据路径的正确性。这是一种非常安全的自检方法能让你在硬件连接前就对排空逻辑充满信心。
AM62L I2C排空机制详解:解决FIFO阈值不整除的数据传输难题
1. 项目概述与核心价值在嵌入式开发中I2C总线是连接传感器、EEPROM、RTC等外设的“血管”。我们通常关注地址、时序和速率但一个更隐蔽、却直接影响通信稳定性的问题常常被忽略当一次传输的数据量无法被FIFO的触发阈值整除时最后那“零头”数据怎么办如果处理不当轻则数据丢失重则触发硬件错误导致整个通信链路挂起。这正是AM62L Sitara™处理器中I2C模块的“排空机制”所要解决的核心痛点。想象一下你有一个深度为8的RX FIFO并设置当FIFO中有4个数据RXTRSH3时才触发中断或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传输长度N10。那么前8个数据2个完整周期由阈值机制处理最后2个数据10 mod 4 2就成为了“零头”。排空机制正是在这个“零头”场景下激活的。它的工作逻辑如下条件检测在传输即将结束时例如接收到STOP信号或发送计数器DCOUNT减到0硬件会检查FIFO状态。状态判断接收模式检查RX FIFO中是否还有数据且数据量小于(RXTRSH 1)。如果是则置位RDR接收排空就绪状态位。发送模式检查是否还有数据需要写入TX FIFO才能完成传输且剩余待写入量小于(TXTRSH 1)。如果是则置位XDR发送排空就绪状态位。中断触发如果使能了对应的排空中断使能位RDR_IE或XDR_IE则会产生中断。信息提供同时I2C_BUFSTAT寄存器中的RXSTAT接收状态或TXSTAT发送状态位域会更新其值精确等于FIFO中剩余的、需要处理的数据字节数。主机响应主机在中断服务程序ISR或轮询中读取RXSTAT/TXSTAT的值假设为M然后执行M次精确的数据读取或写入操作从而将FIFO“排空”完成整个传输。注意排空机制默认是关闭的。它仅在你明确使能了RDR_IE或XDR_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开销大设置太大数据延迟增加。通常根据单次传输的典型数据量和系统实时性要求来定。对于AM62LFIFO深度是固定的需查数据手册通常为8或16阈值必须小于FIFO深度。2. I2C_BUFSTAT (Buffer Status Register) - 状态读取这是排空阶段的“导航仪”。当RDR或XDR中断发生时必须读取此寄存器以获取剩余数据量。// 在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]关键点RXSTAT和TXSTAT的值是动态的仅在排空场景下为我们提供准确的剩余量。在标准阈值中断下它们通常等于你设置的阈值。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 // 对于100kbpsSCL周期为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; // RXTRSH3, TXTRSH3 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 ((11)|(10)|(17))) { // ... 错误处理与清理 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的配合是难点也是体现其价值的地方。配置流程初始化DMA为I2C的XRDY/RRDY请求配置DMA通道设置初始传输长度为一个阈值周期例如4字节。使能排空中断使能XDR_IE或RDR_IE但不将排空中断映射到DMA。DMA传输循环DMA根据XRDY/RRDY请求自动搬运数据。排空中断处理当排空中断发生时意味着DMA配置的传输长度阈值与实际剩余数据量不匹配。在ISR中读取TXSTAT/RXSTAT获得精确的剩余字节数M。重新配置DMA停止当前DMA通道将其传输长度重新配置为M然后重新启动DMA。单次触发这次DMA传输完成后整个I2C传输也随之结束。关键点你需要确保DMA控制器支持在传输过程中动态改变传输长度Transfer Size。许多DMA控制器如AM62L的EDMA支持通过链接机制或手动更新参数来实现这一点。排空中断ISR的执行必须非常快以免影响总线时序。4.2 常见问题与排查技巧实录在实际调试中排空机制相关的问题往往比较隐蔽。下面是一个常见问题速查表问题现象可能原因排查步骤与解决方案根本未触发排空中断1. 排空中断未使能 (RDR_IE/XDR_IE0)。2. 传输长度恰好是(Threshold1)的整数倍。3. 传输未正常结束如被NACK终止。1. 检查I2C_IRQENABLE_SET寄存器。2. 确认传输长度DCOUNT。计算DCOUNT % (Threshold1)是否不为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/XDR和RRDY/XRDY中断处理逻辑冲突或重复计数。2. 在排空中断后又错误处理了一次阈值中断。1. 理清状态机RRDY/XRDY用于“批量搬运”RDR/XDR用于“收尾”。两者是互斥的。2. 在排空处理后应确保全局数据索引已等于总长度并退出数据搬运循环。总线锁死或SCL被拉低发生了接收溢出ROVR或发送欠载XUDF且未正确处理。1. 检查ROVR和XUDF中断状态。使能并处理这些错误中断。2. 接收溢出通常因主机读取太慢发送欠载因主机写入太慢。优化代码或使用DMA。3. 发生错误后可能需要软件复位I2C模块或重新初始化。深度避坑技巧状态机设计将I2C数据传输视为一个状态机。RRDY/XRDY是“批量状态”RDR/XDR是“结束状态”。设计清晰的switch-case或标志位来区分避免逻辑交叉。调试利器寄存器快照在关键中断入口处如RDRISR开头将I2C_CNT、I2C_BUFSTAT、I2C_IRQSTATUS_RAW等寄存器值打印或保存下来。这能帮你清晰看到触发排空时的精确上下文。模拟边界条件主动测试边界情况。如果你的阈值是4就专门测试传输长度为1、2、3、4、5、7、8、9字节的情况。观察中断触发顺序和数据完整性。关于I2C_CNT的妙用手册提到主机可以不读BUFSTAT而是通过内部计算DCOUNT mod (Threshold1)来获得剩余数量。这意味着如果你在传输过程中持续更新一个本地的剩余字节计数器初始值为DCOUNT每处理一个阈值就减去Threshold1那么在排空中断时这个本地计数器的值就等于需要处理的剩余字节数。这为某些不允许频繁读寄存器的超低功耗场景提供了另一种思路。4.3 系统测试模式下的排空验证AM62L的I2C模块提供了系统测试模式System Test Mode这对于在不连接外部物理设备的情况下验证排空逻辑极其有用尤其是在开发初期。如何进入测试模式将I2C_SYSTEST[15] ST_EN位设置为1。将I2C_SYSTEST[13:12] TMODE设置为11回环模式SCL/SDA IO控制。在回环模式下验证排空在控制器发送模式下写入I2C_DATA的数据会通过内部路径回环到接收FIFO。你可以设置一个非阈值整数倍的传输长度如13字节。使能XDR_IE和RDR_IE虽然回环但机制仍在。启动传输并观察中断触发情况。理论上你会先看到XRDY中断对应批量发送然后是XDR中断发送排空接着由于数据回环会看到RRDY中断批量接收最后是RDR中断接收排空。在RDR中断中读取RXSTAT并读取剩余数据与发送的数据进行对比可以完美验证整个排空数据路径的正确性。这是一种非常安全的自检方法能让你在硬件连接前就对排空逻辑充满信心。