TMS320F28003x SPI通信协议详解:从原理到寄存器配置与实战
2026/7/25 23:15:49 网站建设 项目流程

1. SPI接口核心原理与工作模式解析

SPI,全称Serial Peripheral Interface,即串行外设接口,是嵌入式系统中最经典、最常用的同步串行通信协议之一。它不像UART那样需要复杂的波特率校准,也不像I2C那样需要复杂的地址寻址和应答机制。SPI的核心思想极其简洁:一个主设备(Master)通过时钟线(SCLK)控制通信节奏,同时通过两根数据线(MOSI和MISO)与一个或多个从设备(Slave)进行全双工数据交换。这种“一主多从”的架构,使得它在需要高速、实时数据交换的场景中,如传感器数据采集、TFT屏幕驱动、Flash存储器读写等,具有无可替代的优势。

为什么SPI能如此高效?关键在于其硬件实现的简洁性。通信的时序完全由硬件移位寄存器控制,CPU只需要在初始化时配置好参数,之后的数据搬运工作就可以交给DMA或中断处理,极大地解放了CPU资源。在TMS320F28003x这类高性能实时微控制器中,SPI模块更是被高度集成和优化,支持高达16级深度的FIFO、灵活的时钟模式以及强大的DMA联动能力,让开发者能够轻松构建出稳定可靠的高速数据链路。

1.1 深入理解SPI的四线制与三线制

标准的SPI通信需要四根线:

  1. SCLK (Serial Clock): 串行时钟,由主设备产生,是所有数据收发的节拍器。
  2. MOSI (Master Output Slave Input): 主设备输出,从设备输入。主设备通过这根线向从设备发送数据。
  3. MISO (Master Input Slave Output): 主设备输入,从设备输出。从设备通过这根线向主设备返回数据。
  4. CS/SS (Chip Select / Slave Select): 片选信号,低电平有效。主设备通过控制这根线的电平,来选择要与哪个从设备进行通信。这是实现“一主多从”的关键。

在TMS320F28003x的寄存器描述中,它们对应的引脚名称为SPICLK、SPISIMO、SPISOMI和SPISTE。这里需要注意,SPISIMO和SPISOMI的命名是从SPI模块自身的视角出发的。对于主设备,SPISIMO是它的输出(对应MOSI),SPISOMI是它的输入(对应MISO);对于从设备,则恰好相反。

除了标准的四线制,SPI还支持一种三线制(Single-BiDirectional)模式。在此模式下,通过配置SPICCR.0(SPISW位)或相关的控制位,SPI模块可以复用同一根数据线进行半双工通信。这在引脚资源紧张或者只需要单向大数据流传输的场景下非常有用。例如,在驱动一个只接收命令而不返回数据的显示模块时,就可以启用三线制模式,节省一个GPIO引脚。

1.2 时钟极性(CPOL)与时钟相位(CPHA)的四种组合

SPI协议最让初学者困惑的莫过于时钟极性和相位的配置。这决定了数据在时钟信号的哪个边沿被采样和输出,是确保主从设备正确同步的基石。TMS320F28003x通过SPICTL.3(CLKPOLARITY)和SPICTL.4(CLK_PHASE)这两个寄存器位来控制,形成了四种经典模式(通常被称为Mode 0, 1, 2, 3)。

  • 模式0 (CPOL=0, CPHA=0): 时钟空闲时为低电平。数据在时钟的上升沿被采样(接收),在时钟的下降沿被输出(发送)。这是最常见的一种模式。
  • 模式1 (CPOL=0, CPHA=1): 时钟空闲时为低电平。数据在时钟的下降沿被采样,在时钟的上升沿被输出。
  • 模式2 (CPOL=1, CPHA=0): 时钟空闲时为高电平。数据在时钟的下降沿被采样,在时钟的上升沿被输出。
  • 模式3 (CPOL=1, CPHA=1): 时钟空闲时为高电平。数据在时钟的上升沿被采样,在时钟的下降沿被输出。

