1. 项目概述
如果你在嵌入式系统开发中打过交道,尤其是用过TI的DSP或者各类MCU,那么SPI接口绝对是你绕不开的一个老朋友。它不像I2C那样需要复杂的地址协议,也不像UART那样依赖精确的波特率匹配,SPI以其简单粗暴的硬件同步方式,在短距离、高速数据交换场景中牢牢占据了一席之地。今天,我们不聊那些泛泛而谈的SPI基础,而是直接深入到德州仪器TMS320C672x DSP的SPI模块内部,看看这颗高性能浮点DSP是如何实现这个经典接口的,以及在实际项目中,我们该如何配置它才能让它跑得既快又稳。
TMS320C672x系列DSP内置了两个独立的SPI模块,它们远不止是简单的移位寄存器。这个模块支持从基础的3线模式到带有硬件流控和片选的5线模式,提供了可编程的字符长度、时钟极性与相位、以及精细的时序延迟控制。对于需要连接高速ADC、DAC、Flash存储器或者多个传感器从设备的系统来说,理解并驾驭好这个SPI模块,意味着你能在硬件层面榨取出更高的通信带宽和更可靠的稳定性。接下来,我会结合手册中的核心内容和实际调试经验,带你拆解它的工作原理、配置要点,并分享几个在C672x上配置SPI时容易踩坑的细节。
2. SPI核心原理与C672x模块架构解析
2.1 SPI通信的本质:主从同步与全双工
SPI的全称是Serial Peripheral Interface,其核心思想是主从同步和全双工。你可以把它想象成两个人面对面坐着,中间摆着一张桌子(共享的通信线路),其中一个人(主设备)负责拍桌子(产生时钟SCLK)来规定说话的节奏。每拍一下桌子,双方就同时交换一张小纸条(一位数据)。主设备通过MOSI(Master Out Slave In)线送出纸条,从设备通过MISO(Master In Slave Out)线送回纸条。这个过程是同时进行的,所以是全双工。
在C672x的SPI模块中,对应的信号线是:
- SPIx_CLK: 串行时钟,由主设备驱动。
- SPIx_SIMO: 主设备输出、从设备输入线。
- SPIx_SOMI: 主设备输入、从设备输出线。
- SPIx_SCS: 从设备片选(Slave Chip Select),低电平有效,用于在多个从设备中选择一个。
- SPIx_ENA: 使能/握手信号,用于4/5线模式下的硬件流控。
模块内部的核心是一个16位的移位寄存器。当你把要发送的数据写入SPIDAT0或SPIDAT1的低16位时,硬件就会在时钟的驱动下,按照你设定的顺序(MSB或LSB优先)和长度(2-16位),将数据一位一位地“推”到SIMO线上。同时,SOMI线上的数据也被一位一位地“拉”进这个移位寄存器。一次传输完成后,接收到的数据会被自动搬运到**接收缓冲寄存器SPIBUF**中,并置位接收完成标志,触发中断或DMA事件。这个“写入即启动,完成即缓存”的机制,是SPI高效的关键。
2.2 C672x SPI模块的独特优势与模式选择
相比于许多MCU上功能简单的SPI,C672x的SPI模块提供了更强大的灵活性和鲁棒性,这主要得益于其丰富的引脚模式和可编程时序控制器。
1. 引脚模式(3/4/5线)的工程意义:
- 3线模式: 最基本,只有CLK、SIMO、SOMI。适用于单一主设备对单一从设备的场景。配置简单,但缺乏多从设备管理和流控能力。
- 4线带片选(SCS)模式: 增加了
SPIx_SCS引脚。这是支持多从设备的基础。主设备通过控制不同的GPIO(注意,C672x每个SPI模块只有一个专用的SCS引脚,多从机通常需用普通GPIO模拟)来选中目标从机。模块内部的SPIDELAY寄存器可以编程SCS信号相对于时钟的建立和保持时间,这对于满足某些严格时序的外设(如Flash芯片)至关重要。 - 4线带使能(ENA)模式: 增加了
SPIx_ENA引脚,用于硬件流控。从设备在完成一次接收、尚未准备好下一次发送时,可以拉低ENA信号,告诉主设备“等等我”。主设备会自动等待ENA变高后才发起下一次传输。这彻底避免了因从设备处理速度慢而导致的数据覆盖(Overrun)错误,在从设备是低速MCU或需要复杂数据处理的场景下非常有用。 - 5线模式: SCS和ENA引脚同时启用。这是功能最全的模式,兼具多从设备管理和硬件流控。在实际的多从机系统中,每个从机都有自己的ENA线连接到主设备,主设备通过SCS选中一个从机,并通过监测该从机的ENA线来决定何时发起传输。手册中特别警告了5线从模式下的一个陷阱:如果
SPIINT0.ENABLEHIGHZ位设为0(推挽输出),且从机在SCS撤销前就写入了新数据,ENA可能会保持有效,导致总线冲突。因此,在5线从模式下,强烈建议将ENABLEHIGHZ设为1(开漏输出),并在SPIx_ENA引脚外部上拉。
2. 数据格式的自动处理:这是C672x SPI一个非常贴心的地方。很多SPI模块需要软件手动处理数据对齐。比如你要发送一个7位的数据0x40(二进制100 0000),并且是MSB优先。你可能会纠结是该写0x4000到数据寄存器(左移9位),还是写0x0040。在C672x上,你永远只需要写入右对齐的数据,即0x0040。模块内部的格式化逻辑会根据SPIFMTx.CHARLEN(字符长度)和SPIFMTx.SHIFTDIR(移位方向)自动完成位提取和移位。接收时也一样,无论实际收到数据在移位寄存器中处于什么位置,读出的SPIBUF中的数据总是右对齐的,高位补零。这个设计大大减轻了驱动开发的负担。
3. 关键寄存器深度解读与配置策略
手册中列出了大量的寄存器,但核心的、需要重点理解的并不多。配置SPI就像组装一台精密仪器,每一步设置都要知其所以然。
3.1 模式控制与引脚功能寄存器
SPIGCR1 (Global Control Register 1) - 模式总开关这是SPI模块的“大脑”。MASTER位决定主从,CLKMOD位控制时钟源(主模式必须为1,使用内部波特率发生器;从模式为0,使用外部输入时钟)。ENABLE位是整个模块的使能位,务必在配置完所有其他参数后再将其置1。LOOPBACK位用于回环测试,在硬件连接前验证软件配置是否正确非常有用。
SPIPC0 (Pin Control Register 0) - 引脚功能映射这个寄存器决定某个物理引脚是作为SPI的特殊功能使用,还是作为普通GPIO。例如,要配置为5线主模式,需要设置SOMIFUN=1,SIMOFUN=1,CLKFUN=1,ENAFUN=1,SCSFUN0=1。一个常见的错误是使能了SPI模块但引脚功能未配置,导致信号无法输出到引脚上。配置值0x0F01就对应5线模式所有功能引脚使能。
3.2 数据格式与时钟配置寄存器
SPIFMTx (Format Registers) - 通信协议定制C672x有4个格式寄存器(SPIFMT0-3),通过SPIDAT1.DFSEL来选择。这允许主设备在与不同从设备通信时快速切换格式。从设备只能使用SPIFMT0。
CHARLEN[4:0]: 字符长度,2-16位。这里有个坑:如果你设置字符长度为9-16位,你必须使用32位写操作(例如*(volatile uint32_t*)&spi_regs->SPIDAT0)一次性写入整个32位的SPIDAT1寄存器(高16位是格式选择等控制位,低16位是数据)。如果只写16位,高位控制位可能处于随机状态,导致传输异常。PRESCALEx[7:0]: 波特率预分频值。SPI时钟频率 = SYSCLK2 / (PRESCALEx + 1)。SYSCLK2是DSP的外设时钟。计算时要注意,有效范围是7-255,即分频系数为8-256。POLARITY和PHASE: 这是SPI配置的核心难点,决定了时钟空闲电平和数据采样边沿。共有4种模式组合(CPOL, CPHA)。必须与从设备的数据手册要求严格匹配,否则数据会错位。我们会在下一节详细分析时序。SHIFTDIR: 移位方向,0为MSB优先(最常见),1为LSB优先。WDELAY: 字间延迟,即两次传输之间插入的空闲时钟周期数,用于给从设备预留处理时间。
3.3 时序精细调节寄存器
SPIDELAY (Delay Register) - 解决时序难题的利器这是体现C672x SPI强大功能的关键寄存器。当使用SCS或ENA引脚时,可以通过它微调信号边沿关系,以满足苛刻的外设时序要求。
C2TDELAY: Chip Select to Transmission Delay。从片选有效到第一个时钟沿开始的延迟(以SYSCLK2周期计)。用于满足从设备片选建立时间(t_SU(CS))。T2CDELAY: Transmission to Chip Select Delay。从最后一个时钟沿到片选无效的延迟。用于满足从设备片选保持时间(t_HD(CS))。T2EDELAY/C2EDELAY: 与ENA信号相关的超时设置。例如,主设备发出SCS后,如果超过C2EDELAY时间仍未检测到从设备拉低ENA,则会触发超时错误。
配置心得:在驱动一个新的SPI外设时,首先对照其数据手册的时序图,确定其对t_SU(CS)和t_HD(CS)的要求。然后根据SYSCLK2的频率,计算出需要多少个时钟周期的延迟,并配置到C2TDELAY和T2CDELAY中。不要依赖默认值,很多通信不稳定问题都源于此处时序不匹配。
3.4 数据与中断寄存器
SPIDAT0/1 & SPIBUF & SPIEMU - 数据交换核心
SPIDAT0: 基本的16位数据写入触发传输。SPIDAT1: 高16位包含DFSEL(选择SPIFMT0-3)、CSHOLD(传输后保持片选有效,用于连续传输)等控制位,低16位是数据。写入SPIDAT1的低16位才会启动传输,仅写高16位不会。SPIBUF: 读取接收数据的地方。读取操作会清除接收中断标志SPIFLG.RXINTFLG。SPIEMU:SPIBUF的“镜像”,读取它不会清除任何标志。这在调试和仿真时非常有用,你可以随意查看接收缓冲区而不影响中断状态。
SPIFLG & SPIINT0 - 状态与中断控制
SPIFLG.RXINTFLG: 接收完成标志。OVRNINTFLG: 溢出标志(从设备数据未及时取走,新数据已覆盖)。SPIINT0.RXINTEN: 接收中断使能。OVRNINTEN: 溢出中断使能。DMAREQEN: DMA请求使能。- 一个关键实践:在中断服务程序(ISR)中,必须先读取
SPIBUF数据,再检查SPIFLG的其他状态位(如溢出)。因为读SPIBUF会清除RXINTFLG,但不会清除OVRNINTFLG,这样你就能区分是正常接收还是发生了溢出错误。
4. 时钟模式与数据时序实战分析
时钟极性(CPOL)和相位(CPHA)的组合定义了SPI的四种模式。这是连接任何SPI设备时必须首先确认的参数,配错了数据肯定对不上。
4.1 四种模式详解与配置
模式由SPIFMTx.POLARITY(CPOL)和SPIFMTx.PHASE(CPHA)两位决定:
- 模式0 (CPOL=0, CPHA=0): 时钟空闲时为低电平。数据在时钟的第一个边沿(上升沿)被采样。这意味着数据必须在时钟上升沿之前保持稳定(在上升沿捕获)。这是最常用的模式。
- 模式1 (CPOL=0, CPHA=1): 时钟空闲时为低电平。数据在时钟的第二个边沿(下降沿)被采样。数据在时钟上升沿变化,在下降沿保持稳定。
- 模式2 (CPOL=1, CPHA=0): 时钟空闲时为高电平。数据在时钟的第一个边沿(下降沿)被采样。
- 模式3 (CPOL=1, CPHA=1): 时钟空闲时为高电平。数据在时钟的第二个边沿(上升沿)被采样。
如何选择模式?这完全取决于你的从设备。你必须查阅从设备的数据手册。例如,很多SPI Flash芯片工作在模式0或模式3。一个快速判断的方法是看时序图:找到数据采样点对应的时钟边沿,以及时钟线在空闲时的电平。
4.2 主模式配置流程与示例代码
假设我们需要将C672x配置为SPI主设备,以模式0、8位数据、MSB优先、波特率10MHz(SYSCLK2=100MHz)与一个ADC通信。
步骤1:确定配置参数
- 模式:主模式,5线(使用SCS和ENA)。
- 时钟:CPOL=0, CPHA=0。
- 分频:PRESCALE = SYSCLK2 / 波特率 - 1 = 100MHz / 10MHz - 1 = 9。
- 字符长度:CHARLEN = 8。
- 移位方向:SHIFTDIR = 0 (MSB first)。
- 引脚功能:使能CLK, SIMO, SOMI, SCS, ENA。
步骤2:寄存器配置代码(C语言示例)
// 假设spi0_base为SPI0模块的基地址,已定义为volatile寄存器结构体指针 void SPI_Master_Init(void) { // 1. 软件复位SPI模块 spi0_base->SPIGCR0 = 0x00000001; // 置位RESET位 // 等待复位完成(可选,插入少量延时或检查状态) delay_us(10); spi0_base->SPIGCR0 = 0x00000000; // 清除RESET位,模块退出复位 // 2. 配置引脚功能为SPI (5-pin mode) spi0_base->SPIPC0 = 0x00000F01; // SOMI, SIMO, CLK, ENA, SCS[0] 功能使能 // 3. 配置数据格式寄存器0 (SPIFMT0) spi0_base->SPIFMT0 = 0 | (0 << 20) // WDELAY: 字间延迟0个时钟 | (0 << 19) // SHIFTDIR: 0=MSB first | (1 << 18) // WAITENA: 1=启用ENA握手(5线模式需要) | (0 << 17) // POLARITY: 0 (CPOL=0) | (0 << 16) // PHASE: 0 (CPHA=0) | (9 << 8) // PRESCALE: 分频值9 (100MHz/(9+1)=10MHz) | (8 << 0); // CHARLEN: 字符长度8位 // 4. 配置延迟寄存器(根据从设备时序要求设置) spi0_base->SPIDELAY = 0 | (5 << 24) // C2EDELAY: 片选到ENA超时(根据需要设置) | (10 << 16) // T2EDELAY: 传输结束到ENA超时 | (2 << 8) // T2CDELAY: 最后时钟到片选无效延迟=2个SYSCLK2周期 | (2 << 0); // C2TDELAY: 片选有效到第一时钟延迟=2个SYSCLK2周期 // 5. 配置中断(本例使用轮询,故先禁用中断) spi0_base->SPIINT0 = 0x00000000; // 禁用所有中断和DMA请求 // 6. 最后,使能SPI模块并设置为Master模式 spi0_base->SPIGCR1 = 0 | (1 << 24) // ENABLE: 1=使能模块 | (0 << 16) // LOOPBACK: 0=正常模式 | (1 << 1) // CLKMOD: 1=主模式时钟源 | (1 << 0); // MASTER: 1=主模式 }步骤3:数据收发函数(轮询方式)
uint16_t SPI_Master_Transfer(uint16_t tx_data) { volatile uint32_t spi_status; // 等待发送缓冲区就绪(即上一次传输完成) while ((spi0_base->SPIFLG & 0x00000200) == 0) { // SPIFLG.BUFFULL_FLAG = 0 表示缓冲区空,可以写入 // 在实际应用中,这里应添加超时机制,防止死循环 } // 清除可能存在的接收中断标志(通过读SPIBUF) // 如果之前有未读的数据,先读出来丢弃,避免溢出 if (spi0_base->SPIFLG & 0x00000001) { // 检查RXINTFLG uint16_t dummy = spi0_base->SPIBUF; } // 写入数据到SPIDAT1,同时选择格式寄存器0,并保持片选(CSHOLD=1用于连续传输) spi0_base->SPIDAT1 = (0x0000 << 16) | tx_data; // DFSEL=00 (SPIFMT0), CSHOLD=0 // 等待接收完成 while ((spi0_base->SPIFLG & 0x00000001) == 0) { // 等待RXINTFLG置位 // 同样,应添加超时 } // 读取接收到的数据 uint16_t rx_data = spi0_base->SPIBUF; return rx_data; }注意:上述轮询代码为了清晰省略了超时处理和错误检查。在生产代码中,必须为每个while循环添加超时计数器,防止因外设故障导致程序卡死。例如,可以循环检查一个递增的计数器,超过一定阈值后触发错误处理流程。
5. 常见问题排查与调试技巧实录
即便配置看起来正确,SPI通信也常常出问题。下面是我在多个项目中总结出的排查清单和实战技巧。
5.1 通信完全无响应的排查步骤
- 检查物理连接:这是最基础也最容易被忽略的。用万用表或示波器检查
SPIx_CLK、SPIx_SIMO、SPIx_SCS引脚是否有输出?电压电平是否正确?SPIx_SOMI引脚是否被正确设置为输入?特别注意:在从模式下,SPIx_CLK和SPIx_SIMO必须由外部主设备驱动,DSP对应引脚应配置为输入。 - 确认时钟和电源:DSP的SYSCLK2时钟是否正常?从设备是否已上电?复位引脚是否已释放?
- 验证软件配置顺序:绝对不要在模块使能(
SPIGCR1.ENABLE=1)的情况下修改除数据寄存器外的大部分配置寄存器。正确的顺序是:复位 -> 配置所有参数(引脚、格式、延迟等)-> 最后使能模块。 - 使用回环测试:将
SPIGCR1.LOOPBACK位设为1。在这种模式下,内部将SIMO连接到SOMI。主设备发送的数据会被自己接收。这是一个极好的隔离硬件问题的方法。如果回环测试能成功收发数据,说明软件配置和DSP的SPI模块本身是好的,问题一定出在硬件连接或从设备上。 - 检查引脚复用:C672x的很多引脚功能是复用的。确保你的SPI引脚没有被其他外设(如GPIO、McASP)占用。除了配置
SPIPC0,有时还需要检查芯片级的引脚复用控制寄存器。
5.2 数据错位或误码的排查
- 首要怀疑对象:时钟模式(CPOL/CPHA):这是导致数据错位的头号原因。用示波器同时抓取
SPIx_CLK和SPIx_SIMO/SPIx_SOMI信号。对照从设备数据手册的时序图,检查:- 时钟空闲电平是否正确?
- 数据是在时钟的哪个边沿变化的?哪个边沿稳定的?
- 数据的变化和稳定边沿是否符合你设置的
PHASE值? - 一个技巧:如果发现数据总是差一位,很可能是
PHASE设反了。
- 检查字符长度(CHARLEN):如果你要传8位数据,但CHARLEN设成了9,那么会多出一个时钟周期,接收端会多读一位(通常是0),导致数据整体移位。确保主从双方的位宽一致。
- 检查移位方向(SHIFTDIR):主从设备的MSB/LSB优先顺序必须一致。通常设备手册会明确说明。
- 观察片选时序:如果使用了SCS,用示波器看
SPIx_SCS的边沿和第一个/最后一个时钟沿的关系。是否满足从设备的建立和保持时间要求?如果不满足,调整SPIDELAY寄存器中的C2TDELAY和T2CDELAY。 - 降低波特率:在高波特率下,信号完整性问题(如过冲、振铃)会导致误码。尝试大幅降低
SPIFMTx.PRESCALE的值(即降低波特率),看通信是否恢复正常。如果恢复了,就需要检查PCB布局、走线长度、是否缺少匹配电阻等硬件问题。
5.3 高级调试技巧与工具使用
- 利用SPIEMU寄存器:在调试时,不要总是读
SPIBUF,因为读它会清除中断标志。可以多读SPIEMU寄存器,它是SPIBUF的镜像,读取不影响状态。方便你随时查看接收缓冲区的最新内容。 - 中断与DMA的权衡:对于低速、不频繁的传输,轮询足够简单。但对于高速连续传输(如音频流、图像数据),必须使用DMA。否则CPU会被频繁的SPI中断严重拖累。配置
SPIINT0.DMAREQEN,并设置好dMAX控制器,让数据在SPI缓冲区和内存之间自动搬运。 - 溢出错误处理:
SPIFLG.OVRNINTFLG置位意味着新的数据已经覆盖了还未被读取的旧数据。这通常是因为从设备(或DSP的从模式)处理速度跟不上主设备的发送速度。解决方案:- 增加
WDELAY(字间延迟)。 - 使用带
SPIx_ENA的4线或5线模式,让硬件自动流控。 - 提高从设备的中断优先级或使用DMA来加速数据搬移。
- 在主设备发送流程中,检查从设备的就绪状态(如果有其他GPIO指示)。
- 增加
- 示波器/逻辑分析仪是终极武器:当软件排查无从下手时,一定要用硬件工具抓取波形。一套清晰的SPI波形(CLK, SCS, MOSI, MISO)可以直观地暴露所有时序问题、数据内容问题。学会使用逻辑分析仪的SPI协议解码功能,能极大提升调试效率。
最后,分享一个我踩过的坑:在为一个TMS320C6727设计板子时,SPI通信时好时坏。最终发现是SPIx_SCS信号线在PCB上走线过长(>10cm),且靠近一个开关电源,受到了严重干扰。在SPIx_SCS引脚靠近DSP端串联了一个33欧姆的电阻,并在从设备端对地加了20pF电容,问题得以解决。教训是:即使对于数字信号,在高速或长距离传输时,也要考虑信号完整性问题。