Tiva C系列I2C μDMA突发传输原理与实战配置详解
2026/7/23 1:19:09 网站建设 项目流程

1. 项目概述与核心价值

在嵌入式系统开发中,I2C总线因其简洁的硬件连接和灵活的通信方式,成为了连接各类传感器、存储器和外设的“血管”。然而,当数据吞吐量增大或系统实时性要求提高时,传统的轮询或中断驱动数据传输方式往往会成为性能瓶颈,大量消耗宝贵的CPU周期。这时,硬件加速机制的价值就凸显出来了。本文将以德州仪器(TI)的Tiva™ C系列微控制器(如TM4C129)为蓝本,深入剖析其I2C模块与μDMA控制器协同工作的内部机制。核心在于理解如何利用μDMA的突发(BURST)传输模式,配合I2C模块内置的FIFO,实现一种“设置后不管”的高效数据传输流水线。这对于从事电机控制、多传感器数据融合、工业物联网网关等高实时性、高数据吞吐量嵌入式应用开发的工程师来说,是提升系统整体性能、优化CPU负载分配的关键技术。通过本文,你将不仅了解寄存器如何配置,更能洞悉dma_reqdma_sreq这两个关键硬件信号在后台如何“智能”调度数据流,从而设计出更高效、更可靠的通信驱动。

2. I2C与μDMA协同工作原理深度解析

要驾驭这套机制,首先得跳出单纯配置寄存器的层面,从系统架构的角度理解各个组件扮演的角色及其交互关系。我们可以把整个数据传输过程想象成一个现代化的物流仓库系统。

2.1 核心组件角色扮演

  • I2C主模块(Master):相当于物流公司的“调度中心”。它负责发起通信、产生时钟信号(SCL)、发送目标地址(从设备地址)并控制整个数据传输的流程(起始、停止、重复起始条件)。
  • Tx/Rx FIFO:这是位于I2C模块内部的“临时周转仓库”。Tx FIFO用于缓存待发送的数据,Rx FIFO用于缓存接收到的数据。其深度有限(通常是8级或16级),核心作用是平滑数据流,解决CPU/内存与相对低速的I2C总线之间的速度不匹配问题。
  • μDMA控制器:这是不知疲倦的“自动化搬运机器人”。它的任务是在CPU初始化设定好后,自动在系统内存(SRAM)和I2C的FIFO“仓库”之间搬运数据块,完全解放CPU。
  • I2CMBLEN寄存器:这是给“搬运机器人”μDMA下达的“本次搬运任务单”,指定了本次突发传输需要搬运的总字节数。
  • dma_sreq 与 dma_req 信号:这是FIFO“仓库”向“搬运机器人”μDMA发出的两种不同类型的“需求信号”。
    • dma_sreq(单次请求):相当于仓库管理员喊一声“来搬一箱货!”。只要FIFO非空(对于Rx)或非满(对于Tx),且达到了预设的触发水平,就会产生此请求,触发μDMA进行一次单次传输(通常是一个数据单元,如一个字节或一个字)。
    • dma_req(突发请求):相当于仓库管理员说“有一大批货到了,快来连续搬运!”。这是一个更高效的请求模式,通常在满足特定条件(如FIFO中的数据量达到一个较高的阈值,默认常为4字节)时发出,允许μDMA在一次响应中连续进行多次数据传输,最大化总线利用率。

2.2 协同工作流程总览

整个高效传输的链条始于软件的正确配置。首先,你需要配置μDMA通道,告诉它源地址(内存)、目标地址(I2C FIFO数据寄存器)和传输模式。然后,配置I2C模块,使能FIFO并设置触发水平。最关键的一步是,在启动I2C传输前,软件可以预先填充Tx FIFO(对于发送)或确保Rx FIFO为空(对于接收)。当你在I2C主控制状态寄存器(I2CMCS)中设置BURST位并启动传输后,硬件逻辑便自动接管:

  1. 对于主发送(Master Transmit):I2C模块开始发送地址帧。与此同时,Tx FIFO的填充状态被持续监控。一旦其数据量低于某个阈值,便会向μDMA发出请求(dma_sreqdma_req),μDMA随即响应,将内存中的数据块搬入Tx FIFO,确保在I2C总线需要发送下一个数据字节时,FIFO中总有数据可用,避免总线等待。
  2. 对于主接收(Master Receive):I2C模块接收到的数据会源源不断地存入Rx FIFO。当Rx FIFO中的数据量达到触发水平时,便会向μDMA发出请求,μDMA则及时将数据从Rx FIFO搬移到安全的内存缓冲区,防止FIFO溢出导致数据丢失。