如何选择正确的模式?这里没有通用答案,完全取决于你的从设备芯片的数据手册。你必须严格按照从设备要求的时序图来配置主设备的CPOL和CPHA。一个常见的技巧是:观察从设备数据手册的时序图,看它的数据线(DI或DO)在时钟的哪个边沿是稳定的(即“数据建立”),那个边沿通常就是主设备应该采样的边沿;而数据变化的边沿,就是主设备输出的边沿。

注意: 在TMS320F28003x的参考手册中,对CLK_PHASE的描述是“时钟相位选择”,它决定了数据输出是否提前半个时钟周期。当CLK_PHASE=0时,数据在时钟边沿变化;当CLK_PHASE=1时,数据提前半个周期变化。这与上述常见的Mode定义在表述上略有不同,但本质是相通的。最可靠的方法是参照手册中的波形图(如图24-7)进行配置。

1.3 主从模式下的数据流与控制逻辑

SPI模块可以配置为主模式(SPICTL.2 (MASTER_SLAVE) = 1)或从模式(=0)。这个选择决定了谁产生时钟以及数据的流向。

在主模式下

  • SPI模块自动产生SPICLK时钟信号。波特率由SPIBRR寄存器根据系统低速外设时钟(LSPCLK)计算得出,计算公式为:当SPIBRR = 3~127时,波特率 = LSPCLK / (SPIBRR + 1);当SPIBRR = 0,1,2时,波特率 = LSPCLK / 4。这意味着最高波特率不能超过LSPCLK/4。
  • 数据从SPISIMO引脚输出,从SPISOMI引脚输入。
  • 主设备通过控制SPISTE引脚(通常拉低)来选通目标从设备。在驱动多个从设备时,通常需要用额外的GPIO来模拟多个片选信号,如手册24.2.1节所述。

在从模式下

  • SPI模块的SPICLK引脚变为输入,接收来自主设备的时钟信号。此时,从设备的波特率由主设备决定,但输入时钟频率同样不能超过LSPCLK/4。
  • 数据从SPISOMI引脚输出,从SPISIMO引脚输入。
  • SPISTE引脚变为输入,作为从设备使能信号。只有当SPISTE为低电平时,从设备才会驱动SPISOMI数据线;当其为高电平时,SPISOMI呈高阻态,避免总线冲突。SPICTL.1 (TALK)位可以软件控制从设备是否输出数据,为0时强制SPISOMI为高阻态。

数据移位与缓冲机制: 数据通信的核心是16位移位寄存器SPIDAT。当主设备启动传输(向SPIDATSPITXBUF写入数据)时,数据从MSB开始,随着每个SPICLK时钟边沿,从SPIDAT中移出到MOSI线,同时MISO线上的数据被移入SPIDAT的LSB。当指定长度的数据位(由SPICCR.3-0 (SPICHAR)定义,1-16位)全部移位完成后,SPIDAT中的接收数据会被自动搬运到接收缓冲寄存器SPIRXBUF中,并置位中断标志SPISTS.6 (INT_FLAG)

这里有一个关键细节:SPITXBUF是发送缓冲寄存器。你可以向SPITXBUF写入下一个要发送的数据,而当前数据正在SPIDAT中移位发送。一旦当前发送完成,SPITXBUF中的数据会自动加载到SPIDAT中开始下一轮发送,从而实现连续传输。如果SPITXBUF为空,则发送停止。

2. TMS320F28003x SPI寄存器配置详解

理解了原理,我们进入实战环节:如何通过配置TMS320F28003x那一大堆寄存器,让SPI模块按照我们的意愿工作。很多新手看着手册里几十个寄存器位会感到头晕,其实只要抓住几个核心寄存器,按流程配置,一切都会变得清晰。

2.1 关键功能寄存器精讲

SPI的寄存器主要分为几类:控制类、状态类、数据类和FIFO扩展类。我们挑最核心的几个来拆解。

