1. I2C协议核心原理与CC323x实现概览
I2C,全称Inter-Integrated Circuit,是飞利浦半导体(现恩智浦)在1980年代设计的一种简单、高效的双线制串行通信总线。它之所以能在嵌入式领域经久不衰,核心在于其极简的硬件需求——仅需两根线(串行数据线SDA和串行时钟线SCL)就能构建一个多主多从的通信网络。对于资源受限的嵌入式系统,尤其是像TI CC323x这类集成了Wi-Fi的无线微控制器,I2C是连接各类传感器(如温湿度、加速度计)、EEPROM、RTC、IO扩展器等低速外设的首选方案。它完美平衡了通信速率、硬件复杂度和功耗。
CC323x的I2C模块是一个高度集成的硬件控制器,它把协议中繁琐的位时序、起始/停止条件生成、地址匹配、应答位处理等底层细节全部用硬件实现。这意味着开发者无需再用GPIO模拟时序,也无需担心时序精度问题,可以将精力完全集中在应用逻辑上。该模块支持标准模式(100 kbps)和快速模式(400 kbps),具备主从模式、中断驱动、DMA传输以及FIFO缓冲等高级特性。其寄存器组是软件与硬件I2C控制器交互的唯一窗口,理解并熟练配置这些寄存器,是从“能用”到“用好”I2C的关键跨越。
2. CC323x I2C寄存器架构深度解析
CC323x的I2C模块寄存器分为主控(Master)和从机(Slave)两套,地址从0x4002.0000开始映射。一个非常关键且容易混淆的点是地址偏移。从机寄存器的基地址在TivaWare驱动库中通常定义为0x800,但这是一个相对于I2C模块基地址的偏移量。因此,当你直接操作寄存器时,从机寄存器I2CSOAR的实际物理地址是0x4002.0000 + 0x800 = 0x4002.0800。务必注意这一点,避免地址计算错误导致配置失效。
所有寄存器操作都有一个重要的前提:必须首先使能I2C模块时钟。通过系统控制模块中的I2CLCKEN寄存器位使能后,还需要等待至少3个系统时钟周期,才能安全地对I2C模块的寄存器进行读写。这是许多初始化失败案例的根源——操之过急。
2.1 主控模式核心寄存器组
主控寄存器负责发起和控制整个I2C总线事务。我们可以将其分为几个功能集群来理解。
2.1.1 通信控制与状态寄存器(I2CMCS)
I2CMCS寄存器是主控模式的“大脑”,它集控制命令与状态反馈于一身。其位域功能复杂但逻辑清晰:
- 控制位(写操作):
RUN、START、STOP、ACK、BURST等。通过组合写入这些位,可以命令硬件执行“发送起始条件+发送地址+发送数据+发送停止条件”等一系列复杂操作。例如,写入0x0000.0007(STOP=1, START=1, RUN=1)即表示发起一次带停止条件的单字节传输。 - 状态位(读操作):
BUSBSY(总线忙)、ERROR(传输错误)、ARBLST(仲裁丢失)、DATACK(数据应答)等。软件需要通过轮询这些位来确认操作结果。BUSBSY位尤其重要,在发起任何新传输前,必须确认其为0(总线空闲)。
这个寄存器“一写一读”的特性要求开发者必须清晰地知道当前操作是配置命令还是查询状态。手册中的“Write Field Decoding”表格(表7-6)是理解各种命令组合的钥匙,它详细列出了在不同当前状态下,写入I2CMCS[6:0]各比特位组合所触发的硬件行为。
2.1.2 时钟与速率配置寄存器(I2CMTPR)
I2C通信的时序由SCL时钟严格定义。I2CMTPR寄存器(Timer Period Register)用于设置SCL时钟的频率。其计算依据公式:TPR = (System Clock / (2 * (SCL_LP + SCL_HP) * SCL_CLK)) - 1其中,SCL_LP和SCL_HP是硬件固定的SCL低电平和高电平周期数(通常为6和4),SCL_CLK是你期望的I2C时钟频率(如100000 Hz),System Clock是系统时钟频率(如80 MHz)。
代入公式:TPR = (80,000,000 / (2 * (6+4) * 100,000)) - 1 = 39。因此,要向I2CMTPR的TPR字段写入39(0x27)。这个寄存器的PULSEL字段还用于设置毛刺抑制宽度,在电气环境嘈杂时,适当增加此值(如设置为3个时钟)可以有效滤除SCL和SDA线上的短时干扰脉冲,提升通信可靠性。
2.1.3 数据与地址寄存器(I2CMDR, I2CMSA)
I2CMSA(Master Slave Address):低7位存放目标从机地址(A6-A0),第0位(R_S)指示传输方向(0为写/发送,1为读/接收)。例如,要向地址为0x3B的从机写入数据,则需写入(0x3B << 1) | 0x0 = 0x76。I2CMDR(Master Data):存放待发送或刚接收到的数据字节。这里有一个重要提示:该寄存器是读敏感的。这意味着在某些操作模式下,读取该寄存器的动作本身可能会触发硬件状态机的变化(如清除接收缓冲区标志)。因此,务必严格按照数据流顺序操作,避免随意读取。
2.2 从机模式核心寄存器组
从机寄存器的基地址偏移为0x800。其核心是I2CSCSR(Slave Control/Status Register)和I2CSDR(Slave Data Register)。
2.2.1 从机控制状态寄存器(I2CSCSR)
与主控的I2CMCS类似,I2CSCSR也是一个多功能寄存器。其关键位包括:
RREQ(读)/DA(写):当从机被寻址为接收器(主机要写数据给从机)时,硬件会自动置位RREQ。此时从机软件需要从I2CSDR读取数据,然后该位会自动清零。DA位则用于使能或禁用整个从机功能。TREQ(读)/TXFIFO(写):当从机被寻址为发送器(主机要从从机读数据)时,硬件置位TREQ。从机软件需要将待发送数据写入I2CSDR,然后该位清零。TXFIFO位用于使能发送FIFO。FBR(读)/RXFIFO(写):仅当RREQ=1时有效,指示接收到的第一个字节(通常是寄存器地址)已就绪。RXFIFO位用于使能接收FIFO。
从机的命令序列(如图7-13所示)清晰地描述了这些状态位的流转逻辑,是编写从机中断服务程序(ISR)的路线图。
2.2.2 从机地址与应答控制
I2CSOAR:设置从机的7位自身地址。CC323x支持一个备用地址I2CSOAR2,可通过OAR2EN位使能,这在需要响应两个不同地址的场景下非常有用。I2CSACKCTL:这是一个高级功能寄存器。当从机需要根据接收到的数据或命令的有效性来决定发送ACK(确认)还是NACK(非确认)时,可以通过设置ACKOEN和ACKOVAL位来覆盖硬件的自动应答行为。例如,如果从机接收到的命令码非法,它可以置ACKOEN=1和ACKOVAL=1,从而在下一次应答周期发送一个NACK信号给主机。
2.3 中断与FIFO机制
2.3.1 中断处理流程
CC323x的I2C中断系统设计精良,分为三层:
- 原始中断状态(I2CMRIS/I2CSRIS):硬件事件发生时,对应位置1。
- 中断掩码(I2CMIMR/I2CSIMR):决定哪些原始中断可以向上传递。1为使能,0为屏蔽。
- 已屏蔽中断状态(I2CMMIS/I2CSMIS):这是最终提交给CPU中断控制器的状态。只有当
RIS位和对应的IM位同时为1时,MIS位才为1,触发中断。
中断服务程序的标准流程是:进入ISR后,首先读取I2CMMIS或I2CSMIS寄存器判断中断源,处理相应事件(如从FIFO读/写数据),然后向I2CMICR或I2CSICR寄存器的对应位写1来清除中断标志。切记,只能通过写ICR寄存器来清除中断,直接写RIS或MIS寄存器是无效的。
2.3.2 FIFO操作与DMA支持
FIFO(先入先出缓冲区)是提升吞吐量、降低CPU中断频率的利器。相关寄存器集中在0xF00偏移地址附近。
I2CFIFOCTL:配置FIFO。RXTRIG/TXTRIG设置触发中断的水位线(例如,RX FIFO中有4个数据时触发接收中断)。RXASGNMT/TXASGNMT决定FIFO分配给主控还是从机使用,这在主从一体应用中需谨慎配置。RXFLUSH/TXFLUSH位用于清空FIFO。I2CFIFOSTATUS:实时反映FIFO状态,如空、满、高于触发水位等。I2CFIFODATA:读写FIFO数据的端口。当使能FIFO后,数据操作应通过此寄存器而非I2CMDR/I2CSDR。
一个重要警告:手册明确指出,应用程序不应在连续的事务中,在I2CSDR寄存器与TX FIFO(通过I2CFIFODATA)之间切换使用。这意味着你一旦决定使用FIFO模式进行一批数据传输,在这批传输完成前,都应使用FIFO接口。混合使用会导致不可预知的行为。
DMA的使能也在I2CFIFOCTL中(DMARXENA/DMATXENA)。当使能DMA且FIFO触发条件满足时,硬件会自动发起DMA请求,将数据从FIFO搬移到内存或反之,极大解放CPU。
3. 主从模式配置与数据传输实战
理解了寄存器,我们通过两个具体场景来串联配置流程:主控发送和从机接收。
3.1 主控模式单字节发送配置详解
假设系统时钟80MHz,目标SCL为100kHz,向地址0x3B的从机发送一个字节数据0x5A。
步骤1:硬件与时钟初始化
// 1. 使能I2C模块时钟 (假设系统控制寄存器地址为 SYSCTL_BASE) HWREG(SYSCTL_BASE + I2CLCKEN_OFFSET) |= 0x1; // 使能I2C时钟 // 2. 等待至少3个系统时钟周期。通常用一个小延时循环。 delay_cycles(10); // 3. 配置GPIO引脚复用为I2C功能,并设置为开漏模式(必须!) HWREG(GPIO_PAD_CONFIG_REG) |= (I2C_FUNC_SEL << PIN_MUX_SHIFT); HWREG(GPIO_PAD_CONFIG_REG) |= (1 << IODEN_BIT); // 使能开漏步骤2:I2C主控模块初始化
#define I2C0_BASE 0x40020000 // 4. 主控功能使能,并确保从机模式关闭(如果不需要) HWREG(I2C0_BASE + I2CMCR_OFFSET) = 0x10; // MFE=1, SFE=0, LPBK=0 // 5. 配置SCL时钟周期:TPR = (80M / (2*10*100k)) -1 = 39 HWREG(I2C0_BASE + I2CMTPR_OFFSET) = 39; // 写入TPR值 // 6. (可选)配置中断。例如,使能传输完成中断。 HWREG(I2C0_BASE + I2CMIMR_OFFSET) = 0x01; // IM=1, 使能主中断步骤3:执行单字节发送事务
// 7. 设置从机地址和方向(写) uint32_t slave_addr = 0x3B; HWREG(I2C0_BASE + I2CMSA_OFFSET) = (slave_addr << 1) | 0x0; // R/S=0, 写 // 8. 准备要发送的数据 HWREG(I2C0_BASE + I2CMDR_OFFSET) = 0x5A; // 9. 发起传输:生成START,运行,并在结束后生成STOP // I2CMCS: [BUSBSY, CLKTO, ERROR, ADRACK, DATACK, ARBLST, IDLE, ...] // 我们写入控制位:STOP=1, START=1, RUN=1 -> 0x07 HWREG(I2C0_BASE + I2CMCS_OFFSET) = 0x00000007; // 10. 轮询等待传输完成(或等待中断) while (HWREG(I2C0_BASE + I2CMCS_OFFSET) & 0x01) { // BUSY位为1,表示控制器忙 } // 11. 检查错误 uint32_t status = HWREG(I2C0_BASE + I2CMCS_OFFSET); if (status & 0x02) { // ERROR位 // 处理错误:地址无应答(ADRACK)或数据无应答(DATACK) if (status & 0x04) { // ADRACK // 从机地址错误或设备不存在 } // ... 其他错误处理 }关键点:步骤9中写入
I2CMCS的0x07是一个复合命令。硬件会依次执行:在总线上产生START条件 -> 发送I2CMSA中的地址帧(含R/W位)-> 发送I2CMDR中的数据字节 -> 产生STOP条件。整个过程由硬件状态机自动完成,软件只需触发并等待。
3.2 从机模式中断驱动接收示例
配置从机地址为0x3B,使能接收中断,在中断服务程序中读取数据。
步骤1:从机基础配置
// 1. 使能I2C时钟、配置GPIO(同上,略) // 2. 配置从机自身地址 HWREG(I2C0_BASE + I2CSOAR_OFFSET) = 0x3B; // 7位地址,无需移位 // 3. 使能从机功能,并可选使能接收FIFO uint32_t csr_value = 0; csr_value |= 0x01; // DA=1, 使能设备 // csr_value |= 0x04; // RXFIFO=1, 使能接收FIFO (如果使用FIFO) HWREG(I2C0_BASE + I2CSCSR_OFFSET) = csr_value; // 4. 使能从机数据中断 HWREG(I2C0_BASE + I2CSIMR_OFFSET) = 0x01; // DATAIM=1 // 5. 全局使能I2C模块中断(需配合NVIC设置,此处略)步骤2:从机中断服务程序(ISR)框架
void I2C0_Slave_IRQHandler(void) { uint32_t mis_status = HWREG(I2C0_BASE + I2CSMIS_OFFSET); if (mis_status & 0x01) { // DATAMIS 中断 // 读取从机状态寄存器,判断是接收请求还是发送请求 uint32_t scsr = HWREG(I2C0_BASE + I2CSCSR_OFFSET); if (scsr & 0x01) { // RREQ = 1, 主机要写数据给本从机(接收模式) // 检查是否是第一个字节(可能是寄存器地址) if (scsr & 0x04) { // FBR = 1 g_reg_addr = HWREG(I2C0_BASE + I2CSDR_OFFSET); // 读取第一个字节(地址) } else { g_rx_data = HWREG(I2C0_BASE + I2CSDR_OFFSET); // 读取数据字节 // 处理数据,例如存入缓冲区 g_data_buffer[g_reg_addr] = g_rx_data; } } if (scsr & 0x02) { // TREQ = 1, 主机要从本从机读数据(发送模式) // 根据请求(可能是上一步收到的寄存器地址)准备数据 uint8_t data_to_send = g_data_buffer[g_current_addr]; HWREG(I2C0_BASE + I2CSDR_OFFSET) = data_to_send; } // 清除从机数据中断标志 HWREG(I2C0_BASE + I2CSICR_OFFSET) = 0x01; // 写1清除DATAIC } // 处理其他中断源,如STOP、START... if (mis_status & 0x04) { // STOPMIS // 主机发送了STOP条件,一次传输结束,可进行后续处理 HWREG(I2C0_BASE + I2CSICR_OFFSET) = 0x04; // 清除STOPIC } }注意:在从机接收模式下,读取
I2CSDR寄存器的操作会清除RREQ状态位。因此,ISR中必须在判断RREQ为1后及时读取数据,否则硬件可能会因等待超时而产生错误。
4. 高级功能与性能优化技巧
4.1 突发传输与FIFO高效使用
对于需要连续读写多个字节的场景(如读取传感器的一批数据),使用单字节模式效率低下。应使用突发传输模式。
主控突发读操作流程:
- 设置
I2CMBLEN寄存器为要读取的字节数N。 - 在
I2CMCS寄存器中,设置BURST=1,ACK=1(最后一个字节前自动发送ACK),START=1,RUN=1,但注意RUN和BURST位互斥,在突发模式下RUN应写0。具体组合需查表7-6,例如对于接收突发,可能是BURST=1, ACK=1, START=1, RUN=0。 - 传输启动后,硬件会自动处理时钟和应答。数据会通过FIFO进出。
- 通过查询
I2CFIFOSTATUS或使能RX FIFO请求中断(RXIM)来及时读取I2CFIFODATA中的数据。 - 传输完成后,
I2CMBCNT寄存器会递减至0。如果中途因NACK提前终止,此寄存器会显示已传输的字节数。
FIFO触发水位设置技巧:I2CFIFOCTL中的RXTRIG和TXTRIG。对于接收,如果CPU处理速度较慢,可以设置较高的触发值(如6),让FIFO积累更多数据再产生中断,减少中断次数。对于发送,如果数据产生速度慢,可以设置较低的触发值(如1),让FIFO一有空闲就请求数据,避免总线空闲等待。
4.2 时钟低超时与总线监控
I2CMCLKOCNT寄存器用于设置SCL线被从机拉低(时钟拉伸)的最大时间。这是一个重要的可靠性特性。如果某个从机设备故障,长时间拉低SCL,会导致整个总线挂起。通过设置一个合理的超时计数值(例如,对应10ms),当SCL低电平持续时间超过此限,主控的CLKTO状态位会被置位,并可产生中断,让主控有机会从错误中恢复(例如复位总线)。
I2CMBMON寄存器允许软件直接读取SCL和SDA线的电平状态。这在调试总线故障时极其有用。例如,你可以写一个简单的函数,在通信失败后打印出I2CMBMON的值,如果SCL和SDA都是高,说明总线物理连接可能正常但设备无应答;如果SDA一直为低,可能是总线仲裁失败或设备死锁拉低了数据线。
4.3 仲裁与多主支持
I2C是多主总线。当多个主设备同时发起传输时,硬件通过“线与”机制进行仲裁:谁先发送一个高电平而其他主设备发送低电平,谁就丢失仲裁。CC323x的I2CMCS寄存器中的ARBLST位会指示仲裁丢失事件。在支持多主的应用中,你的主控代码在发起传输前,应检测BUSBSY位确认总线空闲。一旦发生仲裁丢失,硬件会自动切换到从机接收模式,并尝试接收赢得仲裁的主机发来的数据。你的软件应检查ARBLST位,如果置位,则中止当前传输计划,并可能需要在总线空闲后重试。
5. 调试实战与常见问题排查
基于寄存器开发I2C,遇到问题很常见。以下是一个系统化的排查清单:
问题1:通信完全无响应,SCL/SDA无波形。
- 检查清单:
- 时钟与电源:确认I2C模块时钟(
I2CLCKEN)已使能,并等待了足够延时。 - GPIO配置:确认引脚已正确复用为I2C功能(
CONFMODE),并且开漏输出使能(IODEN)已设置。这是最容易被忽略的一点!I2C总线要求开漏,如果不使能,MCU可能无法正确释放总线。 - 上拉电阻:确认SCL和SDA线上有适当的上拉电阻(通常4.7kΩ-10kΩ)。CC323x内部可能有可配置的上拉,但外部上拉通常更可靠。
- 主/从使能:确认
I2CMCR寄存器中的MFE或SFE位已正确设置。
- 时钟与电源:确认I2C模块时钟(
问题2:主机能发出起始条件和地址,但收不到应答(NACK)。
- 排查步骤:
- 地址与方向:用逻辑分析仪或示波器抓取波形,确认发送的7位地址和R/W位是否正确。注意
I2CMSA寄存器中地址是左移1位后的值。 - 从机状态:确认从机设备已上电,工作正常,且地址匹配。
- 速率问题:主机SCL时钟是否过快?尝试降低
I2CMTPR的值以降低波特率。某些低速从机在高速下可能无法响应。 - 电气问题:总线电容是否过大导致边沿太缓?缩短走线或减小上拉电阻值。
- 地址与方向:用逻辑分析仪或示波器抓取波形,确认发送的7位地址和R/W位是否正确。注意
问题3:能收到应答,但数据错误或中断不触发。
- 深入检查:
- 中断配置:确认NVIC中已使能I2C中断,并且
I2CMIMR/I2CSIMR中的相应中断掩码位已打开。 - 中断清除:在ISR中,是否正确地向
I2CMICR/I2CSICR的对应位写1来清除中断?这是最常见的ISR错误,忘记清除会导致中断持续触发。 - FIFO与数据寄存器混淆:如果使能了FIFO,是否还在操作
I2CMDR/I2CSDR?应统一使用I2CFIFODATA。 - 时序竞争:在轮询
BUSBSY或ERROR位时,是否给了硬件足够的时间?在写入命令寄存器后,立即读取状态寄存器可能得到旧值。建议在关键操作后加入短暂延时再查询。
- 中断配置:确认NVIC中已使能I2C中断,并且
问题4:使用DMA时数据混乱。
- 核心要点:
- DMA与FIFO:DMA是与FIFO配合工作的。确保
I2CFIFOCTL中的DMARXENA/DMATXENA已使能,并且RXASGNMT/TXASGNMT配置正确。 - DMA通道配置:正确配置DMA源/目标地址、传输数据宽度(应为字节)、传输数量。确保DMA传输数量与
I2CMBLEN设置或实际需求匹配。 - 中断协调:DMA完成中断和I2C传输完成中断可能都需要处理。清楚每个中断的意义:DMA中断表示数据搬运完成,I2C中断表示总线事务完成。
- DMA与FIFO:DMA是与FIFO配合工作的。确保
调试利器:I2CMBMON与I2CMCS状态位。当通信异常时,第一时间读取并打印这两个寄存器的值。I2CMBMON告诉你总线物理状态,I2CMCS的ERROR、ARBLST、DATACK、ADRACK等位直接指向问题根源。结合逻辑分析仪查看实际波形,几乎可以定位所有协议层问题。
最后,务必参考TI官方提供的TivaWare或SimpleLink SDK中的I2C驱动源码。虽然直接操作寄存器让你对硬件有绝对控制力,但官方驱动提供了经过充分测试的API和用例,是学习和验证的绝佳资料。在实际项目中,往往在底层寄存器理解的基础上,结合使用成熟的驱动库,才能在开发效率和系统可靠性上取得最佳平衡。