这个过程完全由硬件并行处理,CPU仅在初始化和传输完成中断时介入,实现了极高的效率。

注意:一个常见的误解是认为使能BURST模式后,μDMA会无视FIFO状态一次性搬完I2CMBLEN指定的所有数据。实际上,μDMA的搬运节奏始终受FIFO的请求信号控制,BURST模式只是允许μDMA在收到dma_req时进行连续搬运,其总搬运量仍受I2CMBLEN限制。I2CMBCNT寄存器会递减计数,归零后请求停止,直到下一个BURST命令。

3. 关键寄存器配置与信号触发逻辑详解

理解了宏观流程,我们深入到寄存器配置和硬件信号产生的微观逻辑。这是驱动开发中最具挑战性的部分,配置不当极易导致数据传输不完整或硬件挂起。

3.1 主模块发送(Tx)时的μDMA请求逻辑

当主模块配置为发送且使能了BURST模式时,请求信号的产生逻辑是性能调优的关键。

// 假设配置步骤(基于TivaWare库风格伪代码) // 1. 配置I2C主时钟、GPIO等(略) // 2. 配置并启用μDMA通道,设置传输大小为8位(字节),目标地址为I2C FIFO数据寄存器 // 3. 配置I2C FIFO控制寄存器 (I2CFIFOCTL) HWREG(I2C0_BASE + I2C_O_FIFOCTL) = (TXTRIG_1 << I2C_FIFOCTL_TXTRIG_S) | // 设置Tx FIFO触发水平,例如1 I2C_FIFOCTL_TXFIFOEN; // 使能Tx FIFO // 4. 设置突发传输长度 (I2CMBLEN) HWREG(I2C0_BASE + I2C_O_MBLEN) = 64; // 本次突发传输总共需要发送64字节 // 5. (可选但推荐)在启动BURST前,预先填充一部分数据到Tx FIFO // 这可以避免总线启动后因FIFO空而产生的初始延迟 for(int i=0; i<4; i++) { HWREG(I2C0_BASE + I2C_O_FIFODATA) = tx_buffer[i]; } // 6. 写入从机地址,设置R/S位为0(发送) HWREG(I2C0_BASE + I2C_O_MSA) = (slave_addr << 1) | 0x0; // 7. 写入控制命令,启动带BURST的传输 // 假设使用START + BURST (无STOP),命令码根据表21-5为 0x40 (0100 0000b) HWREG(I2C0_BASE + I2C_O_MCS) = I2C_MCS_BURST | I2C_MCS_START;

此时,硬件逻辑开始工作:

  • 场景A:如果I2CMBLEN >= 4,并且Tx FIFO的填充水平低于设定的触发值(例如,我们设TXTRIG=1,表示FIFO非满时即可请求),那么在使能BURST的瞬间,如果FIFO是空的,dma_reqdma_sreq会同时置位dma_req的置位是因为满足了突发请求的条件(长度>=4且FIFO未满)。μDMA会优先响应突发请求,开始连续向FIFO填充数据。
  • 场景B:如果I2CMBLEN < 4,即使使能BURST,硬件也不会产生dma_req(因为不满足突发长度条件)。此时,仅当Tx FIFO非满且填充水平低于触发值时,dma_sreq会置位,触发μDMA进行单次传输。μDMA会通过单次请求,一次一个字节(或字)地搬运,直到累计搬运的字节数达到I2CMBLEN设定值。

3.2 主模块接收(Rx)时的μDMA请求逻辑

接收模式的逻辑与发送对称,但方向相反。

// 1. 配置I2C FIFO控制寄存器 (I2CFIFOCTL) 用于接收 HWREG(I2C0_BASE + I2C_O_FIFOCTL) = (RXTRIG_4 << I2C_FIFOCTL_RXTRIG_S) | // 设置Rx FIFO触发水平,例如4 I2C_FIFOCTL_RXFIFOEN; // 使能Rx FIFO // 2. 设置突发接收长度 (I2CMBLEN) HWREG(I2C0_BASE + I2C_O_MBLEN) = 128; // 准备接收128字节 // 3. 写入从机地址,设置R/S位为1(接收) HWREG(I2C0_BASE + I2C_O_MSA) = (slave_addr << 1) | 0x1; // 4. 写入控制命令,启动带BURST的接收 // 命令码需包含ACK=1(自动应答),例如 0x44 (0100 0100b) HWREG(I2C0_BASE + I2C_O_MCS) = I2C_MCS_BURST | I2C_MCS_START | I2C_MCS_ACK;

