TMS320F2803x SPI通信实战:从原理到FIFO优化与3线制避坑
1. 项目概述与SPI核心价值在嵌入式系统开发中微控制器MCU与外部传感器、存储器、显示屏等外设的通信是家常便饭。面对I2C、UART、SPI这几种常见的串行通信协议我个人的经验是当你的项目对数据传输速度有要求且连接的外设数量不多、布线空间有限时SPISerial Peripheral Interface往往是那个最直接、最高效的选择。它不像I2C那样需要复杂的地址寻址和应答机制也不像UART那样依赖事先约定好的波特率SPI凭借其全双工、主从同步的简洁设计在短距离、点对点或一点对多点的通信场景中表现非常出色。这次我们以德州仪器TI的TMS320F2803x系列微控制器为例来深入聊聊SPI。这颗芯片在电机控制、数字电源等领域应用很广其内置的SPI模块功能相当完善支持主从模式、可编程时钟、4级FIFO甚至还有3线制模式。理解并玩转它意味着你能更高效地驱动各类SPI外设比如读取高精度ADC的数据、控制LED驱动芯片或者与另一个MCU进行高速数据交换从而把CPU从繁重的字节搬运工作中解放出来专注于核心算法。无论你是刚开始接触嵌入式通信的新手还是想优化现有系统性能的老手搞懂SPI的底层原理和TMS320F2803x上的具体实现都是一项非常实用的技能。2. SPI基础原理与通信模型拆解在开始配置寄存器之前我们必须先弄清楚SPI到底是怎么工作的。很多人一开始容易被那几根线弄糊涂其实它的核心思想非常简单同步、全双工、主从控制。2.1 四线制基本通信模型一个标准的SPI通信需要四根线SCLK (Serial Clock) 时钟信号由主设备产生。所有数据位的传输和采样都严格跟随这个时钟的边沿这是“同步”二字的由来。MOSI (Master Out Slave In) 主设备输出从设备输入。主设备通过这根线发送数据给从设备。MISO (Master In Slave Out) 主设备输入从设备输出。从设备通过这根线发送数据给主设备。SS/CS (Slave Select / Chip Select) 片选信号通常低电平有效。主设备通过拉低对应从设备的片选线来“选中”它告诉它“接下来我要和你通信了”。这是实现一个主设备连接多个从设备的关键。在TMS320F2803x的数据手册里这四根线分别被命名为SPICLK、SPISIMO对应MOSI、SPISOMI对应MISO和SPISTE对应SS。通信过程可以想象成两个面对面的移位寄存器。主设备和从设备内部各有一个移位寄存器。当时钟信号跳变时双方寄存器里的数据同时被推出一位从最高位MSB开始并通过MOSI和MISO线交换。经过8个或16个时钟周期后两个寄存器里的内容就完成了互换。主设备在交换过程中既可以主动发送有效数据也可以发送虚拟数据Dummy Data以触发从设备返回数据。这种同时进行发送和接收的能力就是“全双工”。2.2 时钟极性(CPOL)与时钟相位(CPHA)这是SPI配置中最容易出错的地方直接决定了数据在时钟的哪个边沿被采样和输出。它由两个位控制时钟极性 (CLKPOLARITY, CPOL) 定义SPICLK空闲时的电平状态。CPOL 0 时钟空闲时为低电平。CPOL 1 时钟空闲时为高电平。时钟相位 (CLK_PHASE, CPHA) 定义数据在时钟的第几个边沿被采样。CPHA 0 数据在时钟的第一个边沿即SCLK的第一个跳变沿可能是上升沿也可能是下降沿取决于CPOL被采样。CPHA 1 数据在时钟的第二个边沿被采样。这两者的组合形成了四种模式Mode 0, 1, 2, 3。不同的外设芯片可能支持不同的模式必须严格按照外设数据手册的要求来配置主设备的CPOL和CPHA否则通信必然失败。TMS320F2803x的参考手册里提供了清晰的波形图我强烈建议在调试时用逻辑分析仪抓取SPICLK、MOSI、MISO的波形与手册中的时序图对照这是排查时钟模式问题最直接的方法。注意 很多初学者会忽略片选信号SPISTE的用法。手册里特别提到不推荐将SPISTE引脚直接固定接为有效电平。正确的做法是主设备在发起一次完整的数据帧传输前将对应从设备的SPISTE拉低有效在传输完成后立即拉高无效。这可以防止在总线空闲时从设备误将线上的噪声当作时钟和数据导致内部移位计数器失步。如果发现从设备数据错乱尝试复位其SPISWRESET位来重置内部位计数器是一个有效的恢复手段。3. TMS320F2803x SPI模块深度解析与配置流程了解了基本原理我们进入实战环节看看在TMS320F2803x上如何具体操作。它的SPI模块是一个高度可配置的硬件外设我们通过配置一系列寄存器来控制它。3.1 关键功能寄存器一览在写代码前脑子里要对这几个核心寄存器有张地图SPICCR (配置控制寄存器) 包含软件复位(SPISWRESET)、时钟极性(CLKPOLARITY)、字符长度(SPICHAR[3:0])等全局配置。SPICTL (控制寄存器) 控制主从模式(MASTER_SLAVE)、时钟相位(CLK_PHASE)、发送使能(TALK)、中断使能等。SPIBRR (波特率寄存器) 决定SPI的通信速率。计算公式是核心当SPIBRR 3~127时波特率 LSPCLK/ (SPIBRR 1)当SPIBRR 0,1,2时波特率 LSPCLK/ 4。其中LSPCLK是低速外设时钟由系统时钟分频得到。SPIFFTX/RX/CT (FIFO相关寄存器) 用于启用和配置4级深度的发送/接收FIFO设置中断触发水位线(TXFFIL,RXFFIL)和传输延迟(FFTXDLY)。SPIDAT (数据寄存器) 16位移位寄存器数据移入移出的实际位置。SPITXBUF (发送缓冲寄存器) 你要发送的数据先写到这里硬件会在适当时机自动将其加载到SPIDAT中移位发出。SPIRXBUF (接收缓冲寄存器) 接收完成的数据会从SPIDAT转移到这里供CPU读取。SPISTS (状态寄存器) 查看中断标志(INT_FLAG)、接收器溢出标志(OVERRUN_FLAG)、发送缓冲器满标志(BUFFULL_FLAG)。3.2 标准模式初始化与数据收发步骤根据手册的指导一个稳健的SPI初始化流程应该是这样的我习惯称之为“复位-配置-使能”三步法进入复位状态 将SPICCR.SPISWRESET位清零。这一步至关重要它让SPI模块内部逻辑复位到一个已知的稳定状态。在SPISWRESET0期间你可以安全地修改几乎所有配置寄存器而不用担心产生毛刺或意外传输。配置参数 按需设置各个寄存器。通过SPICTL.MASTER_SLAVE选择主从模式。根据外设要求配置SPICCR.CLKPOLARITY和SPICTL.CLK_PHASE。根据LSPCLK频率和所需波特率计算并设置SPIBRR。这里有个坑手册指出当(SPIBRR1)为奇数且大于3时SPICLK的波形会不对称高电平或低电平周期多一个LSPCLK周期。在驱动某些对时钟占空比敏感的外设时需要留意这一点尽量让(SPIBRR1)为偶数。通过SPICCR.SPICHAR设置数据位长度1-16位。记住写入SPIDAT或SPITXBUF的数据必须左对齐而从SPIRXBUF读出的数据是右对齐的。清除可能存在的旧状态标志INT_FLAG,OVERRUN_FLAG。中断配置如果使用非FIFO模式使能SPICTL.SPIINTENA传输完成中断和SPICTL.OVERRUNINTENA接收溢出中断。两者共享同一个中断向量SPIINT。FIFO模式先使能SPIFFTX.SPIFFENA开启FIFO功能。然后分别设置发送和接收FIFO的中断触发水位线TXFFIL,RXFFIL再使能对应的FIFO中断TXFFIENA,RXFFIENA。此时发送和接收有独立的中断向量SPITXINT和SPIRXINT。退出复位启动模块 将SPICCR.SPISWRESET位置1。SPI模块开始正常工作。初始化完成后数据收发就简单了发送数据 检查SPISTS.BUFFULL_FLAG是否为0或FIFO非满然后将数据写入SPITXBUF。接收数据 等待SPISTS.INT_FLAG置位或FIFO接收中断然后从SPIRXBUF读取数据。查询式收发示例非FIFO主模式// 发送一个字节并接收返回字节 uint16_t SPI_TransmitReceive(uint16_t txData) { // 等待发送缓冲区为空 while(SpiaRegs.SPISTS.bit.BUFFULL_FLAG 1) {} // 写入发送数据启动传输 SpiaRegs.SPITXBUF txData; // 等待接收完成中断标志 while(SpiaRegs.SPISTS.bit.INT_FLAG 0) {} // 读取接收到的数据 return SpiaRegs.SPIRXBUF; }3.3 FIFO功能实战与性能优化TMS320F2803x的SPI模块支持4级深度的硬件FIFO这是一个大幅提升效率、降低CPU中断频率的利器。没有FIFO时每收发一个字节或16位字都可能产生一次中断CPU忙于进出中断上下文效率低下。启用FIFO后你可以一次性写入最多4个数据到发送FIFO硬件会自动依次发送同样硬件可以连续接收最多4个数据存入接收FIFO再一次性通知CPU读取。配置和使用FIFO的关键点启用与复位 设置SPIFFTX.SPIFFENA1启用FIFO增强功能。通过SPIFFTX.TXFIFO和SPIFFRX.RXFIFO位可以分别复位发送和接收FIFO的指针。中断水位线设置 这是精髓所在。TXFFIL定义了发送FIFO中断的触发条件当FIFO中剩余的数据量TXFFST小于或等于这个设定值时触发SPITXINT中断提示你可以补充数据了。通常设置为0或1表示快发完了就中断。RXFFIL定义了接收FIFO中断的触发条件当FIFO中已存的数据量RXFFST大于或等于这个设定值时触发SPIRXINT中断提示你可以来取数据了。可以根据你的处理能力设置为1、2、3或4。可编程传输延迟SPIFFCT.FFTXDLY这个功能非常实用。它定义了FIFO中的一个字传输到发送移位寄存器SPIDAT之间的延迟以SPICLK周期数为单位。设置为0就是背靠背连续发送。如果你的SPI外设比如一些低速的EEPROM或ADC在两次传输之间需要一定的恢复时间就可以通过设置这个延迟来实现“免 glue logic”无需外部逻辑电路的适配。例如设置FFTXDLY10则每发送完一个字会自动插入10个SPICLK周期的空闲时间再发送下一个字。FIFO模式下的中断服务程序框架思路// 假设RXFFIL设置为4即接收满4个字才中断 interrupt void SPIRX_INT_ISR(void) { uint16_t i, data[4]; // 检查并清除中断标志 if(SpiaRegs.SPIFFRX.bit.RXFFINT 1) { SpiaRegs.SPIFFRX.bit.RXFFINTCLR 1; // 清除接收FIFO中断标志 // 一次性读取FIFO中所有数据 for(i 0; i 4; i) { data[i] SpiaRegs.SPIRXBUF; } // 处理 data[0]~data[3]... } // 其他中断标志处理... PieCtrlRegs.PIEACK.all PIEACK_GROUP6; // 应答PIE中断 }4. 3线制SPI模式的应用与避坑指南为了节省一个IO引脚SPI可以运行在3线制模式。此时SPISIMO和SPISOMI合并为一条双向数据线主模式下叫SPIMOMI从模式下叫SPISISO。节省引脚是有代价的通信变成了半双工并且软件上需要特别注意。4.1 3线制模式配置与数据流首先需要通过设置SPIPRI.TRIWIRE1来启用3线模式。引脚配置也随之变化主模式 使用SPICLK,SPISTE,SPISIMO此时作为双向SPIMOMI。SPISOMI引脚可释放为普通GPIO。从模式 使用SPICLK,SPISTE,SPISOMI此时作为双向SPISISO。SPISIMO引脚可释放为普通GPIO。最关键的是SPICTL.TALK发送使能位的控制。在3线模式下因为收发共用一条线同一时间只能有一个方向的数据流。4.2 主模式下的收发代码与常见陷阱手册提供的代码示例非常典型但需要理解其背后的原因主设备发送数据Uint16 data 0x55AA; Uint16 dummy; SpiaRegs.SPICTL.bit.TALK 1; // 1. 打开发送通道 SpiaRegs.SPITXBUF data; // 2. 写入数据启动传输 while(SpiaRegs.SPISTS.bit.INT_FLAG ! 1) {} // 3. 等待传输完成 dummy SpiaRegs.SPIRXBUF; // 4. 必须读取清除自己收到的“垃圾数据”为什么第4步必须读因为在3线模式下主设备发送的数据也会通过内部回路被自己接收。如果不读取SPIRXBUF接收缓冲区会一直处于“满”的状态阻塞后续操作。主设备接收数据从从设备读数据Uint16 receivedData; Uint16 dummy 0x0000; // 虚拟数据内容任意 SpiaRegs.SPICTL.bit.TALK 0; // 1. 关键关闭发送通道 SpiaRegs.SPITXBUF dummy; // 2. 写入虚拟数据只为产生时钟 while(SpiaRegs.SPISTS.bit.INT_FLAG ! 1) {} // 3. 等待从设备数据接收完成 receivedData SpiaRegs.SPIRXBUF; // 4. 读取从设备发来的真实数据为什么第1步要关闭TALK因为此时主设备是接收方不应该驱动数据线。关闭TALK后主设备输出的数据线呈高阻态从设备才能安全地驱动数据线发送数据。主设备写入SPITXBUF的dummy数据只是为了产生SPICLK时钟信号并不会真正出现在引脚上因此也不会被自己接收所以接收缓冲区里就是从设备发来的数据。避坑要点 在3线制主从通信中切换数据方向发送转接收或接收转发送时务必在操作间隙留出足够的时间并严格按“先改TALK位再进行下一次数据传输”的顺序操作。我曾在一个项目中因为切换过快导致从设备来不及切换IO方向造成数据冲突通信失败。一个稳妥的做法是在切换TALK位后插入几个空指令的短暂延时。5. 系统集成、调试与问题排查实录将SPI模块集成到整个系统中除了核心配置还有一些“外围”事项和常见坑点需要关注。5.1 GPIO多路复用器与引脚配置TMS320F2803x的引脚功能是可配置的。在使用SPI前必须通过GPIO多路复用控制寄存器GPxMUX将对应引脚配置为SPI功能而不是普通的GPIO。例如要将GPIO16配置为SPISIMOA可能需要设置GpioCtrlRegs.GPAMUX.bit.GPIO16 1具体值需查数据手册。此外对于输入引脚如从模式下的SPICLK,SPISIMO建议将GPIO输入限定GPxQSELn设置为异步模式通常设为11b以避免不必要的同步采样延迟影响高速SPI通信。内部上拉电阻则根据实际硬件连接通过GPyPUD寄存器配置使能或禁用。5.2 中断与PIE模块连接SPI的中断需要连接到CPU的可编程中断扩展模块PIE。你需要在PIE向量表中为SPI中断可能是SPIINT,SPIRXINT,SPITXINT分配对应的中断服务程序ISR地址。在PIE中断使能寄存器PIEIER中使能对应的PIE组中断。在CPU级中断使能寄存器IER中使能对应的CPU中断。最后全局使能中断EINT。在ISR结束时不要忘记清除SPI模块内部的中断标志并写1到PIE应答寄存器PIEACK的对应位以通知PIE可以再次响应该组中断。5.3 典型问题排查速查表以下是我在多年调试中总结的一些常见问题及排查思路现象可能原因排查步骤与解决方法完全无通信无波形1. SPI模块未使能 (SPISWRESET0)。2. 引脚功能未配置为SPI。3. 主设备未产生时钟。1. 检查SPICCR.SPISWRESET是否为1。2. 用示波器或逻辑分析仪检查SPICLK引脚。确认GPIO MUX配置正确。3. 主模式下检查SPICTL.MASTER_SLAVE1且SPICTL.TALK1发送数据时。能发送但接收数据全为0或固定值1. 从设备未正确响应。2. 时钟极性(CPOL)或相位(CPHA)不匹配。3. 从设备片选SPISTE未控制好。1. 确认从设备供电、焊接正常。2.重点检查CPOL和CPHA设置务必与从设备手册一致。用逻辑分析仪对比时钟边沿和数据变化沿。3. 确保主设备在传输期间将对应从设备的SPISTE拉低。接收数据错位如字节顺序反了数据位长度(SPICHAR)或对齐方式错误。1. 检查SPICCR.SPICHAR是否与从设备数据位宽一致如8位或16位。2. 确认写入SPITXBUF的数据是左对齐的读取SPIRXBUF后是右对齐的需要进行必要的移位操作。通信一段时间后出错1. 接收溢出(OVERRUN_FLAG)。2. 从设备失去同步。1. 检查是否及时读取SPIRXBUF。在非FIFO模式若新数据到来时旧数据未被读取会置位溢出标志。需在中断或查询中及时读走数据。2. 检查SPISTE信号是否在非传输期间为高防止干扰。尝试软件复位(SPISWRESET)从设备SPI模块。FIFO模式下中断不触发1. FIFO未使能。2. 中断水位线(TXFFIL/RXFFIL)设置不当。3. PIE或CPU中断未使能。1. 确认SPIFFTX.SPIFFENA1。2. 检查TXFFIL和RXFFIL值。例如想接收4个字后中断需设RXFFIL4并使能RXFFIENA。3. 检查PIE和CPU各级中断使能位以及全局中断状态。3线模式通信异常1.TALK位切换时机错误。2. 未及时读取“垃圾数据”。1. 主设备接收前必须先置TALK0再发送虚拟数据启动时钟。2. 主设备发送后必须读取SPIRXBUF以清空自收的数据。调试SPI逻辑分析仪是你的最佳伙伴。它能同时捕获时钟、数据、片选所有信号直观地展示时序关系、数据内容是验证配置、定位问题的终极武器。当通信异常时不要只盯着代码看一定要抓取实际波形进行分析往往能发现配置与预期不符的细节问题。