TI Tiva I2C驱动开发:从寄存器配置到DMA突发传输实战

发布时间:2026/7/25 11:17:44

TI Tiva I2C驱动开发:从寄存器配置到DMA突发传输实战 1. 项目概述与I2C核心价值搞嵌入式开发这些年I2C总线绝对是我打交道最多的通信协议之一。它不像SPI那样需要一堆片选线也不像UART那样对时钟同步有严格要求。两根线一根时钟SCL一根数据SDA就能把一堆传感器、EEPROM、RTC时钟芯片、IO扩展器这些低速外设串联起来这种简洁高效的设计让它在资源受限的微控制器世界里如鱼得水。无论是读取温湿度传感器的数据还是配置一个音频解码芯片的寄存器I2C都是首选方案。但简洁的背后是严谨的时序和复杂的内部状态机。很多新手朋友在配置I2C时常常被那一堆状态位、中断标志和时序要求搞得晕头转向代码调通了也不知道为什么。更进阶一点当需要传输大量数据时比如从图像传感器连续读取一帧数据如果还用CPU一个个字节去轮询搬运效率低下不说CPU也被完全绑死系统实时性无从谈起。这时DMA直接存储器访问和FIFO先进先出队列的威力就显现出来了。它们能让数据在总线和内存之间“自动”搬运CPU只需发号施令然后就可以去处理其他任务等DMA干完活再来个中断通知一下系统吞吐量和响应速度瞬间提升一个档次。今天我就以德州仪器TI的Tiva/Stellaris系列微控制器为例带大家彻底吃透I2C。我们不只讲“怎么配”更要深挖“为什么这么配”。我会从最基础的通信原理和寄存器位定义讲起一直深入到如何利用µDMA和FIFO实现高效的突发Burst传输。你会发现理解了这些底层机制无论是调试通信故障还是进行性能优化都会变得得心应手。2. I2C通信基础与TI模块架构解析2.1 I2C协议核心机制回顾在深入寄存器之前我们必须对I2C协议本身有个清晰的认识。你可以把它想象成一场有严格规则的会议SCL是会议主持人敲的桌子控制节奏SDA是参会人员发言的数据线。会议开始前总线是空闲的SDA和SCL都被上拉电阻拉至高电平。起始START与停止STOP条件这是会议的开场和散场。当SCL为高时SDA一个从高到低的跳变就是START宣告会议开始当SCL为高时SDA一个从低到高的跳变就是STOP宣布会议结束。这两个条件必须由主机产生。地址帧与数据帧START之后主机首先发送一个7位或10位的从机地址加上1位读写方向位0写1读。这8位构成一个地址帧。总线上所有从机都会收听这个地址只有地址匹配的从机才会回应一个ACK应答信号。之后传输的就是一个个8位的数据帧每个数据帧后都跟着一个ACK/NACK位。ACK低电平表示成功接收NACK高电平通常表示接收失败或传输结束。时钟同步与仲裁I2C支持多主机。如果两个主机同时开始传输它们会通过“线与”逻辑进行仲裁。谁先试图在SDA上输出高电平而实际检测到低电平谁就仲裁失败退出竞争。这个过程完全由硬件处理对软件透明。2.2 TI I2C模块内部架构与工作模式TI的I2C模块是一个高度集成的硬件控制器它把上述复杂的协议逻辑全部用硬件实现了。对我们程序员来说主要跟以下几个部分打交道主/从模式控制器模块可以配置为主机或从机。作为主机时它负责产生SCL时钟、START/STOP条件以及控制整个传输流程。作为从机时它监听总线响应自己的地址。波特率发生器通过配置I2CMTPR寄存器我们可以设定SCL时钟的频率标准模式100kbps快速模式400kbps。其计算公式是核心SCL_PERIOD 2 * (1 TPR) * (SCL_LP SCL_HP) * CLK_PRD。其中SCL_LP低电平周期固定为6SCL_HP高电平周期固定为4CLK_PRD是系统时钟周期。比如系统时钟80MHz想要100kbps代入公式计算TPR (80,000,000 / (2*(64)*100,000)) - 1 39。数据寄存器I2CMDR/I2CSDR在非FIFO模式下这是数据进出的门户。主机发送时写I2CMDR接收时读I2CMDR从机则使用I2CSDR。FIFO先进先出队列这是提升性能的关键。模块内部集成了TX和RX FIFO深度通常是8级。启用FIFO后数据不再是单个字节搬运而是可以批量写入或读出极大地减少了CPU中断频率。µDMA通道这是性能飞跃的引擎。µDMA是TI微控制器上的一个智能DMA控制器。我们可以将I2C的FIFO与µDMA通道关联起来。当TX FIFO空到一定级别或RX FIFO有数据达到一定级别时硬件会自动向µDMA发出请求µDMA则无需CPU干预直接从内存搬运数据到FIFO或从FIFO搬运数据到内存。中断与状态系统一系列状态寄存器I2CMCS,I2CMRIS等和中断掩码寄存器I2CMIMR用于监控传输状态忙、错误、仲裁丢失等和触发中断。整个模块的工作本质上就是通过配置一系列寄存器让这个硬件状态机按照我们期望的流程单字节发送、接收、带重复START的复合操作等自动运行起来。注意TI的I2C模块寄存器分为主模块偏移0x000-0x034和从模块偏移0x800-0x820两组以及共用的FIFO相关寄存器0xF00-0xF08。编程时务必使用正确的基础地址进行偏移。3. 关键寄存器深度剖析与配置逻辑只看手册的寄存器列表是远远不够的我们必须理解每个关键位在传输流程中的实际作用。这里我挑几个最核心、最容易出错的寄存器结合我的踩坑经验给大家掰开揉碎了讲。3.1 主控状态寄存器I2CMCS传输流程的指挥棒I2CMCS寄存器是主机模式下的“大脑”它同时兼具控制写和状态查询读功能很多位是双功能的这是理解它的难点也是关键。BUSY (位0) / RUN (位0写)读 (BUSY)这是你判断一次单次传输是否完成的最主要标志。当你向I2CMCS写入命令启动传输后必须循环查询这一位直到它从1变为0才表示本次字节传输包括地址和数据的收发及ACK已经完成。在BUSY1期间读取其他状态位可能是无效的。写 (RUN)在标准非Burst模式下将此位置1结合START、STOP等位可以启动一次传输。关键点RUN位和BURST位是互斥的在Burst模式下RUN位必须写0。ERROR (位1) / START (位1写)读 (ERROR)这是最重要的错误标志。如果一次传输后BUSY变0但ERROR为1说明出了问题通常是地址ADRACK或数据DATACK未被从机应答NACK。每次传输完成后检查ERROR位是必须的。写 (START)将此位置1表示命令中需要产生一个START或重复START条件。ADRACK (位2) / STOP (位2写)读 (ADRACK)如果ERROR1且ADRACK1说明从机地址未被应答。写 (STOP)将此位置1表示在本次传输序列后产生一个STOP条件。在接收多个字节时通常需要在最后一个字节前将ACK位见下文设为NACK然后在命令中置位STOP以优雅地结束传输。DATACK (位3) / ACK (位3写)读 (DATACK)如果ERROR1且DATACK1说明数据字节未被应答。写 (ACK)此位控制主机在接收模式下的应答行为。写0表示主机在接收到一个字节后将回复一个NACK通常用于告诉从机“这是最后一个字节别发了”。写1表示主机在接收到字节后回复ACK。对于多字节接收最后一个字节前应配置为NACK。BUSBSY (位6) / BURST (位6写)读 (BUSBSY)反映整个I2C总线的状态。为1表示总线正忙可能有其他主机在通信为0表示总线空闲。在发起传输前检查此位可以避免在总线忙时误操作。写 (BURST)这是启用FIFODMA突发传输的关键。将此位置1将使能主机的Burst模式。在此模式下数据传输将通过FIFO进行并由I2CMBLEN突发长度和I2CMBCNT突发计数寄存器控制RUN位在此模式下无效。手册中的表7-6Write Field Decoding for I2CMCS[6:0]是一张“密码表”它定义了在不同当前状态下Idle, Master Transmit, Master Receive写入I2CMCS寄存器的不同位组合会触发状态机跳转到哪个下一个状态执行什么操作。编程时我们其实就是根据想要的传输类型单发、单收、多发、多收、带重复START去查这张表拼出对应的控制字然后写入I2CMCS。3.2 主控定时周期寄存器I2CMTPR与时钟计算I2CMTPR寄存器负责生成精确的SCL时钟。除了之前提到的TPR字段它的PULSEL字段位18-16非常实用但常被忽略毛刺抑制脉冲宽度。I2C总线是开漏输出靠上拉电阻拉高边沿变化相对较慢容易受到噪声干扰产生毛刺。PULSEL允许你设置一个滤波宽度以系统时钟数为单位只有宽度超过这个值的脉冲才会被识别为有效的SCL或SDA变化。在电气环境嘈杂的场合比如电机附近适当设置此值例如0x3表示3个时钟滤波可以极大提高通信稳定性。当然滤波会增加信号延时在高速模式下需权衡。3.3 FIFO与DMA相关寄存器精讲这是实现高效传输的核心。I2CFIFOCTL(偏移 0xF04)FIFO控制寄存器。TXTRIG(位11-8):TX FIFO触发级别。当TX FIFO中的数据量小于或等于这个级别时会触发TX FIFO请求产生中断或DMA请求。例如FIFO深度为8设置TXTRIG2则当FIFO中数据少于等于2个时就会请求新数据。RXTRIG(位3-0):RX FIFO触发级别。当RX FIFO中的数据量大于这个级别时会触发RX FIFO请求。例如设置RXTRIG4则当FIFO中数据多于4个时就会请求将数据搬走。配置心得触发级别的设置需要在响应速度和系统开销间平衡。设得太低如1中断/DMA请求会非常频繁增加系统负载设得太高如7可能造成FIFO溢出对于RX或欠载对于TX。通常设置为FIFO深度的一半如4是个不错的起点。I2CMBLEN(偏移 0x30) 和I2CMBCNT(偏移 0x34)Burst模式专属寄存器。I2CMBLEN你希望DMA一次突发传输的总字节数。比如你要发送100个字节可以设置I2CMBLEN100。I2CMBCNT这是一个只读寄存器实时显示当前突发传输中剩余的字节数。当DMA开始搬运这个值会从I2CMBLEN开始递减减到0时本次Burst传输结束。你可以通过查询此寄存器是否为0来判断一次DMA突发传输是否完成。中断掩码寄存器I2CMIMR需要特别注意位10 TXFEIM的说明。手册明确提示当主机正在从RX FIFO执行RX Burst时应清除屏蔽TXFEIM中断在启动TX FIFO传输之前再取消屏蔽它。这是因为在Burst模式下TX和RX的DMA请求信号是复用的如果不这样做可能会产生错误的中断。这是一个典型的“手册里小字注明调试时坑你半天”的细节。4. 从零构建I2C驱动从轮询到DMA突发传输理解了寄存器我们来动手写代码。我会以TM4C123GH6PM这款经典Cortex-M4芯片为例展示如何一步步实现一个健壮的I2C主机驱动。4.1 硬件初始化与基础单字节传输首先是最基础的初始化配置引脚和I2C模块时钟。// 假设使用I2C0 SCL - PB2, SDA - PB3 void I2C0_Init(void) { // 1. 使能GPIOB和I2C0外设时钟 SYSCTL-RCGCGPIO | (1 1); // 使能GPIOB时钟 SYSCTL-RCGCI2C | (1 0); // 使能I2C0时钟 __asm__ volatile(NOP); // 插入少量延时等待时钟稳定 __asm__ volatile(NOP); // 2. 配置PB2, PB3为I2C功能开漏输出 GPIOB-AFSEL | (1 2) | (1 3); // 启用复用功能 GPIOB-ODR | (1 2) | (1 3); // 配置为开漏 GPIOB-PCTL ~(GPIO_PCTL_PB2_M | GPIO_PCTL_PB3_M); // 清除旧配置 GPIOB-PCTL | (GPIO_PCTL_PB2_I2C0SCL | GPIO_PCTL_PB3_I2C0SDA); // 映射到I2C0 GPIOB-DEN | (1 2) | (1 3); // 使能数字功能 // 3. 初始化I2C主机模式禁用节能模式 I2C0-MCR 0x00000010; // 将MCR寄存器写为0x10确保主机功能使能 // 4. 配置SCL时钟为100kHz (假设系统时钟为16MHz) // TPR (SysClk / (2 * (SCL_LP SCL_HP) * SCL_CLK)) - 1 // (16,000,000 / (2 * 10 * 100,000)) - 1 7 I2C0-MTPR 0x00000007; // TPR7, PULSEL0默认旁路滤波 }接下来实现一个最基础的单字节发送函数采用轮询Polling方式。这是理解I2C状态机运作的最佳起点。#define I2C_TIMEOUT 100000 // 定义一个超时计数器防止死循环 bool I2C0_MasterWriteByte(uint8_t slave_addr, uint8_t data) { // 1. 等待总线空闲 uint32_t timeout I2C_TIMEOUT; while((I2C0-MCS (1 6)) (timeout--)); // 检查BUSBSY位 if(timeout 0) return false; // 总线忙超时 // 2. 写入从机地址和方向位0表示写 I2C0-MSA (slave_addr 1) 0xFE; // 地址左移1位最低位R/S置0 // 3. 将要发送的数据放入数据寄存器 I2C0-MDR data; // 4. 发送命令产生START运行并在传输后产生STOP // 查表7-6Idle状态R/S0, BURST0, STOP1, START1, RUN1 // 对应I2CMCS[6:0] 0b0001111 0x07 I2C0-MCS 0x00000007; // 5. 等待本次传输完成BUSY位变0 timeout I2C_TIMEOUT; while((I2C0-MCS 0x01) (timeout--)); // 检查BUSY位 if(timeout 0) { // 超时处理可尝试发送STOP条件恢复总线 I2C0-MCS 0x00000004; // 仅发送STOP return false; } // 6. 检查是否有错误发生ERROR位 if(I2C0-MCS 0x02) { // 检查ERROR位 // 可以进一步检查ADRACK或DATACK位确定错误类型 return false; } return true; // 发送成功 }这个函数虽然简单但包含了I2C主机单次传输的所有关键步骤等总线、设地址、放数据、发命令、等完成、查错误。任何一步出问题通信都会失败。4.2 中断方式处理多字节传输轮询方式会阻塞CPU。对于多字节传输或需要及时响应的系统使用中断更合适。我们需要配置NVIC嵌套向量中断控制器和I2C的中断。volatile uint8_t i2c_tx_buffer[32]; volatile uint8_t i2c_tx_index 0; volatile uint8_t i2c_tx_length 0; volatile bool i2c_transfer_done false; volatile bool i2c_error false; void I2C0_InterruptInit(void) { // 初始化步骤同前... I2C0_Init(); // 使能I2C0的中断主中断IM I2C0-MIMR | 0x00000001; // 置位IM位 // 在NVIC中使能I2C0中断 NVIC-ISER[0] (1 (INT_I2C0 - 16)); // INT_I2C0需查阅具体芯片头文件 NVIC-IP[INT_I2C0] (3 5); // 设置中断优先级例如优先级3 } void I2C0_MasterWriteMultiByte_IT(uint8_t slave_addr, uint8_t *data, uint8_t len) { // 等待总线空闲... // 设置从机地址写方向... I2C0-MSA (slave_addr 1) 0xFE; // 复制数据到缓冲区并设置索引和长度 for(int i0; ilen; i) { i2c_tx_buffer[i] data[i]; } i2c_tx_index 1; // 第一个字节将通过MDR发送 i2c_tx_length len; i2c_transfer_done false; i2c_error false; // 放入第一个数据 I2C0-MDR i2c_tx_buffer[0]; // 发送命令START, RUN但先不发送STOP // Idle状态R/S0, BURST0, STOP0, START1, RUN1 - 0b0001011 0x0B I2C0-MCS 0x0000000B; // 然后等待中断服务程序处理后续字节 } // I2C0中断服务程序 void I2C0_Handler(void) { uint32_t status I2C0-MMIS; // 读取屏蔽后的中断状态 if(status 0x01) { // 主中断触发传输完成或请求下一字节 if(I2C0-MCS 0x02) { // 检查ERROR位 i2c_error true; I2C0-MCS 0x00000004; // 发生错误发送STOP I2C0-MICR | 0x01; // 清除中断 return; } // 判断当前是发送还是接收通过I2CMSA的R/S位或上下文 // 这里以发送为例 if(i2c_tx_index i2c_tx_length) { // 还有数据要发送 I2C0-MDR i2c_tx_buffer[i2c_tx_index]; // 继续发送命令无START/STOP // Master Transmit状态R/S0, BURST0, STOP0, START0, RUN1 - 0b0000001 0x01 I2C0-MCS 0x00000001; } else { // 所有数据发送完毕发送STOP条件结束传输 // Master Transmit状态R/S0, BURST0, STOP1, START0, RUN0 - 0b0000100 0x04 I2C0-MCS 0x00000004; i2c_transfer_done true; } I2C0-MICR | 0x01; // 清除主中断 } // 可以处理其他中断如仲裁丢失、NACK等 if(status 0x10) { // NACK中断 i2c_error true; I2C0-MICR | 0x10; // 清除NACK中断 // ... 错误处理 } }中断方式将CPU从轮询等待中解放出来但程序逻辑变得异步需要维护状态变量i2c_tx_index,i2c_transfer_done等并妥善处理中断服务程序中的重入和资源竞争问题。4.3 使用µDMA与FIFO实现高效突发传输当数据量很大例如读写EEPROM的一页、读取图像传感器的一行时中断方式每个字节都进一次中断开销依然很大。这时就该µDMA和FIFO上场了实现“无人值守”的数据搬运。核心思想我们告诉I2C模块和µDMA控制器“这里有一块内存数据要发送长度是N你们俩配合自动通过FIFO搬过去搬完了再通知我”。整个过程CPU几乎不干预。以下是配置步骤和关键代码#include inc/hw_udma.h // TI的µDMA头文件 uint8_t dma_tx_buffer[256]; // 位于内存中的待发送数据 void I2C0_DMA_Init(void) { // 1. 初始化I2C同上 I2C0_Init(); // 2. 启用I2C的DMA功能并配置FIFO I2C0-MCR | (1 2); // 使能主机的DMA功能如果MCR有此位具体看手册 // 配置FIFO触发级别例如TXTRIG2, RXTRIG4 I2C0-FIFOCTL (2 8) | (4 0); // TXTRIG2, RXTRIG4 // 3. 配置µDMA通道假设使用通道0作为I2C TX通道 // 3.1 使能µDMA时钟 SYSCTL-RCGCDMA | 0x01; while(!(SYSCTL-PRDMA 0x01)) {}; // 等待时钟就绪 // 3.2 配置DMA控制表Control Table在内存中的地址 // 通常使用TI库函数或直接配置UDMA_CTLBASE寄存器 // 3.3 配置通道属性源地址是内存数组目的地址是I2C FIFO数据寄存器 // 传输模式基本模式一次传输完成即停止 // 数据大小8位 // 源地址增量目的地址不增量外设寄存器 // 仲裁大小Arbitration Size每次触发传输一批数据例如8个字节 // ... 具体的寄存器配置较为复杂通常依赖TI的驱动库函数例如 // uDMAChannelAttributeEnable(UDMA_CHANNEL_I2C0_TX, UDMA_ATTR_USEBURST); // uDMAChannelControlSet(UDMA_CHANNEL_I2C0_TX, UDMA_SIZE_8 | UDMA_SRC_INC_8 | UDMA_DST_INC_NONE | UDMA_ARB_8); // uDMAChannelTransferSet(UDMA_CHANNEL_I2C0_TX, UDMA_MODE_BASIC, dma_tx_buffer, (void*)(I2C0-FIFODATA), 256); } bool I2C0_MasterWriteBurst_DMA(uint8_t slave_addr, uint8_t *data, uint32_t len) { // 0. 参数检查确保len不超过DMA缓冲区等 if(len 256) return false; // 1. 等待总线空闲 // ... // 2. 设置从机地址写 I2C0-MSA (slave_addr 1) 0xFE; // 3. 设置突发传输长度 I2C0-MBLEN len; // 告诉I2C模块这次突发总共要传len个字节 // 4. 准备DMA源数据 memcpy(dma_tx_buffer, data, len); // 5. 使能I2C主机的TX DMA请求 I2C0-MIMR | (1 8); // 使能TXIM中断DMA请求基于此 // 6. 使能µDMA通道 // uDMAChannelEnable(UDMA_CHANNEL_I2C0_TX); // 7. **关键一步启动Burst传输** // 查表7-6Idle状态R/S0, BURST1, STOP1, START1, RUN0 // 对应I2CMCS[6:0] 0b0101110 0x2E (注意STOP1表示传输完自动发STOP) // 如果需要传输后不发STOP用于复合操作则STOP0命令字为0x2A I2C0-MCS 0x0000002E; // 8. 此时硬件自动开始工作 // - I2C主机产生START条件发送地址帧。 // - TX FIFO为空触发TX DMA请求。 // - µDMA收到请求从dma_tx_buffer搬运第一个数据块例如8字节到I2C0-FIFODATA。 // - I2C硬件将FIFO中的数据依次发出。 // - FIFO数据减少低于TXTRIG时再次触发DMA请求直到搬完I2CMBLEN指定的长度。 // - 传输完成后I2C硬件自动产生STOP条件。 // 9. 等待传输完成可以通过查询I2CMBCNT或使能DMA完成中断 while(I2C0-MBCNT ! 0) { // 可选加入超时机制 } // 或者等待DMA传输完成中断 // 10. 检查错误 if(I2C0-MCS 0x02) { return false; } // 11. 清理禁用DMA通道清除中断标志等 // uDMAChannelDisable(UDMA_CHANNEL_I2C0_TX); I2C0-MIMR ~(1 8); return true; }重要提示在Burst接收模式下从从机读取大量数据除了配置RX DMA通道和I2CMBLEN还需要特别注意I2CMCS寄存器中ACK位的设置。通常除了最后一个字节前面的字节主机都应回复ACK。这需要在启动Burst前通过写I2CMCS的ACK位位3为1来使能自动ACK。对于最后一个字节的NACK有些模块的Burst模式可以自动处理有些则需要通过特殊配置或中断来处理务必查阅具体芯片的参考手册。5. 实战调试技巧与常见问题排查理论再完美调试时也难免遇到问题。下面是我总结的一些常见坑点和排查方法。5.1 通信完全无响应抓取波形是关键现象程序卡在等待BUSBSY或BUSY位的地方或者快速返回错误。排查步骤硬件检查首先用万用表测量SCL和SDA线电压。空闲时是否被上拉电阻拉高通常3.3V如果一直是低电平可能是硬件短路、引脚配置错误未配置为开漏ODR或从设备故障。示波器/逻辑分析仪这是最强大的工具。抓取SCL和SDA波形。有没有START条件如果没有检查程序是否成功写入了I2CMCS启动命令。START后SCL有没有时钟脉冲发出如果没有检查I2CMTPR配置是否正确I2C模块时钟是否使能。地址帧是否正确对比发出的7位地址1位R/W是否与从机地址匹配。注意很多从机芯片的地址在手册中给出的是7位形式我们需要左移一位并加上R/W位。例如地址0x48写操作是(0x481) | 0 0x90读操作是(0x481) | 1 0x91。地址帧后SDA在第9个时钟周期是否被从机拉低ACK如果一直是高NACK说明地址不对或从机未就绪。软件检查引脚复用配置确认GPIO的AFSEL复用功能选择和PCTL端口控制寄存器是否正确配置到了I2C功能。这是最常被忽略的一步。时钟使能延迟在使能外设时钟RCGCI2C后必须等待几个周期再访问其寄存器。通常插入几个NOP指令或循环等待PR寄存器对应位就绪。从机速度确认你的I2C配置速度100k/400k是否在从机设备支持范围内。5.2 能发送地址但发送/接收数据失败现象地址ACK正常但后续数据出错或ERROR位被置位且DATACK1。排查时序问题用逻辑分析仪看数据位的时序是否满足从机要求。在高速模式400kbps下微控制器主频是否足够高TPR计算是否正确可以尝试降低速率测试。从机状态某些从机如EEPROM在写入后需要一段“写入周期Write Cycle Time”在此期间它不会响应。如果你在这段时间内发起读请求必然会收到NACK。必须查阅从机手册加入足够的延时。多字节协议很多I2C设备有多字节读写协议。例如写EEPROM时通常要先发送内存地址2字节再发送数据。你的数据序列是否符合从机的协议要求FIFO/DMA模式下的配置如果在Burst模式下失败检查I2CMBLEN设置是否大于实际要传输的数据量DMA的源/目的地址、传输数据宽度、增量设置是否正确FIFO触发级别TXTRIG/RXTRIG是否设置合理是否因为触发太晚导致FIFO溢出/欠载5.3 使用DMA时数据错乱或传输不完整现象DMA传输后数据内容不对或者长度不对。排查数据对齐确保DMA传输的数据缓冲区在内存中的地址符合DMA的要求有时需要4字节对齐。缓存一致性如果CPU有数据缓存D-Cache而DMA操作直接访问物理内存绕过缓存就会导致缓存数据与内存实际数据不一致。在启动DMA传输前可能需要清洗Clean缓存区在DMA传输完成后可能需要无效Invalidate缓存区。这是Cortex-M7等带缓存内核上的常见问题。DMA传输大小与仲裁大小uDMAChannelControlSet中设置的“仲裁大小Arbitration Size”决定了DMA每次被触发时传输的数据项数量。它必须与I2CFIFOCTL中的触发级别协调。例如仲裁大小设为8那么TX FIFO的TXTRIG最好小于等于8 - FIFO深度以确保FIFO不会欠载。中断竞争DMA传输完成中断和I2C传输完成中断可能几乎同时发生。确保中断服务程序能正确处理这种竞态条件避免重复操作或状态混乱。5.4 调试问题速查表现象可能原因排查工具/方法总线一直忙BUSBSY11. 硬件SDA/SCL被拉低2. 从机死机锁住总线3. 程序未正确发送STOP条件示波器看波形检查上拉电阻尝试发送STOP恢复地址无应答ADRACK11. 从机地址错误2. 从机未上电或损坏3. 从机忙如EEPROM在写周期4. 总线电平不匹配5V vs 3.3V核对从机手册地址测量从机VCC检查电平转换电路数据无应答DATACK11. 从机内部地址或命令错误2. 从机写保护使能3. 时序过快从机跟不上核对通信协议序列检查从机WP引脚降低I2C速率测试能读不能写或反之1. R/W方向位设置错误2. 从机寄存器只读/只写检查I2CMSA寄存器最低位核对从机寄存器属性DMA传输数据错误1. 内存缓存一致性问题2. DMA源/目的地址或传输宽度错误3. 数据缓冲区被意外修改处理缓存检查DMA配置结构体使用静态或专用缓冲区通信随机出错1. 电源噪声或地线干扰2. 总线电容过大导致边沿过缓3. 上拉电阻阻值过大增加电源滤波缩短走线减小上拉电阻如从10k改为4.7k启用I2C模块的毛刺滤波PULSEL最后分享一个我调试I2C的黄金法则永远不要完全相信软件状态位要用逻辑分析仪看波形。软件读到的BUSY、ERROR是硬件对总线状态的解读而波形是原始事实。当通信异常时逻辑分析仪能直观地告诉你START有没有地址对不对ACK有没有时钟有没有数据对不对STOP有没有这比盲目地修改代码要高效得多。

相关新闻