接收流程如下:

  1. 初始时Rx FIFO为空,无请求。
  2. 主I2C开始接收数据,从机返回的字节被存入Rx FIFO。
  3. 一旦Rx FIFO中有数据dma_sreq立即置位,通知μDMA“有货可取”。
  4. 如果Rx FIFO中累积的数据达到或超过4字节(这是dma_req触发的常见阈值),dma_req也会置位。μDMA将启动突发传输,快速将数据块从FIFO搬移到内存。
  5. μDMA持续工作,直到从Rx FIFO中累计取走的字节数等于I2CMBLEN中设定的值,随后请求信号撤销,传输暂停。

3.3 从模块的μDMA支持

从模块(Slave)同样可以利用μDMA和FIFO。其核心控制位在从控制状态寄存器(I2CSCSR)的TXFIFORXFIFO位。

  • 从发送:当主设备请求数据时,如果从设备的Tx FIFO未满且数据量低于触发水平,会产生dma_req请求μDMA填充数据;如果只是需要下一个字节,则产生dma_sreq
  • 从接收:当主设备发送数据到来时,存入从设备的Rx FIFO。一旦FIFO中有数据,就会产生dma_sreq;数据量超过触发水平则产生dma_req,请求μDMA将数据搬走。

重要实践建议:数据手册中的Note明确指出,应用程序不应在连续的事务中,在I2CSDR(从数据寄存器,非FIFO模式)和Tx FIFO之间切换使用,反之亦然。这意味着,一旦你决定在从设备上使用FIFO+DMA模式进行某个通信流程,就应该在整个流程中保持一致,混合使用可能会导致不可预测的数据错乱或状态机错误。在初始化从设备时,就应明确其工作模式。

4. 配置实战:从零构建一个完整的BURST传输示例

理论需要实践来巩固。让我们以一个具体的场景为例:使用TM4C129的I2C0作为主设备,以突发模式向一个I2C EEPROM(如AT24C256)连续写入128字节的数据。我们将使用μDMA通道0,并采用Ping-Pong缓冲模式以实现连续无间断传输。

4.1 系统初始化与外设使能

首先,必须正确启用相关外设的时钟,这是所有操作的基础。

#include <stdint.h> #include <stdbool.h> #include "inc/hw_memmap.h" #include "inc/hw_types.h" #include "driverlib/sysctl.h" #include "driverlib/gpio.h" #include "driverlib/pin_map.h" #include "driverlib/i2c.h" #include "driverlib/udma.h" void InitSystem(void) { // 1. 配置系统时钟,假设运行在120MHz SysCtlClockSet(SYSCTL_SYSDIV_2_5 | SYSCTL_USE_PLL | SYSCTL_OSC_MAIN | SYSCTL_XTAL_16MHZ); // 2. 使能I2C0和GPIOB(假设I2C0 SCL=PB2, SDA=PB3)的外设时钟 SysCtlPeripheralEnable(SYSCTL_PERIPH_I2C0); SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOB); // 等待外设就绪是良好习惯,避免访问未就绪的寄存器 while(!SysCtlPeripheralReady(SYSCTL_PERIPH_I2C0)); while(!SysCtlPeripheralReady(SYSCTL_PERIPH_GPIOB)); // 3. 使能μDMA控制器时钟 SysCtlPeripheralEnable(SYSCTL_PERIPH_UDMA); while(!SysCtlPeripheralReady(SYSCTL_PERIPH_UDMA)); // 4. 配置GPIOB的引脚2和3为I2C功能 GPIOPinConfigure(GPIO_PB2_I2C0SCL); GPIOPinConfigure(GPIO_PB3_I2C0SDA); GPIOPinTypeI2CSCL(GPIO_PORTB_BASE, GPIO_PIN_2); // 注意:SDA引脚必须配置为开漏输出 GPIOPinTypeI2C(GPIO_PORTB_BASE, GPIO_PIN_3); }

