I2C从机通信深度解析:从协议到CC26x2寄存器实战

发布时间:2026/7/26 6:47:23
I2C从机通信深度解析:从协议到CC26x2寄存器实战 1. I2C从机通信从协议到寄存器操作的深度解析在嵌入式开发中I2C总线因其简洁的两线制SDA数据线和SCL时钟线和灵活的多主多从架构成为了连接微控制器与各类传感器、存储器、RTC等外设的首选协议。然而很多开发者对I2C的理解往往停留在“主设备发起从设备响应”的层面对于从设备内部如何精准响应、状态如何管理、寄存器如何协同工作常常是“知其然而不知其所以然”。这直接导致了调试时面对通信失败、数据错乱等问题无从下手。今天我们就以德州仪器TICC13x2/CC26x2系列无线MCU的I2C模块为例深入剖析其从机Slave模式的命令序列与寄存器配置把这块硬骨头啃透。无论你是正在调试一个温湿度传感器还是试图让MCU作为从机与另一个主处理器通信理解这些底层机制都将让你事半功倍。I2C从机工作的核心可以比喻为一个训练有素的餐厅服务员。主设备是顾客发出点餐写或结账读的指令。从机服务员必须时刻监听广播总线上的起始条件识别是否是呼叫自己地址匹配然后根据指令准备数据处理接收请求或提供数据处理发送请求。CC13x2/CC26x2的I2C模块硬件为我们封装了监听、地址匹配、时钟拉伸等复杂时序我们开发者要做的就是通过配置一系列寄存器告诉这个“硬件服务员”我们的地址是什么以及当它接到“订单”后我们该如何处理。这个过程就是通过操作SOAR、SSTAT、SCTL、SDR等一系列寄存器来实现的。接下来我们将拆解这个“服务员”的工作手册让你不仅能配置更能理解每一个配置动作背后的意图。2. 核心思路状态机驱动下的从机响应模型要理解CC13x2/CC26x2的I2C从机操作必须建立起一个清晰的状态机模型。官方文档中的命令序列图Figure 23-13并非简单的操作步骤而是揭示了硬件内部状态迁移与软件响应之间的交互逻辑。这个模型的核心是“请求-响应”机制由硬件状态位TREQ, RREQ驱动软件通过轮询或中断方式感知并处理。2.1 从机工作状态机解析从机的生命周期始于空闲Idle状态。此时I2C模块的从机部分处于监听模式持续扫描SDA和SCL线等待主设备发送起始START条件。一旦检测到START硬件便开始接收紧随其后的7位或10位从机地址以及读写位。这个过程完全由硬件自动完成无需软件干预。当地址与我们在SOAR寄存器中设置的自身地址匹配且读写位指示为“写”即主设备要向从机写数据时硬件会自动回应一个ACK应答信号并将状态寄存器SSTAT中的RREQ接收请求位置1。同时如果这是该次传输的第一个数据字节FBR首字节接收位也会被置1。此时硬件会通过“时钟拉伸”Clock Stretching主动将SCL线拉低暂停总线时钟等待软件处理。整个系统就从“空闲监听”状态进入了“数据待处理”状态。同理如果地址匹配后的读写位指示为“读”即主设备要从从机读数据硬件在回应ACK后会将SSTAT寄存器中的TREQ发送请求位置1并同样启动时钟拉伸。软件的任务就是在检测到RREQ或TREQ置位后及时读取或写入SDR从机数据寄存器以完成本次字节的传输。完成操作后硬件会自动清除对应的请求位并释放时钟线总线控制权交还给主设备从机回到监听状态等待下一个字节或停止条件。这个由硬件状态位驱动、软件及时响应的循环构成了I2C从机通信的基石。2.2 轮询与中断模式的选择考量如何及时检测到RREQ或TREQ被置位通常有两种策略轮询Polling和中断Interrupt。轮询模式简单直接程序在一个循环中不断读取SSTAT寄存器检查TREQ和RREQ位。这种方式的优点是实现简单不依赖中断控制器在简单的单任务系统中足够用。但其缺点也显而易见CPU被长时间占用在空循环上功耗高且无法及时响应其他事件在复杂的多任务或低功耗应用中不适用。中断模式则是更高效的选择。通过配置SIMR从机中断屏蔽寄存器我们可以使能DATAIM数据中断、STARTIM起始条件中断和STOPIM停止条件中断。当对应事件发生时硬件会设置SRIS原始中断状态寄存器中的相应位如果该中断在SIMR中被使能则会触发一个控制器级中断CPU可以立即跳转到中断服务程序ISR中进行处理。在ISR中我们通过读取SMIS屏蔽中断状态寄存器或SRIS来确定具体的中断源并进行相应操作最后写SICR中断清除寄存器来清除中断标志。中断模式解放了CPU允许其在通信间隙处理其他任务或进入低功耗模式是实际工程中的首选。选择轮询还是中断需要根据系统的实时性要求、功耗约束以及整体软件架构来权衡。3. 从机寄存器配置详解与实战操作理解了状态机模型后我们进入实战环节逐一拆解每个关键寄存器的功能、配置方法及背后的设计逻辑。CC13x2/CC26x2的I2C从机寄存器主要分为几类地址配置、状态控制、数据缓冲和中断管理。3.1 身份标识SOARSlave Own Address RegisterSOAR寄存器是I2C从机在总线上的“身份证”其位[6:0]OAR字段用于设置7位从机地址。这是从机能够被主设备寻址的唯一依据。配置非常简单只需将目标地址例如0x68写入该寄存器即可。需要注意的是I2C协议规定地址0x00通常用作广播地址而0x78到0x7F范围内的部分地址被保留应避免使用。在配置时地址需要左对齐放置在这7个比特位中。例如要设置地址为0x3A二进制0111010则写入SOAR寄存器的值应为0x3A。这个操作通常在从机初始化阶段完成一次之后除非地址需要动态改变否则无需再写。注意SOAR寄存器只支持7位地址模式。如果系统中需要使用10位地址需要查阅具体MCU的数据手册看是否支持以及如何配置CC13x2/CC26x2的I2C模块通常以7位地址模式为主进行描述。3.2 状态与控制的核心SSTAT与SCTL这是一个非常特殊且关键的寄存器对它们共享同一个物理地址偏移0x04。对该地址进行“读”操作访问的是SSTAT状态寄存器进行“写”操作访问的则是SCTL控制寄存器。这种设计节省了地址空间但要求程序员在代码中必须清晰区分读写操作。SSTATSlave Status Register - 读操作这个寄存器是我们感知从机状态的眼睛。我们需要重点关注三个位RREQ (位0): 接收请求。当主设备寻址本从机进行写操作并且一个数据字节已接收就绪等待软件读取时此位被硬件置1。此时接收到的数据存放在SDR寄存器中。TREQ (位1): 发送请求。当主设备寻址本从机进行读操作并且请求从机发送一个数据字节时此位被硬件置1。此时软件需要将待发送的数据写入SDR寄存器。FBR (位2): 首字节接收。仅当RREQ1时此位有效。它指示当前SDR中的数据是否是紧随地址字节之后的第一个数据字节。这对于解析基于命令-数据的协议非常有用例如第一个字节是命令码后续字节是数据。读取SDR寄存器后此位会自动清零。SCTLSlave Control Register - 写操作这个寄存器目前只有一个有效控制位DA位0设备激活。将该位写1使能I2C从机功能写0则禁用从机功能。在初始化序列中在配置好地址后我们需要通过写SCTL向地址0x04写入数据来将DA位置1从而激活从机使其开始监听总线。这是一个开关性质的配置。3.3 数据交换的枢纽SDRSlave Data RegisterSDR寄存器是数据进出从机的唯一通道它是一个8位的寄存器。其行为是双向的当从机处于接收状态RREQ1时SDR是只读的从软件视角。硬件将接收到的数据字节放入其中。软件应读取SDR以获取数据读取操作会间接清除RREQ状态或为清除创造条件配合硬件自动完成。当从机处于发送状态TREQ1时SDR是可写的。软件需要将待发送的字节写入SDR。写入操作会间接清除TREQ状态并使硬件开始将数据位驱动到SDA线上。这里有一个关键机制时钟拉伸Clock Stretching。当RREQ或TREQ被置1后从机硬件会自动拉低SCL线迫使主设备等待。这个等待会一直持续到软件执行了对应的读或写SDR操作为止。这个机制为软件提供了宝贵的响应时间确保数据不会因为软件处理不及时而丢失或出错。因此在中断服务程序或轮询处理中一旦检测到请求位必须尽快完成对SDR的访问。3.4 中断管理四件套SIMR, SRIS, SMIS, SICR对于采用中断模式的应用程序需要熟练操作这四个寄存器来管理中断生命周期。SIMR (Slave Interrupt Mask Register) - 中断屏蔽用于开启或关闭特定中断源向CPU的传递。位0 (DATAIM) 控制数据请求/接收中断位1 (STARTIM) 控制检测到起始条件中断位2 (STOPIM) 控制检测到停止条件中断。通常我们最关心数据中断所以会将DATAIM置1。STARTIM和STOPIM可用于监控总线活动或实现更复杂的协议状态机。SRIS (Slave Raw Interrupt Status Register) - 原始中断状态这是一个只读寄存器反映中断事件的原始状态不受SIMR屏蔽的影响。即使DATAIM为0屏蔽当数据事件发生时SRIS.DATARIS位仍然会被置1。这个寄存器常用于调试查看所有发生过的硬件事件。SMIS (Slave Masked Interrupt Status Register) - 屏蔽后中断状态这是实际触发CPU中断的标志寄存器。它的值是SRIS SIMR按位与的结果。只有当SRIS中的事件发生且SIMR中对应中断被使能SMIS中的对应位才会置1进而向CPU产生中断请求。在中断服务程序ISR中我们首先读取SMIS来判断是哪个使能的中断触发了本次调用。SICR (Slave Interrupt Clear Register) - 中断清除这是一个只写寄存器读取无意义。向其中的STOPIC、STARTIC或DATAIC位写1可以清除对应的SRIS和SMIS状态位。这是清除中断标志、防止同一中断重复触发的标准操作。必须在中断处理例程的末尾执行此操作。中断处理的标准流程是进入ISR → 读取SMIS确定中断源 → 根据中断源处理业务如读/写SDR→ 向SICR对应位写1以清除中断标志 → 退出ISR。4. 从机命令序列的代码实现与流程剖析现在我们将理论转化为代码结合官方提供的命令序列图实现一个完整的、可工作的I2C从机。我们假设场景是CC26x2作为从机地址设为0x50需要能够接收主设备发来的数据也能根据请求返回数据。我们采用中断模式以提高效率。4.1 从机初始化流程初始化是为从机通信搭建舞台任何差错都可能导致后续通信完全失败。以下是基于TI驱动库DriverLib或直接寄存器操作的初始化步骤详解使能外设时钟任何对I2C模块寄存器的访问前提是其时钟域已被使能。通过配置PRCM电源与时钟管理模块的相关寄存器来实现。使用DriverLib函数最为便捷#include ti/drivers/pin/PINCC26XX.h #include ti/drivers/I2C.h // 使用DriverLib #include ti/devices/cc13x2_cc26x2/driverlib/prcm.h #include ti/devices/cc13x2_cc26x2/driverlib/ioc.h #include ti/devices/cc13x2_cc26x2/driverlib/i2c.h // 1. 使能I2C外设时钟在运行模式下 PRCMPeripheralRunEnable(PRCM_PERIPH_I2C0); // 如果需要支持睡眠模式也需相应使能 // PRCMPeripheralSleepEnable(PRCM_PERIPH_I2C0); // PRCMPeripheralDeepSleepEnable(PRCM_PERIPH_I2C0); // 加载时钟设置 PRCMLoadSet();这一步相当于给I2C模块接通了电源。PRCMLoadSet()函数调用是必须的它使能时钟的配置生效。配置I/O引脚复用将MCU的物理引脚映射到I2C的SDA和SCL功能上。这需要通过IOCI/O控制器模块配置。CC13x2/CC26x2的引脚功能是高度可配置的。// 2. 配置I2C引脚。假设使用I2C0SDA接DIO5 SCL接DIO6具体请查手册 // 将DIO5配置为I2C SDA 开启上拉电阻I2C总线要求 IOCPinTypeI2c(I2C0_BASE, IOID_5, IOID_6); // 或者使用更底层的DriverLib函数分别配置 // IOCPortConfigureSet(IOID_5, IOC_PORT_I2C_I2C0_SDA, IOC_I2C_SDA_EN); // IOCPortConfigureSet(IOID_6, IOC_PORT_I2C_I2C0_SCL, IOC_I2C_SCL_EN);引脚配置错误是最常见的通信失败原因之一。务必确认数据手册中指定引脚支持I2C功能并且上拉电阻已正确启用通常MCU内部可配置但总线上可能仍需外部上拉。配置从机地址与使能这是从机身份的核心设置。// 3. 初始化I2C模块为从机模式复位后默认状态此步可省略但显式设置更安全 // 先确保主模式关闭从模式开启 HWREG(I2C0_BASE I2C_O_MCR) 0; // 假设先清零MCR // 或者使用I2CMasterControl(I2C0_BASE, 0); // 禁用Master // 4. 设置从机自身地址到SOAR寄存器 (偏移0x0)。设置地址为0x50。 HWREG(I2C0_BASE I2C_O_SOAR) 0x50; // 写入7位地址0x50 // 5. 使能从机功能通过写SCTL寄存器偏移0x4的DA位为1。 // 注意向偏移0x4地址写入是操作SCTL寄存器。 HWREG(I2C0_BASE I2C_O_SCTL) 0x01; // 仅设置DA位为1 // 6. 可选但推荐配置中断 // 使能数据中断接收或发送请求 HWREG(I2C0_BASE I2C_O_SIMR) I2C_SIMR_DATAIM; // 如果需要也可以使能START/STOP中断 // HWREG(I2C0_BASE I2C_O_SIMR) I2C_SIMR_DATAIM | I2C_SIMR_STARTIM | I2C_SIMR_STOPIM; // 在NVIC嵌套向量中断控制器中使能I2C中断 IntEnable(INT_I2C0);至此一个最基本的、采用中断响应的I2C从机就初始化完成了。它现在正在监听总线等待地址0x50的呼叫。4.2 中断服务程序ISR的实现逻辑中断服务程序是响应总线请求的“事件处理中心”。它的执行必须快速、准确并且要妥善清除中断标志。// 全局变量用于在ISR和主程序间传递数据 volatile uint8_t g_i2cReceivedData 0; volatile uint8_t g_i2cDataToSend 0xAA; // 示例待发送数据 volatile bool g_dataReady false; // I2C0中断服务程序 void I2C0_IRQHandler(void) { uint32_t intStatus; // 1. 读取屏蔽后的中断状态寄存器(SMIS)确定中断来源 intStatus HWREG(I2C0_BASE I2C_O_SMIS); // 2. 处理数据中断接收或发送请求 if (intStatus I2C_SMIS_DATAMIS) { // 读取从机状态寄存器(SSTAT)判断是接收请求还是发送请求 uint32_t slaveStatus HWREG(I2C0_BASE I2C_O_SSTAT); if (slaveStatus I2C_SSTAT_RREQ) { // RREQ1: 主设备正在写入数据从机需要读取 // 检查是否是第一个字节FBR位可能有用 // uint8_t isFirstByte (slaveStatus I2C_SSTAT_FBR) ? 1 : 0; // 从SDR寄存器读取数据 g_i2cReceivedData HWREG(I2C0_BASE I2C_O_SDR) 0xFF; g_dataReady true; // 通知主循环数据已收到 // 注意读取SDR操作本身会帮助硬件清除RREQ状态 } else if (slaveStatus I2C_SSTAT_TREQ) { // TREQ1: 主设备正在读取数据从机需要写入 // 将待发送数据写入SDR寄存器 HWREG(I2C0_BASE I2C_O_SDR) g_i2cDataToSend; // 注意写入SDR操作本身会帮助硬件清除TREQ状态 } // 清除数据中断标志 HWREG(I2C0_BASE I2C_O_SICR) I2C_SICR_DATAIC; } // 3. 处理START条件中断如果需要 if (intStatus I2C_SMIS_STARTMIS) { // 检测到总线START条件可以用于监控或重置内部状态机 // ... HWREG(I2C0_BASE I2C_O_SICR) I2C_SICR_STARTIC; } // 4. 处理STOP条件中断如果需要 if (intStatus I2C_SMIS_STOPMIS) { // 检测到总线STOP条件标志一次传输结束 // ... HWREG(I2C0_BASE I2C_O_SICR) I2C_SICR_STOPIC; } }这个ISR清晰地展示了处理流程先读SMIS判断中断源然后根据SSTAT的状态位决定是执行读SDR还是写SDR操作最后写入SICR清除对应的中断标志。g_dataReady标志位是一个简单的信号量用于在主循环中通知有新数据到达。4.3 主程序中的协同工作主程序或任务需要与ISR协同工作。它负责初始化和提供待发送数据并处理接收到的数据。int main(void) { // 硬件初始化时钟、GPIO等 Board_initGeneral(); // 初始化I2C从机调用前面描述的初始化函数 i2cSlaveInit(); // 准备一些初始数据用于当主设备读取时发送 g_i2cDataToSend 0x55; while(1) { // 主循环处理其他任务 // ... // 检查是否有新接收到的数据 if(g_dataReady) { g_dataReady false; // 处理接收到的数据 g_i2cReceivedData // 例如点亮LED通过串口打印等 processReceivedData(g_i2cReceivedData); // 可选更新待发送数据用于下一次主设备读取 g_i2cDataToSend g_i2cReceivedData 1; } // 低功耗处理如果没有其他任务可以进入低功耗模式中断会自动唤醒CPU // Power_sleep(); } }这个主循环结构非常典型。在低功耗应用中当g_dataReady为false且无其他任务时CPU可以进入睡眠模式。I2C总线活动产生的中断会将CPU唤醒进入ISR处理数据设置g_dataReady标志CPU醒来后主循环检测到该标志并进行处理之后可能再次休眠。这种“中断驱动事件处理”的模式能极大降低系统平均功耗。5. 深度避坑指南与高级调试技巧即使按照手册和示例代码配置在实际硬件调试中你依然可能会遇到各种诡异问题。下面这些“坑”是我和很多同行用时间和头发换来的经验。5.1 时钟拉伸与超时处理主从设备的握手陷阱时钟拉伸是从机控制时序的关键机制但也可能成为死锁的源头。当从机的TREQ或RREQ置1后硬件会拉低SCL。如果软件因为某种原因如中断被禁用、优先级太低、死循环未能及时读/写SDRSCL将一直被拉低导致主设备等待超时整个总线挂死。排查与解决确保中断响应及时检查I2C中断的优先级是否被其他长时间中断阻塞。在RTOS中确保ISR执行时间尽可能短复杂处理交给任务。主设备侧增加超时一个健壮的主设备驱动程序应该在对从机发起读写操作时加入SCL线超时检测。如果SCL被拉低超过一定时间如10ms主设备应主动复位总线发送一个STOP条件或者重新初始化I2C模块从而释放从机。从机侧看门狗在从机软件中可以考虑添加一个看门狗定时器。如果从机代码卡死在某个状态无法响应I2C请求看门狗会复位MCU虽然粗暴但能保证系统恢复。更精细的做法是在一个高优先级定时器中断里检查SCL线状态如果发现被自己拉低太久可以尝试软件复位I2C从机模块先禁用再重新使能。5.2 共享地址寄存器SSTAT/SCTL的访问冲突SSTAT和SCTL共享同一地址读写代表不同寄存器。这是一个需要极度小心的地方。考虑以下场景在中断服务程序中你需要先读SSTAT判断状态然后根据状态写SDR最后可能还要写SCTL进行某些控制虽然不常见。如果在这期间主程序或其他中断也误操作了这个地址就会导致不可预知的行为。最佳实践集中访问将对I2C从机寄存器的访问尽可能集中在同一个模块或任务中避免多处分散访问。使用临界区保护如果必须在多个上下文中访问在访问序列前后使用中断禁用/使能或信号量进行保护。uint32_t intMask IntDisable(); // 进入临界区 uint32_t status HWREG(I2C0_BASE I2C_O_SSTAT); // 读SSTAT if(status I2C_SSTAT_RREQ) { myData HWREG(I2C0_BASE I2C_O_SDR); // 读SDR } // ... 其他操作 IntEnable(intMask); // 退出临界区使用DriverLib APITI的DriverLib提供了封装好的函数如I2CSlaveStatus()和I2CSlaveDataPut()/I2CSlaveDataGet()这些函数内部可能已经考虑了访问安全优先使用它们。5.3 中断标志清除的时机与顺序中断标志清除不当会导致中断重复触发或丢失。规则是在中断服务程序ISR处理完相应事件后即将退出前清除该中断标志。清除操作是向SICR寄存器的对应位写1。常见错误过早清除在读取SSTAT或访问SDR之前就清除了DATAIC标志。如果清除后、访问SDR前发生了新的总线事件新的中断可能会被误认为已处理或者状态位发生变化导致判断错误。忘记清除导致ISR退出后中断标志依然存在CPU会立即再次进入同一中断形成死循环。错误清除向SICR写了错误的值或者试图通过读操作来清除标志无效。正确的顺序以数据中断为例进入ISR。读取SMIS确认是DATAIM中断。读取SSTAT判断是RREQ还是TREQ。根据状态读取或写入SDR寄存器这个操作可能隐含地改变了某些内部状态但中断标志仍需显式清除。向SICR寄存器的DATAIC位写1清除中断。退出ISR。5.4 总线冲突、噪声与上拉电阻I2C总线是开漏输出依靠上拉电阻将线路拉到高电平。上拉电阻的选择至关重要阻值太大上升沿过慢在高速模式如400kHz Fast-mode下可能无法在规定时间内达到高电平阈值导致数据错误。阻值太小虽然边沿变快但会增加静态功耗并且在总线冲突时灌电流过大可能损坏IO口。通常对于3.3V系统在标准模式100kHz下4.7kΩ是一个常用值在快速模式400kHz下可能需要减小到2.2kΩ或1kΩ。总线电容来自导线、连接器、器件引脚也会影响上升时间电容越大所需上拉电阻越小。最可靠的方法是使用示波器观察SDA和SCL波形确保上升沿陡峭且无过冲或振铃。总线噪声也可能导致意外起始条件或数据错误。确保SDA和SCL走线尽可能短并远离噪声源如电源、电机驱动线。在恶劣环境中可以考虑使用屏蔽线或在信号线上串联小电阻如22Ω-100Ω来抑制振铃。5.5 利用MCR寄存器进行回环测试CC13x2/CC26x2的I2C模块的MCR主配置寄存器中有一个LPBKLoopback回环位。当此位置1时模块进入测试模式内部的I2C主从接口被短接在一起。这意味着你可以让同一个MCU的I2C主模式向自己的从模式发送数据无需外部连接。这是调试从机逻辑的绝佳工具。操作步骤正常初始化从机设置地址使能DA。初始化主机配置时钟等。将MCR寄存器的LPBK位置1。使用主机向从机地址发送数据。在从机中断中你应该能收到主机发来的数据。通过回环测试可以完全排除外部硬件连接、上拉电阻、总线干扰等问题将故障范围锁定在软件配置和代码逻辑上极大提高调试效率。测试完成后别忘了将LPBK位清零以恢复正常操作。