SPI从机模式深度解析:中断、DMA与FIFO的实战配置与避坑指南

发布时间:2026/7/26 22:08:33
SPI从机模式深度解析:中断、DMA与FIFO的实战配置与避坑指南 1. 从机模式的核心逻辑与设计考量SPI从机模式说白了就是“听命行事”。当你的设备被配置为从机时它的一切通信节奏都掌握在外部主设备手中。主设备提供时钟SPICLK和片选SPIEN可选从机只能在时钟边沿到来时被动地发送或接收数据。这种看似被动的角色恰恰是很多嵌入式系统中传感器、执行器、外设扩展芯片的常态。理解从机模式关键在于理解其“响应式”的工作机制以及如何高效、可靠地处理这种被动数据流。从机模式的核心挑战在于实时性。主设备随时可能发起传输从机必须在极短的时间内准备好要发送的数据并妥善处理接收到的数据。如果处理不及时就会发生数据丢失下溢或数据覆盖上溢。为了解决这个问题现代SPI控制器如TI的这款提供了三种核心机制中断、DMA和FIFO。这三种机制并非互斥而是可以组合使用以适应不同的应用场景和对系统资源CPU负载、响应延迟、数据吞吐量的不同要求。中断驱动是最基础也最灵活的方式。CPU通过响应TX_EMPTY发送寄存器空和RX_FULL接收寄存器满等中断事件来及时填充发送数据或读取接收数据。这种方式对CPU占用较高但编程模型直观适用于数据量不大或传输间隔较长的场景。DMA直接存储器访问则是为了解放CPU。当使能DMA请求后数据在SPI控制器和内存之间的搬运工作由DMA控制器接管CPU仅在传输开始和结束时进行配置和状态检查。这极大地降低了CPU中断负载特别适合大数据量、高频率的连续传输。但DMA的配置相对复杂且需要协调SPI控制器和DMA控制器的时序。FIFO先入先出缓冲区是一个数据缓存区。它像一个蓄水池在发送端CPU可以提前写入多个数据字到FIFOSPI控制器按需从中取出发送避免了因CPU响应不及时导致的发送下溢在接收端SPI控制器可以将连续收到的多个数据字暂存于FIFO等待CPU或DMA批量读取避免了接收溢出。FIFO的引入平滑了数据流降低了对实时响应的苛刻要求是提升通信鲁棒性的关键。在实际项目中选择哪种或哪几种组合取决于你的具体需求。如果你需要极低的响应延迟且数据包很小纯中断可能就够了。如果需要连续传输数KB的传感器数据那么“FIFO DMA”的组合几乎是必选项。而“FIFO 中断”则是一个折中方案在保证一定数据吞吐的同时编程模型比DMA更简单。2. 关键寄存器配置与工作原理解析要驾驭SPI从机必须和它的寄存器打交道。这些寄存器就是控制器的大脑你的配置决定了它如何“思考”和“反应”。我们重点看几个在从机模式下至关重要的寄存器。2.1 模式控制与通道配置设定身份与规则首先你需要通过SPI_MODULCTRL寄存器告诉控制器“你现在是从机了”。将MS位设置为1控制器就进入从机模式此时它会等待外部主设备提供的SPICLK和SPIEN信号。PIN34位决定了是使用3线模式无单独片选还是4线模式。在从机模式下通常使用4线模式片选信号用于指示传输的开始和结束。接下来SPI_CHCONF寄存器是通道配置的核心它定义了通信的具体规则TRM(位13-12): 设置为00选择“发送与接收模式”这是最常用的全双工模式。WL(位11-7): 设置SPI字长。例如7h对应8位Fh对应16位1Fh对应32位。这里有个关键细节无论字长多少数据在32位的SPI_TX和SPI_RX寄存器中总是右对齐的。这意味着如果你传输8位数据你需要写入SPI_TX寄存器的低8位高24位会被忽略读取时有效数据也在SPI_RX的低8位。POL和PHA(位1和位0): 这是SPI的时钟极性和相位决定了数据在时钟的哪个边沿被采样和输出。必须与主设备严格匹配否则通信必然失败。CPOL0/CPHA0和CPOL1/CPHA1是两种最常用的模式。FFER和FFEW(位28和位27): 这两个位分别启用接收和发送方向的FIFO缓冲区。这是提升性能的关键。启用后TX_EMPTY和RX_FULL中断的含义会从“寄存器空/满”变为“FIFO几乎空/几乎满”触发阈值由SPI_XFERLEVEL寄存器中的AEL和AFL值决定。DMAR和DMAW(位15和位14): 使能DMA读请求和写请求。当DMAR使能且接收FIFO中的数据量达到AFL阈值时会向DMA控制器发出读请求当DMAW使能且发送FIFO中的空闲空间达到AEL阈值时会发出写请求。特别注意一旦使能了DMA请求对应的TX和RX中断就会被屏蔽两者不能同时使用。2.2 中断状态与使能事件的感知与响应SPI_IRQSTATUS和SPI_IRQENABLE这对寄存器用于管理中断事件。TX_EMPTY: 发送寄存器或FIFO为空或几乎空。这是通知CPU“该准备下一个要发送的数据了”的信号。RX_FULL: 接收寄存器或FIFO为满或几乎满。这是通知CPU“有数据到了快来取”的信号。TX_UNDERFLOW: 发送下溢。当主设备开始传输但发送寄存器/FIFO为空无数据可发时触发。这是一个错误事件意味着数据丢失。在从机模式下如果使能了FIFO理论上可以通过合理设置AEL来避免但如果CPU/DMA填充不及时仍可能发生。RX_OVERFLOW: 接收溢出。当接收寄存器/FIFO已满但又有新数据到来时触发。这同样是一个严重的错误事件意味着新数据覆盖了旧数据。启用FIFO并设置合理的AFL阈值是避免溢出的主要手段。EOW: 字计数结束。当使用FIFO且使能了字计数功能SPI_XFERLEVEL[WCNT]非零在传输了指定数量的字后触发。这对于需要精确控制传输数据块大小的应用非常有用。使能相应位(SPI_IRQENABLE)后这些事件就会触发硬件中断。在中断服务程序(ISR)中你需要先读取SPI_IRQSTATUS来判断是哪个事件处理完后例如向SPI_TX写数据以清除TX_EMPTY源或从SPI_RX读数据以清除RX_FULL源必须向SPI_IRQSTATUS的对应位写1来清除中断状态否则中断线会一直保持有效。2.3 传输级别寄存器FIFO与DMA的节拍器SPI_XFERLEVEL寄存器是协调FIFO和DMA工作的“节拍器”。AEL(Almost Empty Level): 发送FIFO几乎空阈值。当发送FIFO中的剩余空间大于或等于AEL时会触发TX_EMPTY中断或DMA写请求。例如FIFO深度为16字AEL设为4则当FIFO中空闲位置4时就会请求CPU/DMA来填充数据。AFL(Almost Full Level): 接收FIFO几乎满阈值。当接收FIFO中已存数据量大于或等于AFL时会触发RX_FULL中断或DMA读请求。WCNT(Word Count): 字计数器。当使用FIFO时可以设置一个非零值。控制器在传输完WCNT个数据字后会产生EOW中断。这在需要传输固定长度数据包的场景下非常方便可以精确地在传输完成后得到通知而不必依赖片选信号的边沿在某些主设备驱动中片选可能在连续传输中一直保持有效。配置心得AEL和AFL的设置是一种权衡。设得太小如1中断/DMA请求会非常频繁增加系统开销设得太大则缓冲作用减弱增大了下溢/溢出的风险。通常建议设置为FIFO深度的1/4到1/2例如对于32字节的FIFOAEL和AFL设为8是一个不错的起点可以在响应速度和缓冲区余量之间取得平衡。3. 从机模式驱动实现中断、DMA与FIFO的实战配置理解了原理和寄存器我们来看如何把它们组合起来写一个健壮的SPI从机驱动。这里我以最常见的“中断FIFO”和“DMAFIFO”两种模式为例给出具体的配置步骤和代码片段。3.1 基础初始化与从机配置无论采用哪种高级模式基础的初始化流程是类似的使能模块时钟通过系统时钟控制模块打开SPI控制器的时钟门控。引脚复用配置将MCU的对应物理引脚配置为SPI功能MISO, MOSI, SPICLK, SPIEN。软件复位通过写SPI_SYSCONFIG[SOFTRESET]位将控制器恢复到已知的初始状态并轮询SPI_SYSSTATUS[RESETDONE]位直到复位完成。配置主从模式写SPI_MODULCTRL[MS] 1设置为从机模式。配置通道参数写SPI_CHCONF寄存器设定字长(WL)、时钟相位极性(PHA,POL)、片选极性(EPOL)并根据需要使能FIFO(FFER,FFEW)和DMA(DMAR,DMAW)。// 示例配置SPI为从机模式8位字长CPOL0/CPHA0使能发送和接收FIFO void SPI_Slave_Init(void) { // 1. 使能SPI模块时钟 (此处为示例具体函数依平台而定) PRCMPeripheralClkEnable(PRCM_GSPI, PRCM_RUN_MODE_CLK); // 2. 配置引脚复用为SPI功能 (示例) PinTypeSPI(PIN_01, PIN_MODE_7); // MISO PinTypeSPI(PIN_02, PIN_MODE_7); // MOSI PinTypeSPI(PIN_03, PIN_MODE_7); // SPICLK PinTypeSPI(PIN_04, PIN_MODE_7); // SPIEN // 3. 软件复位 SPIReset(GSPI_BASE); while(!(SPISysStatusGet(GSPI_BASE) SPI_SYSSTATUS_RESETDONE)); // 等待复位完成 // 4. 设置为从机模式4线制使用SPIEN SPIModuleControlSet(GSPI_BASE, SPI_MODULCTRL_MS_MODE_SLAVE | SPI_MODULCTRL_PIN34_4PIN); // 5. 配置通道收发模式8位字长CPOL0, CPHA0SPIEN高有效使能TX/RX FIFO uint32_t ui32Config SPI_CHCONF_TRM_TX_RX | // 收发模式 SPI_CHCONF_WL_8BIT | // 8位字长 SPI_CHCONF_EPOL_HIGH_ACTIVE | // SPIEN高电平有效 SPI_CHCONF_POL_LOW | // CPOL0 SPI_CHCONF_PHA_FIRST_EDGE | // CPHA0 SPI_CHCONF_FFEW_ENABLE | // 使能发送FIFO SPI_CHCONF_FFER_ENABLE; // 使能接收FIFO SPIChannelConfigSet(GSPI_BASE, ui32Config); // 6. 配置FIFO阈值AEL和AFL均设为8假设FIFO深度32字节 SPIFIFOLvlSet(GSPI_BASE, SPI_XFERLEVEL_AEL_OF(8) | SPI_XFERLEVEL_AFL_OF(8)); // 7. 使能SPI通道 SPIEnable(GSPI_BASE); }3.2 中断驱动模式实现在中断模式下我们主要响应TX_EMPTY和RX_FULL事件。// 全局变量用于数据交换 volatile uint8_t g_ui8TxBuffer[256]; volatile uint8_t g_ui8RxBuffer[256]; volatile uint32_t g_ui32TxIndex 0; volatile uint32_t g_ui32RxIndex 0; volatile uint32_t g_ui32DataLength 0; // 需要发送/接收的数据总长度 void SPI_Slave_Int_Init(void) { // ... 执行上述基础初始化步骤1-7 ... // 8. 注册中断服务函数 SPIIntRegister(GSPI_BASE, SPI_Slave_IRQHandler); // 9. 使能特定的中断发送空和接收满 SPIIntEnable(GSPI_BASE, SPI_INT_TX_EMPTY | SPI_INT_RX_FULL); // 10. 使能全局中断NVIC IntMasterEnable(); } // SPI从机中断服务函数 void SPI_Slave_IRQHandler(void) { uint32_t ui32Status; // 读取并清除中断状态 ui32Status SPIIntStatus(GSPI_BASE, true); SPIIntClear(GSPI_BASE, ui32Status); // 处理发送空中断需要填充发送FIFO if(ui32Status SPI_INT_TX_EMPTY) { // 计算FIFO中剩余的空闲位置简化处理实际需根据AEL和FIFO状态计算 // 这里假设每次中断都尽可能填满FIFO while((SPIFIFOLvlGet(GSPI_BASE) SPI_FIFO_TX_EMPTY) (g_ui32TxIndex g_ui32DataLength)) { SPIDataPutNonBlocking(GSPI_BASE, g_ui8TxBuffer[g_ui32TxIndex]); } // 如果所有数据已发送完毕可以禁用TX_EMPTY中断避免不必要的触发 if(g_ui32TxIndex g_ui32DataLength) { SPIIntDisable(GSPI_BASE, SPI_INT_TX_EMPTY); } } // 处理接收满中断需要读取接收FIFO if(ui32Status SPI_INT_RX_FULL) { // 读取FIFO中的所有可用数据 while(!(SPIFIFOLvlGet(GSPI_BASE) SPI_FIFO_RX_EMPTY) (g_ui32RxIndex sizeof(g_ui8RxBuffer))) { SPIDataGetNonBlocking(GSPI_BASE, (uint32_t*)g_ui8RxBuffer[g_ui32RxIndex]); } // 这里可以添加判断当接收数据达到预期长度时进行后续处理 } // 注意TX_UNDERFLOW和RX_OVERFLOW是错误通常也应处理例如记录错误标志 if(ui32Status (SPI_INT_TX_UNDERFLOW | SPI_INT_RX_OVERFLOW)) { // 设置错误标志在主循环中处理 g_bSPIError true; } }注意事项在中断服务程序(ISR)中务必先读取中断状态再清除中断标志。顺序反了可能导致丢失在读取状态后、清除标志前发生的中断。使用NonBlocking非阻塞函数读写数据寄存器因为在ISR中阻塞等待会带来不可预测的延迟。TX_EMPTY中断在通道刚使能时就会立即触发一次因为此时发送寄存器/FIFO肯定是空的。因此在使能中断前最好先预填充一些数据到发送FIFO或者准备好发送缓冲区和索引。3.3 DMA驱动模式实现DMA模式配置更为复杂因为它涉及SPI控制器和DMA控制器的协同。以下是一个概念性流程SPI侧配置在SPI_CHCONF中使能DMAR和/或DMAW。配置SPI_XFERLEVEL中的AFL和AEL它们将作为DMA请求的触发阈值。DMA控制器配置发送DMA通道源地址为内存中的发送缓冲区目的地址为SPI的SPI_TX寄存器或FIFO写入地址。传输宽度需与SPI字长匹配8/16/32位。配置为外设到内存的传输由SPI的DMA写请求触发。接收DMA通道源地址为SPI的SPI_RX寄存器或FIFO读取地址目的地址为内存中的接收缓冲区。配置为内存到外设的传输由SPI的DMA读请求触发。启动传输使能SPI通道然后启动DMA传输。DMA控制器会在SPI FIFO达到阈值时自动搬运数据。传输完成处理DMA传输完成通常会产生DMA中断。在DMA完成中断中你可以检查传输的字节数是否正确然后处理接收到的数据或准备下一次发送。// 伪代码展示DMA配置思路 void SPI_Slave_DMA_Init(void) { // ... 执行基础初始化步骤1-7 ... // 在步骤5中需要额外使能DMA请求 ui32Config | SPI_CHCONF_DMAR_ENABLE | SPI_CHCONF_DMAW_ENABLE; SPIChannelConfigSet(GSPI_BASE, ui32Config); // 配置DMA控制器以TI的UDMA为例流程类似 // 1. 配置DMA控制表设置源地址、目的地址、传输数量、仲裁大小等。 // 2. 将DMA通道与SPI的DMA请求信号例如SPI_RX_DMA_REQ, SPI_TX_DMA_REQ关联。 // 3. 设置DMA传输模式为“基本模式”或“Ping-Pong模式”后者可实现双缓冲提高效率。 // 4. 使能DMA通道。 // 预填充发送缓冲区 memcpy(g_ui8TxBuffer, data_to_send, send_len); g_ui32DataLength send_len; // 使能SPI通道传输开始 SPIEnable(GSPI_BASE); // DMA传输会由SPI的FIFO阈值事件自动触发 }DMA配置核心要点数据对齐确保DMA的传输数据宽度与SPI字长对齐并且内存缓冲区地址也满足该数据宽度的对齐要求例如32位传输要求地址4字节对齐。仲裁大小(Arbitration Size)DMA控制器一次传输的单元大小。应设置为与AEL/AFL阈值相匹配的值。例如如果AEL8字节且SPI字长为8位那么DMA的仲裁大小可以设置为8这样每次DMA写请求就正好填充8字节到FIFO。传输完成依赖DMA中断来判断一批数据是否传输完毕而不是SPI的EOW中断。EOW在DMA模式下可能仍有其他用途。3.4 FIFO模式下的传输流程与字计数使用当使能FIFO后数据传输流程发生了变化。控制器不再针对单个字的传输产生频繁的事件而是基于FIFO的填充水平来触发中断或DMA请求。这大大减轻了CPU的负担。字计数(WCNT)功能是一个高级特性。当你在SPI_XFERLEVEL[WCNT]中设置一个非零值NSPI控制器会在传输完N个SPI字后产生一个EOW中断。这个功能非常有用固定长度数据包如果你与主设备约定每次传输固定N个字那么EOW中断就是一个清晰的“本次传输结束”信号比监测片选信号更可靠特别是主设备可能在一个片选有效期内发起多次传输时。与DMA配合你可以将DMA的传输总数设置为WCNT然后同时使能EOW中断。当DMA完成传输即搬运了N个字且SPI也发送完N个字时EOW中断触发你可以安全地进行后续处理确保数据已经完全“飞出”或“收进”。配置字计数的示例// 配置传输字计数为128 SPIFIFOLvlSet(GSPI_BASE, SPI_XFERLEVEL_WCNT_OF(128)); // 使能EOW中断 SPIIntEnable(GSPI_BASE, SPI_INT_EOW);在EOW中断服务程序中你需要清除中断标志并可以执行诸如切换数据缓冲区、通知主循环数据块就绪等操作。4. 常见问题、调试技巧与避坑指南在实际调试SPI从机驱动时你肯定会遇到各种奇怪的问题。下面是我踩过的一些坑和总结的排查思路。4.1 通信完全无数据检查时钟和相位极性(POL,PHA)这是最常见的问题。务必使用逻辑分析仪或示波器抓取主设备发出的SPICLK、MOSI和SPIEN信号确认其极性和相位。然后严格对照着配置从机的POL和PHA位。一个简单的记忆方法是CPHA决定了数据采样的边沿第一个边沿还是第二个边沿CPOL决定了时钟空闲时的电平。检查引脚配置确认MISO、MOSI、SPICLK、SPIEN的引脚复用功能是否配置正确。一个常见的错误是把MISO和MOSI接反了。检查从机使能确认SPI_MODULCTRL[MS]已设置为1从机并且SPI_CHCTRL或相关使能位已经置起通道已启用(SPIEnable。检查主设备片选确认主设备的SPIEN信号是否有效以及从机配置的片选极性(EPOL)是否与之匹配。4.2 能收不能发或能发不能收检查数据方向配置在SPI_CHCONF中IS位选择接收数据线DPE0和DPE1位控制发送数据线。确保你的硬件连接使用哪条数据线与软件配置一致。例如如果你将设备MISO接主设备MOSI这是标准的那么从机应配置为在SPIDAT[0]上发送(DPE00)并从SPIDAT[1]接收(IS1)。检查发送数据准备在从机模式下必须在主设备发起传输拉低片选并给出时钟之前将要发送的第一个数据写入发送寄存器(SPI_TX)或FIFO。如果主设备开始传输时你的发送缓冲区是空的就会触发TX_UNDERFLOW并且发送出去的数据是未定义的通常是旧数据或0。检查中断/DMA是否正常响应如果使用中断用调试器检查是否进入了ISR。如果使用DMA检查DMA控制器的状态寄存器确认传输是否被触发并完成。4.3 数据错位或字节顺序错误检查字长(WL)和数据对齐确保主从双方设置的SPI字长一致都是8位、16位等。记住数据在SPI_TX/RX寄存器中是右对齐的。如果你要发送一个16位数据0xABCD你应该写入SPI_TX寄存器的值是0x0000ABCD高16位会被忽略。读取时你从SPI_RX读到的32位值其低16位才是有效数据。检查字节序(Endianness)如果传输的是多字节数据如32位整数需要确认主从设备系统的字节序大端/小端是否一致。如果不一致需要在软件层进行字节序转换。4.4 FIFO/DMA模式下数据丢失或错误阈值(AEL,AFL)设置不当阈值设得太高可能导致FIFO在请求到来前就已下溢或溢出。设得太低则请求过于频繁。建议结合传输速率和CPU/DMA响应时间来调整。例如如果SPI时钟是1MHzCPU处理一次中断需要10us那么FIFO深度至少需要能容纳10us内传输的数据量10字节1MBpsAEL应小于深度 - 该数据量。DMA传输宽度/数量不匹配确保DMA的传输数据宽度8/16/32位与SPI字长完全一致。同时DMA的传输总数量应与你需要搬运的数据总量匹配。如果使用WCNT要协调好DMA传输计数与WCNT的关系。缓冲区管理问题在DMA或中断FIFO模式下通常使用双缓冲区Ping-Pong Buffer。确保在切换缓冲区时旧数据的传输已经完成新数据已经就位。使用EOW中断或DMA传输完成中断作为缓冲区切换的同步点是非常可靠的。4.5 调试工具与技巧逻辑分析仪是你的最佳朋友投资一个哪怕是最基础的逻辑分析仪如Saleae Logic系列。它能直观地显示SPI总线上的时钟、数据、片选波形让你一眼看出相位、极性、数据内容是否正确是排查硬件时序问题的终极武器。善用寄存器查看在调试器中实时查看关键寄存器SPI_IRQSTATUS中断状态、SPI_CHSTAT通道状态如TXS、RXS、SPI_SYSSTATUS复位状态。这能帮你快速定位是配置错误、中断未触发还是FIFO状态异常。从简到繁先让最简单的“轮询(Polling)模式”工作起来。在轮询模式下你通过不断查询SPI_CHSTAT寄存器的TXS发送空和RXS接收满位来收发数据。这能排除中断和DMA配置的复杂性验证最底层的通信链路和寄存器配置是否正确。添加调试输出在中断服务程序或DMA回调函数的入口处打印简单的日志如果系统支持或者翻转一个GPIO引脚的电平用示波器测量中断响应时间。这能帮你确认中断是否被触发以及ISR的执行时间是否在允许范围内。最后关于从机模式下的TX_UNDERFLOW和RX_OVERFLOW错误我的经验是在使能FIFO并合理设置阈值后这些错误通常意味着你的系统设计或代码逻辑存在根本性问题比如CPU负载过高无法及时响应中断或者DMA带宽不足。它们不应该在正常操作中频繁出现。一旦出现你需要重新评估你的系统实时性是否满足SPI通信速率的要求。