SPI通信事件与中断机制深度解析:从轮询到DMA的高效实践

📅 2026/6/30 8:18:32
SPI通信事件与中断机制深度解析:从轮询到DMA的高效实践
1. 从轮询到事件驱动为什么SPI通信需要精细的事件管理在嵌入式开发里SPISerial Peripheral Interface是我们最常用的同步串行通信接口之一从传感器数据采集、存储器读写到显示屏驱动几乎无处不在。早期做项目为了图省事我经常用轮询Polling的方式操作SPICPU不断地去查状态寄存器看看数据收没收到、发没发完。这种方式在小数据量、低频率的场合还能凑合但一旦数据流变大、系统任务变多CPU就被牢牢绑死在SPI上啥也干不了功耗还居高不下。后来踩过几次坑才明白想让SPI真正高效起来必须用好它的“事件”和“中断”机制。你可以把SPI模块想象成一个有自己想法的小助理。它不会每时每刻都来烦你CPU而是自己默默干活。只有当发生了某些特定、重要的事情时——比如接收缓冲区快满了RX FIFO Event或者一包数据发完了DMA_DONE_TX——它才会举手打断你说“老板这儿有情况需要您处理一下。” 这个“举手”的动作就是中断Interrupt。而那个“特定的事情”就是事件Event。现代MCU的SPI模块比如TI MSPM0系列里的这个其事件管理已经做得非常精细和灵活。它把事件分成了两大类一类是发给CPU的叫CPU中断事件CPU_INT另一类是直接发给DMA控制器的叫DMA触发事件DMA_TRIG。这种分工特别关键。CPU中断适合处理那些非规律性的、需要复杂判断或紧急处理的情况比如通信错误奇偶校验错PER、异常FIFO溢出RXFIFO_OVF。而DMA触发则专为大批量、规律性的数据搬运而生比如持续接收传感器数据流或者在发送缓冲区空出一半时自动填充下一批数据。理解并配置好这两套机制是让SPI通信从“能工作”到“高效、可靠、省电”的关键一步。它直接决定了你的系统响应速度、CPU占用率甚至是电池续航。接下来我们就深入寄存器层面看看这套机制具体是怎么玩转的。2. SPI事件体系架构与核心寄存器组解析要配置SPI的事件我们得先摸清它的“家底”。从你提供的资料来看这个SPI模块的事件管理核心是围绕几组功能清晰的寄存器展开的。别被那一长串地址吓到我们按功能把它们归归类逻辑就清晰了。2.1 事件路由与核心控制寄存器首先有一个总纲性的表格Table 26-2揭示了事件的流向。它告诉我们SPI模块内部可以产生多种事件但这些事件最终要去往两个目的地CPU子系统引发中断或者DMA控制器引发DMA传输。去往CPU的路径是静态的、固定的而去往DMA的路径则可以通过寄存器进行一定程度的配置选择由哪个具体事件比如RX事件还是TX事件来触发。控制事件行为模式的核心寄存器是EVT_MODE。这个寄存器决定了事件线的工作模式对于CPU_INT、DMA_TRIG_RX、DMA_TRIG_TX这三条事件线可以分别设置为禁用模式0事件线关闭即使有事件发生也不会产生任何信号。软件模式1事件发生后对应的原始中断状态RIS寄存器位会置起但不会自动清除。需要软件手动写1到ICLR寄存器来清除这个标志。这种模式给了软件最大的控制权适合用于调试或需要精确掌控清除时机的情况。硬件模式2这是最常用的模式。事件发生后RIS标志位置起并且当该事件被成功响应比如CPU读取了IIDX寄存器或者DMA启动了传输后硬件会自动清除这个RIS标志。这实现了真正的“硬件自动管理”减少了软件开销也避免了因忘记清除标志而导致的重复中断问题。注意EVT_MODE的复位值很有意思INT0_CFG对应CPU_INT复位为1软件模式而INT1_CFG和INT2_CFG对应DMA_TRIG复位为2硬件模式。这暗示了TI的默认设计思路CPU中断更倾向于由软件精细控制而DMA触发则更强调硬件自动化的流畅性。在实际应用中我们通常会把CPU_INT也设置为硬件模式2除非有特殊的调试需求。2.2 CPU中断事件管理寄存器组这是最庞大的一组寄存器地址从0x1020到0x1048。它们专门管理那9个可以触发CPU中断的事件源。我们得理解这几个寄存器是如何协同工作的原始中断状态寄存器RIS这是一个“诚实”的寄存器。只要SPI内部发生了某个事件无论你是否关心它对应的RIS位都会被硬件置为1。你可以把它看作一个所有事件的“总开关状态监视器”。中断屏蔽寄存器IMASK这是你设置的“兴趣列表”。只有那些IMASK对应位被设置为1的事件才会被放行去影响后续流程。如果某个事件的IMASK位是0即使RIS置1了它也会被忽略。被屏蔽的中断状态寄存器MIS这个寄存器是RIS IMASK的结果。它直观地告诉你当前有哪些你感兴趣的事件已经发生了并且正在等待处理。在中断服务程序ISR中查询MIS比查询RIS更直接。中断索引寄存器IIDX这是一个非常聪明的设计。当你读取IIDX时硬件会自动返回当前优先级最高的、已使能即MIS中为1的中断的编号。更重要的是这次读取操作会同时自动清除该中断在RIS和MIS中的标志位。这实现了一种“硬件辅助的中断查询与清除”机制对于编写高效的、无需软件遍历所有中断源的ISR非常有帮助。中断置位ISET与清除ICLR寄存器这两个寄存器允许软件“模拟”事件。向ISET的某位写1可以手动让该事件生效置起RIS向ICLR的某位写1则可以手动清除事件标志。ISET常用于软件自测试而ICLR则是在非硬件自动清除模式下用于手动管理中断标志。这9个中断源RXFIFO_OVF PER RTOUT RX TX TXEMPTY IDLE DMA_DONE_RX DMA_DONE_TX各有其触发场景构成了SPI与CPU交互的全部可能。2.3 DMA触发事件管理寄存器组DMA触发的逻辑比CPU中断简单但目的性更强。它分为接收触发DMA_TRIG_RX和发送触发DMA_TRIG_TX两组独立的寄存器地址分别在0x1050-0x1078和0x1080-0x10A8。每组都包含与CPU中断组类似的IIDXIMASKRISMISISETICLR寄存器但事件源大大精简了。DMA_TRIG_RX只关心两个事件接收超时RTOUT和接收FIFO达到预设水平RX。DMA_TRIG_TX只关心一个事件发送FIFO达到预设水平TX。这种设计体现了DMA的专一性它的任务就是搬运数据。因此触发条件也紧紧围绕着“有数据要搬”RX事件和“有空位可以放数据”TX事件这两个核心。RTOUT的加入则是为了处理异常情况比如从设备无响应时能及时触发DMA停止或通知CPU。2.4 中断与DMA触发条件的详细解读光知道有哪些寄存器还不够我们必须清楚每个事件到底在什么条件下会被触发。这是配置的基石。CPU中断事件条件Table 26-3RXFIFO_OVF (0x01)接收FIFO溢出。这是严重错误意味着数据丢失。通常需要ISR立即处理并检查硬件连接或软件流控。PER (0x02)奇偶校验错误。如果使能了奇偶校验CTL1.PREN1在接收数据校验失败时触发。需要重发数据。RTOUT (0x03)接收超时。仅在从机模式下有效。当片选有效后超过CTL1.RXTIMEOUT个功能时钟周期仍未收到数据时触发。用于检测总线挂死或从机故障。RX (0x04)接收FIFO就绪。当接收FIFO中的数据量达到IFLS.RXIFLSEL寄存器所设定的阈值如1/2满时触发。这是最常用的中断之一用于通知CPU来取走数据。TX (0x05)发送FIFO就绪。当发送FIFO中的空余空间达到IFLS.TXIFLSEL所设定的阈值如1/2空时触发。通知CPU可以填充新的待发送数据。TXEMPTY (0x06)发送FIFO完全空。所有数据都已从FIFO移入移位器并开始发送。这个中断可以用来判断一帧或一段数据是否已完全发出对于需要严格时序控制或关闭片选的场景非常有用。IDLE (0x07)SPI空闲。当一次传输完成STAT.BUSY位由高变低时触发。标志着一次通信事务的结束。DMA_DONE_RX/TX (0x08 0x09)DMA完成。当对应的DMA通道完成传输并发出DONE信号时触发。这允许SPI模块感知DMA传输的完成可以在CPU中断中处理DMA完成后的后续工作如处理数据、重新配置DMA等。DMA触发事件条件Table 26-4 26-5对于DMA_TRIG_RX可选RTOUT或RX事件作为触发源。对于DMA_TRIG_TX只能选择TX事件作为触发源。这里的关键在于DMA的触发逻辑是“或”关系。以DMA_TRIG_RX为例如果你同时使能了RX和RTOUT的IMASK那么这两个事件中的任何一个发生都会向DMA控制器发出触发信号。这在设计高可靠系统时需要留意你可能不希望超时错误也触发一次DMA传输。3. 实战配置CPU中断与DMA触发流程详解理论说再多不如一行代码。下面我们结合常见的两种应用场景看看如何具体配置这些寄存器。3.1 场景一配置CPU中断处理接收数据假设我们需要用中断方式接收不定长的SPI数据。我们的目标是当接收FIFO中的数据达到1/4满时触发CPU中断在ISR中读取数据。步骤1全局初始化与SPI基础配置首先我们需要使能SPI模块的时钟、配置引脚复用、设置SPI为主机模式、通信速率、数据位宽、时钟极性和相位等。这部分是SPI通信的基础假设我们已经通过配置CTL0CTL1CLKCTL等寄存器完成。关键是要在最后才将CTL1.ENABLE位置1启动SPI。步骤2配置FIFO中断阈值我们希望接收FIFO有数据就尽快通知CPU避免溢出所以将阈值设得较低。通过配置IFLS寄存器// 设置接收FIFO中断触发水平为 1/4 满 // RXIFLSEL 0b001 1 SPIx-IFLS (SPIx-IFLS ~SPI_IFLS_RXIFLSEL_Msk) | (1 SPI_IFLS_RXIFLSEL_Pos); // 发送FIFO中断阈值可以根据需要设置本例中未使用TX中断可保持默认步骤3使能目标中断并设置事件模式我们只关心接收事件RX和溢出错误RXFIFO_OVF。先设置事件模式为硬件自动清除然后使能这两个中断。// 1. 设置CPU_INT事件线为硬件自动清除模式 (INT0_CFG 2) SPIx-EVT_MODE (SPIx-EVT_MODE ~SPI_EVT_MODE_INT0_CFG_Msk) | (2 SPI_EVT_MODE_INT0_CFG_Pos); // 2. 在IMASK寄存器中使能RX和RXFIFO_OVF中断 SPIx-CPU_INT.IMASK | (SPI_IMASK_RX_Msk | SPI_IMASK_RXFIFO_OVF_Msk); // 注意IMASK是一个结构体或宏指向0x1028地址的寄存器。这里用点号表示其字段。步骤4配置NVIC嵌套向量中断控制器这是MCU层级的中断管理。我们需要找到SPI模块对应的全局中断号设置它的优先级并最终使能它。// 假设SPI0的中断号为SPI0_IRQn NVIC_SetPriority(SPI0_IRQn 2); // 设置一个合适的优先级 NVIC_EnableIRQ(SPI0_IRQn); // 使能SPI0全局中断步骤5编写中断服务程序ISRISR是中断发生后的处理核心。它的首要任务是快速判断中断源并处理然后清除中断标志。void SPI0_IRQHandler(void) { // 1. 读取IIDX获取最高优先级中断索引硬件会自动清除该中断的RIS/MIS uint32_t int_idx SPIx-CPU_INT.IIDX; // 2. 根据中断索引进行处理 switch(int_idx) { case 0x04: // RX事件 // 循环读取RXDATA寄存器直到接收FIFO为空 (STAT.RFE 1) while((SPIx-STAT SPI_STAT_RFE_Msk) 0) { uint16_t received_data SPIx-RXDATA; // 处理received_data例如存入缓冲区 process_received_data(received_data); } break; case 0x01: // RXFIFO_OVF事件 // 处理溢出错误记录日志、清除错误标志、可能需要复位SPI或采取恢复措施 handle_overflow_error(); // 必须手动清除溢出错误标志因为它可能不会通过读IIDX自动清除取决于设计 SPIx-CPU_INT.ICLR SPI_ICLR_RXFIFO_OVF_Msk; break; // 可以处理其他中断... case 0x00: // 无中断 pending 可能是误入或已处理 default: break; } // 注意通过读取IIDX已自动清除了当前处理的最高优先级中断标志。 // 但如果同时有多个中断pending硬件会在处理完一个后更新IIDX为下一个。 // 因此更稳健的做法是在ISR末尾再读一次IIDX如果非0则继续处理直到为0。 }实操心得在ISR中IIDX的自动清除特性非常方便但要注意它只清除当前最高优先级的中断。如果使能了多个同优先级或需要处理多个中断更常见的做法是直接读取MIS寄存器然后根据MIS的值来分别处理并手动清除ICLR。这给了软件更大的灵活性。另外对于错误类中断如OVF PER一定要在ISR中及时清除标志并处理否则可能导致中断持续触发。3.2 场景二配置DMA实现自动数据收发对于需要连续、高速传输大量数据的场景如读写SPI Flash、刷新显示屏DMA是必不可少的。我们的目标是利用DMA自动将内存中的一个数组发送出去并自动将接收到的数据存入另一个数组。步骤1SPI基础配置与FIFO阈值设置同样先完成SPI的基础配置。对于DMAFIFO阈值的设置会影响DMA的触发频率和效率。通常我们希望DMA传输的粒度大一些以减少总线占用和上下文切换开销。// 设置发送FIFO中断触发水平为 1/2 空 (TXIFLSEL 2 默认值) // 设置接收FIFO中断触发水平为 1/2 满 (RXIFLSEL 2 默认值) // 这样当发送FIFO空出一半空间时触发DMA填充当接收FIFO存满一半时触发DMA读取。 SPIx-IFLS (2 SPI_IFLS_TXIFLSEL_Pos) | (2 SPI_IFLS_RXIFLSEL_Pos);步骤2配置DMA触发事件我们需要分别配置发送和接收的DMA触发源。// 1. 设置DMA_TRIG_TX和DMA_TRIG_RX事件线为硬件自动清除模式 SPIx-EVT_MODE (SPIx-EVT_MODE ~(SPI_EVT_MODE_INT1_CFG_Msk | SPI_EVT_MODE_INT2_CFG_Msk)) | (2 SPI_EVT_MODE_INT1_CFG_Pos) // DMA_TRIG_RX 硬件模式 | (2 SPI_EVT_MODE_INT2_CFG_Pos); // DMA_TRIG_TX 硬件模式 // 2. 配置DMA_TRIG_TX的触发源为TX事件发送FIFO有空位 SPIx-DMA_TRIG_TX.IMASK SPI_IMASK_TX_Msk; // 只使能TX事件作为触发源 // 3. 配置DMA_TRIG_RX的触发源为RX事件接收FIFO有数据 SPIx-DMA_TRIG_RX.IMASK SPI_IMASK_RX_Msk; // 只使能RX事件作为触发源 // 注意通常不会将RTOUT作为DMA触发源除非有特殊需求。步骤3配置DMA控制器这一步是MCU平台相关的。你需要配置DMA通道指定源地址Source Address对于发送DMA是内存数组的地址对于接收DMA是SPIx-RXDATA寄存器的地址。目标地址Destination Address对于发送DMA是SPIx-TXDATA寄存器的地址对于接收DMA是内存数组的地址。传输数据量Transfer Size。触发源Trigger Source选择SPI对应的TX事件和RX事件作为DMA的硬件触发信号。传输模式通常设置为“每次触发传输一个数据单元如16位”。以发送DMA为例的伪代码// 假设使用DMA通道0处理SPI发送 DMA_Channel0-SRC_ADDR (uint32_t)tx_buffer[0]; DMA_Channel0-DST_ADDR (uint32_t)(SPIx-TXDATA); DMA_Channel0-TRANSFER_SIZE BUFFER_SIZE; DMA_Channel0-TRIGGER_SELECT DMA_TRIGGER_SPI0_TX; // 选择SPI0 TX事件作为触发 DMA_Channel0-CONTROL DMA_CTRL_ENABLE | DMA_CTRL_TRIG_MODE_HW; // 使能硬件触发模式步骤4启动传输配置好DMA后需要手动向SPI发送FIFO填入第一个或第一批数据来“启动”这个链条。// 先手动写入1个数据降低TX FIFO水平从而立即产生第一个TX事件触发DMA SPIx-TXDATA tx_buffer[0]; // 或者如果使能了PACKEN32位打包且DMA配置为32位传输则需要写入32位数据 // *(uint32_t*)(SPIx-TXDATA) first_two_data_words; // 随后DMA会在每次TX事件FIFO有空位时被自动触发将后续数据搬入TXDATA。 // 对于接收一旦外部主机开始发送数据RX FIFO达到阈值就会触发接收DMA。步骤5处理DMA完成DMA传输完成后通常会触发一个DMA通道完成中断DMA_DONE_TX/DMA_DONE_RX。我们可以在SPI的CPU中断中处理这个事件或者配置DMA控制器本身的中断。// 在SPI的CPU中断使能中加入DMA完成中断 SPIx-CPU_INT.IMASK | (SPI_IMASK_DMA_DONE_TX_Msk | SPI_IMASK_DMA_DONE_RX_Msk); // 在SPI的ISR中增加对应处理 void SPI0_IRQHandler(void) { uint32_t int_idx SPIx-CPU_INT.IIDX; switch(int_idx) { // ... 其他中断处理 case 0x09: // DMA_DONE_TX // 发送DMA完成可以准备下一批数据或进行后续操作 handle_tx_dma_complete(); // 标志位已被读IIDX自动清除 break; case 0x08: // DMA_DONE_RX // 接收DMA完成处理接收到的数据包 handle_rx_dma_complete(); // 标志位已被读IIDX自动清除 break; } }4. 高级话题调试模式、性能优化与常见陷阱4.1 调试模式下的行为控制在开发过程中我们经常需要暂停CPU进入调试Halt模式来检查寄存器、变量。但外设如SPI可能还在与外部设备通信突然停止会导致数据丢失或通信错误。PDBGCTL寄存器就是用来控制调试时外设行为的。FREE位此位为1时外设完全忽略CPU的Halt状态继续运行。这在调试与时间紧密相关的通信时序时很有用但可能导致你无法检查某一时刻的精确状态。SOFT位当FREE0时此位生效。SOFT0外设立即停止可能导致当前传输被破坏。SOFT1外设在完成当前传输到达一个“安全边界”后才停止。这是最常用、最安全的调试模式保证了数据的一致性。建议在大多数调试场景下设置PDBGCTL 0x00000001FREE0SOFT1是最佳实践。除非你明确知道需要外设自由运行。4.2 性能优化与配置要点FIFO深度与阈值选择首先要查阅数据手册明确你的SPI模块的TX/RX FIFO深度例如是4级还是8级。IFLS阈值的设置需要权衡实时性和中断/DMA触发频率。阈值设得越“敏感”如1/4响应延迟越小但中断/DMA触发更频繁系统开销增大。对于大数据量连续传输设置为1/2或3/4是更好的选择能提高单次传输的数据量提升总线利用率。DMA触发与CPU中断的权衡对于纯粹的、连续的数据流搬运务必使用DMA。CPU中断只应用于处理异常、错误或作为DMA的补充如处理DMA完成事件。将RX和TX事件的IMASK在CPU中断中禁用仅在DMA触发中使能可以避免不必要的CPU中断。时钟与数据打包CLKCTL.SCR决定了SPI的比特率计算公式为比特率 功能时钟频率 / ((SCR1)*2)。合理设置SCR以匹配从设备速率。CTL0.PACKEN打包使能是一个性能利器。当使能后可以对32位数据两个16位帧进行打包一次读写TXDATA/RXDATA就是32位这能将FIFO的有效深度翻倍并减少CPU/DMA访问总线的次数。中断优先级与嵌套如果系统中存在多个中断源需要合理配置NVIC中的中断优先级。SPI通信中断的优先级通常应高于非实时性任务但低于系统关键中断如看门狗。避免在SPI ISR中执行耗时操作。4.3 常见问题与排查实录在实际项目中SPI事件和中断的配置常常会遇到一些“坑”。这里分享几个我踩过的和常见的问题1中断无法进入或者只进入一次。排查思路NVIC配置这是最常见的原因。确认已调用NVIC_EnableIRQ()使能了全局中断并且中断优先级设置正确。SPI事件模式检查EVT_MODE寄存器确认对应事件线如INT0_CFG未被设置为禁用0。如果设置为软件模式1需要确认在ISR中手动清除了ICLR。中断屏蔽确认IMASK寄存器中对应中断位已置1。全局中断使能在启动SPI和使能NVIC之前确保已通过__enable_irq()或类似指令开启了CPU的全局中断。中断标志清除如果是硬件模式确认ISR中通过读取IIDX或写入ICLR清除了标志。在软件模式下必须手动写ICLR。问题2DMA传输不启动或者数据搬运不完整。排查思路DMA触发源使能确认SPIx-DMA_TRIG_RX/TX.IMASK已正确使能了RX或TX事件。初始触发条件DMA需要第一个硬件事件来启动。对于发送确保在启动DMA后手动向TXDATA写入至少一个数据使TX FIFO非满从而产生第一个TX事件。对于接收需要确保外部设备已经开始发送数据。DMA配置匹配检查DMA的源/目标地址、数据宽度8/16/32位、传输次数是否与SPI配置数据位宽DSS、打包PACKEN匹配。例如SPI设置为8位数据DMA也应配置为8位传输。FIFO阈值检查IFLS设置。如果阈值设得太高如TXIFLSEL5即FIFO全空才触发而DMA每次只搬一个数据可能导致FIFO一直无法达到“全空”条件从而无法触发后续DMA。SPI使能顺序建议的初始化顺序是配置SPI寄存器除ENABLE - 配置DMA - 使能DMA通道 - 最后置位CTL1.ENABLE启动SPI。问题3通信中出现数据错乱或丢失。排查思路溢出错误首先检查RIS寄存器中的RXFIFO_OVF位。如果置1说明接收速度大于处理速度。需要优化ISR/DMA效率或提高FIFO触发阈值或降低SPI波特率。奇偶校验错误检查RIS.PER。如果使能了奇偶校验此位置1表示数据在传输中出错。检查硬件连接、接地和电源噪声。时钟相位与极性确认CTL0.SPO和SPH与从设备严格匹配。这是SPI通信中最容易出错的地方之一。中断/DMA响应延迟在高速通信下如果中断响应太慢或DMA启动延迟可能导致FIFO溢出。考虑使用DMA而非中断并优化DMA通道优先级。也可以尝试使用SPI的FIFO“打包”功能来减少事务次数。问题4在调试器中单步执行时SPI通信异常。排查思路检查PDBGCTL寄存器很可能FREE和SOFT位都被默认或意外设置为0导致CPU一旦暂停SPI立即停止破坏了通信时序。将其设置为SOFT1。避免在ISR内设置断点在中断服务程序内单步调试会严重影响实时性可能导致错过后续中断或发生溢出。尽量通过日志或变量来调试ISR逻辑。配置SPI的事件与中断机制是一个从理解硬件架构到精心设计软件流程再到反复调试优化的过程。它没有一成不变的“最佳配置”只有最适合你当前应用场景的“最优解”。希望这篇基于寄存器手册的深度解析能帮你建立起清晰的配置脉络在实际项目中游刃有余。记住多利用STAT寄存器查看FIFO状态多用逻辑分析仪抓取SPI总线波形这两者是调试SPI问题的黄金搭档。