TMS320C6000 DSP I2C模块编程实战:从寄存器配置到EDMA高效传输
1. 项目概述与I2C总线核心价值
在嵌入式系统开发中,设备间的通信是构建复杂功能的基础。面对众多传感器、存储器和外设,如果每个都独占一组并行数据线和地址线,PCB会变得异常复杂,成本也会飙升。这时,I2C(Inter-Integrated Circuit)总线就显现出其巨大价值。它仅用两根线——串行数据线(SDA)和串行时钟线(SCL)——就构建了一个支持多主多从的通信网络,极大地简化了硬件设计。我第一次在TMS320C6000 DSP项目中使用I2C连接外部EEPROM和温度传感器时,就被这种简洁而强大的协议所折服。
TMS320C6000系列DSP集成的I2C模块,完全兼容Philips(现NXP)的I2C总线规范v2.1,支持高达400kbps的快速模式。它不仅仅是一个简单的串行接口,更是一个集成了时钟同步、噪声滤波、仲裁逻辑和DMA事件触发的智能外设。对于需要高效、可靠地进行芯片间通信的DSP应用,如工业控制、音频处理或图像采集系统,深入理解并掌握这个模块的编程,是释放DSP全部潜力的关键一步。本文将从一个资深嵌入式工程师的视角,带你深入TMS320C6000 DSP的I2C模块内部,不仅解读寄存器手册上的“是什么”,更重点分享实际项目中“怎么用”和“为什么这么用”的实战经验。
2. I2C模块架构与工作模式深度解析
2.1 模块整体架构与数据流
TMS320C6000的I2C模块远不止是两根GPIO引脚。其内部架构是一个精心设计的数字状态机,核心围绕着几组关键寄存器展开。理解数据流向是编程的第一步。如图2所示,模块的核心是两对数据寄存器: I2CDXR/I2CXSR 用于发送, I2CDRR/I2CRSR 用于接收。
当你作为CPU或EDMA控制器需要发送数据时,你将数据写入 I2CDXR 。模块内部的移位逻辑会在适当时机将数据从I2CDXR拷贝到发送移位寄存器 I2CXSR ,然后一位一位地从SDA引脚移出,整个过程与SCL引脚上产生的时钟同步。接收过程则相反:数据从SDA引脚一位位移入接收移位寄存器 I2CRSR ,当一个完整的数据字节(或1-8位数据单元)接收完毕后,再被拷贝到 I2CDRR 中,供CPU或EDMA读取。
注意 :这里有一个极易出错的细节。I2CDXR和I2CDRR是CPU/EDMA访问的“缓冲”寄存器,而I2CXSR和I2CRSR是内部“移位”寄存器,程序员无法直接访问。你必须清楚,向I2CDXR写入数据,并不意味着数据立刻出现在总线上;从I2CDRR读取数据,也不代表总线上的数据被立即捕获。数据的实际收发与总线时钟严格同步,并由模块的状态机控制。混淆这两组寄存器的角色,是导致通信时序错误的一个常见原因。
2.2 四大操作模式与角色切换逻辑
模块支持四种基本操作模式,这决定了它在总线上的行为。很多新手会混淆“主/从”和“收/发”这两组概念。实际上,它们是正交的:
- 主发送模式 :模块作为总线主机,并向外发送数据。这是主机发起通信时的默认起始模式。主机首先发送从机地址和写方向位(R/W=0)。
- 主接收模式 :模块作为总线主机,但从外部接收数据。主机不能直接进入此模式,必须先从主发送模式发送从机地址和读方向位(R/W=1),然后模块会自动切换为主接收模式。
- 从接收模式 :模块作为总线从机,接收主机发来的数据。这是所有从机设备上电后的初始状态,时刻监听总线上的地址。
- 从发送模式 :模块作为总线从机,向主机发送数据。从机必须首先在从接收模式下,识别到自己的地址且主机发送的R/W位为1(读请求),然后才能切换到此模式。
模式切换完全由硬件根据总线上的命令自动完成,但软件需要通过配置 I2CMDR 寄存器中的 MST (主模式使能)位来声明自己希望扮演的角色。例如,当你将MST置1,模块就准备好争夺总线控制权成为主机;置0,则模块乖乖做从机,等待被呼叫。
2.3 起始、停止与重复起始条件
这是I2C协议的“标点符号”,决定了数据帧的边界。
- 起始条件 :当SCL为高电平时,SDA线发生一个从高到低的跳变。这个独特的信号告诉总线上所有设备:“注意,我要开始传输了”。在代码中,你需要同时设置 MST=1 和 STT=1 来让模块产生起始条件。
- 停止条件 :当SCL为高电平时,SDA线发生一个从低到高的跳变。这表示“本次传输结束,总线即将释放”。通过设置 STP=1 来产生停止条件。
- 重复起始条件 :在一次通信序列中,主机可以在不释放总线(不发送停止条件)的情况下,再次发送一个起始条件。这用于在不放弃总线控制权的前提下,切换通信方向或寻址另一个从机。例如,主机先写入从机A的某个寄存器地址,然后发送重复起始条件,再以读模式访问同一从机A以读取数据。当总线忙( BB=1 )时,设置 STT=1 就会产生一个重复起始条件,而非起始条件。
3. 关键寄存器配置与编程精要
寄存器是软件与I2C硬件模块对话的窗口。盲目照搬示例代码往往不通,必须理解每个关键位背后的含义。
3.1 模式寄存器详解与配置策略
I2CMDR 是模块的“大脑”,几乎所有关键功能都由此寄存器控制。
- IRS :这是总开关。在修改任何其他I2C配置寄存器(如I2COAR, I2CCLKH/L等)之前, 必须先将IRS清零以复位模块 。配置完成后,再置1以启用模块。这是一个硬性规定,违反它会导致配置不生效或通信异常。
- RM :重复模式。这是高效编程的关键。当 RM=1 时,模块在完成当前数据字节传输后,会自动将下一个待发送数据从I2CDXR加载到I2CXSR(发送时),或自动将I2CRSR中的数据拷贝到I2CDRR(接收时),并等待下一个数据字节。这非常适合连续的数据块传输。当 RM=0 (非重复模式)时,每传输一个字节都需要CPU干预来准备下一个数据或读取已接收数据。在需要高吞吐量的场景,务必使用RM=1并结合中断或EDMA。
- STT/STP :如前所述,用于产生起始和停止条件。 一个重要的经验是:STT和STP位是“瞬时命令”位。硬件在执行完相应操作后会自动将其清零。 因此,你的程序不应该去“清除”它们,而是查询状态位来判断操作是否完成。
- FDF :自由数据格式。置1时,通信帧中不包含地址字节,直接传输数据。这仅用于通信双方事先约定好的特殊场合,一般与标准I2C设备通信时保持为0。
- BC :位计数。定义每个数据单元包含多少位(1-8)。99%的应用中都是8位(BC=000),即一个字节。除非与某些使用非标准位宽的特种设备通信,否则不要改动。
3.2 时钟配置:从DSP内核频率到SCL波特率
这是配置的难点,也是通信稳定的基石。如图11所示,SCL频率的生成是一个两级分频过程。
第一步:配置预分频器(I2CPSC)
输入时钟
SYSCLK2
经过预分频器,产生模块工作时钟。
手册中明确警告,模块工作时钟必须在6.7MHz到13.3MHz之间!
这是硬性约束。计算公式为:
模块时钟频率 = SYSCLK2频率 / (IPSC + 1)
其中
IPSC
是写入I2CPSC寄存器的值。例如,如果
SYSCLK2 = 100MHz
,要得到约10MHz的模块时钟,则
IPSC = (100 / 10) - 1 = 9
。
第二步:配置高低电平分频器(I2CCLKH/L)
模块时钟再经过两个独立的分频器(ICCH和ICCL),分别控制SCL线高电平和低电平的持续时间。最终的SCL频率计算公式为:
SCL频率 = 模块时钟频率 / [(ICCH + d) + (ICCL + d)]
其中
d
是一个依赖于IPSC值的内部延迟因子(IPSC=0时d=7,IPSC=1时d=6,IPSC>=2时d=5)。这个
d
的存在是为了满足I2C协议对建立/保持时间的要求。
实战配置示例
:假设我们需要在
SYSCLK2=150MHz
的系统中产生一个标准的100kHz I2C时钟。
-
首先确定模块时钟。取中间值10MHz,则
IPSC = (150 / 10) - 1 = 14。验证:150 / (14+1) = 10MHz,在6.7-13.3MHz范围内,合格。 -
然后计算ICCH和ICCL。我们希望SCL占空比为50%(高电平时间=低电平时间)。假设IPSC=14>=2,则d=5。设
ICCH = ICCL = N。 公式简化为:100kHz = 10,000,000 Hz / [(N+5) + (N+5)] = 10,000,000 / (2N+10)解得:2N + 10 = 100,N = 45。 因此,设置ICCH = ICCL = 45。
// C语言配置示例
void I2C_ClockConfig(void) {
// 1. 确保I2C模块复位 (IRS=0)
I2CMDR &= ~(0x8000); // 清除IRS位
// 2. 配置预分频器 (假设SYSCLK2=150MHz)
I2CPSC = 14; // 产生10MHz模块时钟
// 3. 配置高低电平分频器,产生100kHz SCL
I2CCLKH = 45;
I2CCLKL = 45;
// 4. 重新使能I2C模块
I2CMDR |= 0x8000; // 设置IRS位
}
3.3 地址寄存器与数据寄存器
- I2COAR :自身地址寄存器。当模块作为从机时,它用这个地址来响应主机的寻址。7位地址模式时,地址填写在bit 6-0,bit 7保留。10位地址模式时,高7位(11110XX)由硬件处理,低8位需正确设置。
- I2CSAR :从机地址寄存器。当模块作为主机时,它把要访问的从机地址写在这里。同样需注意7位/10位模式。
- I2CDXR :数据发送寄存器。你要发送的数据写在这里。
- I2CDRR :数据接收寄存器。你从这里读取接收到的数据。
一个关键技巧 :在非重复模式(RM=0)下,向I2CDXR写入数据会 自动启动 一次发送过程(如果模块已配置为发送方)。而在重复模式(RM=1)下,你通常需要提前准备好多个数据到缓冲区,由DMA或中断服务程序依次写入I2CDXR。
4. 三种数据流控制方式实战
TMS320C6000的I2C模块提供了三种控制数据传输的方式:位查询、中断和EDMA事件。选择哪种方式,取决于你对系统实时性和CPU占用率的要求。
4.1 位查询:简单直接,但效率低下
这种方式通过轮询 I2CSTR 状态寄存器中的标志位来控制流程。例如,发送时查询 XRDY (发送就绪),接收时查询 RRDY (接收就绪)。
// 位查询方式发送一个字节
void I2C_MasterWriteByte(unsigned char slaveAddr, unsigned char data) {
// 配置为主发送模式,非重复模式,7位地址
I2CMDR = 0x2600; // IRS=1, MST=1, TRX=1, RM=0, STT=0, STP=0, ...
// 写入从机地址 (写方向 R/W=0 已隐含在TRX=1中)
I2CSAR = slaveAddr;
// 写入要发送的数据
I2CDXR = data;
// 设置STT位,启动传输(发送地址+数据)
I2CMDR |= 0x2000; // 设置STT位
// 轮询等待传输开始(总线忙)
while((I2CSTR & 0x20) == 0); // 等待BB位变1
// 轮询等待数据发送完成
while((I2CSTR & 0x10) == 0); // 等待XRDY位变1 (数据已从DXR移走)
// 设置STP位,产生停止条件
I2CMDR |= 0x0800; // 设置STP位
// 轮询等待停止条件完成
while((I2CSTR & 0x20) != 0); // 等待BB位变0
}
注意 :位查询方式会 完全占用CPU ,在等待期间CPU无法执行其他任务。它仅适用于极简单的、非实时的或初始化阶段的通信。在复杂的多任务系统中应避免使用。
4.2 中断驱动:平衡性能与复杂性
中断方式允许CPU在I2C模块准备就绪时再介入,大大提高了CPU效率。你需要配置 I2CIER 中断使能寄存器,并编写中断服务程序。
关键中断源:
- ARDY :寄存器访问就绪。当可以安全写入I2CMDR(例如配置STT/STP)时触发。
- RRDY :接收数据就绪。当I2CDRR中有新数据时触发。
- XRDY :发送数据就绪。当I2CDXR为空,可以写入下一个数据时触发。
- NACK :未收到应答。当从机未返回ACK时触发,通常意味着寻址错误或从机故障。
- AL :仲裁丢失。当模块作为主机在总线仲裁中失败时触发。
中断服务程序编写要点 :
- 进入ISR后,首先读取 I2CISR 中断源寄存器,确定是哪个事件触发了中断。
- 根据事件进行相应处理(如从I2CDRR读数据,或向I2CDXR写数据)。
- 必须手动清除中断标志 !在I2CISR中向对应位写1可以清除该中断源。不清除会导致中断持续触发。
- 如果是NACK或AL错误,需要进行错误恢复处理(例如重试、复位总线等)。
// 中断方式主发送示例框架
volatile int txBufferIndex = 0;
volatile int txBufferLength = 0;
unsigned char txBuffer[256];
void I2C_ISR(void) {
unsigned int intSource = I2CISR;
if(intSource & 0x0001) { // XRDY中断
if(txBufferIndex < txBufferLength) {
I2CDXR = txBuffer[txBufferIndex++]; // 写入下一个数据
} else {
// 所有数据发送完毕,准备产生停止条件
I2CMDR |= 0x0800; // 设置STP位
// 可能还需要禁用XRDY中断,避免再次进入
I2CIER &= ~0x0001;
}
I2CISR = 0x0001; // 清除XRDY中断标志
}
// 处理其他中断源...
}
4.3 EDMA事件:极致吞吐量的选择
对于大数据量的连续传输(例如从I2C传感器读取一帧图像数据,或向I2C DAC发送一段音频流),EDMA事件是最高效的方式。I2C模块可以产生 REVT (接收事件)和 XEVT (发送事件),直接触发EDMA控制器在I2CDRR和内存之间,或内存和I2CDXR之间搬运数据,完全无需CPU干预。
配置步骤 :
- 配置I2C模块为重复模式(RM=1),并设置好其他参数。
-
配置EDMA通道:
- 将I2C的REVT/XEVT链接到EDMA通道的同步事件。
- 设置源/目标地址:对于接收,源地址是I2CDRR的固定地址,目标地址是内存缓冲区;对于发送则相反。
- 设置传输计数和地址索引模式。
- 启动I2C传输(设置STT)。
- I2C模块每完成一个数据的接收/发送,就会触发一次EDMA事件,EDMA自动完成一次数据搬运。当整个数据块传输完毕,EDMA可以产生一个完成中断通知CPU。
这种方式将CPU从繁琐的数据搬运中彻底解放出来,特别适合与DSP的其他高带宽任务(如数字信号处理算法)并发执行。
5. 高级功能与疑难问题排查实录
5.1 时钟同步与仲裁机制
在多主机系统中,时钟同步和仲裁是保证总线正常工作的关键。
- 时钟同步 :所有主机都在SCL线上产生自己的时钟。总线上的SCL信号是所有主机时钟的“线与”结果。低电平周期由时钟低电平期最长的主机决定,高电平周期由时钟高电平期最短的主机决定。TMS320C6000的I2C模块硬件自动处理这个过程,软件无需干预。
- 仲裁 :当两个主机同时发起传输时,它们会继续发送数据,直到出现分歧。在SDA线上,发送“1”(释放总线为高)的主机将检测到自己发出的电平(高)与总线实际电平(被另一个主机拉低)不符,从而判定自己仲裁失败。失败的主机会立即切换到从接收模式,并设置 AL 标志。你的程序必须检测AL标志,并在仲裁失败后执行退避和重试逻辑。常见的策略是等待一个随机时间后重新尝试。
5.2 NACK处理与错误恢复
未收到应答(NACK)是I2C通信中最常见的错误。原因可能是:
- 从机地址错误。
- 从机设备不存在或未上电。
- 从机忙或处于写保护状态。
- 总线物理连接问题(上拉电阻过大、线路过长导致信号畸变)。
当I2C模块作为接收方时,你也可以主动发送NACK来告知发送方停止发送。方法是在接收倒数第二个数据位之前,设置 I2CMDR 中的 NACKMOD 位。
错误恢复流程建议 :
- 在中断服务程序中检测到NACK或AL标志。
- 记录错误次数,超过阈值则上报致命错误。
- 将 IRS 位清零再置一,复位I2C模块内部状态机。
- 重新初始化I2C配置(时钟等)。
- 延迟一段时间(例如几毫秒)后重试操作。
5.3 常见问题排查速查表
| 现象 | 可能原因 | 排查步骤与解决方案 |
|---|---|---|
| 通信完全无响应,SCL/SDA一直为高 | 模块未使能;总线无上拉电阻;从机地址错误。 |
1. 检查IRS位是否为1。
2. 用示波器测量SCL/SDA,确认有上拉,电压正常。 3. 确认I2COAR/I2CSAR地址正确,7/10位模式设置正确。 |
| 能发送起始条件,但收不到ACK | 从机地址错误;从机设备故障;时序不满足从机要求。 |
1. 用逻辑分析仪抓取波形,确认发送的地址字节正确。
2. 检查从机设备电源、复位信号。 3. 重点检查SCL频率是否在从机支持的范围内 (很多EEPROM最高只支持100kHz)。调整I2CCLKH/L。 |
| 能收到ACK,但后续数据出错 | 时钟频率过快,建立/保持时间不足;电源噪声干扰。 |
1. 降低SCL频率(增大ICCH/ICCL值)。
2. 检查电源纹波,在I2C引脚附近增加去耦电容。 3. 检查PCB布线,SCL/SDA线是否过长,是否靠近噪声源。 |
| 多字节传输中丢失数据 | 重复模式(RM)配置错误;中断/EDMA服务不及时。 |
1. 确认RM位设置符合预期。RM=1用于连续传输。
2. 在中断方式下,检查中断优先级是否被其他高优先级中断阻塞。 3. 在查询方式下,检查轮询循环是否被意外打断。 |
| 仲裁频繁丢失 | 多主机程序逻辑缺陷,同时发起请求;总线负载过重。 |
1. 优化多主机软件逻辑,增加随机退避算法。
2. 检查总线负载,减少不必要的通信。 |
| EDMA传输数据错位 | EDMA地址递增模式配置错误;传输计数与I2C数据计数不匹配。 |
1. 核对EDMA的源/目标地址索引(CIDX)配置,接收时目标地址应递增。
2. 如果使用了I2CCNT(数据计数寄存器),确保其值与EDMA传输计数一致。 |
5.4 数字回环测试
在硬件连接之前,强烈建议使用 数字回环模式 进行软件自检。将 I2CMDR 中的 DLB 位置1,模块内部会将SDA和SCL输出短接到输入,这样无需外部设备即可测试发送和接收流程。这是验证你所有配置和驱动程序逻辑是否正确的最快方法。通过回环测试后,再将DLB清零接入真实总线,可以极大降低硬件调试阶段的复杂度。
6. 一个完整的EEPROM读写示例
最后,我们以一个实际的例子收尾:使用TMS320C6000的I2C模块,以中断方式读写一个常见的24LC256 I2C EEPROM。
硬件连接 :SCL、SDA接上拉电阻(通常4.7kΩ),EEPROM的A0-A2地址引脚接地,即7位地址为0x50。
软件流程 :
- 初始化 :配置系统时钟、I2C模块时钟(100kHz)、自身地址(从机模式下用,此处可设任意值)、使能中断。
-
写一个字节
:
- 配置为主发送模式,非重复模式。
- 写入EEPROM地址0x50。
- 写入EEPROM内部的高地址字节。
- 写入EEPROM内部的低地址字节。
- 写入要存储的数据字节。
- 设置STT启动传输,设置STP在发送完数据后产生停止条件。
- 等待传输完成中断或查询BB位。
- 注意 :EEPROM写入需要一定的页编程时间(约5ms),在此期间不会响应ACK。必须加入延时或轮询ACK。
-
读一个字节
(当前地址读):
- 先执行一个“哑写”来设置内部地址指针:发送起始条件、EEPROM地址(写)、内部地址(高、低),但不发送数据,而是发送一个重复起始条件。
- 然后发送EEPROM地址(读),切换为主接收模式。
- 接收一个数据字节后,主机发送NACK,然后发送停止条件。
- 在中断服务程序中读取I2CDRR获得数据。
这个例子涵盖了起始、停止、重复起始、模式切换、NACK控制等关键操作,是掌握I2C编程的一个很好的综合练习。在实际项目中,我通常会为I2C操作封装一个带有超时和重试机制的状态机,并将底层寄存器操作封装成独立的驱动层,这样上层应用代码会更加清晰和健壮。调试时,一把好的逻辑分析仪是必不可少的,它能直观地展示出起始、地址、数据、ACK/NACK和停止条件,让你对总线上的每一比特都了如指掌。
更多推荐
所有评论(0)