1. 串行外设接口配置控制寄存器 (SPICCR)这个寄存器用于一些最基础的全局配置。

  • 位7 (SPISWRESET): SPI软件复位。这是一个非常关键的位。任何对SPI模块的重大配置更改(如修改SPICHAR字符长度、切换主从模式、改变时钟方案)之前,都必须先将此位置0(复位),配置完成后再置1(使能)。否则可能导致不可预测的行为。
  • 位6 (HS_MODE): 高速模式��能。置1可启用SPI引脚驱动器的增强模式,以获得更高的翻转速率。启用前务必确认你的PCB走线和负载电容符合数据手册要求,否则可能造成信号完整性问题。
  • 位3-0 (SPICHAR): 字符长度控制。0000代表1位数据,0001代表2位,...,1111代表16位数据。这里有个坑:如果你设置的数据位不是16位,那么写入SPIDATSPITXBUF的数据必须左对齐,而读取SPIRXBUF得到的数据是右对齐的。手册中的Example 24-1清晰地展示了这一点。

2. 串行外设接口操作控制寄存器 (SPICTL)这个寄存器控制SPI的运行时的行为。

  • 位4 (CLK_PHASE)位3 (CLKPOLARITY): 如前所述,这两位组合决定了SPI的时钟模式。必须与从设备严格匹配。
  • 位2 (MASTER_SLAVE): 主/从模式选择。1为主,0为从。
  • 位1 (TALK): 发送使能。在主模式下,通常保持为1(使能发送)。在从模式下,可以动态控制此位来使能或禁用本从设备的数据输出,用于多从机管理。
  • 位0 (SPIINTENA): SPI中断使能。在非FIFO模式下,控制是否在数据收发完成(INT_FLAG置位)时产生中断。

3. 串行外设接口波特率寄存器 (SPIBRR)决定主模式下的通信速率。其取值与波特率的关系公式必须牢记:

  • SPIBRR = 0, 1, 2时:Baud Rate = LSPCLK / 4
  • SPIBRR = 3 ~ 127时:Baud Rate = LSPCLK / (SPIBRR + 1)

计算示例:假设系统LSPCLK = 50 MHz,我们期望的SPI波特率为10 Mbps。 首先,根据公式反推:SPIBRR = LSPCLK / Baud Rate - 1 = 50MHz / 10Mbps - 1 = 5 - 1 = 4。 将4写入SPIBRR寄存器即可。需要注意的是,实际波特率会有误差,且最高速率受限于GPIO的翻转速度,需查阅芯片数据手册的“AC Electrical Characteristics”章节确认。

4. 串行外设接口状态寄存器 (SPISTS)用于查询SPI模块的当前状态。

  • 位7 (RXFFOVF_FLAG): 接收FIFO溢出标志(仅在FIFO模式下有效)。
  • 位6 (INT_FLAG): SPI中断标志。当一次数据收发完成时,硬件自动置1。此标志必须通过向SPIFFRX.15 (RXFFOVFCLR)写1(FIFO模式)或读取SPIRXBUF(非FIFO模式)来清除。
  • 位5 (OVERRUN_FLAG): 溢出标志。当CPU或DMA还未读取SPIRXBUF中的数据,而新的数据已经接收完成并准备覆盖时,此位置1。这表明数据丢失了。同样需要软件清除。

2.2 FIFO功能配置与使用策略

TMS320F28003x的SPI模块配备了16级深度的发送和接收FIFO,这是提升效率的神器。它允许CPU或DMA一次性写入或读取多个数据,减少中断频率,特别适合批量数据传输。

启用与配置FIFO: FIFO功能由SPIFFTX寄存器控制。

  • 位14 (SPIFFENA): FIFO功能总使能。必须置1才能使用FIFO。
  • 位13 (TXFIFO)位12 (RXFIFO): 发送/接收FIFO复位。置1可复位对应的FIFO指针,通常在初始化或出错恢复时使用。
  • 位4-0 (TXFFIL): 发送FIFO中断触发级别。当发送FIFO中剩余的数据量(TXFFST)小于或等于此值时,触发发送中断(SPITXINT)。合理设置此值可以实现“缓冲区半空”中断,及时补充数据。

接收FIFO的配置在SPIFFRX寄存器。

  • 位4-0 (RXFFIL): 接收FIFO中断触发级别。当接收FIFO中积累的数据量(RXFFST)大于或等于此值时,触发接收中断(SPIRXINT)。合理设置可以实现“缓冲区半满”中断,及时取走数据。

