SPI中断与FIFO配置实战:从寄存器手册到高效驱动开发

📅 2026/7/18 11:20:08
SPI中断与FIFO配置实战:从寄存器手册到高效驱动开发
1. 项目概述从寄存器手册到可落地的驱动实践如果你和我一样常年泡在嵌入式底层那对SPISerial Peripheral Interface肯定不陌生。它简单、高效是连接传感器、存储器、显示屏等外设的“万能胶”。但手册里那些密密麻麻的寄存器描述什么TX_EMPTY、RX_FULL、AEL、AFL是不是经常让你看得头大感觉懂了又好像没完全懂更别提把它们组合起来写出一个既高效又稳定的驱动程序了。我最近在折腾TI的SimpleLink CC323x系列芯片它的SPI模块功能相当完整支持中断、DMA和内置FIFO。但官方手册更像是一本“字典”告诉你每个寄存器位是干什么的却很少说清楚“为什么”要这么配置以及在实际工程中“怎么用”才能避免踩坑。比如那个关键的SPI_XFERLEVEL寄存器手册只说AEL和AFL用来设置“几乎空”和“几乎满”的阈值但阈值设多少合适设错了会怎样中断和DMA请求到底怎么配合FIFO工作这些问题手册里可找不到现成答案。这篇文章就是我啃完几百页手册调试了无数遍代码后的实战总结。我不会照本宣科地复述寄存器定义而是带你穿透那些枯燥的位域描述直击SPI中断与FIFO协同工作的核心逻辑。我们会一起弄明白中断事件是如何被触发和清除的FIFO缓冲区如何扮演数据“蓄水池”的角色平滑数据流如何通过SPI_XFERLEVEL寄存器精细控制DMA或中断的触发时机从而在CPU开销、实时性和数据吞吐量之间找到最佳平衡点无论你是在调CC323x还是在研究其他芯片的SPI模块这里面的思路和避坑经验都是相通的。搞懂了这一套你就能写出那种“心里有底”的驱动代码而不是对着现象盲猜和试错。2. SPI中断机制深度解析事件、状态与清除逻辑很多人觉得SPI中断配置就是开个中断使能位然后在中断服务程序里读读写写。但如果你没搞清楚中断事件的产生、状态位的含义以及正确的清除顺序很可能会遇到中断不触发、中断死锁或者数据丢失这些让人头疼的问题。我们得把中断机制掰开揉碎了看。2.1 核心中断事件详解CC323x的SPI模块提供了几个关键的中断事件源每个都对应着数据传输链路中的一个特定状态。理解它们是正确使用中断的前提。TX_EMPTY (发送寄存器空)这是最常用的发送中断。当发送移位寄存器将当前数据移出且发送数据寄存器或TX FIFO为空时此事件产生。它告诉你“我准备好发送下一个数据了。”在FIFO启用时这个事件的实际含义变为“TX FIFO几乎空”Almost Empty触发阈值由SPI_XFERLEVEL[AEL]决定。这意味着你不需要等到FIFO完全空了才被通知可以提前准备数据实现无缝填充。RX_FULL (接收寄存器满)对应的接收中断。当接收数据寄存器或RX FIFO接收到一个完整的数据字时此事件产生。它告诉你“有数据到了快来取走。”启用FIFO后它意味着“RX FIFO几乎满”Almost Full阈值由SPI_XFERLEVEL[AFL]设置。这允许你批量读取数据而不是每收到一个字节就进一次中断极大降低了CPU中断负载。TX_UNDERFLOW (发送下溢)这个事件容易误解。它不是一个错误而是一个状态指示。在从机模式下当主机发起传输但从机的发送寄存器没有准备好新数据为空时就会发生下溢。此时SPI模块会继续发送但发送的是旧数据或默认值。手册明确提到“does not prevent transmission”。所以在从机发送场景下你需要监控此事件确保在主机选通你之前已经把要发送的数据写入TX寄存器或FIFO。RX_OVERFLOW (接收溢出)这是一个真正的错误事件。当一个新的SPI字已经接收完成但接收寄存器或RX FIFO仍然是满的即上一个数据还没被读走就会发生溢出。新数据会覆盖旧数据导致数据丢失。手册特别强调在从机模式并使用FIFO时不应出现此事件。如果出现了通常意味着你的程序读取数据的速度跟不上主机发送的速度或者中断服务程序响应太慢需要优化。EOW (End-of-Word Count字计数结束)这是一个高级功能仅在使用FIFO时有效。当传输的SPI字数达到SPI_XFERLEVEL[WCNT]寄存器预设的值时触发此中断。它相当于一个“传输完成”中断特别适合已知固定长度数据块的传输。触发后该通道的传输会停止直到你重新配置WCNT并再次使能通道。2.2 中断状态与使能寄存器的协同工作中断的处理流程围绕着两个核心寄存器SPI_IRQSTATUS中断状态寄存器和SPI_IRQENABLE中断使能寄存器。它们的协同逻辑是中断驱动编程的基石。SPI_IRQSTATUS是一个状态寄存器硬件会根据模块内部状态自动置位相应的位。比如当RX FIFO中的数据量达到AFL阈值硬件会自动把RX_FULL状态位置1。这个位只表示“有事情发生了”并不会直接导致CPU中断。SPI_IRQENABLE是一个使能寄存器你可以把它想象成一个个开关。只有当一个事件在SPI_IRQSTATUS中有状态位为1并且在SPI_IRQENABLE中对应的使能位也被打开位为1这个事件才会真正去触发硬件中断线让CPU跳转到中断服务程序。这里有一个极其关键的细节手册里写得有点绕“When an interrupt occurs and a mask is then applied on it (IRQENABLE), the interrupt line is not asserted again, even if the interrupt source has not been serviced.”这句话的意思是如果一个中断事件发生了并且它的使能位是打开的那么中断线会被拉高触发中断。但是如果你在中断服务程序执行期间修改了IRQENABLE寄存器屏蔽了这个中断源那么即使中断源比如FIFO还是满的没有被处理中断线也不会再次被拉高。这可能导致中断“丢失”。因此最佳实践是在中断服务程序ISR中先读取状态处理事务最后再清除状态位。尽量避免在ISR中动态开关中断使能。2.3 中断服务程序的标准操作流程一个健壮的中断服务程序必须遵循固定的步骤否则极易引入错误。基于手册描述标准的流程如下读取中断状态首先读取SPI_IRQSTATUS寄存器保存到一个变量例如ulStatus。这个操作是为了识别是哪个或哪些事件触发了本次中断。因为多个事件可能同时发生。清除中断状态向SPI_IRQSTATUS寄存器中你需要清除的事件对应位写入1。注意这里是写1清零不是写0。例如SPIIntClear(GSPI_BASE, SPI_INT_RX_FULL | SPI_INT_TX_EMPTY)。这一步会释放中断线允许该事件再次触发中断。处理中断源根据ulStatus中的标志位执行相应的操作。这是最关键的一步处理方式因事件而异对于RX_FULL必须读取接收寄存器SPI_RX或从RX FIFO中读取数据。只有执行了读取操作接收缓冲区才有空间容纳新数据才能“移除中断源”。读取的数据量应至少达到AFL定义的阈值否则不会产生新的RX_FULL中断。对于TX_EMPTY必须向发送寄存器SPI_TX或TX FIFO中写入新数据。只有填充了数据才能“移除中断源”。对于TX_UNDERFLOW和RX_OVERFLOW手册明确指出“No action is needed”。这意味着它们的中断源无法通过软件操作直接“移除”它们是由硬件状态直接决定的。清除状态位即可。对于EOW表示预设长度的传输完成。通常在此处进行后续处理如通知任务、重置计数器等。重要提示步骤2和3的顺序有时可以根据优化策略调整但必须理解其影响。先清除状态位可以更快地释放中断线允许其他中断嵌套。但如果你先处理数据特别是耗时操作再清除状态位在此期间该中断无法再次触发。对于高速数据流这可能增加溢出的风险。我个人的习惯是对于简单的数据搬运先清除状态位对于复杂处理先拷贝数据到安全缓冲区再清除状态位。3. FIFO缓冲区配置的艺术AEL与AFL阈值计算FIFOFirst In, First Out缓冲区是提升SPI吞吐量、降低CPU中断频率的神器。但要用好它关键在于理解并正确配置SPI_XFERLEVEL寄存器中的AELAlmost Empty Level和AFLAlmost Full Level。手册里的公式有点反直觉我们得把它翻译成工程师能懂的语言。3.1 AEL与AFL的本质触发水位线你可以把TX FIFO和RX FIFO想象成两个水池。AEL是TX水池的“低水位线”AFL是RX水池的“高水位线”。AEL (Almost Empty Level)针对发送方向。当TX FIFO中剩余的数据量小于或等于AEL设定的值时就会触发TX_EMPTY中断或DMA写请求。它是在问“水池快见底了要不要加水”AFL (Almost Full Level)针对接收方向。当RX FIFO中已存入的数据量大于或等于AFL设定的值时就会触发RX_FULL中断或DMA读请求。它是在报告“水池快满了快来舀水”手册里那个“n-1”的规则是很多人的困惑点。其实很简单如果你想在FIFO还能容纳n个字节时触发TX中断/请求就设置AEL n - 1。如果你想在FIFO中已有n个字节时触发RX中断/请求就设置AFL n - 1。举例说明假设你的SPI字长是8位1字节FIFO深度为64字节。场景一你希望TX FIFO里只剩最后8个字节即空余56字节时就提醒你补充数据那么AEL 8 - 1 7。场景二你希望RX FIFO里攒够32个字节时就通知你读取那么AFL 32 - 1 31。3.2 阈值设定的工程考量设置AEL和AFL不是随便填个数它直接影响了系统性能和可靠性。这里有几个核心原则1. 平衡吞吐量与实时性AFL设置较小如4-8意味着RX FIFO稍微有点数据就触发中断数据读取延迟低实时性好。但中断频率会非常高CPU忙于进出中断整体吞吐量可能下降且在高频下容易因中断响应不及时导致溢出。AFL设置较大如48-60允许RX FIFO积累更多数据才触发一次中断大大降低了中断频率提升了吞吐量CPU有更多时间处理其他任务。但缺点是数据读取的延迟变长对于需要快速响应的场景不友好。实战建议对于连续流数据如音频流设置较大的AFL如深度的一半或3/4以批量处理。对于需要快速响应的控制指令可以结合查询方式或设置较小的AFL。2. 避免阈值设置不当导致的“饥饿”或“溢出”TX侧“饥饿”如果AEL设置过大比如63意味着TX FIFO几乎完全空了才请求新数据。如果CPU/DMA填充数据的速度慢于SPI发送的速度就会导致发送侧“断流”产生TX_UNDERFLOW在从机模式下会发送无效数据。RX侧“溢出”如果AFL设置过大而数据产生速度很快可能在中断触发前FIFO就已经被填满导致RX_OVERFLOW和数据丢失。安全边界一个经验法则是确保在中断响应时间 数据搬运时间内FIFO不会从触发水位线达到满/空的状态。例如假设SPI波特率为1 Mbps处理一个中断最大需要50us那么在这50us内SPI可能接收/发送 1Mbps * 50us / 8 ≈ 6.25字节。你的AFL/AEL设置就应该预留出大于这个值的余量。3. 与DMA的完美配合当使用DMA时AEL和AFL就是DMA请求的触发条件。DMA的突发Burst传输长度最好与FIFO深度 - 阈值相匹配。例如FIFO深度64AFL设为31即32字节触发那么可以将DMA的传输长度设置为32字节。这样一旦触发DMA请求DMA控制器就能一次性搬走32字节的数据效率最高。3.3 FIFO工作模式与WCNT的应用CC323x的SPI FIFO可以配置为三种模式通过SPI_CHCONF寄存器的FFEW和FFER位控制独立模式FFEW1, FFER0或FFEW0, FFER1。FIFO只用于发送或只用于接收此时整个64字节缓冲区专用于一个方向。拆分模式FFEW1, FFER1。FIFO被拆分成两个独立的32字节缓冲区一个用于发送一个用于接收。特别注意在此模式下AEL和AFL的阈值计算基础仍然是64字节但实际管理的是两个32字节的缓冲区。例如设置AFL31意味着接收侧的32字节FIFO中有32字节数据时触发因为31对应n32但这需要主机软件清楚知道拆分边界。WCNT字计数器是一个强大的功能。当你需要传输一个已知长度的数据块时比如读取一个256字节的Flash ID可以设置WCNT256。启动传输后每完成一个字的传输内部计数器减1。当计数器减到0时触发EOW中断并且通道会自动停止。这省去了软件计数的麻烦并能精确地在传输结束时通知CPU非常适合块传输协议。踩坑记录在一次调试中我启用了WCNT并设置了值但忘记在EOW中断后重新加载WCNT和使能通道导致后续的SPI传输无法启动。现象是程序卡住调试发现SPI状态寄存器显示通道未激活。记住EOW中断后通道是停止状态需要软件重新初始化或至少重新使能通道(EN位)才能进行下一次传输。4. 从零构建中断与FIFO模式驱动实现理论讲得再多不如一行代码来得实在。下面我将以CC323x的SDK代码风格为例展示如何一步步配置一个带FIFO和中断的SPI从机驱动。我们会涵盖初始化、中断配置、FIFO设置以及中断服务程序编写。4.1 硬件与软件初始化任何外设驱动的基础都是正确的初始化。对于SPI这包括时钟、引脚、模块本身以及中断控制器。// 假设使用GSPI模块作为从机 #define SPI_BASE GSPI_BASE #define SPI_INT_PERIPH INT_GSPI void SPI_Slave_InitWithFIFO(void) { // 1. 使能SPI模块时钟 PRCMPeripheralClkEnable(PRCM_GSPI, PRCM_RUN_MODE_CLK); // 2. 配置引脚复用将芯片引脚映射到SPI功能 // 假设使用PIN_01为CLK, PIN_02为SOMI, PIN_03为SIMO, PIN_04为CS PinTypeSPI(PIN_01, PIN_MODE_0); // SPICLK PinTypeSPI(PIN_02, PIN_MODE_0); // SOMI (主入从出) PinTypeSPI(PIN_03, PIN_MODE_0); // SIMO (主出从入) PinTypeSPI(PIN_04, PIN_MODE_0); // SPIEN/CS // 3. 软件复位SPI模块确保从一个干净的状态开始 SPIReset(SPI_BASE); // 等待复位完成查询SPI_SYSSTATUS[RESETDONE]位 while(!(SPI_SYSSTATUS_R SPI_SYSSTATUS_RESETDONE)); // 4. 配置SPI为从机模式4线制字长8位 // 注意从机模式下比特率参数通常被忽略由主机时钟决定 SPIConfigSetExpClk(SPI_BASE, PRCMPeripheralClockGet(PRCM_GSPI), 100000, // 此参数在从机模式下通常无效 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. 启用FIFO缓冲区拆分模式各32字节用于发送和接收 // 直接操作寄存器进行精细配置 HWREG(SPI_BASE SPI_O_CHCONF) ~(SPI_CHCONF_FFEW | SPI_CHCONF_FFER); // 先清零 HWREG(SPI_BASE SPI_O_CHCONF) | (SPI_CHCONF_FFEW | SPI_CHCONF_FFER); // 同时使能TX和RX FIFO // 6. 配置FIFO触发阈值 (AEL 和 AFL) // 我们希望TX FIFO剩余8字节空间时请求数据 (AEL7) // RX FIFO有16字节数据时通知读取 (AFL15) HWREG(SPI_BASE SPI_O_XFERLEVEL) (0 16) | // WCNT0不使用字计数功能 (15 8) | // AFL 15 (触发级别16字节) (7 0); // AEL 7 (触发级别8字节) // 7. 注册中断服务程序并使能系统级中断 SPIIntRegister(SPI_BASE, SPI_Slave_IRQHandler); SPIIntEnable(SPI_BASE, SPI_INT_RX_FULL | SPI_INT_TX_EMPTY); // 使能RX和TX中断 // 注意通常我们不在初始化时使能TX_EMPTY因为此时FIFO是空的会立即触发中断。 // 更好的做法是在主程序中当有数据需要发送时先填充一部分数据到FIFO再开启TX_EMPTY中断。 IntEnable(SPI_INT_PERIPH); // 在NVIC中使能SPI中断 // 8. 最后使能SPI通道 SPIEnable(SPI_BASE); }这段初始化代码有几个关键点从机时钟从机的SPIConfigSetExpClk函数中比特率参数通常不起作用时钟由主机提供。FIFO使能通过直接写CHCONF寄存器来同时启用发送和接收FIFO进入拆分模式。阈值设置XFERLEVEL寄存器的配置是核心这里AEL7AFL15是一个兼顾响应和批处理的折中设置。中断使能时机TX_EMPTY中断的使能时机很重要。如果在FIFO完全空的时候使能会立刻触发中断。通常的做法是先手动填充一部分数据到TX FIFO然后再使能TX_EMPTY中断让中断来驱动后续的数据填充。4.2 中断服务程序(ISR)的实现中断服务程序是数据搬运的核心其效率直接影响系统性能。// 定义全局缓冲区或队列用于数据交换 #define TX_BUFFER_SIZE 128 #define RX_BUFFER_SIZE 128 static uint8_t g_ucTxBuffer[TX_BUFFER_SIZE]; static uint8_t g_ucRxBuffer[RX_BUFFER_SIZE]; static volatile uint32_t g_ulTxWriteIndex 0; static volatile uint32_t g_ulTxReadIndex 0; static volatile uint32_t g_ulRxWriteIndex 0; static volatile uint32_t g_ulRxReadIndex 0; static volatile bool g_bTxDataPending false; // 标志是否有数据待发送 void SPI_Slave_IRQHandler(void) { uint32_t ulStatus; uint32_t i; uint8_t ucData; // 1. 读取当前中断状态 ulStatus SPIIntStatus(SPI_BASE, true); // true表示读取原始中断状态 // 2. 清除已识别到的中断状态位写1清零 SPIIntClear(SPI_BASE, ulStatus); // 清除所有触发的中断 // 3. 处理TX_EMPTY中断 (FIFO几乎空) if(ulStatus SPI_INT_TX_EMPTY) { // 计算TX FIFO中剩余空间 (AEL触发意味着空间AEL1) // 更稳健的做法是读取SPI_CHSTAT[TXFFE]和[TXFFF]判断这里简化处理。 // 我们尝试填充到FIFO不为空。在拆分模式下TX FIFO深度为32字节。 // AEL7意味着当剩余空间8字节时触发。我们尝试填充8个字节。 for(i 0; i 8; i) { if(g_bTxDataPending (g_ulTxReadIndex g_ulTxWriteIndex)) { // 从环形缓冲区读取待发送数据 ucData g_ucTxBuffer[g_ulTxReadIndex % TX_BUFFER_SIZE]; g_ulTxReadIndex; SPIDataPutNonBlocking(SPI_BASE, ucData); } else { // 没有更多数据要发送可以发送默认值如0xFF或关闭TX_EMPTY中断 // 这里选择发送0xFF并关闭TX_EMPTY中断避免无意义中断 SPIDataPutNonBlocking(SPI_BASE, 0xFF); // 可选当没有数据时禁用TX_EMPTY中断等有数据时再开启 // SPIIntDisable(SPI_BASE, SPI_INT_TX_EMPTY); // g_bTxDataPending false; // break; // 跳出填充循环 } } // 检查是否所有数据已发送完 if(g_ulTxReadIndex g_ulTxWriteIndex) { g_bTxDataPending false; // 所有数据发送完毕禁用TX_EMPTY中断防止持续触发 SPIIntDisable(SPI_BASE, SPI_INT_TX_EMPTY); } } // 4. 处理RX_FULL中断 (FIFO几乎满) if(ulStatus SPI_INT_RX_FULL) { // AFL15意味着当数据量16字节时触发。我们尝试读取16个字节。 for(i 0; i 16; i) { if(SPIDataGetNonBlocking(SPI_BASE, (uint32_t *)ucData)) { // 成功读取到数据存入接收环形缓冲区 g_ucRxBuffer[g_ulRxWriteIndex % RX_BUFFER_SIZE] ucData; g_ulRxWriteIndex; } else { // 非阻塞读取失败说明FIFO已空跳出循环 // 注意由于是AFL触发理论上不会立即空但谨慎起见。 break; } } // 可以在这里设置一个标志通知主循环或任务有数据到达 // 例如g_bRxDataReady true; } // 5. 处理其他中断根据需要 if(ulStatus SPI_INT_RX_OVERFLOW) { // 接收溢出错误数据已丢失。需要记录错误并可能需要复位接收状态。 // 例如清除RX FIFO重置缓冲区索引。 while(SPIDataGetNonBlocking(SPI_BASE, (uint32_t *)ucData)); // 清空RX FIFO g_ulRxWriteIndex g_ulRxReadIndex; // 重置缓冲区可能丢弃未处理数据 // 记录错误日志... } if(ulStatus SPI_INT_TX_UNDERFLOW) { // 发送下溢。在从机模式下这通常意味着主机在请求数据但我们没及时提供。 // 可以记录或忽略取决于应用需求。 // 如果频繁发生需要检查TX数据准备是否及时。 } if(ulStatus SPI_INT_EOW) { // 字计数传输完成。如果使用了WCNT功能在这里处理传输结束逻辑。 // 例如通知主任务重新配置下一次传输。 } }这个ISR实现了几个关键策略批量处理利用AFL/AEL的阈值每次中断处理一批数据例中为8/16字节而非单字节大幅减少中断次数。非阻塞操作使用NonBlocking函数避免在FIFO空/满时死等。环形缓冲区ISR只负责在硬件FIFO和软件环形缓冲区之间搬运数据将耗时的数据处理如协议解析留给主循环或低优先级任务缩短ISR执行时间。动态中断管理当发送缓冲区为空时主动禁用TX_EMPTY中断避免无数据可发时中断持续触发。当有新数据需要发送时在主程序中先填充一部分数据到FIFO再重新使能该中断。4.3 主程序与驱动的协同中断驱动和FIFO配置好后主程序的工作就变得清晰int main(void) { // 硬件初始化... SPI_Slave_InitWithFIFO(); // 初始时如果有数据要发送先预填充TX FIFO再打开TX中断 if(Prepare_Some_Data_To_Send()) { // 手动填充第一批数据到TX FIFO例如填满一半 Fill_TX_FIFO_Manually(16); // 填充16字节 g_bTxDataPending true; // 现在才使能TX_EMPTY中断让中断驱动后续发送 SPIIntEnable(SPI_BASE, SPI_INT_TX_EMPTY); } while(1) { // 主循环检查接收缓冲区是否有数据 if(g_ulRxWriteIndex ! g_ulRxReadIndex) { // 处理接收到的数据 Process_Received_Data(); } // 检查是否有新的数据需要发送 if(Check_For_New_Tx_Data() !g_bTxDataPending) { // 准备数据到发送缓冲区... g_bTxDataPending true; g_ulTxReadIndex 0; // 重置读取索引 // 同样先手动填充一部分数据到FIFO Fill_TX_FIFO_Manually(16); // 使能TX_EMPTY中断 SPIIntEnable(SPI_BASE, SPI_INT_TX_EMPTY); } // 其他任务... OS_Task_Delay(10); // 如果使用了RTOS } }5. 高级配置与DMA联动对于大数据量传输使用DMA直接存储器访问可以彻底解放CPU。CC323x的SPI模块支持与DMA控制器联动通过SPI_CHCONF寄存器的DMAR和DMAW位使能。5.1 DMA请求触发机制DMA读请求当接收FIFO中的数据量达到或超过AFL阈值时DMAR线被置位向DMA控制器发起读请求。DMA控制器随后从SPI_RX寄存器实际上是FIFO的读出端口读取数据搬运到指定的内存区域。每完成一次读取FIFO中的数据量减少当数据量再次累积到AFL阈值时会触发下一次DMA请求。手册强调用户必须执行正确次数的读取访问由AFL定义否则不会产生新的DMA请求。DMA写请求当发送FIFO中的剩余空间达到或超过AEL阈值时即FIFO变空的程度DMAW线被置位向DMA控制器发起写请求。DMA控制器随后将内存中的数据写入SPI_TX寄存器实际上是FIFO的写入端口。每完成一次写入FIFO剩余空间减少当空间再次达到AEL阈值时触发下一次DMA请求。5.2 配置DMA传输的步骤配置SPI如前所述使能FIFO (FFEW/FFER)设置好AEL和AFL阈值。配置DMA通道为SPI RX分配一个DMA通道配置其源地址为SPI接收数据寄存器地址目标地址为内存缓冲区传输宽度与SPI字长匹配8/16/32位。为SPI TX分配另一个DMA通道配置其源地址为内存缓冲区目标地址为SPI发送数据寄存器地址。设置DMA的传输次数Burst Size和总传输量Transfer Size。Burst Size最好设置为AFL1对于RX或AEL1对于TX以实现最高效的突发传输。使能SPI的DMA请求设置SPI_CHCONF[DMAR] 1和/或SPI_CHCONF[DMAW] 1。启动传输使能SPI通道 (EN1)然后启动DMA传输。DMA会根据FIFO的水位自动请求数据搬运。传输完成处理DMA传输完成通常会触发DMA完成中断。在中断中你可以处理接收到的数据或者准备下一批要发送的数据。重要提示手册明确指出“The DMA requests must be disabled to get TX and RX interrupts”。这意味着中断和DMA请求是互斥的。你不能同时使能TX_EMPTY中断和DMAW请求也不能同时使能RX_FULL中断和DMAR请求。你必须根据需求选择一种数据搬运方式要么用CPU中断要么用DMA。5.3 混合模式与性能权衡在一些复杂场景下可以采用混合模式小数据用中断大数据用DMA对于偶尔的控制命令几个字节使用中断处理响应快。对于大量的数据块如读写Flash使用DMA传输效率高。这就需要你在运行时动态切换SPI通道的配置在发送/接收大数据前关闭中断使能打开DMA使能配置并启动DMA。DMA完成后再切换回中断模式。注意切换时要确保当前传输已完成并妥善处理FIFO中可能残留的数据。6. 调试技巧与常见问题排查即使理解了所有原理调试SPI驱动时依然会遇到各种奇怪的问题。下面是我总结的一些常见“坑”和排查方法。6.1 典型问题速查表现象可能原因排查步骤与解决方案中断根本不触发1. 中断使能位未设置 (SPI_IRQENABLE)。2. NVIC中断未使能。3. 中断服务程序未正确注册或链接。4. 中断状态位在使能前已置位且未清除。1. 检查SPI_IRQENABLE寄存器值。2. 检查系统中断控制器配置。3. 确认中断向量表指向正确。4.在使能中断前先读取并清除SPI_IRQSTATUS寄存器写1清零清除可能存在的陈旧状态。中断只触发一次1. 中断状态位未清除。2. 中断源未移除对于RX_FULL/TX_EMPTY。3. 在ISR中错误地屏蔽了中断使能位。1. 确认ISR中清除了对应的状态位。2. 对于RX_FULL确认执行了读SPI_RX操作对于TX_EMPTY确认执行了写SPI_TX操作。3. 检查ISR中是否误操作了SPI_IRQENABLE寄存器。数据发送/接收不完整或错位1. SPI相位(CPHA)和极性(CPOL)配置与从设备不匹配。2. 字长(WL)设置错误。3. 字节序问题大端/小端。4. FIFO阈值设置不合理导致数据覆盖或丢失。1. 用逻辑分析仪抓取SPI波形核对时钟极性和相位。2. 确认SPI_CHCONF[WL]与设备一致。3. 检查数据寄存器的访问方式SPI字总是右对齐在32位寄存器的LSB。4. 检查RX_OVERFLOW或TX_UNDERFLOW状态调整AFL/AEL阈值或优化ISR/DMA响应速度。从机模式下数据发送异常1. 主机发起传输时从机TX寄存器/FIFO为空导致TX_UNDERFLOW发送了无效数据。2. 片选(SPIEN)极性配置错误。1. 确保在主机拉低片选前从机已将要发送的数据写入TX FIFO。2. 检查SPI_CHCONF[EPOL]位确保与主机片选极性匹配。使用DMA时传输卡住1. DMA请求未使能 (DMAR/DMAW)。2. DMA传输长度与FIFO阈值不匹配。3. DMA和中断同时使能造成冲突。4. DMA目标/源地址或传输宽度配置错误。1. 确认SPI_CHCONF[DMAR]或[DMAW]为1且对应的中断使能位为0。2. 确保DMA单次传输量Burst与AFL1或AEL1匹配。3. 检查DMA通道配置特别是地址自增、数据宽度模式。FIFO模式下数据混乱1. 在拆分模式下错误理解了AEL/AFL的基准仍是64字节。2. 写入/读取FIFO的数据量不是字长的整数倍。3. 在传输过程中更改了FIFO或通道配置。1. 在拆分模式下计算阈值时以64字节为总深度但实际管理32字节缓冲区。2. 确保所有访问都是对齐的且次数正确。3.绝对不要在通道使能(EN1)时修改CHCONF、XFERLEVEL等关键配置寄存器。先禁用通道修改配置再重新使能。6.2 调试实操心得善用状态寄存器遇到问题第一件事是读取SPI_CHSTAT和SPI_IRQSTATUS寄存器。TXS/RXS、TXFFE/TXFFF、RXFFE/RXFFF这些位能直接告诉你FIFO和寄存器的实时状态比猜原因有效得多。逻辑分析仪是你的好朋友SPI是硬件协议用逻辑分析仪或者示波器抓取SCLK, MOSI, MISO, CS四根线的波形可以直观地看到数据是否正确、时序是否匹配、片选是否正常。这是排查硬件连接和基础配置问题的最快方法。从简单模式开始不要一开始就上中断FIFODMA。先配置成最简单的轮询Polling模式确保最基本的字节收发正常。然后逐步增加复杂度先加断再启用FIFO最后上DMA。每步都验证通过能帮你快速定位问题所在阶段。注意配置顺序SPI模块特别是涉及FIFO和DMA时对配置顺序很敏感。一个可靠的初始化顺序是复位模块 - 配置基本参数模式、字长等- 配置FIFO和阈值 - 配置DMA如果需要- 注册中断 - 使能中断 - 最后使能SPI通道 (EN1)。禁用时顺序大致相反。理解“清除”的含义清除中断状态位是写1不是写0。这个反直觉的操作很容易出错。使用SDK提供的SPIIntClear()函数通常更安全。时钟与电源管理确保SPI模块的时钟(PRCM_GSPI)已经使能。在低功耗应用中进入睡眠前要妥善关闭SPI模块和中断唤醒后重新初始化。