1. 项目概述
搞嵌入式开发,I2C总线绝对是绕不开的一道坎。它简单,两根线就能拉起一个通信网络;它也复杂,时序不对、配置出错,通信立马就给你摆脸色看。特别是当你面对像TI TMS320x2806x这类功能强大的微控制器时,手册里动辄十几二十个寄存器,每个位域都藏着玄机,新手看了直挠头,老手也可能在某个不起眼的配置上栽跟头。今天,我就结合自己这些年调试I2C的经验,以TMS320x2806x的I2C模块为例,把寄存器配置这潭水彻底搅清。我们不止看手册上写了什么,更要弄明白它为什么这么写,以及在实际代码里我们该怎么配。从最基础的时钟分频算起,到主从模式切换、数据收发控制,再到中断和FIFO的高级玩法,我会把每个关键寄存器的“脾气秉性”都掰开揉碎了讲。无论你是正在调试一个I2C温湿度传感器,还是想彻底搞懂多主机仲裁背后的机制,这篇文章都能给你提供一份可以直接“抄作业”的配置指南和避坑手册。
2. I2C总线核心机制与寄存器映射总览
在深入每个寄存器之前,我们必须先统一思想:I2C模块的寄存器,是软件(你的代码)与硬件(I2C物理控制器)之间的契约。你写寄存器,是在给硬件下达精确的指令;你读寄存器,是在获取硬件运行的状态。TMS320x2806x的I2C模块寄存器映射相对规整,主要分为几大类:时钟控制类、数据缓冲类、地址与模式控制类、中断状态类以及FIFO控制类。
时钟是I2C通信的脉搏。所有数据位的采样和保持都严格依赖于SCL时钟线的高低电平周期。因此,I2CPSC(预分频器)、I2CCLKL(时钟低电平分频)和I2CCLKH(时钟高电平分频)这三个寄存器是配置的起点,它们共同决定了最终的I2C总线速率。这里有个关键点:模块时钟(Module Clock)是I2C模块内部工作的基础时钟,由I2CPSC对输入时钟(通常是CPU时钟或外设时钟)分频得到。而SCL总线时钟,则由模块时钟经过I2CCLKL和I2CCLKH的二次分频产生。这个关系链必须理清。
数据流是通信的载体。I2CDXR(数据发送寄存器)和I2CDRR(数据接收寄存器)是CPU与I2C模块交换数据的直接窗口。但要注意,它们背后还有移位寄存器(I2CXSR和I2CRSR)在默默工作,负责将并行数据转为串行比特流,或者反过来。I2CCNT(数据计数寄存器)则在非重复模式下,扮演着“通信长度指挥官”的角色。
模式与命令是通信的大脑。I2CMDR(模式寄存器)是绝对的核心,它定义了几乎所有的行为模式:主从模式(MST)、收发方向(TRX)、地址格式(XA)、是否产生起止信号(STT,STP)、是否启用重复模式(RM)或自由数据格式(FDF)等。I2CSAR(从机地址寄存器)则存储了你想要对话的“目标”地址。
中断与状态是系统的耳目。I2CISRC(中断源寄存器)和状态寄存器(虽然输入材料未详细列出I2CSTR,但它是存在的)让你能及时知道通信完成了、数据准备好了、还是发生了仲裁丢失或无应答错误。I2CEMDR(扩展模式寄存器)则提供了一些向后兼容性的微调选项。
FIFO是提升效率的加速器。I2CFFTX和I2CFFRX寄存器用于管理发送和接收FIFO,这在需要连续传输大量数据时,能极大减轻CPU的中断负担,实现更流畅的DMA式数据搬运。
注意:所有对I2C模块寄存器的配置,尤其是
I2CPSC、I2CCLKL、I2CCLKH,必须在模块复位状态下(I2CMDR.IRS = 0)进行。配置完成后,再将IRS位置1来使能模块。在模块运行中修改这些时钟相关寄存器是无效的,甚至可能导致通信异常。
3. 时钟系统配置:从输入时钟到SCL波形
I2C通信的稳定性,十有八九出在时钟上。配置不对,轻则通信失败,重则波形畸变,连逻辑分析仪都抓不到规整的信号。我们一步步来算。
3.1 模块时钟生成:I2CPSC寄存器
I2CPSC寄存器只有低8位(IPSC字段)有效。它的作用是对I2C的输入时钟进行第一次分频,产生模块时钟。公式很简单:
模块时钟频率 = I2C输入时钟频率 / (IPSC + 1)
这里的“I2C输入时钟频率”需要查你所用芯片的数据手册,它通常来源于系统时钟的某个分频。假设我们的系统主频是100MHz,分配给I2C模块的输入时钟也是100MHz。如果我们希望模块时钟为10MHz,那么:IPSC = (100MHz / 10MHz) - 1 = 9。 所以,我们需要向I2CPSC寄存器写入0x0009。
为什么是IPSC+1?这是硬件设计上的惯例,IPSC=0表示1分频(即不分频),IPSC=1表示2分频,以此类推。这确保了分频系数始终为正整数。
3.2 SCL总线时钟生成:I2CCLKL与I2CCLKH寄存器
这是最核心的部分。SCL时钟的一个完整周期由低电平时间(tLOW)和高电平时间(tHIGH)组成。I2CCLKL和I2CCLKH寄存器分别控制这两个时间。
手册给出的公式是:tLOW = 模块时钟周期 * (I2CCLKL + d),tHIGH = 模块时钟周期 * (I2CCLKH + d)。 其中d是一个基于预分频器的调整因子,在TMS320x2806x中,通常d=0(当IPSC=0时)或d=1(当IPSC>0时)。为了简化计算和保证兼容性,一个非常稳妥且常见的做法是,永远确保I2CCLKL和I2CCLKH的值大于等于d所允许的最小值。手册的Note部分明确强调:“These bits must be set to a non-zero value for proper I2C clock generation.”也就是说,这两个寄存器绝对不能配置为0。
实操计算示例: 目标:在标准模式(100kbps)下配置I2C时钟。标准模式要求SCL时钟频率不高于100kHz。 已知:模块时钟已配置为10MHz(周期为0.1µs)。 计算: 一个SCL时钟周期T_SCL = 1 / 100kHz = 10 µs。 我们需要将这个10µs的周期分配给tLOW和tHIGH。根据I2C规范,在标准模式下,tLOW和tHIGH都需要至少4.7µs。为了留有余量并方便计算,我们通常设置为各占5µs。 那么:I2CCLKL = tLOW / 模块时钟周期 - d = 5µs / 0.1µs - 1 = 50 - 1 = 49(0x31)。I2CCLKH = tHIGH / 模块时钟周期 - d = 5µs / 0.1µs - 1 = 50 - 1 = 49(0x31)。
因此,SCL的实际频率为1 / ( (49+1)+(49+1) * 0.1µs ) = 1 / (100 * 0.1µs) = 100kHz, 完美匹配。
快速模式(400kbps)配置: 目标频率400kHz,T_SCL = 2.5µs。规范要求tLOW和tHIGH至少0.13µs。我们仍按对称设计,各占1.25µs。I2CCLKL = I2CCLKH = 1.25µs / 0.1µs - 1 = 12.5 - 1 = 11.5。 这里出现小数,我们必须取整。取I2CCLKL = I2CCLKH = 11(0x0B)。 则实际tLOW = tHIGH = (11+1)*0.1µs = 1.2µs, 周期为2.4µs, 实际频率约为416.7kHz, 略高于目标,但在I2C从设备通常允许的容差范围内(通常±10%)。如果必须精确,则需要调整模块时钟频率或接受微小的不对称。
避坑指南:
- 顺序问题:必须先配置
I2CPSC、I2CCLKL、I2CCLKH,最后再置位I2CMDR.IRS启动模块。乱序会导致不可预测的时钟行为。- 零值陷阱:
I2CCLKL和I2CCLKH写入0是无效配置,可能导致SCL线被拉低无法释放,总线死锁。- 从机时钟:当本设备作为从机时,
I2CCLKL和I2CCLKH的配置不影响自身(时钟由主机提供),但为了模式切换(主从切换)时的一致性,通常也会进行合理配置。- 频率验证:最可靠的方法是用示波器或逻辑分析仪测量实际的SCL波形,检查高/低电平时间是否符合I2C规范和你预期的值。
3.3 时钟配置代码示例
// 假设 I2C 模块基地址为 I2caRegs void I2C_InitClock(uint32_t sysClkHz, uint32_t targetSCLkHz) { // 1. 确保I2C模块处于复位状态 I2caRegs.I2CMDR.bit.IRS = 0; // 2. 配置预分频器,得到约10MHz模块时钟 (示例) // 假设I2C输入时钟等于系统时钟 uint16_t prescale = (sysClkHz / 10000000) - 1; // 目标10MHz模块时钟 if (prescale < 1) prescale = 1; // 保证IPSC >= 1, 使模块时钟稳定 I2caRegs.I2CPSC.all = prescale; // 3. 计算并配置高低电平分频值 // 模块时钟周期 (秒) float moduleClkPeriod = 1.0 / ((float)sysClkHz / (prescale + 1)); // 目标SCL周期的一半 (按对称设计) float halfSCLPeriod = (1.0 / (float)targetSCLkHz) / 2.0; // 计算分频值,d取1 (因为IPSC>0) uint16_t clkDiv = (uint16_t)(halfSCLPeriod / moduleClkPeriod) - 1; // 确保非零 if (clkDiv == 0) clkDiv = 1; I2caRegs.I2CCLKL = clkDiv; I2caRegs.I2CCLKH = clkDiv; // 4. 使能I2C模块 // 注意:其他配置(如模式、地址等)也应在此前或此后完成,但必须在IRS=1前 // I2caRegs.I2CMDR.bit.MST = 1; // 例如,配置为主模式 // I2caRegs.I2CMDR.bit.IRS = 1; // 最后使能模块 }4. 核心控制寄存器详解与配置策略
时钟配好了,只是搭好了舞台。真正指挥通信流程的,是I2CMDR(模式寄存器)这位“总导演”。
4.1 I2CMDR:模式寄存器深度解析
这个16位寄存器几乎囊括了I2C模块的所有核心控制功能。我们按功能分组解读:
4.1.1 主从与收发控制 (MST,TRX)
MST(Bit 10): 主从模式选择。0-从机模式,1-主机模式。一个关键行为:当主机产生STOP条件后,硬件会自动将此位清零。这意味着,一次完整的主机通信结束后,模块会回到从机模式,监听总线。如果你需要连续发起多次主机传输,必须在每次传输前重新置位MST。TRX(Bit 9): 传输方向。0-接收模式,1-发送模式。这个位的意义与MST位相关。在主机模式下,它决定了本次数据传输阶段(地址发送后)的方向;在从机模式下,它由主机发送的地址字节中的R/W位决定,硬件会自动设置。
4.1.2 地址与数据格式 (XA,FDF,BC)
XA(Bit 8): 扩展地址使能。0-使用7位地址模式,1-使用10位地址模式。此位影响I2CSAR(从机地址寄存器)和I2COAR(自身地址寄存器)的解析方式。FDF(Bit 3): 自由数据格式模式。置1时,通信将不包含地址阶段,直接进行数据收发。这用于一些特殊的、非标准I2C协议设备。注意:此模式不支持数字回环模式(DLB=1)。BC(Bits 2-0): 位计数。定义每个数据字节包含多少位(1-8)。重要:BC=000b代表8位,这是最常见的情况。如果设置为其他值(如BC=011b表示3位),则数据在I2CDXR和I2CDRR中必须右对齐。地址字节永远是8位,不受此位影响。
4.1.3 起止与重复控制 (STT,STP,RM)
STT(Bit 13): 起始条件位。仅当MST=1(主机模式)时有效。软件置1后,I2C模块会在总线上产生一个START条件,然后硬件自动清除此位。关键限制:此位在IRS=0(模块复位)时不可写。STP(Bit 11): 停止条件位。仅当MST=1时有效。在非重复模式(RM=0)下,当I2CCNT计数到0且STP=1时,模块会自动产生STOP条件。在重复模式或手动控制时,软件置1可产生STOP条件。一个至关重要的细节:手册提到“The I2C module delays clearing of this bit until after the I2CSTR[SCD] bit is set. To avoid disrupting the I2C state machine, the user must wait until this bit is clear before initiating a new message.” 这意味着,发出STOP指令后,必须等待STP位被硬件自动清零(或查询状态寄存器中的SCD位),才能开始下一次通信,否则会扰乱状态机。RM(Bit 7): 重复模式。仅主机发送器模式有效。RM=1时,I2CCNT被忽略,数据字节在每次写入I2CDXR后发送,直到软件手动设置STP位。这适用于数据流长度不确定或需要连续发送的场景。ARDY中断可以用来判断何时可以写入下一个数据。
4.1.4 其他功能位 (NACKMOD,FREE,DLB,IRS,STB)
NACKMOD(Bit 15): NACK模式。当模块作为接收器时,控制是否在下一个应答周期发送NACK。可用于主动终止接收。FREE(Bit 14): 调试模式位。决定在遇到调试器断点时,I2C模块是否继续运行(Free-run)。在调试实时通信时非常有用。DLB(Bit 6): 数字回环模式。置1时,发送的数据直接内部环回到接收端,用于自测试,无需外部连接。注意:此模式下MST必须为1,且不支持FDF模式。IRS(Bit 5):I2C模块复位/使能位。这是最重要的位之一。0-模块复位/禁用,1-模块使能。所有配置都应在IRS=0时进行,配置完成后置1以启动模块。STB(Bit 4): 起始字节模式。用于兼容一些需要更长时间检测START条件的古老从设备。启用后,会在真正的START条件和地址之前,先发送一个特殊的起始字节(0000 0001b)和一个虚拟应答时钟。
4.2 I2CSAR与I2CCNT:目标与长度
I2CSAR(从机地址寄存器): 当本设备作为主机时,存放你要通信的从机地址。根据I2CMDR.XA位,使用低7位(7位模式)或低10位(10位模式)。在7位模式下,高3位(9-7)必须写0。I2CCNT(数据计数寄存器): 在非重复模式(RM=0)下,指定要传输或接收的数据字节数。写入的值(1-65535)会被复制到内部计数器,每完成一个字节传输,内部计数器减1,但I2CCNT寄存器的值保持不变。当内部计数器减到0且STP=1时,产生STOP条件。特殊值0x0000代表65536。在重复模式(RM=1)下,此寄存器被忽略。
4.3 数据寄存器:I2CDXR与I2CDRR
I2CDXR(数据发送寄存器): CPU将待发送的数据写入此处。写入后,数据会被硬件自动搬运到发送移位寄存器I2CXSR中,并逐位在SDA上移出。如果使能了发送FIFO,此寄存器就是FIFO的入口。I2CDRR(数据接收寄存器): CPU从这里读取接收到的数据。当接收移位寄存器I2CRSR收满一个字节后,数据会自动拷贝到此寄存器。如果使能了接收FIFO,此寄存器就是FIFO的出口。关于数据对齐:当BC设置的数据位宽小于8位时,数据在I2CDRR中是右对齐的,高位是不确定的。同样,写入I2CDXR的数据也必须右对齐。
4.4 典型主机发送流程配置示例
假设我们要作为主机,向地址为0x50的7位地址EEPROM发送3个字节数据{0x00, 0x12, 0x34}。
void I2C_MasterWrite(uint16_t slaveAddr, uint8_t *data, uint16_t dataSize) { // 0. 等待总线空闲(可选,可通过查询I2CSTR状态实现) // while(I2caRegs.I2CSTR.bit.BB == 1); // 等待总线忙标志为0 // 1. 配置为主机、发送器、7位地址、非重复模式、8位数据 I2caRegs.I2CMDR.all = 0; I2caRegs.I2CMDR.bit.MST = 1; // 主机模式 I2caRegs.I2CMDR.bit.TRX = 1; // 发送模式 I2caRegs.I2CMDR.bit.XA = 0; // 7位地址 I2caRegs.I2CMDR.bit.RM = 0; // 非重复模式 I2caRegs.I2CMDR.bit.BC = 0; // 8位数据 I2caRegs.I2CMDR.bit.STP = 1; // 本次传输结束后产生STOP条件 I2caRegs.I2CMDR.bit.IRS = 1; // 使能I2C模块(假设时钟等已提前配好) // 2. 设置从机地址 I2caRegs.I2CSAR = slaveAddr; // 例如 0x50 // 3. 设置要发送的数据字节数 I2caRegs.I2CCNT = dataSize; // 本例为3 // 4. 启动传输(产生START条件) I2caRegs.I2CMDR.bit.STT = 1; // 5. 循环将数据写入I2CDXR for(uint16_t i = 0; i < dataSize; i++) { // 等待发送就绪标志 XRDY (在I2CSTR寄存器中) 置位 while(I2caRegs.I2CSTR.bit.XRDY == 0) { // 可加入超时处理 } I2caRegs.I2CDXR = data[i]; // 写入数据,硬件会自动发送 } // 6. 等待传输完成(STOP条件产生) // 可以通过等待I2CSTR.SCD(STOP条件检测)置位,或等待I2CMDR.STP位被硬件清零 while(I2caRegs.I2CMDR.bit.STP == 1) { // 等待STP位清零,表明STOP条件已产生并完成 } // 或者 while(I2caRegs.I2CSTR.bit.SCD == 0); // 等待STOP条件检测标志 // 7. 传输结束,模块自动回到从机模式(MST位已清零) }5. 中断、FIFO与高级功能配置
对于高效或复杂的通信,仅仅轮询状态标志是不够的。中断和FIFO能极大解放CPU。
5.1 中断管理:I2CISRC与I2CSTR
I2CISRC(中断源寄存器)的INTCODE字段(低3位)指明了当前触发I2C中断的事件。读取这个寄存器会自动清除INTCODE字段(对于某些事件,还会清除I2CSTR中对应的状态标志)。
常见的中断事件代码:
0x01: 仲裁丢失。多主机竞争总线时,本机仲裁失败。0x02: 无应答(NACK)检测。发送的地址或数据未收到从机的应答。0x03: 寄存器访问就绪(ARDY)。在非重复模式下,I2CCNT递减一次或I2CDXR/I2CDRR可访问时触发。0x04: 接收数据就绪(RRDY)。I2CDRR中有新数据可读。0x05: 发送数据就绪(XRDY)。I2CDXR为空,可写入新数据。0x06: STOP条件检测。0x07: 被寻址为从机(AAS)。
中断服务程序(ISR)典型结构:
__interrupt void I2CA_ISR(void) { uint16_t intSource = I2caRegs.I2CISRC.bit.INTCODE; switch(intSource) { case 0x04: // RRDY - 数据接收就绪 rxBuffer[rxIndex++] = I2caRegs.I2CDRR; // 读取数据 // ... 处理数据 break; case 0x05: // XRDY - 数据发送就绪 if(txIndex < txSize) { I2caRegs.I2CDXR = txBuffer[txIndex++]; // 写入下一个数据 } break; case 0x02: // NACK - 无应答错误 // 处理错误:重试、记录日志等 I2C_ErrorHandler(); break; case 0x01: // AL - 仲裁丢失 // 通常只需重新尝试发送 break; // ... 处理其他中断源 default: break; } // 清除PIE中断标志位(根据具体MCU的PIE模块操作) PieCtrlRegs.PIEACK.all = PIEACK_GROUP8; }5.2 FIFO功能配置:I2CFFTX与I2CFFRX
FIFO功能可以缓存多个数据,减少中断频率。TMS320x2806x的I2C FIFO深度为4级。
发送FIFO (I2CFFTX) 配置要点:
- 使能:
I2CFFEN位必须置1以启用整个I2C FIFO模式。 - 复位与使能:先置
TXFFRST=0复位FIFO指针,再置TXFFRST=1使能FIFO操作。 - 中断配置:
TXFFIL设置触发中断的FIFO空余级别。例如,设置为2,则当FIFO中数据量小于等于2(即空余位置大于等于2)时,TXFFINT标志置位。TXFFIENA用于使能该中断。 - 状态查询:
TXFFST位域指示当前FIFO中存有多少个数据。
一个常见的陷阱:手册Note指出,当使能FIFO操作并让I2C退出复位时,由于TXFFST初始为0(空),如果TXFFIL也设置为0,会立即触发发送FIFO中断。因此,建议在使能FIFO后,立即向TXFFINTCLR写1来清除这个可能误触发的标志。
接收FIFO (I2CFFRX) 配置要点:
- 与发送FIFO类似,通过
RXFFRST控制。 - 中断级别:
RXFFIL设置触发中断的FIFO数据量级别。例如,设置为2,则当FIFO中数据量大于等于2时,RXFFINT标志置位。 - 另一个陷阱:手册同样警告,由于
RXFFIL复位值为0,一旦使能接收FIFO,就会因为RXFFST >= 0而立即触发中断。解决方法:在设置RXFFRST=1使能FIFO的同一指令或之前,先设置好一个非零的RXFFIL值(例如1)。
FIFO模式下的数据搬运示例:
// 初始化发送FIFO I2caRegs.I2CFFTX.bit.I2CFFEN = 1; // 使能I2C FIFO模式 I2caRegs.I2CFFTX.bit.TXFFRST = 0; // 复位发送FIFO I2caRegs.I2CFFTX.bit.TXFFIL = 2; // 设置中断级别:当FIFO中数据<=2时触发中断 I2caRegs.I2CFFTX.bit.TXFFIENA = 1; // 使能发送FIFO中断 I2caRegs.I2CFFTX.bit.TXFFRST = 1; // 使能发送FIFO操作 I2caRegs.I2CFFTX.bit.TXFFINTCLR = 1; // 清除可能的初始中断标志 // 在发送FIFO中断服务程序中 if(I2caRegs.I2CFFTX.bit.TXFFINT == 1) { // 检查FIFO中还有多少空位 uint16_t emptySlots = 4 - I2caRegs.I2CFFTX.bit.TXFFST; for(int i=0; i<emptySlots && txIndex < txSize; i++) { I2caRegs.I2CDXR = txBuffer[txIndex++]; // 向FIFO填充数据 } I2caRegs.I2CFFTX.bit.TXFFINTCLR = 1; // 清除中断标志 }6. 实战调试技巧与常见问题排查
理论配置再完美,实际调不通也是白搭。下面分享几个我踩过坑后总结的实战技巧。
6.1 上电初始化和复位序列正确的初始化顺序是成功的一半:
- 将
I2CMDR.IRS位清零,使模块处于复位状态。 - 配置所有静态参数:
I2CPSC,I2CCLKL,I2CCLKH,I2COAR(自身地址,如果作从机),I2CEMDR等。 - 如果需要FIFO,配置
I2CFFTX和I2CFFRX,注意先复位后使能的顺序,以及初始中断标志的清除。 - 配置中断向量和使能PIE/CPU级中断(如果需要)。
- 将
I2CMDR.IRS位置1,使能模块。 - 在每次发起传输前,动态配置
I2CMDR(主从、收发、起止位等)、I2CSAR、I2CCNT。
6.2 通信失败排查清单当I2C通信没有反应时,可以按照以下步骤排查:
| 问题现象 | 可能原因 | 排查方法 |
|---|---|---|
| SCL/SDA线始终为高 | 模块未使能,或引脚复用配置错误。 | 1. 确认I2CMDR.IRS=1。2. 检查GPIO MUX寄存器,确保引脚已配置为I2C功能。 |
| SCL线被持续拉低 | 总线冲突、从设备忙、或I2CCLKL配置为0。 | 1. 用逻辑分析仪抓取波形,看是哪个设备在拉低。 2. 检查 I2CCLKL寄存器值是否为非零。3. 尝试逐个断开从设备,定位故障源。 |
| 主机发送START后无应答 | 从机地址错误、从机不存在、从机忙、总线电平或上拉电阻问题。 | 1. 核对从机设备地址(7位/10位,读写位)。 2. 用示波器测量START后的地址字节波形,看是否与预期一致。 3. 检查上拉电阻值(通常4.7kΩ-10kΩ),测量总线空闲时电压是否为VCC。 4. 从机是否供电正常,是否处于复位或休眠状态。 |
| 能收到应答,但数据错误 | 时钟速率过快、时序不满足从机要求、数据对齐问题。 | 1. 降低SCL时钟频率(增大I2CCLKL/H值)试试。2. 检查 I2CMDR.BC位设置,确认数据位宽。如果非8位,检查数据在I2CDXR是否右对齐。3. 用逻辑分析仪对比发送和接收的数据波形。 |
| 只能通信一次,后续失败 | 未正确等待STOP条件完成或状态未清。 | 在每次传输结束后,必须等待I2CMDR.STP位被硬件清零,或I2CSTR.SCD位置位,才能开始下一次传输。在中断服务程序中尤其要注意。 |
| FIFO模式下数据丢失 | FIFO中断级别设置��当,或未及时响应中断。 | 1. 检查TXFFIL/RXFFIL设置是否合理。对于发送,不宜设置过小导致中断过于频繁;对于接收,不宜设置过大导致FIFO溢出。2. 在中断服务程序中,检查 TXFFST/RXFFST,确保读写了正确数量的数据。3. 确认中断使能位和全局中断已打开。 |
| 多主机时仲裁丢失 | 多主机同时发起传输,本机竞争失败。 | 1. 检查I2CSTR.AL(仲裁丢失)标志。2. 在仲裁丢失中断服务程序中,应重新尝试发送。硬件会自动回到从机模式,需要软件重新置位 MST并发起START。 |
6.3 示波器/逻辑分析仪抓波形要点
- 触发设置:设置为下降沿触发,触发电平设在VCC的30%-70%处,触发源设为SDA线。这样可以稳定捕获到START条件(SCL高时SDA下降沿)。
- 观察内容:
- START条件:SCL高电平期间,SDA是否有明显的下降沿?
- 地址字节:START后的前8个时钟周期,SDA上的数据是否与目标从机地址(左移一位后加上R/W位)匹配?第9个时钟周期,SDA是否被从机拉低(ACK)?
- 数据字节:每个数据字节后的第9个时钟周期,是否有ACK?
- STOP条件:SCL高电平期间,SDA是否有明显的上升沿?
- 时钟波形:SCL的高电平和低电平时间是否与你的计算值相符?波形是否干净,上升/下降沿是否陡峭?
- 电平问题:如果波形上升沿缓慢,出现“圆角”,可能是上拉电阻过大或总线电容过大,尝试减小上拉电阻值(如从10kΩ换为4.7kΩ)。
6.4 软件超时处理在任何等待状态标志(如XRDY,RRDY,ARDY,STP清零)的循环中,务必加入超时机制。否则,一旦硬件故障或从机无响应,程序将永远死等。
#define I2C_TIMEOUT 10000 // 超时计数,根据CPU速度调整 uint32_t timeout = 0; while(I2caRegs.I2CSTR.bit.XRDY == 0) { timeout++; if(timeout > I2C_TIMEOUT) { // 超时处理:复位I2C模块、记录错误、尝试恢复等 I2C_TimeoutHandler(); return ERROR_TIMEOUT; } }调试I2C就像和老朋友沟通,需要耐心和细心。寄存器配置是语法,总线波形是语气,而稳定的通信则是最终达成的默契。希望这份详细的解析和实战指南,能帮你更快地建立起与I2C总线设备之间的这种默契。