TI AM263P MCSPI传输序列与FIFO模式深度解析与实战
1. 项目概述与MCSPI核心价值搞嵌入式开发尤其是用到TI的Sitara系列或者类似的高性能MCUSPI通信绝对是绕不开的坎。你可能用它来读取高精度ADC的数据驱动一块TFT屏幕或者与一个复杂的FPGA进行数据交换。但当你翻开TI那动辄几千页的技术参考手册TRM看到里面密密麻麻的寄存器描述和时序流程图时是不是经常感到一阵头大特别是关于如何正确地启动一次传输、如何优雅地处理收发中断、以及如何利用DMA和FIFO来解放CPU这些细节往往决定了你项目的稳定性和性能上限。今天我们就以TI AM263P微控制器中的MCSPI多通道串行外设接口模块为例抛开那些晦涩的官方术语从一个一线工程师的视角彻底拆解它的传输序列Transfer Sequence和FIFO模式的配置逻辑。SPI本身是一个简单的同步串行协议但MCSPI控制器把它变得异常强大和复杂——它支持多通道独立或并发操作每种传输模式全双工、只发、只收配合中断或DMA再叠加上FIFO缓冲衍生出十几种不同的配置流程。手册里的表格和流程图虽然准确但更像是一张张“处方”告诉你步骤ABCD却很少解释“为什么是A而不是B”。我的目标就是把这些“处方”背后的病理和药理讲清楚让你不仅能照着做更能理解每一步的意图从而在遇到奇葩外设或苛刻时序要求时能自己开出正确的“药方”。2. MCSPI传输序列的核心逻辑与设计思路在深入代码之前我们必须先建立几个核心认知这是理解后续所有操作的基础。2.1 MCSPI的三种基本角色与两种数据传输机制MCSPI模块可以在三种模式下工作控制器Controller模式、外设Peripheral模式以及一个特殊的控制器Turbo模式。在绝大多数情况下我们的MCU作为主机使用的是控制器模式。外设模式通常用于MCU之间通过SPI互联或者模拟一个SPI从设备。Turbo模式则是控制器模式的一个变种针对某些特定的高吞吐量场景进行了优化。无论哪种模式数据搬移的核心机制只有两种中断IRQ和直接内存访问DMA。这是所有现代MCU外设的通用设计哲学中断模式适合数据量小、传输不频繁的场景。每次发送缓冲空TX_EMPTY或接收缓冲满RX_FULL时产生一个中断CPU跳转到中断服务程序ISR中手动读写数据寄存器。这种方式编程直观但频繁的中断会消耗大量CPU资源。DMA模式适合大数据量、连续传输的场景。你只需要配置好源地址、目标地址和数据长度DMA控制器就会在后台自动完成数据在内存和SPI数据寄存器之间的搬运仅在传输开始和结束时通知CPU。这能极大减轻CPU负担实现高效的数据流。手册中所有复杂的序列本质上都是在描述这两种机制在不同角色、不同方向收/发下的组合拳法。2.2 理解“传输结束End of Transfer”的多样性这是最容易让人困惑的地方。一次SPI传输什么时候算结束答案并不是唯一的它取决于你的传输模式和数据量N。手册中的Table 13-36传输结束序列总结表是这个概念的核心。我们来解读一下对于全双工Transmit-and-Receive模式无论用中断还是DMA传输结束的条件都很直观——发送了N个字同时也接收了N个字。因为SPI时钟同时驱动收发收发数量必然相等。对于只发Transmit-Only模式结束条件是发送了N个字。但这里有个关键点在中断模式下你需要等待一个额外的EOT传输结束状态位变为1以确保最后一个bit已经成功移出。对于只收Receive-Only模式这是最复杂的也是Turbo模式存在的主要原因。控制器普通模式要接收N个字你必须先手动触发N次发送因为SPI时钟需要由主机发出实际只关心收到的N个字。所以DMA传输大小是N-1最后一个字通过中断处理。控制器Turbo模式针对需要连续接收的场景做了优化其DMA传输大小变为N-2内部状态机更复杂旨在减少中断次数提升连续流接收效率。外设模式作为从机时钟由外部主机提供因此只需要安静地接收N个字即可逻辑最简单。理解这张表你就掌握了MCSPI传输状态机的“宪法”。后续所有具体的步骤都是这部“宪法”在不同场景下的“实施细则”。2.3 关键软件变量与状态机手册在描述流程时引入了几个关键的“软件变量”如write_count,read_count,last_transfer等。务必注意这些变量不是MCSPI硬件寄存器而是需要你在软件中自己维护的状态标志它们的作用是让主循环Main Process和中断服务程序ISR之间能够同步知道当前传输进行到了哪一步。write_count/read_count分别记录已发送和已接收的数据字word数量。last_transfer一个标志用于告诉主循环“最后一个数据字已经提交给硬件可以等待最终结束了”。channel_enable主要用于Turbo接收模式控制内部状态。last_request在无FIFO的只收模式中标记最后一次接收请求。你的软件状态机必须紧密配合硬件状态机。硬件通过状态位如TXx_EMPTY,RXx_FULL,EOT告诉你“现在可以做什么”而你的软件变量则记录“我们已经做了什么”。两者结合才能准确无误地走完整个传输流程。3. 无FIFO模式下的传输序列详解与实操我们先从最简单的无FIFO模式开始。在这种模式下SPI的发送TX和接收RX缓冲区通常只有一个字word的深度。这意味着数据必须“即产即销”对时序的要求非常严格。3.1 通用传输序列框架无论何种具体模式一个完整的传输都遵循一个通用框架如手册Table 13-35所示。我们可以将其转化为更易懂的伪代码和操作意图// 伪代码通用传输主流程 1. 清除通道中断状态 (MCSPI_IRQSTATUS[channel i bits] 0b1111) - **意图**避免残留的中断标志误触发。 2. 使能所需中断 (MCSPI_IRQENABLE 0x...) - **意图**告诉MCSPI当TX空或RX满时请通知我CPU或DMA。 3. 配置通道参数 (MCSPI_CHCONF_x 0x...) - **意图**设置时钟极性/相位(CPOL/CPHA)、波特率、字长、模式控制器/外设等。这是SPI通信的基石配置错误会导致数据错位。 4. 启动通道 (MCSPI_CHCTRL_x[0] EN 1) - **意图**拉高片选信号对于控制器模式并启动SPI时钟引擎。 5. 等待第一次写请求TX空或DMA写请求 - **意图**SPI引擎就绪可以接收第一个要发送的数据了。对于只收模式这一步也需要写入一个“哑元”dummy数据来产生时钟。 6. 向发送寄存器写入数据 (MCSPI_TX_x data) - **意图**将数据压入发送移位寄存器。 7. 等待传输结束的主机事件 - **意图**这是核心循环根据不同的模式全双工、只发、只收和机制中断、DMA等待write_count/read_count达到N或last_transfer等标志置位。这个“等待”在非阻塞程序中可能是查询状态位在阻塞程序中可能是等待信号量。 8. 停止通道 (MCSPI_CHCTRL_x[0] EN 0) - **意图**结束输拉低片选信号。3.2 全双工Transmit-and-Receive中断模式实操解析假设我们要用通道0以中断方式收发100个字的数据。以下是基于手册Table 13-37和13-38的详细实现和注释// 全局状态变量软件维护 volatile uint32_t g_write_count 0; volatile uint32_t g_read_count 0; uint32_t g_tx_buffer[100]; uint32_t g_rx_buffer[100]; // 主函数中的传输启动流程 void SPI_StartTransceive(void) { // 1. 清除通道0的中断状态位 HW_WR_REG32(MCSPI_BASE MCSPI_IRQSTATUS, (1 0)); // 假设bit0对应通道0 // 2. 使能通道0的TX空和RX满中断 HW_WR_REG32(MCSPI_BASE MCSPI_IRQENABLE, (1 8) | (1 2)); // 假设TX0_EMPTY_EN在bit8, RX0_FULL_EN在bit2 // 3. 配置通道0主模式CPOL0, CPHA0, 16位数据使能中断 uint32_t chconf 0; chconf | (0x0 12); // 选择SPIEN线主模式 chconf | (0x0 8); // CLKG位时钟格式 chconf | (0x1 7); // 使能中断请求 // ... 其他配置位 HW_WR_REG32(MCSPI_BASE MCSPI_CHCONF0, chconf); // 4. 启动通道0 HW_WR_REG32(MCSPI_BASE MCSPI_CHCTRL0, 0x1); // EN位写1 // 5. 主循环等待传输完成 while(!(g_write_count 100 g_read_count 100)) { // 这里可以执行其他低优先级任务或者使用RTOS的等待事件/信号量 // 实际项目中强烈建议使用信号量而非忙等待 } // 8. 传输完成停止通道 HW_WR_REG32(MCSPI_BASE MCSPI_CHCTRL0, 0x0); // EN位写0 // 可选关闭中断使能 HW_WR_REG32(MCSPI_BASE MCSPI_IRQENABLE, 0x0); } // 中断服务程序 (ISR) void MCSPI_IRQHandler(void) { // 读取中断状态判断中断源 uint32_t irq_status HW_RD_REG32(MCSPI_BASE MCSPI_IRQSTATUS); // 处理通道0中断 if (irq_status (1 0)) { // 假设bit0是通道0中断标志 // 清除通道0的状态位非常重要 HW_WR_REG32(MCSPI_BASE MCSPI_IRQSTATUS, (1 0)); uint32_t ch_stat HW_RD_REG32(MCSPI_BASE MCSPI_CHSTAT0); // 读取通道状态 // 情况A发送缓冲区空且还有数据要发 if ((ch_stat (1 8)) (g_write_count 100)) { // 假设TX_EMPTY在bit8 HW_WR_REG32(MCSPI_BASE MCSPI_TX0, g_tx_buffer[g_write_count]); g_write_count; } // 情况B接收缓冲区满且还有数据要收 if ((ch_stat (1 2)) (g_read_count 100)) { // 假设RX_FULL在bit2 g_rx_buffer[g_read_count] HW_RD_REG32(MCSPI_BASE MCSPI_RX0); g_read_count; } } }关键实操心得中断清除顺序一定要先读取MCSPI_IRQSTATUS再写入特定位来清除。有些MCU架构要求通过“写1清0”有些则要求“读后自动清除”或“写入特定值清除”。务必查阅数据手册的精确描述错误的清除方式可能导致中断丢失或死锁。状态判断在ISR中不能只依赖全局中断状态寄存器MCSPI_IRQSTATUS来判断是TX空还是RX满必须结合通道状态寄存器MCSPI_CHSTATx的TXx_EMPTY和RXx_FULL位。因为一个通道中断可能由多种事件触发。变量原子性g_write_count和g_read_count在ISR和主循环中被并发访问必须声明为volatile并且在32位以上架构中如果主循环进行非原子性的读取比较如g_write_count 100可能存在风险。在临界代码段可以考虑使用简单的关中断或原子操作来保护。3.3 只收Receive-Only模式与DMA配置的陷阱只收模式是配置的“重灾区”。我们以控制器普通模式、DMA读取为例对应手册Table 13-45和13-46。关键点在于要接收N个字DMA只需要配置搬运N-1个字。为什么因为SPI是同步接口。作为主机每产生一个时钟周期既会发送一位数据也会接收一位数据。因此要接收数据你必须同时发送数据通常是无效的dummy数据如0x00或0xFF。DMA可以自动完成接收数据的搬运但产生发送时钟的“触发源”需要被管理。流程精讲初始化write_count 0,read_count 0,last_transfer FALSE。启动通道这会拉高片选并开始产生SPI时钟吗不此时时钟是停滞的因为发送缓冲区是空的没有数据可以移出。关键的第一步你需要手动写入第一个数据到MCSPI_TXx寄存器。这个写入动作才真正触发了第一个SPI时钟周期并开始接收第一个数据字。这个操作通常在主程序中在启动通道后立即进行。DMA的工作你配置DMA从MCSPI_RXx寄存器自动搬运数据到内存。当DMA完成了N-1次搬运后即read_count N-1它会停止请求。最后的收尾此时第N个数据字已经通过SPI线接收并停留在MCSPI_RXx寄存器中等待读取。RXx_FULL中断会产生。在对应的ISR里你检查到read_count N-1便设置last_transfer TRUE。主程序检测到这个标志后手动读取最后一次MCSPI_RXx然后停止通道。// 伪代码片段只收DMA模式的核心逻辑 // 主程序 start_channel(); write_dummy_data_to_TX(); // 手动写入第一个dummy数据启动时钟 wait_until(read_count N-1); // 等待DMA完成N-1次搬运 disable_DMA_read_request(); // 关闭DMA请求防止后续干扰 wait_until(last_transfer TRUE); // 等待ISR发出最后一个字就绪的信号 stop_channel(); read_final_data_from_RX(); // 手动读取第N个字 // ISR if (RXx_FULL interrupt) { if (read_count N-1) { // DMA已经搬完了N-1个这是最后一个字 last_transfer TRUE; // 注意这里并不读取寄存器最后一个字由主程序读取。 } else { // 正常情况下DMA会处理ISR不应该进入这个分支。如果进入说明DMA可能未正确配置。 } }避坑指南DMA大小配置这是最容易出错的地方。对于“控制器普通只收模式”DMA传输大小Transfer Size必须设置为N-1而不是N。如果设成NDMA会尝试搬运第N个数据但此时可能因为时序问题导致数据不完整或冲突。第一个时钟的触发务必记住在只收模式下启动通道(EN1)并不会自动开始产生时钟。你必须先写入一个数据到TX寄存器才能“撬动”整个传输流程。这个数据的内容取决于你的从设备通常是无意义的dummy数据。Turbo模式的区别当需要高速连续接收时使用Turbo模式。它的DMA大小是N-2内部有一个更复杂的双缓冲区机制来平滑数据流减少中断延迟的影响。除非你对吞吐量有极致要求否则先从普通模式开始调试。4. FIFO模式提升吞吐量的利器当数据量较大或频率较高时无FIFO模式下的频繁中断会成为系统瓶颈。MCSPI的FIFO模式就是为了解决这个问题而生的。它为每个通道注意多通道下同一时间只能有一个通道使用FIFO配备了缓冲区可以一次性写入或读取多个数据字从而大幅减少中断或DMA请求的次数。4.1 FIFO模式的核心配置与启用启用FIFO模式不是简单地设置一个开关它涉及几个关键寄存器MCSPI_CHCONFx[28] FFER(FIFO Enable Read)使能接收FIFO。MCSPI_CHCONFx[27] FFEW(FIFO Enable Write)使能发送FIFO。MCSPI_XFERLEVEL这是FIFO模式的“大脑”。它不单独属于某个通道而是全局设置FIFO的触发水位线Watermark。你需要配置AEL接收FIFO的“几乎空”级别。当RX FIFO中的数据量小于或等于此值时可能触发中断或DMA请求取决于配置。AFL发送FIFO的“几乎满”级别。当TX FIFO中剩余空间小于或等于此值时可能触发中断或DMA请求。配置流程// 1. 检查并确保其他通道的FIFO未被启用重要 // 2. 配置传输水位线。例如设置当TX FIFO至少有4个空位时触发写请求当RX FIFO至少有4个数据时触发读请求。 HW_WR_REG32(MCSPI_BASE MCSPI_XFERLEVEL, (3 8) | (3 0)); // AFL3, AEL3 (级别是0-indexed3代表第4个元素) // 3. 配置通道控制寄存器使能发送和/或接收FIFO uint32_t chconf HW_RD_REG32(MCSPI_BASE MCSPI_CHCONF0); chconf | (1 28); // 使能接收FIFO (FFER) chconf | (1 27); // 使能发送FIFO (FFEW) HW_WR_REG32(MCSPI_BASE MCSPI_CHCONF0, chconf);4.2 带字计数Word Count与不带字计数的流程差异FIFO模式的传输序列分为两大类带字计数和不带字计数。这是理解FIFO流程图Figure 13-42 至 13-46的关键。带字计数Word Count你确切地知道要传输多少个数据字N。流程相对规整。传输的结束通常由EOWEnd of Word Count中断来标志。当发送或接收的字数达到预设值时硬件产生EOW中断通知主机传输完成。不带字计数Word Count用于流式传输数据长度未知或连续。传输的结束需要由主机主动判断。通常的判断逻辑是发送FIFO为空TXFFE且传输结束EOT状态有效同时结合last_request等软件标志。以FIFO模式全双工带字计数Figure 13-42为例其软件逻辑如下主程序启动通道。等待EOW中断。在EOW中断服务程序中检查read_count是否为0。如果不为0说明RX FIFO中还有数据需要全部读出。在FIFO请求例程被中断或DMA触发中如果TXx_EMPTYTX FIFO有空位则判断剩余待发送数据量write_count。如果剩余数据量大于等于一次FIFO请求的大小write_request_size则一次性写入write_request_size个字到TX FIFO。如果剩余数据量不足一次FIFO请求则只写入最后last_write_request_size个字。对接收侧同理当RXx_FULLRX FIFO有数据时根据read_count判断是批量读取还是读取最后一部分数据。write_request_size和read_request_size是什么它们不是直接配置的寄存器而是由你根据MCSPI_XFERLEVEL设置的水位线和FIFO深度通常是64字节在软件中计算出的一个优化值。例如如果FIFO深度是16个字AFL设为12那么write_request_size可能就是416-12意味着当TX FIFO有空位时你最好一次性写入4个字来填满它以达到最高的总线效率。4.3 FIFO模式下的DMA配置技巧在FIFO模式下使用DMA效能提升最为显著。配置时需要注意突发传输Burst将DMA的源/目标突发大小Burst Size设置为与FIFO水位线相匹配的值。例如如果write_request_size是4则将DMA的发送突发大小也设为4这样一次DMA请求就能搬移4个字与FIFO的物理特性对齐效率最高。流控制在无FIFO模式下DMA请求直接对应单个数据寄存器的空/满。在FIFO模式下DMA请求对应的是FIFO的水位线事件。你需要正确配置MCSPI_CHCONFx中的DMAWDMA写使能和DMARDMA读使能位并确保MCSPI_XFERLEVEL的水位线设置能及时触发DMA。内存对齐确保DMA搬运的内存缓冲区地址与数据宽度如32位对齐可以避免不必要的性能损失或甚至硬件错误。5. 常见问题排查与调试经验实录即使理解了所有流程实际调试MCSPI时依然会遇到各种问题。下面是我在多个项目中总结的“踩坑”记录和排查思路。5.1 问题速查表现象可能原因排查步骤与解决方案完全无数据收发1. 时钟或片选引脚未正确映射/初始化。2. 通道未启动MCSPI_CHCTRLx[0] EN不为1。3. 控制器/外设模式配置错误。1. 检查PinMux配置用示波器测量SCLK和CSn引脚是否有信号。2. 读取MCSPI_CHCTRLx寄存器确认EN位已置1。3. 确认MCSPI_CHCONFx中CLKG、CLKD等位配置与从设备期望的CPOL/CPHA一致。只能发送不能接收1. 只收模式下未写入第一个TX数据来启动时钟。2. 接收中断或DMA未使能。3. 数据位宽不匹配如配置为8位但按32位读取。1. 在只收模式启动通道后立即向MCSPI_TXx写入一个dummy值。2. 检查MCSPI_IRQENABLE中RX_FULL中断是否使能或MCSPI_CHCONFx中DMAR位是否置1。3. 核对MCSPI_CHCONFx中的WL字长设置并确保软件读写的数据类型与之匹配。数据错位如0xAA收到0x55时钟相位(CPHA)或极性(CPOL)错误。这是SPI最经典的问题。用示波器同时捕获SCLK、MOSI、MISO和CSn信号与从设备的数据手册时序图严格比对。调整MCSPI_CHCONFx的CLKG和CLKD位。通常CPHA0表示在时钟的第一个边沿采样CPHA1则表示在第二个边沿采样。DMA传输少一个或多个字DMA传输大小配置错误特别是只收模式。回顾第3.3节。对于“控制器普通只收模式”DMA传输大小应设为N-1。检查DMA配置寄存器的传输计数Transfer Size字段。FIFO模式下传输不稳定或卡死1. 多个通道同时使能了FIFO。2.MCSPI_XFERLEVEL水位线设置不合理。3. 带/不带字计数的结束判断逻辑混淆。1.确保全局只有一个通道的FFER或FFEW为1。在启用前遍历检查所有通道的MCSPI_CHCONFx寄存器。2. 水位线不宜设得过低或过高。对于发送AFL通常设为FIFO深度的一半到四分之三对于接收AEL设为四分之一到一半。先从中间值开始调试。3. 明确本次传输是否有明确长度。有则用EOW中断结束无则需轮询TXFFE和EOT位并配合软件标志。中断频繁触发CPU负载高1. 无FIFO模式下传输大量数据。2. FIFO水位线设置过高AEL太小或AFL太大。1. 对于大数据量传输务必使用DMA或FIFO模式。2. 调整MCSPI_XFERLEVEL。提高AEL值让RX FIFO攒更多数据再中断降低AFL值让TX FIFO空位更多时再中断。目标是减少中断频率但避免FIFO上溢或下溢。从设备响应慢首字节丢失片选(CSn)激活到第一个时钟沿的时间太短。检查MCSPI模块是否支持配置片选建立时间CS Setup Time和保持时间CS Hold Time。在MCSPI_CHCONFx或相关寄存器中寻找相关配置位如TCS、CSE等适当增加建立时间。5.2 调试方法与心得寄存器诊断法当通信异常时不要急于修改代码。首先编写一个简单的寄存器打印函数将MCSPI_CHCONFx、MCSPI_CHCTRLx、MCSPI_CHSTATx、MCSPI_IRQSTATUS等关键寄存器的值以十六进制打印出来。与手册复位值或你的配置预期逐位比对往往能快速发现配置错误。示波器/逻辑分析仪是终极武器软件层面的任何推测都不如用示波器看一眼波形来得直接。重点观察CSn信号是否在传输开始时拉低结束时拉高是否有毛刺SCLK时钟频率是否正确空闲电平CPOL和采样边沿CPHA是否符合预期MOSI/MISO数据数据相对时钟边沿的位置是否正确发送的数据是否与软件写入MCSPI_TXx的值一致接收的数据波形是否与从设备输出一致简化测试法如果复杂传输不通就退回到最简单的模式无FIFO、无DMA、无中断使用轮询Polling模式手册13.1.3.5.2.1.9节进行单字节收发测试。先让最基本的通信跑通然后再逐步叠加中断、DMA、FIFO等高级功能。轮询模式的代码虽然效率低但逻辑简单是验证硬件连接和基础配置的绝佳方式。利用芯片的Loopback功能大多数MCSPI控制器都支持内部回环测试模式通常在MCSPI_CHCONFx寄存器中有一个LB位。启用该模式后发送的数据会直接被内部接收无需外部硬件。这是验证控制器本身软件配置和驱动程序是否正确的最快方法。先通过内部回环测试再连接外部设备可以明确区分是软件问题还是硬件/时序问题。6. 从理论到实践一个完整的FIFODMA传输示例最后我们整合以上所有知识点勾勒一个使用FIFODMA进行全双工大数据块传输的软件框架。假设我们需要通过MCSPI通道1与一个SPI Flash芯片交互连续读写4KB数据。// 宏定义与全局变量 #define SPI_CHANNEL_1_CONF_REG (MCSPI_BASE MCSPI_CHCONF1) #define SPI_CHANNEL_1_CTRL_REG (MCSPI_BASE MCSPI_CHCTRL1) #define SPI_CHANNEL_1_STAT_REG (MCSPI_BASE MCSPI_CHSTAT1) #define SPI_CHANNEL_1_TX_REG (MCSPI_BASE MCSPI_TX1) #define SPI_CHANNEL_1_RX_REG (MCSPI_BASE MCSPI_RX1) #define FIFO_DEPTH_WORDS 16 // 假设FIFO深度为16个字64字节 #define TX_FIFO_AFULL_LEVEL 12 // TX FIFO几乎满水位线当空位4时触发 #define RX_FIFO_AEMPTY_LEVEL 4 // RX FIFO几乎空水位线当有数据4时触发 #define TRANSFER_SIZE_WORDS 1024 // 4KB / 4 bytes per word volatile bool g_spi_transfer_complete false; uint32_t g_tx_data[TRANSFER_SIZE_WORDS]; uint32_t g_rx_data[TRANSFER_SIZE_WORDS]; // DMA完成回调函数 void dma_transfer_complete_callback(void) { // 设置传输完成标志或释放信号量 g_spi_transfer_complete true; } // 主配置与传输函数 void SPI_FIFO_DMA_Transceive(void) { // --- 第1步检查并独占FIFO资源 --- // 遍历所有其他通道确保其MCSPI_CHCONFx[28]和[27]位为0 // ... // --- 第2步配置FIFO全局水位线 --- // AFL TX_FIFO_AFULL_LEVEL, AEL RX_FIFO_AEMPTY_LEVEL uint32_t xfer_level (TX_FIFO_AFULL_LEVEL 8) | (RX_FIFO_AEMPTY_LEVEL 0); HW_WR_REG32(MCSPI_BASE MCSPI_XFERLEVEL, xfer_level); // --- 第3步配置MCSPI通道1使能FIFO --- uint32_t chconf 0; // ... 配置时钟、模式、字长等 ... chconf | (1 28); // 使能接收FIFO (FFER) chconf | (1 27); // 使能发送FIFO (FFEW) chconf | (1 15); // 使能DMA读请求 (DMAR) chconf | (1 14); // 使能DMA写请求 (DMAW) HW_WR_REG32(SPI_CHANNEL_1_CONF_REG, chconf); // --- 第4步配置DMA控制器 --- // 配置DMA通道0用于发送内存 - MCSPI_TX1 // 源地址: g_tx_data // 目标地址: SPI_CHANNEL_1_TX_REG // 传输大小: TRANSFER_SIZE_WORDS // 源突发大小: (FIFO_DEPTH_WORDS - TX_FIFO_AFULL_LEVEL) 4 // 目标突发大小: 同上 // 使能传输完成中断回调函数设为dma_transfer_complete_callback // 配置DMA通道1用于接收MCSPI_RX1 - 内存 // 源地址: SPI_CHANNEL_1_RX_REG // 目标地址: g_rx_data // 传输大小: TRANSFER_SIZE_WORDS // 突发大小: RX_FIFO_AEMPTY_LEVEL 4 // 注意全双工FIFO模式DMA大小直接设为N无需N-1。 // ... // --- 第5步启动传输 --- // 清除中断状态可选DMA模式下可能不依赖CPU中断 HW_WR_REG32(MCSPI_BASE MCSPI_IRQSTATUS, (1 1)); // 清除通道1状态 // 启动DMA通道先启动接收DMA再启动发送DMA顺序可能影响初始触发 // ... // 最后启动MCSPI通道 HW_WR_REG32(SPI_CHANNEL_1_CTRL_REG, 0x1); // --- 第6步等待传输完成 --- while(!g_spi_transfer_complete) { // 进入低功耗模式或执行其他任务 __WFI(); // 等待中断 } // --- 第7步清理 --- HW_WR_REG32(SPI_CHANNEL_1_CTRL_REG, 0x0); // 停止通道 // 关闭DMA清除FIFO使能位等 // ... } // 中断服务程序如果需要处理非DMA错误 void MCSPI_IRQHandler(void) { uint32_t irq_status HW_RD_REG32(MCSPI_BASE MCSPI_IRQSTATUS); // 处理错误中断如溢出错误、超时错误等 if (irq_status (1 1)) { // 通道1中断 uint32_t ch_stat HW_RD_REG32(SPI_CHANNEL_1_STAT_REG); if (ch_stat (1 4)) { // 假设bit4是接收溢出错误位 // 处理错误记录日志重置FIFO可能需要重新初始化通道 // HW_WR_REG32(SPI_CHANNEL_1_CONF_REG, 0x0); // 先禁用 // ... 清理FIFO ... // HW_WR_REG32(SPI_CHANNEL_1_CONF_REG, original_conf); // 重新配置 } HW_WR_REG32(MCSPI_BASE MCSPI_IRQSTATUS, (1 1)); // 清除中断 } }这个示例展示了将FIFO与DMA结合的高效编程模型。核心思想是让硬件MCSPIFIFODMA自动管理数据流CPU仅负责初始化和最终处理。通过精心设置FIFO水位线可以确保DMA请求在数据量“恰到好处”时触发从而实现总线带宽的饱和利用同时将CPU干预降到最低。调试这样的系统时建议先屏蔽DMA在FIFO中断模式下验证数据流是否正确然后再启用DMA。同时密切关注DMA和MCSPI的错误中断标志位它们是你发现潜在数据溢出或配置不当的第一线索。