4.2 I2C主模块与FIFO配置

接下来,初始化I2C主模块,配置通信速率,并关键性地设置FIFO和突发传输参数。

void InitI2C0MasterForBurst(void) { // 1. 初始化I2C为主模式,主机使能 I2CMasterInitExpClk(I2C0_BASE, SysCtlClockGet(), false); // false表示不启用高速模式 // 也可以直接配置寄存器,更精细控制 // HWREG(I2C0_BASE + I2C_O_MCR) = 0x10; // Master Enable // 2. 配置I2C时钟:目标100kHz,系统时钟120MHz // TPR = (SysClk / (2 * (SCL_LP + SCL_HP) * SCL_CLK)) - 1 // 假设SCL_LP=6, SCL_HP=4, SCL_CLK=100000 // TPR = (120,000,000 / (2*10*100,000)) - 1 = (120,000,000 / 2,000,000) -1 = 60 -1 = 59 I2CMasterInitExpClk(I2C0_BASE, SysCtlClockGet(), false); // 或者使用库函数:I2CMasterInitExpClk已计算TPR,这里展示寄存器操作原理 // HWREG(I2C0_BASE + I2C_O_MTPR) = 59; // 3. 配置FIFO控制寄存器 (I2CFIFOCTL) // 使能Tx和Rx FIFO,设置触发水平。 // TXTRIG: 发送FIFO触发水平。设为1,表示FIFO非满时即可请求DMA(更激进,延迟小)。 // RXTRIG: 接收FIFO触发水平。设为4,表示FIFO中有4个或以上数据时,可触发突发请求。 uint32_t fifoCtlValue = 0; fifoCtlValue |= (1 << I2C_FIFOCTL_TXTRIG_S); // TXTRIG = 1 fifoCtlValue |= (4 << I2C_FIFOCTL_RXTRIG_S); // RXTRIG = 4 fifoCtlValue |= I2C_FIFOCTL_TXFIFOEN; // 使能Tx FIFO fifoCtlValue |= I2C_FIFOCTL_RXFIFOEN; // 使能Rx FIFO HWREG(I2C0_BASE + I2C_O_FIFOCTL) = fifoCtlValue; // 4. 设置突发传输长度寄存器 (I2CMBLEN) // 本次示例我们计划传输128字节。这个值决定了DMA传输的总量。 HWREG(I2C0_BASE + I2C_O_MBLEN) = 128; // 5. 清除可能存在的旧中断标志,并配置中断(如果需要) I2CMasterIntClear(I2C0_BASE); // 如果需要使用中断来检测传输完成或错误,在此使能相应中断 // HWREG(I2C0_BASE + I2C_O_MIMR) = I2C_MIMR_IM; // 使能主中断 }

4.3 μDMA通道配置(Ping-Pong模式)

这是实现高效连续传输的核心。Ping-Pong模式使用两个缓冲区(Ping和Pong),当DMA在操作其中一个缓冲区时,CPU可以准备或处理另一个缓冲区的数据,实现并行。

// 定义数据缓冲区 #define BURST_SIZE 128 uint8_t g_ui8PingBuffer[BURST_SIZE]; uint8_t g_ui8PongBuffer[BURST_SIZE]; volatile bool g_bPingBufferReady = true; volatile bool g_bPongBufferReady = true; void InitDMAForI2C0Tx(void) { // 1. 启用μDMA控制器,并设置控制表基地址 uDMAEnable(); uDMAControlBaseSet(&sDMAControlTable[0]); // sDMAControlTable需在链接脚本中分配 // 2. 配置DMA通道属性(假设使用通道0) // 设置通道为I2C0 Tx请求源,高优先级 uDMAChannelAssign(UDMA_CH0_I2C0TX); uDMAChannelAttributeEnable(UDMA_CH0_I2C0TX, UDMA_ATTR_HIGH_PRIORITY | UDMA_ATTR_USEBURST); // 3. 配置Ping缓冲区的传输控制结构 // 传输模式:基本模式(完成一次传输后停止,等待下次触发) // 源地址:Ping缓冲区地址(内存) // 目的地址:I2C0 FIFO数据寄存器 (I2C0_BASE + I2C_O_FIFODATA) // 传输大小:BURST_SIZE (128) 个数据项 // 数据项大小:8位(字节) // 源地址增量:每次传输后源地址+1(从内存数组顺序读取) // 目的地址增量:0(目的地址是固定寄存器,不递增) uDMAChannelControlSet(UDMA_CH0_I2C0TX | UDMA_PRI_SELECT, UDMA_SIZE_8 | UDMA_SRC_INC_8 | UDMA_DST_INC_NONE | UDMA_ARB_128); // 仲裁大小设为128,与传输总数一致 uDMAChannelTransferSet(UDMA_CH0_I2C0TX | UDMA_PRI_SELECT, UDMA_MODE_BASIC, g_ui8PingBuffer, (void*)(I2C0_BASE + I2C_O_FIFODATA), BURST_SIZE); // 4. 配置Pong缓冲区的传输控制结构(使用备用通道控制结构) // 模式、目的地址、数据项属性与Ping相同,仅源地址不同 uDMAChannelControlSet(UDMA_CH0_I2C0TX | UDMA_ALT_SELECT, UDMA_SIZE_8 | UDMA_SRC_INC_8 | UDMA_DST_INC_NONE | UDMA_ARB_128); uDMAChannelTransferSet(UDMA_CH0_I2C0TX | UDMA_ALT_SELECT, UDMA_MODE_BASIC, g_ui8PongBuffer, (void*)(I2C0_BASE + I2C_O_FIFODATA), BURST_SIZE); // 5. 使能DMA通道 uDMAChannelEnable(UDMA_CH0_I2C0TX); }

4.4 启动传输与主循环处理

配置完成后,启动第一次传输,并在主循环或中断中处理缓冲区切换。

void StartI2CBurstTransfer(uint8_t slaveAddr) { // 1. 准备Ping缓冲区数据(例如,要写入EEPROM的数据) for(int i=0; i<BURST_SIZE; i++) { g_ui8PingBuffer[i] = i; // 示例数据 } g_bPingBufferReady = true; g_bPongBufferReady = false; // Pong缓冲区尚未准备好 // 2. 写入从机地址(假设EEPROM写地址) // EEPROM写操作通常需要先发送设备地址(含写位)和内存地址高位、低位。 // 这里简化,假设slaveAddr已是包含写位的完整地址。 // 注意:在BURST模式下,对I2CMSA的写入可能影响状态机,需严格按流程。 // 通常先写地址,再配置DMA和FIFO,最后发START+BURST命令。 I2CMasterSlaveAddrSet(I2C0_BASE, slaveAddr, false); // false表示写 // 3. 确保Tx FIFO为空或预填充少量数据(可选,但可减少初始延迟) // 由于我们使用DMA自动填充,这里可以不预填。 // 4. 写入控制命令,启动BURST传输。 // 命令字: START=1, STOP=0, RUN=0, BURST=1, ACK=X (0), HS=0, QCMD=0 // 根据表21-5,对于Idle状态,START+BURST的命令码为 0x40 (0100 0000b) HWREG(I2C0_BASE + I2C_O_MCS) = I2C_MCS_START | I2C_MCS_BURST; // 注意:此时RUN位为0,BURST位为1,符合表中“0 1 0 X 0 0 1 0”的编码。 // 传输由硬件自动进行。DMA会根据FIFO的请求信号自动填充数据。 // I2C模块会发送START条件、从机地址,然后连续发送FIFO中的数据。 } // DMA传输完成中断服务程序(需在启动文件中关联) void DMA_IRQHandler(void) { uint32_t ui32Status = uDMAIntStatus(); uDMAIntClear(ui32Status); if(ui32Status & UDMA_INT_CH0) { // 通道0中断 // 检查是Ping还是Pong传输完成 uint32_t ui32Mode = uDMAChannelModeGet(UDMA_CH0_I2C0TX); // 实际中应检查通道控制结构的状态位,这里简化处理 static bool bIsPingActive = true; if(bIsPingActive) { // Ping传输完成 // 1. 可以处理Ping缓冲区数据(如果这是接收) // 2. 重新准备Ping缓冲区数据(对于下一次发送) PrepareNextData(g_ui8PingBuffer, BURST_SIZE); g_bPingBufferReady = true; // 3. 如果Pong缓冲区已就绪,立即切换到Pong传输 if(g_bPongBufferReady) { uDMAChannelTransferSet(UDMA_CH0_I2C0TX | UDMA_PRI_SELECT, UDMA_MODE_BASIC, g_ui8PingBuffer, (void*)(I2C0_BASE + I2C_O_FIFODATA), BURST_SIZE); // 注意:在BURST模式下,I2C传输可能仍在进行。 // 只需重新武装DMA通道,当FIFO再次需要数据时,DMA请求会自动触发。 uDMAChannelEnable(UDMA_CH0_I2C0TX); // 重新使能通道,准备下一次传输 } bIsPingActive = false; } else { // Pong传输完成,处理逻辑类似 PrepareNextData(g_ui8PongBuffer, BURST_SIZE); g_bPongBufferReady = true; if(g_bPingBufferReady) { uDMAChannelTransferSet(UDMA_CH0_I2C0TX | UDMA_ALT_SELECT, UDMA_MODE_BASIC, g_ui8PongBuffer, (void*)(I2C0_BASE + I2C_O_FIFODATA), BURST_SIZE); uDMAChannelEnable(UDMA_CH0_I2C0TX); } bIsPingActive = true; } // 注意:对于发送,需要在所有数据(由I2CMBLEN定义)发送完毕后,由I2C模块产生完成中断或状态位变化。 // 此处DMA完成仅表示它搬完了指定数量的数据到FIFO,不代表I2C总线已发送完毕。 } }

在主循环中,你需要监控I2C主状态寄存器(I2CMCS)的BUSY位或等待I2C中断,以确定整个BURST传输何时结束,然后发送STOP条件。

void PollForTransferComplete(void) { // 等待I2C主控制器繁忙结束 while(HWREG(I2C0_BASE + I2C_O_MCS) & I2C_MCS_BUSY) { // 可以在此处添加超时处理 } // 检查错误位 if(HWREG(I2C0_BASE + I2C_O_MCS) & I2C_MCS_ERROR) { // 处理错误:地址无应答(ADRACK)或数据无应答(DATACK) HandleI2CError(); } else { // 传输成功,发送STOP条件结束本次通信 // 在BURST模式下,发送STOP的命令码需要参考表21-5。 // 对于Master Transmit状态,STOP命令是 0x04 (0000 0100b) HWREG(I2C0_BASE + I2C_O_MCS) = I2C_MCS_STOP; } }

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

即便理解了原理和配置步骤,在实际调试中依然会遇到各种问题。以下是我在多个项目中总结出的常见坑点及其解决方案。

5.1 传输挂起,BUSY位永不清除

  • 现象:启动BURST传输后,I2CMCS寄存器的BUSY位一直为1,程序卡死。
  • 排查思路
    1. 检查从设备地址和应答:这是最常见的原因。使用逻辑分析仪或示波器抓取I2C总线波形,确认START条件后发出的第一个字节(地址字节+读写位)是否正确,以及从设备是否返回了ACK(低电平)。如果从设备无应答,主设备会置位ERRORADRACK位,并可能挂起。确保I2CMSA寄存器写入的值正确(7位地址左移1位,最低位为R/W)。
    2. 检查时钟配置:不正确的I2CMTPR值会导致SCL时钟频率超出从设备支持的范围。计算你的TPR值,并用示波器测量SCL实际频率。确保其符合标准模式(100kHz)、快速模式(400kHz)或你设定的模式。
    3. 检查FIFO和DMA配置
      • 确认I2CFIFOCTL寄存器中的TXFIFOENRXFIFOEN已使能。
      • 确认I2CMBLEN寄存器设置了一个大于0的值。
      • 确认μDMA通道已正确使能,并且控制结构中的传输大小(ARB_SIZE)与I2CMBLEN匹配或为其整数倍。
      • 关键点:在BURST模式下,I2CMCSRUN位应为0,BURST位为1。如果错误地将RUN置1,硬件可能进入未定义状态。仔细对照表21-5检查你写入I2CMCS的命令码。
    4. 检查总线冲突与上拉电阻:I2C总线需要上拉电阻(通常4.7kΩ)。如果电阻值过大或缺失,信号无法可靠拉高,会导致通信失败。检查SDA和SCL线是否有对地短路或与其它信号线短路。

5.2 DMA传输未触发或数据不完整

  • 现象:I2C通信似乎启动了,但数据没有发送出去,或者只发送了一部分。
  • 排查思路
    1. 确认DMA请求信号dma_reqdma_sreq是内部信号,难以直接测量。但可以通过间接方式验证:
      • I2CMCS寄存器中,ACTDMATXACTDMARX位��示DMA传输是否活跃。监控这些位。
      • I2CFIFOSTATUS寄存器可以读出Tx/Rx FIFO的当前填充水平。在发送时,如果FIFO一直为空且DMA不活跃,说明DMA请求可能未产生。
    2. 检查FIFO触发水平TXTRIGRXTRIG设置得太高可能会延迟DMA请求。对于发送,尝试将TXTRIG设为最小值(如1)。这能确保FIFO一有空位就请求DMA,减少总线空闲。
    3. 验证DMA通道配置
      • 源地址和目标地址是否正确?目标地址必须是I2CxFIFODATA寄存器。
      • 数据传输大小(8位、16位、32位)是否与I2C数据宽度匹配?I2C FIFO数据寄存器通常是8位访问。
      • DMA传输模式是否正确?对于一次性BURST传输,BASIC模式即可。对于Ping-Pong,需要使用PINGPONG模式或手动切换PRIMARYALTERNATE控制结构。
    4. 检查I2CMBLEN与DMA传输次数的关系I2CMBLEN定义了I2C模块期望通过FIFO传输的总字节数。DMA的传输次数(ARB_SIZE的循环)应至少满足这个数量。如果DMA配置的传输次数少于I2CMBLEN,I2C模块会在FIFO空后等待更多数据,导致超时或挂起。

5.3 中断与状态管理混乱

  • 现象:程序进入了错误的中断服务程序,或者无法准确判断传输何时结束。
  • 解决方案
    1. 清晰区分中断源:I2C完成、DMA完成、错误中断要分开处理。仔细配置I2CMIMR(主中断屏蔽)和I2CSIMR(从中断屏蔽)寄存器,只使能你需要的中断。在中断服务程序(ISR)开始,立即读取I2CMRIS(原始中断状态)或I2CMMIS(屏蔽后中断状态)来识别具体的中断源。
    2. BURST传输完成的判断:在BURST模式下,传输完成的标志不是简单的BUSY位变低。你需要结合以下条件:
      • BUSY位为0。
      • I2CMBCNT(主突发计数)寄存器递减到0。
      • 可能还需要检查ACTDMATX/ACTDMARX位是否为0,确认DMA已停止。 在查询法中,轮询这些条件;在中断法中,可以使能DMARIS(DMA完成中断)或I2C的特定中断(如STOP条件中断,如果配置了)。
    3. 及时清除中断标志:在退出ISR前,必须向I2CMICR(主中断清除)或I2CSICR(从中断清除)寄存器的相应位写入1来清除中断标志,否则会持续触发中断。

5.4 性能优化与权衡

  • FIFO触发水平的权衡:较低的触发水平(如TXTRIG=1)可以减少总线等待时间,提高实时性,但会频繁触发DMA请求(尤其是dma_sreq),增加总线仲裁开销。较高的触发水平(如TXTRIG=6)有利于触发dma_req进行突发传输,效率更高,但可能导致总线在传输开始时有短暂空闲。需要根据具体应用的数据流特性进行测试和调整。
  • I2CMBLEN的设置:此寄存器值决定了单次BURST命令传输的数据量上限。设置过小,需要频繁发起新的BURST命令,增加软件开销;设置过大,如果传输中途出错,需要重传的数据量也大。通常将其设置为DMA缓冲区大小或一次事务处理的数据包大小。
  • DMA优先级与仲裁大小:在有多路DMA竞争的系统总线时,提高I2C相关DMA通道的优先级可以保证数据传输的实时性。ARB_SIZE(仲裁大小)定义了DMA在一次dma_req响应中连续传输的最大数据项数,将其设置为与I2CMBLEN相同或为其约数,可以最大化突发传输效率。

调试此类复杂外设交互,逻辑分析仪是必不可少的工具。不仅要抓取I2C总线上的SDA/SCL信号,如果MCU支持,还应尝试利用其调试模块(如ITM)实时打印关键寄存器的值(如I2CMCSI2CFIFOSTATUSI2CMBCNT),这能让你清晰地看到硬件状态机的每一步变化,从而快速定位问题根源。记住,配置顺序很重要:先初始化DMA和控制结构,再配置I2C FIFO和I2CMBLEN,最后写入从机地址和发送BURST启动命令。

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

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

立即咨询