SPI从机模式深度解析:寄存器配置、中断处理与FIFO/DMA实战
1. SPI从机模式核心概念与工作逻辑在嵌入式开发中SPISerial Peripheral Interface协议因其简单、高速和全双工的特性成为连接微控制器与各类外设如传感器、存储器、显示屏的首选方案。很多开发者对SPI主机编程驾轻就熟但一涉及到从机模式尤其是需要处理实时、异步的主机请求时就容易感到棘手。这通常是因为从机模式的核心逻辑是“被动响应”——你的设备不再是自己命运的主宰而是时刻准备着听从外部主机的调遣。理解这种被动性是写好从机代码的第一步。SPI从机模式的本质可以类比为一个随时待命的接线员。主机设备是发起呼叫的客户它控制着通信的时钟SPICLK和片选信号SPIEN如果有的话。当时钟信号开始跳动片选信号有效拉低或拉高取决于极性配置时就意味着主机发起了对话。作为从机你的任务就是在时钟沿的驱动下要么将预先准备好的数据从MISO线发送出去要么从MOSI线读取主机发来的数据或者两者同时进行。整个过程从机无法主动发起通信也无法控制通信的速率它只能在主机搭建的“舞台”上按照主机设定的“节奏”时钟频率、相位、极性进行“表演”。这种被动性带来了几个关键的设计挑战第一数据准备的及时性。你必须在主机发起传输前就把要发送的数据放入发送寄存器否则就会发送无效数据下溢。第二数据处理的实时性。当主机数据到来时你必须及时从接收寄存器中取走否则新数据会覆盖旧数据造成溢出。第三状态管理的精确性。你需要准确知道发送寄存器何时空需要填充新数据、接收寄存器何时满需要读取数据并处理可能发生的错误如下溢、溢出。为了解决这些挑战SPI控制器硬件提供了中断、DMA和FIFO等机制而我们的工作就是通过配置一系列寄存器将这些硬件能力正确地组织起来构建一个稳定可靠的从机通信服务。2. 从机模式下的关键寄存器深度解析要让SPI模块工作在从机模式并按照我们的预期响应主机需要对一组核心寄存器进行精细配置。这些寄存器共同定义了从机的行为模式、数据格式、中断响应以及缓冲区管理策略。下面我们逐一拆解不仅看它们是什么更要理解为什么这么配置。2.1 模块与通道控制寄存器设定身份与使能首先我们必须明确告知硬件“你现在是一个从机。”这是通过SPI_MODULCTRL寄存器完成的。该寄存器的MS位是模式选择的关键。将其设置为1模块即进入从机模式。此时SPICLK和SPIEN信号将由外部主机提供模块内部的相关时钟生成逻辑将被禁用。另一个需要注意的位是PIN34它决定了我们使用3线制还是4线制SPI。在3线制PIN341下SPIEN引脚不被用作片选通信仅依靠SPICLK、MOSI和MISO三根线通常用于单从机系统或需要节省引脚的场景。在4线制PIN340下SPIEN引脚作为片选信号这是多从机系统的标准配置主机通过拉低或拉高特定从机的SPIEN引脚来选中它进行通信。接下来是SPI_CHCTRL寄存器它的EN位是通道的“总开关”。在从机模式下你必须在任何数据传输发生之前将这个通道使能。一个常见的误区是认为主机发起片选信号后从机才需要使能实际上从机通道需要提前处于“就绪”状态才能正确识别并响应主机的片选和时钟信号。禁用通道会立即停止所有正在进行的传输因此在非必要情况下如系统低功耗模式不建议频繁开关通道。2.2 通道配置寄存器SPI_CHCONF定义通信规则SPI_CHCONF寄存器是从机配置的核心它定义了与主机通信的所有物理层和链路层规则。配置错误将直接导致通信失败。TRM (Transmit and Receive Mode): 设置为00代表全双工的发送-接收模式。这是最常用的模式MISO和MOSI线同时有效。WL (Word Length): 字长设置。必须与主机严格匹配否则读到的数据将是错位的。常见设置为7h(8位)、Fh(16位) 或1Fh(32位)。例如对于8位数据你需要将待发送的数据右对齐放入32位的SPI_TX寄存器的低8位读取时也从SPI_RX寄存器的低8位获取有效数据。POL (Clock Polarity) 与 PHA (Clock Phase): 这是SPI通信的“方言”决定了时钟空闲时的电平POL以及数据在时钟的哪个边沿被采样PHA。共有4种模式组合CPOL/CPHA 0/0, 0/1, 1/0, 1/1。必须与主机模式完全一致。例如POL0, PHA0 表示时钟空闲时为低电平数据在时钟的第一个边沿上升沿被采样。EPOL (SPIEN Polarity): 片选信号有效极性。EPOL0表示片选低电平有效常态高选中时拉低EPOL1则相反。这需要根据主机的硬件连接和配置来决定。FFER 与 FFEW: 这是启用FIFO缓冲区的开关。FFER1为接收启用FIFOFFEW1为发送启用FIFO。启用后数据将先进入FIFO缓冲区再与移位寄存器交互大大增强了数据处理的弹性。DMAR 与 DMAW: DMA请求使能位。当启用FIFO后可以进一步开启DMA让DMA控制器自动搬运FIFO中的数据从而将CPU彻底解放出来。DMAR对应接收DMA请求DMAW对应发送DMA请求。注意在配置SPI_CHCONF时有一个隐蔽的“坑”。寄存器中的IS、DPE0、DPE1位共同决定了数据引脚的模式。对于标准的全双工模式TRM00通常配置为IS1从SPIDAT[1]即MISO线接收主机数据DPE00从SPIDAT[0]即MOSI线发送数据DPE11SPIDAT[1]不用于发送。务必参考数据手册中的真值表进行配置错误的组合可能导致无法预测的行为。2.3 中断与状态寄存器构建事件驱动响应从机需要及时响应主机轮询Polling方式会大量占用CPU资源且实时性差因此中断驱动是更优选择。相关寄存器构成了从机的事件响应中枢。SPI_IRQENABLE: 中断使能寄存器。你需要在这里订阅你关心的事件。对于典型的从机收发场景至少需要使能TX_EMPTY_ENABLE发送寄存器空和RX_FULL_ENABLE接收寄存器满这两个中断。如果启用了FIFO这两个中断的含义会变为“FIFO几乎空”和“FIFO几乎满”。错误中断如TX_UNDERFLOW和RX_OVERFLOW也建议使能以便在出错时能及时感知。SPI_IRQSTATUS: 中断状态寄存器。当硬件事件发生时对应的状态位会被置1。中断服务程序ISR的第一要务就是读取这个寄存器以判断是哪个或哪几个事件触发了本次中断。在处理完事件后必须通过向该寄存器的对应位写1来清除状态标志否则中断线将一直保持有效导致中断持续触发或无法响应新中断。SPI_CHSTAT: 通道状态寄存器。其中的TXS和RXS位分别指示发送寄存器和接收寄存器的空/满状态在轮询方式下会用到。EOT位指示一个SPI字传输完成但在使用FIFO时此位无意义。2.4 传输级别寄存器SPI_XFERLEVELFIFO的精细调优当启用FIFO后SPI_XFERLEVEL寄存器就变得至关重要。它决定了中断和DMA请求触发的“水位线”。AEL (Almost Empty Level): “几乎空”阈值。当发送FIFO中剩余的数据量小于或等于AEL设置的值时触发TX_EMPTY中断或DMA写请求提示你需要尽快补充数据。例如FIFO深度为64字节AEL设置为8则当FIFO中数据少于8字节时就会告警。AFL (Almost Full Level): “几乎满”阈值。当接收FIFO中已存的数据量大于或等于AFL设置的值时会触发RX_FULL中断或DMA读请求提示你需要尽快读取数据避免溢出。WCNT (Word Count): 字计数器。这是一个高级功能用于在传输特定数量的SPI字后产生EOWEnd-of-Word中断。例如设置为100则在传输完100个数据字后触发中断。这在需要精确控制传输数据块大小的场景下非常有用。设置为0则禁用此功能。合理设置AEL和AFL是平衡性能和稳定性的关键。设置得过低如AEL1AFL63会导致中断过于频繁增加CPU负担设置得过高则可能在主机高速连续传输时因响应不及时导致FIFO下溢或溢出。通常建议设置为FIFO深度的1/4到1/2并根据实际传输速率和CPU处理能力进行调整。3. 从机模式中断处理流程与实战代码理解了寄存器我们来看如何用代码将它们组织起来构建一个中断驱动的SPI从机。整个过程就像搭建一个自动化的流水线硬件负责搬运数据、触发事件软件中断服务程序负责在关键时刻进行补给和收货。3.1 从机初始化与中断配置步骤初始化是从机稳定工作的基石步骤必须严谨。以下是一个基于典型嵌入式C语言的初始化序列并附上了每一步的意图解析。// 1. 启用SPI模块时钟此函数名依具体MCU的驱动库而定 PRCMPeripheralClkEnable(PRCM_GSPI, PRCM_RUN_MODE_CLK); // *目的任何外设工作前必须先给其提供时钟信号否则寄存器访问都可能失败。 // 2. 配置引脚复用将MCU内部SPI信号连接到正确的物理引脚 PinTypeSPI(PIN_05, PIN_MODE_0); // 配置MISO PinTypeSPI(PIN_06, PIN_MODE_0); // 配置MOSI PinTypeSPI(PIN_07, PIN_MODE_0); // 配置SPICLK PinTypeSPI(PIN_08, PIN_MODE_0); // 配置SPIEN (CS) // *目的现代MCU引脚功能多样必须明确告知芯片这几个引脚用于SPI通信。 // 3. 软件复位SPI模块确保从一个已知的干净状态开始 SPIReset(GSPI_BASE); // *目的清除所有寄存器可能存在的残留状态避免之前配置的干扰。 // 4. 配置SPI为从机模式及基本参数 SPIConfigSetExpClk(GSPI_BASE, PRCMPeripheralClockGet(PRCM_GSPI), // 获取SPI模块输入时钟频率 0, // 从机模式下此参数比特率被忽略由主机决定 SPI_MODE_SLAVE, // 关键设置为从机模式 SPI_SUB_MODE_0, // 对应 CPOL0, CPHA0 (SPI_HW_CTRL_CS | // 硬件控制片选 SPI_4PIN_MODE | // 使用4线模式含片选 SPI_TURBO_OFF | // 关闭Turbo模式从机通常不需要 SPI_CS_ACTIVEHIGH | // 片选高电平有效根据实际电路调整 SPI_WL_8)); // 8位字长 // *目的一次性完成通信格式的配置。特别注意从机模式下载波频率参数无效。 // 5. 注册中断服务函数 SPIIntRegister(GSPI_BASE, SPI_Slave_IRQHandler); // *目的将我们编写的处理函数地址告诉中断控制器当中断发生时能跳转执行。 // 6. 使能特定的中断源 SPIIntEnable(GSPI_BASE, SPI_INT_RX_FULL | SPI_INT_TX_EMPTY); // *目的我们关心“接收满”和“发送空”这两个事件故使能它们的中断。 // 7. 最后使能SPI通道 SPIEnable(GSPI_BASE); // *注意一定要在所有配置完成后再使能通道。过早使能可能导致不可预知的传输。3.2 中断服务程序ISR的编写要点与陷阱规避中断服务程序是响应实时事件的核心其编写必须快速、准确、完整。一个健壮的ISR模板如下// SPI从机中断服务函数 void SPI_Slave_IRQHandler(void) { uint32_t ui32Status; uint32_t ui32RxData; // 第一步读取并保存中断状态 ui32Status SPIIntStatus(GSPI_BASE, true); // true表示读取原始中断状态 // 第二步立即清除已触发的中断标志位非常重要 SPIIntClear(GSPI_BASE, ui32Status); // 第三步根据状态位分别处理 // 处理发送寄存器空或FIFO几乎空中断 if(ui32Status SPI_INT_TX_EMPTY) { // 检查是否还有数据需要发送 if(g_sTxDataCount 0) { // 从应用程序的缓冲区加载下一个数据到发送寄存器或FIFO SPIDataPut(GSPI_BASE, g_pTxDataBuffer[g_sTxIndex]); g_sTxDataCount--; // 如果启用了FIFO可能需要一次性写入多个数据以满足AEL要求 } else { // 没有更多数据要发送可以禁用TX_EMPTY中断避免无意义的中断 // SPIIntDisable(GSPI_BASE, SPI_INT_TX_EMPTY); // 或者填充一个默认值如0xFF以避免下溢错误 SPIDataPut(GSPI_BASE, 0xFF); } } // 处理接收寄存器满或FIFO几乎满中断 if(ui32Status SPI_INT_RX_FULL) { // 循环读取直到RXS状态位显示接收寄存器为空 while(SPI_CHSTAT_RXS 0) { // 假设SPI_CHSTAT_RXS是一个宏用于检查状态位 SPIDataGetNonBlocking(GSPI_BASE, ui32RxData); // 非阻塞读取 // 将读取到的数据通常取低8/16位存入应用程序缓冲区 g_pRxDataBuffer[g_sRxIndex] (uint8_t)(ui32RxData 0xFF); g_sRxDataCount; } // 如果启用了FIFO可能需要一次性读取多个数据直到低于AFL水位线 } // 处理错误中断 if(ui32Status SPI_INT_TX_UNDERFLOW) { // 发送下溢主机开始传输时从机发送寄存器/FIFO为空。 // 这通常是因为数据准备太慢。需要记录错误并可能重置发送队列。 g_bTxUnderflowError true; // 补救措施立即向发送寄存器写入一个值防止持续错误 SPIDataPut(GSPI_BASE, 0xFF); } if(ui32Status SPI_INT_RX_OVERFLOW) { // 接收溢出新数据到来时接收寄存器/FIFO已满旧数据被覆盖。 // 这通常是因为数据读取太慢。需要记录错误并可能丢弃缓冲区内所有数据重新开始。 g_bRxOverflowError true; // 补救措施紧急清空接收FIFO/寄存器 uint32_t ui32Dummy; while(SPI_CHSTAT_RXS 0) { // 快速清空 SPIDataGetNonBlocking(GSPI_BASE, ui32Dummy); } } }实操心得在ISR中“读取状态”和“清除标志”这两步的顺序和操作至关重要。必须先读取状态再清除标志。因为清除操作可能会瞬间改变硬件状态如果在清除后再读取可能会丢失同时发生的其他中断事件信息。另外清除标志时最好使用读取到的ui32Status值作为参数这样可以精确清除已处理的中断源避免误操作其他未触发的中断标志。4. FIFO缓冲区在从机模式下的高级应用与DMA集成对于需要连续、高速传输数据的场景例如从机作为数据采集器向主机发送大量传感器读数或者从主机接收长指令序列仅靠单字节寄存器和频繁的中断会使得CPU疲于奔命系统实时性下降。此时FIFOFirst In, First Out缓冲区与DMADirect Memory Access的组合就成了提升性能的利器。4.1 FIFO模式下的配置与工作流启用FIFO后数据流发生了变化。以发送为例CPU或DMA不再直接与SPI_TX寄存器交互而是与一个更深例如64字节的FIFO缓冲区交互。数据被写入FIFO然后由硬件自动、按顺序地加载到发送移位寄存器中。接收过程反之亦然。配置启用FIFO在SPI_CHCONF寄存器中设置FFEW1启用发送FIFO和/或FFER1启用接收FIFO。配置SPI_XFERLEVEL寄存器设置AEL决定何时触发“发送FIFO几乎空”事件TX_EMPTY中断或DMA请求。例如对于64字节FIFO设置AEL16意味着当FIFO中剩余空间大于等于16字节时就会请求补充数据。设置AFL决定何时触发“接收FIFO几乎满”事件RX_FULL中断或DMA请求。例如设置AFL48意味着当FIFO中数据量达到48字节时就会请求读取数据。可选设置WCNT如果需要进行固定数据块传输可设置字计数。传输完WCNT个数据后会触发EOW中断。FIFO模式下的中断处理变化TX_EMPTY中断含义变为“FIFO有足够空间 AEL容纳新数据”。在ISR中你需要一次性写入至少AEL个数据直到填满FIFO或没有更多数据可写。RX_FULL中断含义变为“FIFO中有足够数据 AFL可供读取”。在ISR中你需要一次性读取至少AFL个数据直到FIFO为空或缓冲区满。4.2 集成DMA实现零CPU开销传输DMA可以接管在FIFO和系统内存之间搬运数据的工作。配置得当CPU只需在传输开始前设置好源地址、目标地址和数据量传输完成后处理一个完成中断即可。发送方向内存 - FIFO的DMA配置思路配置SPI通道FFEW1,DMAW1使能发送DMA请求。配置DMA控制器源地址应用程序中待发送数据的数组地址内存。目标地址SPI模块的发送数据寄存器SPI_TX地址。传输宽度与SPI字长匹配如8位、16位。触发源选择SPI发送DMA请求线。当发送FIFO空间 AEL时SPI模块自动拉高DMA请求线。DMA控制器收到请求自动从内存搬运一个数据字到SPI_TX寄存器。写入后FIFO计数器增加。重复步骤3-4直到DMA完成预设的传输数量然后产生DMA传输完成中断通知CPU。接收方向FIFO - 内存的DMA配置思路配置SPI通道FFER1,DMAR1使能接收DMA请求。配置DMA控制器源地址SPI模块的接收数据寄存器SPI_RX地址。目标地址应用程序中用于存储接收数据的数组地址内存。传输宽度与SPI字长匹配。触发源选择SPI接收DMA请求线。当接收FIFO中的数据量 AFL时SPI模块自动拉高DMA请求线。DMA控制器收到请求自动从SPI_RX寄存器搬运一个数据字到内存。读取后FIFO计数器减少。重复步骤3-4直到DMA完成预设的传输数量。注意事项使用DMA时必须禁用对应的SPI中断如使用发送DMA则禁用TX_EMPTY中断否则硬件事件会同时触发中断和DMA请求造成冲突。此外要确保DMA配置的传输数据量Burst Size与SPI_XFERLEVEL中设置的AEL/AFL水位线协调避免DMA单次搬运量过大或过小影响效率。5. 从机开发常见问题排查与调试技巧即便完全按照手册配置在实际开发中仍会遇到各种问题。以下是一些典型问题及其排查思路很多都是“踩坑”后总结的经验。5.1 通信完全无反应检查清单物理连接用示波器或逻辑分析仪检查SPICLK、MOSI、MISO、SPIEN四根线是否有信号。确保主机确实发出了片选和时钟信号。电源与时钟确认从机设备已上电且SPI模块的时钟PRCMPeripheralClkEnable已使能。模式与极性这是最高频的错误原因。用逻辑分析仪抓取SPICLK和MOSI/MISO的波形对照数据手册确认主从双方的CPOL、CPHA、片选极性(EPOL)是否绝对一致。一个常见的快速测试方法是尝试四种模式组合0,0; 0,1; 1,0; 1,1。引脚复用确认MCU的SPI引脚功能已正确映射到物理引脚PinTypeSPI函数。通道使能确认在主机发起通信前已执行SPIEnable(GSPI_BASE)。5.2 能收到数据但数据错误或错位检查清单字长WL匹配确保主从双方设置的字长相同。如果主机发16位从机按8位读数据就会错位。数据对齐SPI数据总是右对齐存储在32位的SPI_RX寄存器中。读取后你需要屏蔽掉高位无用数据。例如对于8位字长有效数据是rx_data 0xFF。中断处理不及时如果接收数据很快而你的RX_FULL中断服务程序处理太慢例如在里面做了复杂计算或打印日志可能导致FIFO溢出新数据覆盖旧数据。优化ISR仅做必要的数据搬运将处理逻辑放到主循环中。发送下溢TX_UNDERFLOW如果从机发送的数据不正确检查是否发生了TX_UNDERFLOW。这意味主机开始时钟时你的发送寄存器/FIFO是空的。务必在主机片选有效前预先填充至少第一个数据到发送寄存器。5.3 中断无法触发或异常触发检查清单全局中断使能在调用SPIIntEnable之前是否已使能MCU的全局中断例如调用IntMasterEnable()中断向量表中断服务函数是否已正确注册到中断向量表SPIIntRegister函数内部通常会处理。中断标志清除最容易被忽略的一点。在ISR中是否清除了对应的中断状态位SPIIntClear如果没有清除中断只会触发一次或者持续触发。中断使能顺序建议的初始化顺序是配置模块 - 注册ISR -清除所有可能悬而未决的中断标志- 使能特定中断 - 最后使能通道。避免一使能通道就因为某个状态位已经置位而立即进入中断。FIFO水位线AEL/AFL设置如果启用了FIFO但中断不触发检查AEL/AFL设置是否合理。例如如果你只向发送FIFO写了1个字节但AEL设置为8那么TX_EMPTY中断将不会触发因为“几乎空”的条件剩余空间8不满足。5.4 使用逻辑分析仪进行调试一个逻辑分析仪即使是便宜的USB款是调试SPI通信的“神器”。它能同时捕获多路信号并解码SPI协议。连接将分析仪的通道分别连接到SPICLK、MOSI、MISO、SPIEN。设置在分析仪软件中设置正确的阈值电压并添加SPI解码器输入正确的CPOL、CPHA参数。观察主机片选SPIEN是否有效时钟SPICLK是否正常产生频率是否符合预期MOSI和MISO线上的数据是否与你的代码期望一致数据是在时钟的哪个边沿被采样这与你的PHA配置是否匹配对比将捕获到的波形与你的代码逻辑如ISR中打印的发送/接收数据进行对比能快速定位是软件配置问题还是硬件时序问题。掌握SPI从机模式的精髓在于深刻理解其“被动响应”的本质并熟练运用中断、DMA、FIFO等机制来化解被动性带来的实时性挑战。从寄存器配置的每一个比特位到中断服务程序里的每一行码都需要严谨对待。希望这篇详尽的解析能帮助你构建出稳定、高效的SPI从机设备让你在嵌入式通信的世界里更加游刃有余。