FIFO中断模式的转变: 这是一个非常重要的概念。当启用FIFO后(SPIFFENA=1),SPI的中断逻辑完全改变了。

  • 原来的SPIINT中断(非FIFO模式下的收发完成中断)被禁用
  • 取而代之的是两个独立的中断:SPITXINT(发送FIFO中断)和SPIRXINT(接收FIFO中断)。
  • 在PIE(外设中断扩展模块)中,你需要重新映射中断服务函数。SPIRXINT通常映射到原来的SPIINT向量,而SPITXINT则使用一个独立的向量。

延迟发送控制寄存器 (SPIFFCT)SPIFFCT寄存器的低8位(FFTXDLY7-0)用于设置FIFO发送延迟。它定义了从FIFO传输一个字到发送移位寄存器SPIDAT之间,需要等待多少个SPICLK周期。这个功能对于连接一些低速外设(如EEPROM、慢速ADC)非常有用,可以避免因数据发送过快而导致从设备来不及响应。设置为0可实现背靠背连续发送;设置为最大值255则获得最大间隔。

2.3 中断与DMA的协同配置

高效的SPI通信离不开中断和DMA。TMS320F28003x为SPI提供了灵活的中断源和DMA触发机制。

中断配置要点

  1. 确定模式: 首先决定使用非FIFO模式还是FIFO模式。对于单次或低频数据传输,非FIFO模式(查询INT_FLAG或使能SPIINT中断)足够简单。对于高速流式数据,必须使用FIFO模式。
  2. 使能PIE中断: 在PIE控制器中,使能对应的中断组和中断位。对于FIFO模式,通常是SPIRXINT(可能对应PIE中的SPIINT)和SPITXINT
  3. 编写ISR: 在中断服务程序中,首先要读取SPIFFRXSPIFFTX中的中断标志位(RXFFINTTXFFINT)来判断中断来源,然后进行相应的FIFO读写操作。操作完成后,必须向SPIFFRX.14 (RXFFINTCLR)SPIFFTX.13 (TXFFINTCLR)位写1来清除中断标志,否则会持续进入中断。

DMA配置策略: SPI模块可以产生SPITXDMASPIRXDMA两个触发信号,分别用于触发DMA的写操作(发送数据)和读操作(接收数据)。DMA与FIFO的配合堪称“性能黄金搭档”。

  • 发送DMA配置SPITXDMA触发在TXFFST <= TXFFIL时产生。手册24.3.8.1节给出了一个绝佳的配置公式。假设你要发送128个字,设置TXFFIL=8。那么:

    • DMA传输次数 = (128 / 8) - 1 = 15(即进行16次DMA传输)。
    • DMA突发大小 = (16 - 8) - 1 = 7(即每次DMA触发搬运8个字到SPI的TX FIFO)。
    • 关键点: DMA突发大小必须 <= (16 - TXFFIL),以防止DMA向已满的FIFO写数据导致丢失。
  • 接收DMA配置SPIRXDMA触发在RXFFST >= RXFFIL时产生。手册24.3.8.2节同样有公式。假设要接收200个字,设置RXFFIL=4。那么:

    • DMA传输次数 = (200 / 4) - 1 = 49(进行50次传输)。
    • DMA突发大小 = 4 - 1 = 3(每次DMA触发从SPI的RX FIFO搬运4个字)。
    • 关键点: DMA突发大小最好等于RXFFIL,并且是总接收字数的整数因子,以确保DMA能完整、准确地搬空所有数据。

3. 从零开始:SPI模块初始化与数据收发实战

理论说得再多,不如一行代码。下面我们以一个具体的场景为例:配置TMS320F28003x的SPI-A模块为主机,以模式0(CPOL=0, CPHA=0)、10MHz波特率、16位数据格式,与一个SPI从设备通信,并启用接收FIFO中断。

3.1 初始化步骤与代码实现

初始化SPI必须遵循一个严格的顺序,否则可能导致通信失败。

第一步:引脚复用配置在操作任何外设之前,首先要将对应的GPIO引脚配置为外设功能,而不是普通的数字IO。

