TMS320F28003x SPI通信协议详解:从原理到寄存器配置与实战 📅 2026/7/19 12:40:10 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通信需要四根线SCLK (Serial Clock) 串行时钟由主设备产生是所有数据收发的节拍器。MOSI (Master Output Slave Input) 主设备输出从设备输入。主设备通过这根线向从设备发送数据。MISO (Master Input Slave Output) 主设备输入从设备输出。从设备通过这根线向主设备返回数据。CS/SS (Chip Select / Slave Select) 片选信号低电平有效。主设备通过控制这根线的电平来选择要与哪个从设备进行通信。这是实现“一主多从”的关键。在TMS320F28003x的寄存器描述中它们对应的引脚名称为SPICLK、SPISIMO、SPISOMI和SPISTE。这里需要注意SPISIMO和SPISOMI的命名是从SPI模块自身的视角出发的。对于主设备SPISIMO是它的输出对应MOSISPISOMI是它的输入对应MISO对于从设备则恰好相反。除了标准的四线制SPI还支持一种三线制Single-BiDirectional模式。在此模式下通过配置SPICCR.0SPISW位或相关的控制位SPI模块可以复用同一根数据线进行半双工通信。这在引脚资源紧张或者只需要单向大数据流传输的场景下非常有用。例如在驱动一个只接收命令而不返回数据的显示模块时就可以启用三线制模式节省一个GPIO引脚。1.2 时钟极性(CPOL)与时钟相位(CPHA)的四种组合SPI协议最让初学者困惑的莫过于时钟极性和相位的配置。这决定了数据在时钟信号的哪个边沿被采样和输出是确保主从设备正确同步的基石。TMS320F28003x通过SPICTL.3CLKPOLARITY和SPICTL.4CLK_PHASE这两个寄存器位来控制形成了四种经典模式通常被称为Mode 0, 1, 2, 3。模式0 (CPOL0 CPHA0) 时钟空闲时为低电平。数据在时钟的上升沿被采样接收在时钟的下降沿被输出发送。这是最常见的一种模式。模式1 (CPOL0 CPHA1) 时钟空闲时为低电平。数据在时钟的下降沿被采样在时钟的上升沿被输出。模式2 (CPOL1 CPHA0) 时钟空闲时为高电平。数据在时钟的下降沿被采样在时钟的上升沿被输出。模式3 (CPOL1 CPHA1) 时钟空闲时为高电平。数据在时钟的上升沿被采样在时钟的下降沿被输出。如何选择正确的模式这里没有通用答案完全取决于你的从设备芯片的数据手册。你必须严格按照从设备要求的时序图来配置主设备的CPOL和CPHA。一个常见的技巧是观察从设备数据手册的时序图看它的数据线DI或DO在时钟的哪个边沿是稳定的即“数据建立”那个边沿通常就是主设备应该采样的边沿而数据变化的边沿就是主设备输出的边沿。注意 在TMS320F28003x的参考手册中对CLK_PHASE的描述是“时钟相位选择”它决定了数据输出是否提前半个时钟周期。当CLK_PHASE0时数据在时钟边沿变化当CLK_PHASE1时数据提前半个周期变化。这与上述常见的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。当主设备启动传输向SPIDAT或SPITXBUF写入数据时数据从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位那么写入SPIDAT和SPITXBUF的数据必须左对齐而读取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)写1FIFO模式或读取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后SPIFFENA1SPI的中断逻辑完全改变了。原来的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触发机制。中断配置要点确定模式 首先决定使用非FIFO模式还是FIFO模式。对于单次或低频数据传输非FIFO模式查询INT_FLAG或使能SPIINT中断足够简单。对于高速流式数据必须使用FIFO模式。使能PIE中断 在PIE控制器中使能对应的中断组和中断位。对于FIFO模式通常是SPIRXINT可能对应PIE中的SPIINT和SPITXINT。编写ISR 在中断服务程序中首先要读取SPIFFRX或SPIFFTX中的中断标志位RXFFINT或TXFFINT来判断中断来源然后进行相应的FIFO读写操作。操作完成后必须向SPIFFRX.14 (RXFFINTCLR)或SPIFFTX.13 (TXFFINTCLR)位写1来清除中断标志否则会持续进入中断。DMA配置策略 SPI模块可以产生SPITXDMA和SPIRXDMA两个触发信号分别用于触发DMA的写操作发送数据和读操作接收数据。DMA与FIFO的配合堪称“性能黄金搭档”。发送DMA配置SPITXDMA触发在TXFFST TXFFIL时产生。手册24.3.8.1节给出了一个绝佳的配置公式。假设你要发送128个字设置TXFFIL8。那么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个字设置RXFFIL4。那么DMA传输次数 (200 / 4) - 1 49进行50次传输。DMA突发大小 4 - 1 3每次DMA触发从SPI的RX FIFO搬运4个字。关键点 DMA突发大小最好等于RXFFIL并且是总接收字数的整数因子以确保DMA能完整、准确地搬空所有数据。3. 从零开始SPI模块初始化与数据收发实战理论说得再多不如一行代码。下面我们以一个具体的场景为例配置TMS320F28003x的SPI-A模块为主机以模式0CPOL0 CPHA0、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 (CPOL0 CPHA0) // 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函数封装了SPICCR、SPICTL、SPIBRR等寄存器的关键配置。如果你需要更精细的控制可以分别调用SPI_setClockPolarity、SPI_setPhase、SPI_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; }实操心得在实际项目中我强烈建议使用DMAFIFO中断的组合来处理持续的数据流。例如在通过SPI读取一个加速度传感器时可以配置DMA循环模式将SPI接收FIFO的数据自动搬运到内存中的一个环形缓冲区。应用程序只需要从环形缓冲区消费数据即可完全避免了在高速数据流中频繁进入中断带来的性能瓶颈。对于发送也可以预先将一帧命令数据放入数组然后用DMA一次性发送效率极高。4. 高级应用、调试与故障排查实录掌握了基础配置和简单收发后我们来看看更复杂的应用场景和那些让人头疼的调试问题。4.1 多从机系统与软件片选管理TMS320F28003x的每个SPI模块只有一个硬件SPISTE引脚。当需要连接多个SPI从设备时硬件片选不够用。手册24.2.1节给出了标准解决方案使用普通GPIO来模拟片选信号。实现方法将SPI模块的SPISTE引脚配置为GPIO输出并初始化为高电平无效状态。为每个从设备分配一个独立的GPIO作为片选线。在发起与某个从设备的通信前将其对应的GPIO片选线拉低其他片选线保持高电平。通信结束后再将该片选线拉高。// 定义三个从设备的片选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)是否为1SPICTL.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中断服务程序中必须清除RXFFINT或TXFFINT标志。使用DMA时数据丢失或混乱1. DMA突发大小与FIFO触发级别不匹配。2. DMA传输次数计算错误。3. DMA源/目的地址未正确递增。4. SPI和DMA的初始化顺序有误。1.严格按手册24.3.8节的公式计算DMA_BURST_SIZE和DMA_TRANSFER_SIZE。2. 检查DMA配置寄存器DMACHSIZE和DMACHSIZE。3. 确认DMACHSRC和DMACHDST的地址模式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四条线上的每一个比特。重点关注时钟空闲电平 对应CPOL。数据采样边沿 MOSI/MISO数据在CLK的哪个边沿稳定被采样哪个边沿变化。片选时序 CS的建立和保持时间是否满足从设备要求。数据内容 发送和接收的数据是否与预期一致。最后分享一个我踩过的坑在一次调试中SPI通信极不稳定时好时坏。逻辑分析仪显示波形正常但数据就是不对。最后发现是软件中在SPI传输过程中频繁开关了全局中断导致某些关键时序被中断服务程序打断。解决方案是在SPI的连续传输关键段如DMA搬运期间使用Interrupt_disableMaster()和Interrupt_enableMaster()进行临界区保护。嵌入式开发中对时序敏感的外设操作一定要有全局中断管理的意识。