深入解析I2C总线协议与TI MCU的DMA+FIFO高效传输配置
1. I2C接口基础与核心价值
搞嵌入式开发这么多年,I2C总线绝对是我打交道最多的外设接口之一。从早期的51单片机到现在的ARM Cortex-M系列,几乎每个项目里都能看到它的身影。简单来说,I2C就像是一条精简版的“设备内部高速公路”,只用两根线就能把一堆传感器、存储芯片、IO扩展器这些“乘客”串联起来,让主控MCU这个“调度中心”能有序地和它们对话。
为什么I2C能这么流行?我总结下来就三个字:省、简、稳。省是省引脚,现在MCU的引脚都是寸土寸金,I2C只用SCL(时钟)和SDA(数据)两根线,相比并口动辄8根、16根数据线,简直是“空间管理大师”。简是协议简单,虽然底层时序有讲究,但上层应用逻辑清晰,地址寻址、读写控制、应答机制一套下来,很容易理解和实现。稳是可靠性不错,在干扰不大的板级环境中,只要上拉电阻匹配、走线别太放飞自我,通信基本稳如老狗。
但“简单”不代表“简陋”。I2C协议在设计上就考虑得很周全。它支持多主多从架构,虽然实际中单主多从用得最多。每个从设备都有一个7位或10位的唯一地址,主设备发起通信时先“喊名字”(发送地址帧),被点名的从设备才会回应。数据传输是同步、半双工的,意味着收发不能同时进行,且每一位数据的变化都严格跟随SCL时钟的节拍。每传输完一个字节(8位数据),接收方必须发送一个应答(ACK)或非应答(NACK)信号,这个机制是保证数据完整性的关键。
在实际项目中,I2C最常见的应用场景就是连接各种“小玩意儿”。比如我想读取环境温湿度,接个SHT30传感器;需要记录一些掉电不丢失的参数,挂个AT24Cxx系列的EEPROM;要给系统提供精确时间,那就用DS3231这类RTC芯片。这些芯片几乎都标配I2C接口,极大地简化了硬件设计和软件驱动开发。
然而,当数据量变大或者对实时性要求提高时,传统的“查询-等待”式I2C操作就会暴露出问题。主控MCU需要不断地轮询状态、搬运数据,大量CPU时间被耗在简单的IO操作上。这时候,DMA(直接内存访问)技术就该登场了。DMA就像一个“专职搬运工”,CPU只需要告诉它“从哪搬、搬多少、搬到哪”,它就能在后台利用总线空闲时间完成数据转移,完全解放CPU。而FIFO(先入先出缓冲区)则是DMA的好搭档,它作为数据中转站,可以平滑总线突发数据,让DMA的搬运更高效。在TI的Tiva/Stellaris系列MCU中,其I2C模块就深度集成了FIFO和µDMA控制器,为我们优化传输性能提供了硬件基础。接下来,我们就深入这个模块的寄存器世界,看看如何配置才能让它火力全开。
2. 核心寄存器详解与配置逻辑
看芯片手册最头疼的就是那一大堆寄存器,每个比特位都代表不同含义。但别慌,我们化繁为简,抓住I2C通信流程中的几个关键“控制开关”和“状态窗口”就行。以TI的I2C模块为例,其寄存器虽然多,但可以按功能分为几大类:主控配置类、数据传输类、中断与状态类,以及FIFO与DMA控制类。理解每一类寄存器的核心作用,配置时才能心中有数。
2.1 主控配置类寄存器:设定通信规则
这类寄存器决定了I2C模块以何种“身份”和“节奏”工作。
I2CMSA (Master Slave Address Register) - 目标地址寄存器这是每次通信的起点。你需要在这里写入你想对话的从设备地址。它的结构非常直观:
- 位[7:1] (SA字段):存放7位从机地址。比如从机地址是0x3B (二进制0111011),你就写入
0x3B << 1,也就是0x76。这里有个关键细节:I2C协议规定地址帧是8位,高7位是地址,最低位是读写方向位(0写,1读)。所以手册里说的“写入0x76”,其实是把地址0x3B左移了一位,并且方向位默认为0(写操作)。如果你想发起读操作,就需要写入(0x3B << 1) | 0x01,即0x77。 - 位[0] (R/S位):读写方向选择。但请注意,在TI的这个实现中,这个位是只读的,它反映的是你写入SA字段时最低位的值。也就是说,你通过控制写入SA字段的数值的bit0,来间接设置读写方向。这是一个容易混淆的点。
I2CMTPR (Master Timer Period Register) - 时钟速率寄存器它决定了SCL时钟线的速度,是I2C通信的“心跳”控制器。其核心是TPR字段(位[6:0])。配置公式手册已经给出:SCL_PERIOD = 2 * (1 + TPR) * (SCL_LP + SCL_HP) * CLK_PRD其中SCL_LP(SCL低电平时间)固定为6个系统时钟,SCL_HP(SCL高电平时间)固定为4个系统时钟,CLK_PRD是系统时钟周期。 举个例子:系统时钟80MHz,想得到100kHz的标准模式SCL。 计算:SCL_PERIOD = 1 / 100kHz = 10us。CLK_PRD = 1 / 80MHz = 0.0125us。 代入公式:10 = 2 * (1 + TPR) * (6+4) * 0.0125=>10 = 2 * (1+TPR) * 10 * 0.0125=>10 = 0.25 * (1+TPR)=>1+TPR = 40=>TPR = 39。 所以,向I2CMTPR寄存器写入0x27(39的十六进制)即可。这里有个坑:TPR的值范围是1-127,计算出的值必须在这个范围内,否则无法产生正确的时钟。同时,PULSEL字段(位[18:16])用于设置毛刺抑制宽度,在电磁环境复杂的场合,适当增加此值(如设置为3或4)可以有效滤除SCL和SDA线上的窄脉冲干扰,提升通信稳定性。
I2CMCR (Master Configuration Register) - 主配置寄存器这个寄存器位不多,但位[4](I2C Master Enable)是关键。必须将其置1,才能使能I2C主模块的功能。通常在初始化序列的最后一步设置它。
2.2 数据传输与命令执行类寄存器:发起通信动作
配置好规则后,就要通过这类寄存器发起具体的读写操作。
I2CMDR (Master Data Register) - 主数据寄存器这是数据进出的门户。在发送时,你把要发送的字节写进去;在接收时,你从这里读取收到的字节。但要特别注意:这是一个对读取操作敏感的寄存器(read-sensitive)。在非FIFO/Burst模式下,当你读取I2CMDR时,硬件可能会自动触发某些动作(如准备下一次接收)。因此,一定要在确认数据就绪(通过状态位)后再去读取,并且遵循手册规定的读取时机。
I2CMCS (Master Control/Status Register) - 主控制/状态寄存器这是整个I2C主控操作的“指挥中心”和“仪表盘”,功能高度复用,读写操作意义不同,需要仔细区分。
作为控制寄存器(写操作)时,你通过组合设置它的低7位(RUN, START, STOP, ACK, BURST等),来下达具体的命令。例如,写入0x0000.0007(二进制...0111)意味着:置位RUN、START、STOP,即“启动传输->运行->完成后停止”这样一个单字节发送命令序列。手册中的表7-6用状态机形式详细列举了各种命令组合,是编程时的核心参考。
作为状态寄存器(读操作)时,它告诉你当前通信的状态。几个关键状态位:
- BUSY (位0):为1表示I2C控制器正忙,此时其他状态位可能无效。必须在BUSY=0时,才能写入新的命令。
- ERROR (位1):为1表示上次操作出错(地址无应答或数据无应答)。任何错误发生后,必须软件处理(如重试)并清除错误状态,才能继续。
- ARBLST (位4):仲裁丢失。在多主系统中,如果两个主机同时发起通信,硬件仲裁失败的一方会看到此位置1,此时应退出发送,稍后重试。
- BUSBSY (位6):总线忙。为1表示I2C总线上有通信在进行(可能是本机或其他主机),此时不能发起START。在发起通信前检查此位是良好的习惯。
实操心得:对I2CMCS寄存器的操作,最稳妥的流程是“先读后写,检查状态”。在写入任何控制命令前,先读取I2CMCS,确保BUSY=0且ERROR=0。写入命令启动传输后,通过轮询BUSY位等待传输完成,完成后再检查ERROR位确认成功。这个“读取-判断-写入-等待-验证”的循环,是稳定I2C操作的基础。
2.3 FIFO与DMA相关寄存器:开启高效传输
当需要传输大量数据时,直接操作I2CMDR会让CPU疲于奔命。FIFO和DMA就是为此而生的性能加速器。
I2CFIFOCTL (FIFO Control Register) & I2CFIFOSTATUS (FIFO Status Register)这两个寄存器管理着深度为8字节的硬件FIFO。
- I2CFIFOCTL:主要设置FIFO的触发阈值(TXTRIG, RXTRIG)。例如,设置RXTRIG=4,意味着当RX FIFO中的数据达到或超过4字节时,才会触发DMA请求或中断。合理设置阈值可以减少中断或DMA请求的频率,在数据吞吐量和系统响应速度间取得平衡。
- I2CFIFOSTATUS:实时反映FIFO状态,如TXFF(发送FIFO满)、RXFE(接收FIFO空)、TXLVL(发送FIFO当前数据量)、RXLVL(接收FIFO当前数据量)。在调试时,监控这些位可以帮助你判断数据流是否顺畅,FIFO是否溢出或下溢。
I2CMBLEN (Master Burst Length Register) - 主模式突发长度寄存器这是配置DMA传输的核心。你在这里设置一个突发传输(Burst)的总字节数。当使能Burst模式并启动传输后,DMA控制器会依据这个长度,自动从内存搬运数据到TX FIFO,或从RX FIFO搬运数据到内存,直到搬完这个数量的字节为止。例如,你想通过DMA发送100字节数据,如果设置I2CMBLEN=100,DMA会一次处理完;你也可以设置为20,然后启动5次Burst传输,这提供了更大的灵活性。
I2CMBCNT (Master Burst Count Register) - 主模式突发计数寄存器这是一个只读寄存器,实时显示当前突发传输中还剩多少字节未完成。它的值会随着DMA的搬运而递减,当减到0时,一次Burst传输就结束了。你可以轮询这个寄存器来了解传输进度。
中断屏蔽寄存器(I2CMIMR/I2CSIMR)与状态寄存器(I2CMRIS/I2CSRIS)这些寄存器用于管理中断。你需要先在中断屏蔽寄存器中使能关心的中断源(如传输完成、FIFO达到阈值、错误等),然后在状态寄存器中查看哪个中断被触发,并在中断清除寄存器(I2CMICR/I2CSICR)中写1清除对应的中断标志。
重要提示:手册中特别强调了一个细节:当主模块正在从RX FIFO执行接收突发(RX Burst)时,应清除I2CMIMR中的TXFEIM(发送FIFO空中断屏蔽)位以屏蔽该中断;而在启动TX FIFO传输之前,再将其取消屏蔽。这是因为在RX Burst期间,TX FIFO必然是空的,如果使能了其中断,可能会产生大量无用的中断请求,浪费CPU资源。
3. 主从模式命令序列与代码实现解析
理解了寄存器,我们就要把它们串起来,按照I2C协议的状态机流程来操作。手册里的流程图(Figure 7-7 到 7-13)就是最好的“武功秘籍”。我们将其翻译成更直白的步骤和代码逻辑。
3.1 主设备单字节发送流程
这是最基础的操作,流程如下:
- 等待总线空闲:读取I2CMCS寄存器,检查BUSBSY位是否为0。如果不为0,说明总线正被占用,需要等待。
- 设定从机地址与方向:向I2CMSA寄存器写入目标从机地址(左移一位后,最低位为0表示写)。
- 准备数据:向I2CMDR寄存器写入要发送的数据字节。
- 发送启动、运行、停止命令:向I2CMCS寄存器写入
0x7(二进制0111,即START=1, STOP=1, RUN=1)。 - 等待传输完成:循环读取I2CMCS寄存器,等待BUSY位变为0。
- 检查错误:传输完成后,检查I2CMCS寄存器的ERROR位。如果为0,表示从机已应答,发送成功;如果为1,表示无应答,发送失败,需要进行错误处理(如重试)。
用C语言伪代码表示这个流程会更清晰:
// 假设已初始化I2C模块,并配置好时钟速率(I2CMTPR) bool I2C_Master_SendByte(uint8_t slaveAddr, uint8_t data) { // 1. 等待总线空闲 while(I2C_MASTER->MCS & I2C_MCS_BUSBSY) { // 可选:加入超时机制,防止死等 } // 2. 设置从机地址(写方向) I2C_MASTER->MSA = (slaveAddr << 1); // 最低位0表示写 // 3. 写入待发送数据 I2C_MASTER->MDR = data; // 4. 发送命令:产生START,运行,并在传输后产生STOP I2C_MASTER->MCS = I2C_MCS_START | I2C_MCS_STOP | I2C_MCS_RUN; // 5. 等待传输完成 while(I2C_MASTER->MCS & I2C_MCS_BUSY) { // 等待BUSY位清零 } // 6. 检查错误 if(I2C_MASTER->MCS & I2C_MCS_ERROR) { // 发送失败,处理错误(如重试) return false; } return true; // 发送成功 }3.2 主设备单字节接收流程
接收流程与发送类似,但方向相反,且涉及ACK/NACK的控制:
- 等待总线空闲:同样检查BUSBSY。
- 设定从机地址与方向:向I2CMSA写入地址,此时最低位应为1(读方向)。
- 发送启动、运行命令(先不发停止):向I2CMCS写入
0x3(二进制0011,START=1, RUN=1, STOP=0, ACK=0)。这里ACK=0意味着主机在接收完一个字节后,将发送NACK信号,告知从机“我只读一个字节,别再发了”。 - 等待接收完成:轮询BUSY位。
- 检查错误:检查ERROR位。
- 读取数据:从I2CMDR寄存器读取接收到的字节。
- 发送停止条件:向I2CMCS写入
0x4(二进制0100,STOP=1),产生STOP信号结束本次通信。
3.3 主设备多字节传输与重复起始条件
实际应用中,单字节操作较少,更多的是多字节读写。例如读取一个传感器的寄存器值,往往是先写寄存器地址,再读数据。这需要用到重复起始条件(Repeated START)。
流程:写寄存器地址 -> 重复START -> 读数据
- 发送START,写入从机地址(写方向),发送寄存器地址字节(这是一个标准的单字节发送流程,但最后不发STOP)。
- 此时总线仍由主机掌控。主机再次发送一个START信号(即重复起始条件),然后写入从机地址(读方向)。
- 启动接收流程,读取一个或多个数据字节。
- 发送STOP结束。
这个流程对应手册中的Figure 7-11。关键点在于,第一次传输(写地址)结束时,不能发送STOP信号(即I2CMCS命令中的STOP位为0),这样才能保持总线控制权,紧接着发起重复START。在代码实现上,需要将单次发送和接收函数组合,并精细控制STOP信号的产生时机。
3.4 从设备模式下的基本流程
从设备模式相对被动,其核心是响应主机的呼叫。
- 配置自身地址:向I2CSOAR寄存器写入本设备的I2C地址。
- 使能从机:设置I2CSCSR寄存器的相应位,使能从机响应。
- 等待请求:轮询I2CSCSR寄存器的TREQ(发送请求)或RREQ(接收请求)位。
- 处理请求:
- 如果TREQ=1,表示主机请求读取数据,从机应将数据写入I2CSDR寄存器。
- 如果RREQ=1,表示主机发送了数据,从机应从I2CSDR寄存器读取数据。
- 循环或中断处理:完成一次数据交换后,清除请求标志,继续等待下一次请求。
从机模式的难点在于实时性要求。如果采用轮询方式,CPU必须频繁检查状态位,效率低下。因此,从机模式强烈建议使用中断。使能接收和发送中断后,当主机访问时,CPU会跳入中断服务程序进行快速的数据读写操作,从而解放主循环。
4. DMA与FIFO协同工作模式深度优化
轮询和中断方式在低速或零星数据传输时没问题,但当你要连续读取一个图像传感器的大量数据,或者需要以最高速率记录传感器信息时,它们就会成为系统性能的瓶颈。DMA+FIFO的组合拳,就是为了解决这个痛点。
4.1 工作原理:自动化数据流水线
我们可以把整个过程想象成一个“智能快递系统”:
- FIFO:相当于快递柜。发送时,DMA把包裹(数据)从仓库(内存)批量送到快递柜(TX FIFO),I2C模块再按顺序取出交给快递员(总线)。接收时则相反,快递员把包裹放入快递柜(RX FIFO),DMA再批量搬回仓库。
- DMA:就是那个不知疲倦的搬运机器人。它独立于CPU工作,通过专门的DMA通道连接存储器和外设(这里是I2C的FIFO)。
- I2C Burst模式:是启动这个自动化流程的开关。当你在I2CMCS寄存器中设置BURST位并启动传输后,I2C模块就会根据I2CMBLEN设定的包裹总数,自动协调DMA和FIFO进行搬运。
发送流程(Master Transmit with DMA):
- 配置DMA通道:设置源地址(内存数组)、目的地址(I2CFIFODATA寄存器)、传输数据量(=I2CMBLEN)。
- 配置I2C主模块:设置从机地址、使能TX FIFO、设置TX FIFO触发阈值(TXTRIG)。
- 将待发送数据填入内存源数组。
- 使能DMA通道,并启动I2C Burst传输(向I2CMCS写入包含BURST位的命令)。
- I2C模块发现TX FIFO未满,触发DMA请求(
dma_req)。DMA控制器响应,将数据从内存搬至TX FIFO。 - I2C模块从TX FIFO取出数据,通过SDA线发送出去。
- 重复步骤5-6,直到累计发送字节数达到I2CMBLEN设定值,传输自动停止。
接收流程(Master Receive with DMA):
- 配置DMA通道:设置源地址(I2CFIFODATA寄存器)、目的地址(内存数组)、传输数据量。
- 配置I2C主模块:设置从机地址(读方向)、使能RX FIFO、设置RX FIFO触发阈值(RXTRIG)。
- 使能DMA通道,并启动I2C Burst接收。
- I2C模块接收数据并存入RX FIFO。
- 当RX FIFO中的数据达到触发阈值,触发DMA请求。DMA控制器将数据从RX FIFO搬至内存。
- 重复步骤4-5,直到收满I2CMBLEN个字节。
4.2 关键配置与避坑指南
要让这套“智能快递系统”稳定高效运行,有几个配置细节必须注意:
1. FIFO触发阈值的权衡I2CFIFOCTL寄存器中的TXTRIG和RXTRIG决定了何时触发DMA请求。
- 设置过小(如1或2):FIFO稍有数据就触发DMA,请求频繁,增加了总线仲裁开销,可能降低整体吞吐率。
- 设置过大(如6或7):虽然减少了DMA请求次数,但增大了传输延迟。对于接收,如果阈值设得太高,在FIFO存满之前可能不会触发DMA,如果此时主设备发送速度很快,可能导致FIFO溢出。
- 经验值:对于大多数应用,设置为4是一个不错的起点。它在延迟和总线开销之间取得了较好的平衡。你可以根据实际数据流量和系统负载进行微调。
2. Burst长度(I2CMBLEN)与DMA传输量的匹配这是一个必须保持一致的硬性规定:I2CMBLEN寄存器设置的值必须等于DMA通道配置的传输总字节数。如果DMA配置的传输量小于I2CMBLEN,DMA会提前停止,但I2C模块还在等待更多数据,会导致超时或错误。如果DMA传输量更大,则DMA在完成I2CMBLEN指定的传输后,会继续尝试搬运,但I2C已无新数据,可能导致DMA错误或读取到无效数据。
3. 中断与DMA的协同即使使用了DMA,中断仍然很有用,但角色变了。我们不再用中断来处理每个字节,而是用中断来通知“一批数据”的传输完成。
- 使能DMA完成中断:在
I2CMIMR寄存器中使能DMATXIM(发送DMA完成中断)和/或DMARXIM(接收DMA完成中断)。 - 中断服务程序(ISR):在DMA完成中断中,你需要做的是: a. 清除I2C模块的DMA完成中断标志(在
I2CMICR中写1清除)。 b. 清除DMA控制器本身的中断标志。 c. 处理已经传输完成的一整块数据(例如,解析传感器数据包、将数据存入队列等)。 d. 如果需要连续传输,则重新配置DMA源/目标地址和数据量,并重新使能DMA通道及I2C Burst。
4. 错误处理在DMA传输过程中,I2C总线错误(如NACK)仍然可能发生。由于CPU不参与过程,因此必须使能错误中断(NACKIM等)。在错误中断服务程序中,你需要:
- 停止当前的DMA传输(禁用DMA通道)。
- 停止I2C Burst操作。
- 读取状态寄存器分析错误原因。
- 执行恢复操作(如复位I2C模块、重新初始化等),并根据业务逻辑决定是否重传。
深度避坑指南:关于“TXFEIM中断屏蔽”的再强调。手册中的那个Note非常关键。在纯RX Burst期间,TX FIFO是闲置且为空的,如果
TXFEIM被使能,就会不断产生TX FIFO空中断,这纯粹是噪声。所以,在启动RX Burst前,务必清除I2CMIMR中的TXFEIM位。反之,在启动TX Burst前,再将其置位。很多驱动库代码忽略了这一点,虽然可能不影响功能,但会在后台产生大量无用中断,增加系统开销,在低功耗应用中尤其需要注意。
5. 实战:从零配置带DMA的I2C主设备收发
理论说得再多,不如一行代码。下面我们以一个具体的场景为例,实战如何配置TI Tiva C系列MCU的I2C模块,实现基于DMA的连续数据读取。假设我们要从一个I2C温度传感器(地址0x48)连续读取256个字节的采样数据。
5.1 硬件与软件初始化
首先,完成最基本的硬件和模块使能:
// 1. 使能I2C模块所在的外设时钟(例如I2C0) SYSCTL->RCGCI2C |= (1 << 0); // 使能I2C0时钟 // 等待外设时钟稳定 while(!(SYSCTL->PRGCI2C & (1 << 0))) {}; // 2. 配置GPIO引脚为I2C功能,并启用开漏输出 // 假设SCL在PB2, SDA在PB3 SYSCTL->RCGCGPIO |= (1 << 1); // 使能GPIOB时钟 GPIOB->AFSEL |= (1 << 2) | (1 << 3); // PB2, PB3启用备用功能 GPIOB->PCTL &= ~((0xF << 8) | (0xF << 12)); // 清除原有复用 GPIOB->PCTL |= ((3 << 8) | (3 << 12)); // 配置PB2, PB3为I2C功能 GPIOB->ODR |= (1 << 2) | (1 << 3); // 使能开漏输出(关键!) GPIOB->DEN |= (1 << 2) | (1 << 3); // 数字使能 // 3. 初始化I2C主模块,禁用任何可能的活动状态 I2C0->MCR = 0x0000.0010; // Master Function Enable, 其他位默认 // 4. 配置I2C时钟为100kHz(系统时钟80MHz) // TPR = (80e6 / (2 * (6+4) * 100000)) - 1 = 39 I2C0->MTPR = 39;5.2 DMA控制器的配置
接下来,配置µDMA控制器。这里需要参考具体的µDMA寄存器手册,但流程是通用的:
// 假设使用DMA通道8用于I2C0 RX // 1. 使能DMA控制器时钟 SYSCTL->RCGCDMA |= (1 << 0); // 2. 等待DMA时钟就绪 while(!(SYSCTL->PRGCDMA & (1 << 0))) {}; // 3. 配置DMA通道控制结构(通常是一个链表或描述符) // 这里以简化模型为例,假设直接配置通道寄存器 // 设置传输模式:基本模式,每次传输后地址递增 DMA->CHCTRL[8].CTRL = DMA_CTRL_MODE_BASIC | DMA_CTRL_SIZE_8 | DMA_CTRL_SRC_INC_NONE | DMA_CTRL_DST_INC_8; // 设置源地址:I2C FIFO数据寄存器(只读,地址不变) DMA->CHCTRL[8].SRC = (uint32_t)&(I2C0->FIFODATA); // 设置目标地址:内存中的数组(地址递增) extern uint8_t sensorData[256]; DMA->CHCTRL[8].DST = (uint32_t)sensorData; // 设置传输数据量:256字节 DMA->CHCTRL[8].COUNT = 256; // 4. 使能DMA通道 DMA->CHCTRL[8].CTRL |= DMA_CTRL_EN;5.3 I2C模块的DMA与FIFO配置
现在,将I2C模块和DMA关联起来,并配置FIFO:
// 1. 配置FIFO控制寄存器 // 使能RX FIFO,设置RX触发阈值为4字节,TX触发阈值暂时无关 I2C0->FIFOCTL = I2C_FIFOCTL_RXFIFOEN | (4 << I2C_FIFOCTL_RXTRIG_S); // 2. 设置突发传输长度为256字节,与DMA配置匹配 I2C0->MBLEN = 256; // 3. 配置中断屏蔽寄存器 // 使能接收DMA完成中断和错误中断,屏蔽TX FIFO空中断(因为本次是纯接收) I2C0->MIMR = I2C_MIMR_DMARXIM | I2C_MIMR_NACKIM | I2C_MIMR_ARBLOSTIM; // 特别注意:清除TXFEIM,手册要求RX Burst时屏蔽它 I2C0->MIMR &= ~I2C_MIMR_TXFEIM; // 4. 设置从机地址(读方向) I2C0->MSA = (0x48 << 1) | 0x01; // 地址0x48,最低位1表示读 // 5. 启动DMA Burst接收 // 命令:START=1, RUN=1, BURST=1, ACK=1 (主机自动回复ACK直到最后), STOP=1 (传输完成后停止) // 根据手册表7-6,对于初始IDLE状态,读方向、BURST模式、ACK=1、STOP=1、START=1、RUN=0 // 对应 I2CMSA[0]=1, BURST=1, ACK=1, STOP=1, START=1, RUN=0 I2C0->MCS = I2C_MCS_START | I2C_MCS_STOP | I2C_MCS_ACK | I2C_MCS_BURST; // 注意:在BURST模式下,RUN位应保持为0。传输由DMA和FIFO自动管理。5.4 中断服务程序处理
最后,编写中断服务程序来处理传输完成或错误:
void I2C0_DMA_IRQHandler(void) { uint32_t status = I2C0->MRIS; // 读取原始中断状态 // 处理接收DMA完成中断 if(status & I2C_MRIS_DMARXRIS) { // 清除中断标志 I2C0->MICR = I2C_MICR_DMARXIC; // 此时,sensorData数组中应已存有256字节数据 processSensorData(sensorData, 256); // 处理数据的用户函数 // 如果需要连续采集,可以在这里重新配置DMA COUNT和DST,并重新使能DMA通道 // 然后再次启动I2C Burst接收(注意总线状态) } // 处理NACK错误(地址或数据无应答) if(status & I2C_MRIS_NACKRIS) { I2C0->MICR = I2C_MICR_NACKIC; // 清除错误标志 // 进行错误恢复,例如:复位I2C模块,重新初始化,记录错误日志等。 handleI2CError(); } // 处理仲裁丢失错误(多主系统) if(status & I2C_MRIS_ARBLOSTRIS) { I2C0->MICR = I2C_MICR_ARBLOSTIC; // 仲裁丢失通常只需稍后重试 // 可以设置一个重试标志,在主循环中重新发起传输 } }5.5 调试技巧与性能评估
配置完成后,如何验证和优化?以下是我常用的方法:
1. 逻辑分析仪是必备工具连接SCL和SDA线到逻辑分析仪,可以直观看到:
- 起始条件、地址帧、数据帧、应答位、停止条件是否符合预期。
- 时钟频率是否准确为100kHz。
- 在DMA Burst传输时,数据流是否连续,有无异常的长时间停顿(可能意味着DMA或FIFO配置不当)。
2. 监控关键寄存器在调试初期,可以在主循环中打印或通过调试器观察关键寄存器:
I2CMCS:检查BUSY, ERROR状态。I2CMBCNT:在Burst传输中,它应该从设定值递减到0,可以直观看到传输进度。I2CFIFOSTATUS:查看TXLVL和RXLVL,确认FIFO是否在正常填充和排空。如果RXLVL长时间为0或一直为满,说明数据流可能堵塞。
3. 性能粗略估算假设系统时钟80MHz,I2C速率100kHz。
- 无DMA轮询方式:每传输1字节,CPU至少花费数十条指令来轮询状态、读写数据。传输256字节会完全占用CPU相当长时间。
- DMA+FIFO方式:CPU只在启动传输和处理传输完成中断时介入。传输256字节的主要时间就是I2C总线时间:
256字节 * 9时钟/字节(8数据+1应答) / 100kHz ≈ 23ms。在这23ms内,CPU可以完全自由地执行其他任务。中断处理开销可能只有几十微秒。
这种性能提升在需要高频采集或实时处理的系统中是至关重要的。它使得MCU能够轻松应对高速数据流,而不会因为忙于搬运数据而错过其他关键事件。