// 假设使用SPI-A, 引脚为GPIO16(SPISIMOA), GPIO17(SPISOMIA), GPIO18(SPICLKA), GPIO19(SPISTEA) // 1. 先配置GPIO MUX1寄存器,选择外设功能 GPIO_setMasterCore(16, GPIO_CORE_CPU1); // 选择CPU1核控制 GPIO_setPinConfig(GPIO_16_SPISIMOA); // GPIO16 配置为 SPISIMOA GPIO_setPinConfig(GPIO_17_SPISOMIA); // GPIO17 配置为 SPISOMIA GPIO_setPinConfig(GPIO_18_SPICLKA); // GPIO18 配置为 SPICLKA GPIO_setPinConfig(GPIO_19_SPISTEA); // GPIO19 配置为 SPISTEA (作为主机,可配置为GPIO手动控制) // 2. 设置引脚方向 GPIO_setDirectionMode(16, GPIO_DIR_MODE_OUT); // SIMO 输出 GPIO_setDirectionMode(17, GPIO_DIR_MODE_IN); // SOMI 输入 GPIO_setDirectionMode(18, GPIO_DIR_MODE_OUT); // CLK 输出 GPIO_setDirectionMode(19, GPIO_DIR_MODE_OUT); // STE 输出, 用于片选 // 3. (可选)关闭输入限定器,因为SPI是高速同步信号 GPIO_setQualificationMode(16, GPIO_QUAL_ASYNC); GPIO_setQualificationMode(17, GPIO_QUAL_ASYNC); GPIO_setQualificationMode(18, GPIO_QUAL_ASYNC); GPIO_setQualificationMode(19, GPIO_QUAL_ASYNC);

第二步:SPI模块基本参数配置使用TI提供的DriverLib库函数可以简化寄存器操作。

#include "driverlib.h" void SPI_Master_Init(void) { // 1. 使能SPI-A模块的时钟(假设使用外设时钟控制寄存器) SysCtl_enablePeripheral(SYSCTL_PERIPH_CLK_SPIA); // 2. 软件复位SPI模块(进入配置状态) SPI_disableModule(SPIA_BASE); // 等待复位完成 DEVICE_DELAY_US(10); // 3. 配置SPI为控制器(主机)模式 SPI_setConfig(SPIA_BASE, DEVICE_LSPCLK_FREQ, SPI_PROT_POL0PHA0, SPI_MODE_MASTER, 10000000, 16); // 参数解释: // DEVICE_LSPCLK_FREQ: 系统LSPCLK频率,用于计算SPIBRR // SPI_PROT_POL0PHA0: 时钟模式0 (CPOL=0, CPHA=0) // SPI_MODE_MASTER: 主模式 // 10000000: 期望的波特率10MHz,库函数内部会计算并设置SPIBRR // 16: 数据位长度16位 // 4. 使能FIFO功能,并设置FIFO中断触发级别 SPI_enableFIFO(SPIA_BASE); // 相当于设置 SPIFFTX.SPIFFENA = 1 SPI_setFIFOInterruptLevel(SPIA_BASE, SPI_FIFO_TX0, SPI_FIFO_RX4); // 参数解释:发送FIFO空(<=0)时产生TX中断,接收FIFO有4个或以上数据时产生RX中断 // 5. 配置FIFO发送延迟(可选,根据从设备速度调整) SPI_setFIFODelay(SPIA_BASE, 0); // 设置为0,无延迟,连续发送 // 6. 使能SPI模块操作 SPI_enableModule(SPIA_BASE); }

这个SPI_setConfig函数封装了SPICCRSPICTLSPIBRR等寄存器的关键配置。如果你需要更精细的控制,可以分别调用SPI_setClockPolaritySPI_setPhaseSPI_setBaudRate等函数。

第三步:中断配置

// 启用PIE中断 Interrupt_enableMaster(); // 总中断使能 // 注册SPI接收中断服务函数(假设使用FIFO模式,接收中断向量为SPIINTA) Interrupt_register(INT_SPIA_RX, &SPIA_RX_ISR); Interrupt_enable(INT_SPIA_RX); // 在PIE向量表中使能SPI-A接收中断(第6组,第1个中断) PieCtrl_enableInterrupt(PIE_GROUP6, PIE_INTERRUPT_SPIA_RX);

第四步:编写中断服务函数

