资讯详情

TMS320F28335的I2C硬件模块:从寄存器到代码实现

📅 2026/9/15 23:17:03 | 华诺云谱 👁 阅读
TMS320F28335的I2C硬件模块:从寄存器到代码实现
简介面向嵌入式开发者的 TMS320F28335 数字信号处理器 I2C 总线硬件实现工程包适合正在学习 C28x 系列 DSP 以及 I2C 通信协议的工程师和高年级学生。该工程以 DSP 主控芯片与 AT24C02 电可擦除存储器之间的读写通信为线索完整呈现了寄存器初始化、起始与停止条件、器件地址发送、数据读写等关键流程同时提及 TMS320F281x 系列 DSP 的常见外部接口帮助读者建立对 C28x 家族外设资源的整体认知。压缩包共包含 83 个文件体积仅为 235KB文件类型以 C/C 源码、汇编程序、链接命令文件、工程配置文件以及编译生成的输出文件为主内有完整 CCS 工程模板和库文件可直接导入开发环境进行编译和调试。该资源已有 634 人浏览学习。通过这套工程代码读者可以快速掌握 DSP 的 I2C 驱动编写与调试方法并将其灵活移植到电机控制、传感器采集等实际项目中。1. 把I2C当UART写的DSP工程师先停下来看这套硬件时序做DSP开发的人大多对SCI和SPI很熟遇到EEPROM、RTC、温度传感器这类I2C器件时第一反应往往是拿GPIO软模拟一套时序。在TMS320F28335上这么做不是不行但你会失去一个关键优势这款芯片的I2C模块自带16字节发送/接收FIFO、多主模式仲裁和总线错误检测软模拟把这些全部归零。更现实的问题是软模拟的时序在中断频繁、主频跑到150MHz的系统中很容易被拉长400kHz快速模式下SCL高电平宽度抖动一旦超过300ns器件不认账问题还难以复现。本文直接从TMS320F28335的I2C硬件模块出发把引脚配置、时钟计算、寄存器操作和C/C代码串成一条完整链路顺带讲清楚tms320f281x系列DSP那些外部接口——很多人以为281x和28335外设差不多实际在I2C这件事上两代芯片的差异直接决定方案选型。2. TMS320F28335的I2C硬件模块FIFO、从机地址和总线仲裁2.1 I2C外设在28335里的位置与整体结构TMS320F28335的I2C模块是一个符合Philips I2C规范标准后为NXP的外设支持最高400kbit/s的快速模式也支持100kbit/s标准模式。它的核心结构是一个串行时钟发生器、一个发送/接收移位寄存器、一个从机地址比较器、两个中断源发送和接收各一个、以及各16字节深度的发送FIFO和接收FIFO。这个模块在C2000系列里属于中规模外设不需要像EMIF那样配置等待状态和位宽也不像McBSP那样要和DMA联动才能高效工作。它的寄存器全部映射在外设帧空间地址从0x6C00开始连续排列。和很多MCU的I2C外设不同28335的I2C模块没有独立的时钟使能位——它挂在系统时钟上模块使能通过I2CMDR寄存器里的IRS位控制。#define I2C_BASE 0x00006C00 #define I2CMDR 0x00006C20 // 模块模式寄存器2.2 时钟计算I2CPSC、I2CCLKL、I2CCLKH三个寄存器的配合硬件I2C区别于软件模拟的核心就是它有时钟发生器。SCL的每个周期由低电平宽度和高电平宽度两部分决定分别由I2CCLKL和I2CCLKH寄存器控制。I2CPSC则负责把系统时钟分频得到模块内部时钟这个内部时钟是I2CCLKL/I2CCLKH的计数基准在标准模式下要求模块时钟频率在7MHz到12MHz之间快速模式下也要在7MHz到12MHz之间。具体的计算公式如下module_clock SYSCLKOUT / (I2CPSC 1) SCL_low_time (I2CCLKL 1) * module_clock_period SCL_high_time (I2CCLKH 1) * module_clock_period SCL_freq 1 / (SCL_low_time SCL_high_time)以28335默认的150MHz系统时钟为例目标400kbit/s快速模式取I2CPSC 2模块时钟就是150 / 3 50MHz这已经超出12MHz范围说明分频不够。取I2CPSC 14模块时钟为150 / 15 10MHz落在允许区间。SCL周期为1 / 400000 2500ns对应2500 / 100 25个模块时钟周期按照占空比接近50%来分配I2CCLKL 12I2CCLKH 12即低电平13个时钟周期1300ns高电平13个周期1300ns。void i2c_clk_init(void) { I2CPSC 14; // 模块时钟 150MHz / (141) 10MHz I2CCLKL 12; // SCL低电平 13 * 100ns 1300ns I2CCLKH 12; // SCL高电平 13 * 100ns 1300ns }这里有个容易踩的坑I2CPSC在I2C模块未复位时写入不会立即生效必须先设I2CMDR的IRS 0让模块复位配置完PSC和CLK后再置IRS 1。TI的例程经常把这段写在后面很多人习惯性地先开IRS导致时钟还是默认值。2.3 从机地址与寄存器位域用C语言结构体操作外设寄存器28335的I2C寄存器在头文件里被定义成结构体形式I2C模块的寄存器组可以按下面的方式映射typedef volatile struct { Uint32 I2COAR; // 自身地址 Uint32 I2CIER; // 中断使能 Uint32 I2CSTR; // 状态 Uint32 I2CCLKL; // 时钟低 Uint32 I2CCLKH; // 时钟高 Uint32 I2CCNT; // 数据计数 Uint32 I2CDRR; // 接收数据 Uint32 I2CSAR; // 从机地址 Uint32 I2CMDR; // 模式 Uint32 I2CISRC; // 中断源 Uint32 I2CMDR2; // 模式扩展含FIFO相关 } I2C_Registers;写从机地址时注意I2CSAR的低7位是从机地址第8位在扩展寻址模式下是扩展地址位。对于最常见的写入EEPROM比如AT24C02从机地址0xA0I2C总线上的第一字节是7位地址左移一位I2CSAR寄存器实际存入的是低7位地址也就是0x50。C/C工程师容易在这里把0xA0当成地址写进寄存器结果发送到总线上的地址变成了0x140从机根本不应答。3. tms320f281x系列的外部接口全景从2812的SPI到28335的I2C3.1 281x系列外设接口清单与I2C缺失问题tms320f281x系列典型型号TMS320F2812和TMS320F28335在外部接口上的差异不只是I2C这么简单。2812有一组128K×16位的外部存储器接口XINTF支持三个独立片选区地址总线19位数据总线16位可以并行接SRAM、NOR Flash这是28335没有的——28335只有一个14位地址、16位数据的XINTF使用范围窄很多。但反过来2812整颗芯片没有硬件I2C模块。281x系列可用的串行接口包括两个SCIUART、一个SPI、一个McBSP。用2812接EEPROM或者SHT30这类传感器标准做法是用SPI接口或者GPIO软件模拟I2C。SPI接EEPROM是最省事的AT25系列和AT24系列选型时先看主控端——2812只有SPI就直接选AT25不要选AT24然后自己去软件模拟I2C时序不稳定还会拖慢主循环。3.2 28335的接口升级新增I2C与更灵活的GPIO复用F28335与281x相比接口层面最重要的变化就是加入了I2C和CAN模块的增强。28335的I2C引脚复用在GPIO28SDAA和GPIO29SCLA上这两个引脚同时也可以配置成普通的通用数字量IO。这个复用关系在代码里是这样处理的// 使能GPIO28为I2C SDA功能 GpioCtrlRegs.GPAMUX2.bit.GPIO28 2; // 选择第2功能I2C SDA // 使能GPIO29为I2C SCL功能 GpioCtrlRegs.GPAMUX2.bit.GPIO29 2; // 选择第2功能I2C SCLGPIO28/29在28335里属于A组第二组复用引脚每个引脚有4个可选功能对应GPAMUX2寄存器的两位字段。值0是通用IO值1是SCI相关或外部中断值2是I2C功能值3是某种外设输出。C2000的GPIO复用规则在不同系列里并不一致2812的GPIO是单独I/O复用寄存器28335则引入MUX寄存器组的概念迁移代码时不能直接照抄。3.3 外部接口选型从模块化角度决定I2C还是SPI对比维度TMS320F2812TMS320F28335I2C硬件模块无有标准/快速模式SPI1路16级FIFO1路16级FIFOSCIUART2路16级FIFO3路16级FIFOMcBSP1路可配SPI/语音2路可配SPI/语音CAN1路eCAN2路eCANA/BXINTF外部存储器接口3个片选区19位地址1个片选区14位地址主频150MHz150MHz从这张表能看出一个设计结论在F28335上优先使用硬件I2C接低速传感器和EEPROM把SPI留给高速ADC或者Flash芯片。I2C在28335上只占用GPIO28/29两个引脚而且SCL是硬件产生时序不会因为中断响应延迟而变形。2812上如果非要接I2C设备要么用McBSP配置成SPI模式再接SPI转I2C桥接芯片常见做法不推荐要么用GPIO软件模拟并保证I2C总线上拉电阻和线路电容在允许范围内。3.4 硬件设计要点上拉电阻和电平匹配I2C是开漏结构SCL和SDA两根线必须接上拉电阻。28335的GPIO28/29在I2C模式下是开漏输出内部没有上拉必须在PCB上放4.7kΩ到10kΩ的上拉电阻。取值逻辑是总线速度快则电阻取小400kHz时常见4.7kΩ线路电容大则也要取小但要权衡灌电流——电阻太小会让引脚灌入过多电流。3.3V供电时2kΩ电阻灌流约1.65mA5V系统就要重新计算一般不推荐28335直接和5V器件连接需要电平转换或串接限流电阻。4. C/C实现从零配置到读写EEPROM的完整代码4.1 寄存器级初始化函数复用GPIO、FIFO、中断的完整设置用寄存器操作写28335 I2C驱动时从TI的官方库函数往底层转换的关键在于理解初始化顺序。下面这段C函数是硬件I2C最精简可用的初始化流程void I2C_init(Uint32 sysclk) { // 1. 配置GPIO复用 EALLOW; GpioCtrlRegs.GPAMUX2.bit.GPIO28 2; // SDA GpioCtrlRegs.GPAMUX2.bit.GPIO29 2; // SCL // 2. 复位I2C模块使PSC和CLK写入生效 I2cRegs.I2CMDR.bit.IRS 0; // 3. 配置分频系数以150MHz主频、400kbit/s为例 I2cRegs.I2CPSC 14; // 150MHz/(141)10MHz模块时钟 I2cRegs.I2CCLKL 12; // 低电平13*100ns1300ns I2cRegs.I2CCLKH 12; // 高电平13*100ns1300ns // 4. 使能模块和FIFO使能发送/接收FIFO中断 I2cRegs.I2CMDR.bit.IRS 1; I2cRegs.I2CMDR.bit.DLB 0; // 正常模式非回环 I2cRegs.I2CMDR.bit.TRM 1; // 主机模式 I2cRegs.I2CMDR.bit.MST 1; // 主控器模式 I2cRegs.I2CMDR2.bit.FIFOEN 1; // 使能FIFO I2cRegs.I2CIER.bit.RXRDY_IE 1; // 接收就绪中断 I2cRegs.I2CIER.bit.XRDY_IE 1; // 发送就绪中断 EDIS; }对这段代码有几个参数要说明I2CPSC的分频值决定了模块时钟的频率在400kHz快速模式下模块时钟应该在7MHz到12MHz之间所以10MHz是合理选择。I2CCLKL和I2CCLKH分别决定SCL低电平和高电平持续多少个模块时钟周期各达到1300ns的原因是400kHz要求单周期总时长2.5µs300ns以内的偏差通常都能容忍。I2CMDR里MST和TRM两个位必须同时为1才是主控发送模式如果漏了MST位模块会进入从机模式这时发送操作永远得不到ACK信号。4.2 主控发送函数起始条件、从机地址、数据、停止条件硬件I2C的发送流程比软件模拟简单很多——你只需要把数据写入发送数据寄存器I2CDXR模块自己处理SCL时钟、ACK采样和总线仲裁。但要注意写入I2CDXR的时机必须等前一个数据已经移位发送完毕。void I2C_write_bytes(Uint16 slave_addr, Uint16 *data, Uint16 len) { Uint16 i; // 1. 设置从机地址低7位有效 I2cRegs.I2CSAR slave_addr 0x7F; // 2. 设置要发送的字节数 I2cRegs.I2CCNT len; // 3. 清除状态寄存器中的总线忙和停止条件位 I2cRegs.I2CSTR.all 0; // 4. 发送起始条件STT1模块自动产生START并发送地址 I2cRegs.I2CMDR.bit.STT 1; I2cRegs.I2CMDR.bit.STP 0; // 5. 循环写入数据 for(i 0; i len; i) { while(I2cRegs.I2CSTR.bit.XRDY 0); // 等待发送就绪 I2cRegs.I2CDXR data[i]; // 最后一个字节写入后检查是否发送完成 if(i len - 1) { while(I2cRegs.I2CSTR.bit.XRDY 0); // 等待SCD停止条件已发出 } } }这个函数的关键点不在循环本身而在I2CCNT的用法。I2CCNT需要在发送起始条件之前设置模块在发送START后自动按照I2CCNT的数值连续发数据。最后一个字节写入I2CDXR后STP位必须在SCL时钟高电平期间被写入才能正确产生停止条件。如果STP过晚写入总线会停在低电平从机认为数据传输未结束。常见做法是在发送最后一个字节前先设置I2CMDR的STP位或者通过硬件FIFO配合I2CCNT一次完成。4.3 主控接收函数读EEPROM时的重复起始位从EEPROM读数据时总线上的时序是先发一个写命令从机地址0再发寄存器地址然后发重复起始位再发读命令从机地址1最后读取数据并产生停止条件。28335硬件模块对重复起始位的支持方式是在读操作时把I2CMDR的STT位再次置1同时保持STP0。void I2C_read_bytes(Uint16 slave_addr, Uint16 mem_addr, Uint16 *buf, Uint16 len) { Uint16 i; // 第一阶段假装要写发送从机地址和内存寄存器地址 I2cRegs.I2CSAR slave_addr 0x7F; I2cRegs.I2CCNT 1; // 先发1个字节内存地址 I2cRegs.I2CMDR.bit.STT 1; // 起始 I2cRegs.I2CMDR.bit.TRM 1; // 发送模式 I2cRegs.I2CMDR.bit.STP 0; while(I2cRegs.I2CSTR.bit.XRDY 0); I2cRegs.I2CDXR mem_addr; // 发送内存地址 while(I2cRegs.I2CSTR.bit.XRDY 0); // 第二阶段切换为接收发重复起始位 I2cRegs.I2CCNT len; // 要接收的字节数 I2cRegs.I2CMDR.bit.TRM 0; // 切换为接收模式 I2cRegs.I2CMDR.bit.STT 1; // 重复起始位 I2cRegs.I2CMDR.bit.STP 1; // 最后一个字节后自动停止 // 第三阶段等待接收 for(i 0; i len; i) { while(I2cRegs.I2CSTR.bit.RXRDY 0); buf[i] I2cRegs.I2CDRR; // 读出数据自动清RXRDY } while(I2cRegs.I2CSTR.bit.SCD 0); // 等待总线释放 }第二阶段里STT和STP同时置1是硬件模块的合法操作模块会先发重复起始位然后在I2CCNT计数值递减到0时自动产生停止条件。注意I2CMDR的TRM位在重复起始位发出之前必须切到接收模式否则模块会继续发数据而不是等从机返回数据。接收完成循环里的每次读取I2CDRR都会清除RXRDY标志这个清除动作是硬件自动的不能像发送循环那样额外去清零状态位。4.4 FIFO模式下的中断处理批量传输时用16字节缓冲上面的代码是逐字节查询方式在连续读写大批量数据比如128字节EEPROM页写时效率不高。28335的I2C模块支持FIFO模式发送和接收各有16字节深度的FIFO与CPU的交互可以变成一次中断处理多个字节interrupt void I2C_INT_ISR(void) { Uint16 int_source; int_source I2cRegs.I2CISRC.bit.INT_SRC; // 读取中断源 switch(int_source) { case 1: // 发送FIFO中断 if(tx_index tx_len) { // 一次把剩余数据写入FIFO最多16字节 while(tx_index tx_len tx_index - tx_hw_index 16) { I2cRegs.I2CDXR tx_buffer[tx_index]; } } else { // 数据发完允许停止条件 I2cRegs.I2CMDR.bit.STP 1; } break; case 2: // 接收FIFO中断 while(rx_index rx_len (I2cRegs.I2CSTR.bit.RXFFST ! 0)) { rx_buffer[rx_index] I2cRegs.I2CDRR; } break; } }FIFO中断的核心是I2CFFTX和I2CFFRX两个寄存器的配合。I2CFFTX的TXFFIL位设置发送FIFO中断触发阈值I2CFFRX的RXFFIL位设置接收FIFO中断触发阈值。例如TXFFIL 8表示FIFO中剩余字节数小于等于8时触发中断。使用FIFO模式后注意I2CMDR里的重复模式位和FIFO使能位是在I2CMDR2里单独控制的和I2CIER的中断使能是两套独立逻辑。4.5 从C到C封装I2C为类时的注意事项用C写28335的驱动时最常见的问题是外设寄存器的访问权限。28335的头文件把寄存器定义成了全局性结构体指针如果封装成类要避免在构造函数里访问寄存器——C全局对象构造时机在main之前此时DSP的时钟和GPIO配置都还没执行构造函数里访问I2C寄存器等于访问一个还没上电的外设。class I2C_Device { private: uint16_t slave_addr; void wait_for_free(); public: I2C_Device(uint16_t addr) : slave_addr(addr) {} void init(); void write(uint16_t reg, uint16_t* data, uint16_t len); void read(uint16_t reg, uint16_t* data, uint16_t len); };这个类的设计里init是普通成员函数而不在构造函数中完成确保在main明确执行后再调用。类的初始化列表也指向了一个关键测试点如果某个I2C设备地址是固定的可以在构造函数里就赋值。C封装还有一个好处是可以用模板让一个类适配不同地址的设备而不必复制函数代码。5. 总线仲裁与多主模式DSP和MCU同时挂在I2C上单主模式是绝大多数28335 I2C应用的默认形态但不少电力电子控制系统里DSP和一个管理MCU会挂同一条I2C总线多点通信时总线仲裁机制就值得关注。硬件I2C模块在发送数据时每个SCL高电平期间采样SDA如果发现总线上SDA电平与自己输出的不一致比如自己输出高、总线被别的设备拉低就判定发生仲裁丢失模块会关闭输出并置位仲裁丢失标志位。void I2C_check_arb_lost(void) { if(I2cRegs.I2CSTR.bit.ARDY 0) { // 仲裁没有发生或已经完成 } if(I2cRegs.I2CSTR.bit.AL 1) { // 仲裁丢失需要重新发起传输 I2cRegs.I2CSTR.bit.AL 1; // 写1清除 I2cRegs.I2CMDR.bit.STT 1; // 重新发起起始条件 } }多主模式下有一个容易忽略的寄存器I2COAR自身地址寄存器。它决定了28335作为从机时能响应哪些地址。如果I2COAR的值和总线上另一个主设备地址冲突仲裁几乎必然失败。检查I2COAR的值是最快的定位方法。另外I2C总线上的上拉电阻在多主模式下要特别注意两个主设备共用一组上拉没有问题但任一键合了过小的下拉会使总线整体性能下降。6. 时序验证三招示波器看波形、回环测试、总线出错恢复硬件I2C的调试和SPI不一样——SPI有时钟对着时钟沿采数据就行I2C只有两根线没有参考时钟出了时序问题只能靠示波器或者逻辑分析仪。第一招是把示波器两个通道分别接在SDA和SCL上触发方式设为SDA下降沿抓一次读EEPROM的完整时序。重点看SCL高电平的宽度是否稳定在1µs左右100kHz模式或1.25µs400kHz模式占空比50%时如果发现个别SCL高电平被拉长到5µs以上说明代码在等待期间被中断打断模块时钟没有继续输出这是正常现象但会影响低速器件的容错——此时可把I2CCLKKL调小一点补偿平均频率。第二招是回环测试把I2CMDR的DLB位数字回环置1模块的发送输出被内部连接到接收输入。这个模式下不需要外部连接就能验证初始化代码是否正确。写一个数据到I2CDXR再从I2CDRR读回来数据一致说明模块时钟、分频和中断配置都工作正常。回环测试是纯数字测试不涉及IO电气特性只能用来验证寄存器配置。void I2C_loopback_test(void) { Uint16 test_val 0x5A; // 进入回环模式数字回环 I2cRegs.I2CMDR.bit.DLB 1; // 复位FIFO指针 I2cRegs.I2CFFTX.bit.TXFFRST 1; I2cRegs.I2CFFRX.bit.RXFFRST 1; // 写一个字节 I2cRegs.I2CDXR test_val; // 等待接收就绪 while(I2cRegs.I2CSTR.bit.RXRDY 0); // 读取回环数据 Uint16 rd_val I2cRegs.I2CDRR; if(rd_val test_val) { // 模块配置正确 } else { // 寄存器配置错误检查时钟分频 } // 退出回环模式 I2cRegs.I2CMDR.bit.DLB 0; }第三招是总线出错恢复。I2C总线最常见的错误是SCL正常、SDA被拉死永远为低——通常是因为某个从机处于某种异常状态等不到停止条件。硬件恢复方式是让主机在SCL上打9个时钟脉冲从机会在收到第9个脉冲后释放SDA。由于28335的I2C模块没有直接发送时钟脉冲的功能位常见做法是把GPIO28SDA切回普通GPIO输出高电平GPIO29SCL配置成普通GPIO并手动翻转9次。很多I2C总线锁死的问题都出在EEPROM写周期未完成时就断电恢复操作已经够用。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。