__interrupt void SPIA_RX_ISR(void) { uint16_t rxData[16]; // 假设我们一次最多处理16个数据 uint16_t rxCount; // 1. 检查中断源是否为接收FIFO中断 if(SPI_getInterruptStatus(SPIA_BASE, SPI_INT_RXFF) == true) { // 2. 获取当前接收FIFO中的数据个数 rxCount = SPI_getRxFIFOStatus(SPIA_BASE); // 读取RXFFST的值 // 3. 确保不会溢出我们的缓冲区 if(rxCount > 16) rxCount = 16; // 4. 从接收FIFO中循环读取数据 for(int i = 0; i < rxCount; i++) { rxData[i] = SPI_readDataNonBlocking(SPIA_BASE); // 这里可以对rxData[i]进行处理... } // 5. 清除接收FIFO中断标志(至关重要!) SPI_clearInterruptStatus(SPIA_BASE, SPI_INT_RXFF); } // 6. 必须应答PIE中断 PieCtrl_clearIntFlag(PIE_GROUP6, PIE_INTERRUPT_SPIA_RX); Interrupt_clearACKGroup(INTERRUPT_ACK_GROUP6); }

3.2 主从设备数据收发流程

配置完成后,就可以进行数据收发了。以下是一个简单的查询式发送和中断式接收的例子。

主机发送数据(查询方式)

void SPI_Master_Transmit(uint16_t *pData, uint16_t size) { // 1. 拉低片选信号,选中从设备 GPIO_writePin(19, 0); // 假设GPIO19连接从设备的CS // 2. 循环发送数据 for(int i = 0; i < size; i++) { // 等待发送FIFO有空间(非FIFO模式则等待TXBUF空) while(SPI_getTxFIFOStatus(SPIA_BASE) == 16) { ; // 等待,直到TX FIFO非满 } // 写入数据到发送FIFO,启动传输 SPI_writeDataNonBlocking(SPIA_BASE, pData[i]); } // 3. (可选)等待所有数据发送完成 while(SPI_isBusy(SPIA_BASE)) { ; // 等待SPI移位寄存器空闲 } // 4. 拉高片选信号,结束本次传输 GPIO_writePin(19, 1); }

主机接收数据(结合发送,全双工): SPI是全双工的,发送的同时也在接收。通常我们发送一个“哑元”数据(如0x0000或0xFFFF)来驱动时钟,从而读取从设备的返回数据。

uint16_t SPI_Master_Receive(uint16_t dummyData) { uint16_t receivedData; // 1. 拉低片选 GPIO_writePin(19, 0); // 2. 发送哑元数据,同时启动接收 SPI_writeDataNonBlocking(SPIA_BASE, dummyData); // 3. 等待接收完成(非FIFO模式下查询INT_FLAG, FIFO模式下等待数据进入RX FIFO) // 这里以查询RXFFST为例(FIFO模式) while(SPI_getRxFIFOStatus(SPIA_BASE) == 0) { ; // 等待接收FIFO有数据 } // 4. 读取接收到的数据 receivedData = SPI_readDataNonBlocking(SPIA_BASE); // 5. 拉高片选 GPIO_writePin(19, 1); return receivedData; }

实操心得:在实际项目中,我强烈建议使用DMA+FIFO+中断的组合来处理持续的数据流。例如,在通过SPI读取一个加速度传感器时,可以配置DMA循环模式,将SPI接收FIFO的数据自动搬运到内存中的一个环形缓冲区。应用程序只需要从环形缓冲区消费数据即可,完全避免了在高速数据流中频繁进入中断带来的性能瓶颈。对于发送,也可以预先将一帧命令数据放入数组,然后用DMA一次性发送,效率极高。

4. 高级应用、调试与故障排查实录

掌握了基础配置和简单收发后,我们来看看更复杂的应用场景和那些让人头疼的调试问题。

4.1 多从机系统与软件片选管理

TMS320F28003x的每个SPI模块只有一个硬件SPISTE引脚。当需要连接多个SPI从设备时,硬件片选不够用。手册24.2.1节给出了标准解决方案:使用普通GPIO来模拟片选信号

实现方法

  1. 将SPI模块的SPISTE引脚配置为GPIO输出,并初始化为高电平(无效状态)。
  2. 为每个从设备分配一个独立的GPIO作为片选线。
  3. 在发起与某个从设备的通信前,将其对应的GPIO片选线拉低,其他片选线保持高电平。
  4. 通信结束后,再将该片选线拉高。
// 定义三个从设备的片选GPIO #define CS_DEVICE1 GPIO_PIN_5 // GPIO5 #define CS_DEVICE2 GPIO_PIN_6 // GPIO6 #define CS_DEVICE3 GPIO_PIN_7 // GPIO7 void SPI_SelectDevice(uint16_t csPin) { // 先将所有片选置为高电平(无效) GPIO_writePin(5, 1); GPIO_writePin(6, 1); GPIO_writePin(7, 1); // 短暂延时,确保总线稳定(根据从设备复位时间调整) DEVICE_DELAY_US(1); // 选中目标设备 GPIO_writePin(csPin, 0); DEVICE_DELAY_US(1); // 建立时间 } void SPI_DeselectDevice(uint16_t csPin) { GPIO_writePin(csPin, 1); DEVICE_DELAY_US(1); // 保持时间 }

注意事项:不同从设备对片选信号的建立时间(t_SU(CS))和保持时间(t_HD(CS))可能有��求,需要在拉低片选后和拉高片选前插入适当的延时(DEVICE_DELAY_US)。

4.2 与不同数据宽度设备的通信

SPI从设备的数据宽度可能不是16位,可能是8位、12位或24位。TMS320F28003x的SPI支持1-16位可编程数据宽度,但需要正确处理数据对齐。

案例:与一个8位SPI ADC通信假设ADC需要先发送一个8位命令(0x58),然后读取两个8位数据(高字节和低字节)。

// 1. 重新配置SPI为8位数据模式(在通信前临时更改) SPI_disableModule(SPIA_BASE); SPI_setDataSize(SPIA_BASE, SPI_DATA_SIZE_8); // 设置为8位 SPI_enableModule(SPIA_BASE); // 2. 发送8位命令 SPI_writeDataNonBlocking(SPIA_BASE, 0x58); // 数据左对齐写入,实际发送0x5800的高8位 while(!SPI_getInterruptStatus(SPIA_BASE, SPI_INT_RXRDY)); // 等待发送完成 SPI_clearInterruptStatus(SPIA_BASE, SPI_INT_RXRDY); uint16_t dummy = SPI_readDataBlockingNonFIFO(SPIA_BASE); // 清空接收缓冲(可能是无效数据) // 3. 发送哑元数据,读取第一个8位数据(高字节) SPI_writeDataNonBlocking(SPIA_BASE, 0x00); while(!SPI_getInterruptStatus(SPIA_BASE, SPI_INT_RXRDY)); SPI_clearInterruptStatus(SPIA_BASE, SPI_INT_RXRDY); uint16_t rxHigh = SPI_readDataBlockingNonFIFO(SPIA_BASE); // 读取到的16位数,高8位是有效数据 uint8_t dataHigh = (rxHigh >> 8) & 0xFF; // 右移取高8位 // 4. 发送哑元数据,读取第二个8位数据(低字节) SPI_writeDataNonBlocking(SPIA_BASE, 0x00); while(!SPI_getInterruptStatus(SPIA_BASE, SPI_INT_RXRDY)); SPI_clearInterruptStatus(SPIA_BASE, SPI_INT_RXRDY); uint16_t rxLow = SPI_readDataBlockingNonFIFO(SPIA_BASE); uint8_t dataLow = (rxLow >> 8) & 0xFF; // 5. 恢复SPI为16位模式(如果需要) SPI_disableModule(SPIA_BASE); SPI_setDataSize(SPIA_BASE, SPI_DATA_SIZE_16); SPI_enableModule(SPIA_BASE);

关键点:在8位模式下,我们写入SPIDAT的16位数据,只有高8位会被发送。同样,接收到的16位数据,有效数据也在高8位。这就是“左对齐写入,右对齐读出”原则在非16位模式下的体现。

4.3 常见问题排查速查表

在调试SPI时,你几乎一定会遇到下面这些问题。这里我总结了一个快速排查清单。

现象可能原因排查步骤与解决方案
完全无通信,用逻辑分析仪看不到时钟和数据1. SPI模块未使能。
2. 引脚复用配置错误。
3. 主从模式设置反。
4. 片选信号未有效拉低。
1. 检查SPICCR.7 (SPISWRESET)是否为1,SPICTL.2 (MASTER_SLAVE)是否正确。
2. 用GPIO功能点亮LED,确认引脚控制权正确。
3. 确认SPISTE或模拟片选GPIO的波形。
有时钟,但数据线全是高电平或低电平1. 从设备未上电或损坏。
2. 从设备进入睡眠/关断模式。
3. 主设备TALK位被禁用(从模式时)。
4. 数据线被外部电路拉死。
1. 测量从设备电源和复位信号。
2. 检查从设备是否需要特定唤醒命令。
3. 检查SPICTL.1 (TALK)位。
4. 断开从设备,测量主设备引脚输出是否正常。
数据错位或采样错误1.时钟极性(CPOL)和相位(CPHA)配置错误
2. 波特率过高,从设备跟不上。
3. 信号完整性差(过冲、振铃)。
这是最高频的问题!
1.用逻辑分析仪同时抓取主设备的CLK、MOSI和从设备的MISO,对照从设备数据手册的时序图,一个边沿一个边沿地核对。
2. 降低波特率(增大SPIBRR)测试。
3. 检查PCB布线,在时钟和数据线上串接小电阻(如22Ω-100Ω)。
只能发送一次数据,后续发送失败1. 发送缓冲未就绪标志未清除。
2. 接收溢出(OVERRUN_FLAG)未处理。
3. 在FIFO模式下,中断标志未正确清除。
1. 在非FIFO模式,确保在写入新数据前SPISTS.5 (BUFFULL_FLAG)为0。
2. 检查并清除SPISTS.5 (OVERRUN_FLAG)
3. 在FIFO中断服务程序中,必须清除RXFFINTTXFFINT标志。
使用DMA时数据丢失或混乱1. DMA突发大小与FIFO触发级别不匹配。
2. DMA传输次数计算错误。
3. DMA源/目的地址未正确递增。
4. SPI和DMA的初始化顺序有误。
1.严格按手册24.3.8节的公式计算DMA_BURST_SIZEDMA_TRANSFER_SIZE
2. 检查DMA配置寄存器DMACHSIZEDMACHSIZE
3. 确认DMACHSRCDMACHDST的地址模式(POST-INCREMENT)。
4. 先初始化SPI和FIFO,再配置并启动DMA。
通信间歇性失败,高波特率下更明显1. 电源噪声。
2. 地线回路不良。
3. 时钟信号抖动大。
4. 未启用SPI高速模式(HS_MODE)。
1. 在MCU和从设备的电源引脚就近放置去耦电容(如100nF + 10uF)。
2. 确保主从设备共地良好。
3. 降低波特率或缩短通信线长度。
4. 尝试设置SPICCR.6 (HS_MODE)=1,并确认硬件支持。

调试终极武器:逻辑分析仪没有逻辑分析仪调试SPI就像蒙着眼睛走路。一个哪怕是最基础的逻辑分析仪(如Saleae Logic 8),也能直观地展示CLK、MOSI、MISO、CS四条线上的每一个比特。重点关注:

  1. 时钟空闲电平: 对应CPOL。
  2. 数据采样边沿: MOSI/MISO数据在CLK的哪个边沿稳定(被采样),哪个边沿变化。
  3. 片选时序: CS的建立和保持时间是否满足从设备要求。
  4. 数据内容: 发送和接收的数据是否与预期一致。

最后,分享一个我踩过的坑:在一次调试中,SPI通信极不稳定,时好时坏。逻辑分析仪显示波形正常,但数据就是不对。最后发现是软件中在SPI传输过程中频繁开关了全局中断,导致某些关键时序被中断服务程序打断。解决方案是,在SPI的连续传输关键段(如DMA搬运期间),使用Interrupt_disableMaster()Interrupt_enableMaster()进行临界区保护。嵌入式开发中,对时序敏感的外设操作,一定要有全局中断管理的意识。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询