1. 项目概述与核心价值
在嵌入式开发领域,尤其是涉及高速传感器数据采集、实时显示驱动或与复杂外设通信的场景,如何高效、可靠地处理串行外设接口的数据流,是每个工程师都会面临的挑战。传统的轮询方式不仅占用大量CPU时间,在应对突发、高速数据流时也显得力不从心,容易导致数据丢失或系统响应延迟。这时,中断与直接内存访问技术的结合,就成为了提升系统性能和实时性的关键。
以德州仪器的MSPM0系列微控制器为例,其SPI模块提供了一套高度灵活且强大的事件管理机制。这套机制的核心思想,是将SPI通信过程中的各种状态(如FIFO半满、传输完成、接收超时等)抽象为“事件”。这些事件既可以作为CPU中断的源头,唤醒CPU进行即时处理;也可以作为DMA触发信号,自动发起一次内存与外设间的数据传输,完全解放CPU。
我接手过不少项目,从简单的温湿度传感器读取到复杂的TFT屏刷屏,但凡涉及到连续、批量数据的SPI传输,最终都绕不开对中断和DMA的精细配置。很多新手工程师面对手册里密密麻麻的寄存器描述,常常感到无从下手,配置出来的系统要么中断响应不及时,要么DMA传输不触发,调试过程苦不堪言。这篇文章,我就以MSPM0的SPI模块为蓝本,结合我踩过的坑和总结的经验,为你彻底拆解SPI中断与DMA触发机制的设计思路、寄存器配置细节以及实战中的避坑指南。无论你是刚接触MSPM0,还是希望优化现有SPI驱动,相信都能从中找到清晰的路径。
2. SPI事件系统架构深度解析
MSPM0的SPI模块将中断与DMA触发统一在“事件发布者”的框架下,这种设计非常清晰。理解这个架构,是进行正确配置的第一步。
2.1 事件发布者模型:CPU_INT 与 DMA_TRIG
根据技术手册中的Table 23-2,SPI模块内部有三个独立的事件发布者:
- CPU_INT: 负责向CPU子系统发布中断事件。这是一个静态路由,意味着中断信号到CPU的路径是固定的。
- DMA_TRIG_RX: 负责在接收事件发生时,向DMA控制器发布触发信号,用于启动一次DMA读取(从SPI RXDATA寄存器到内存)。
- DMA_TRIG_TX: 负责在发送事件发生时,向DMA控制器发布触发信号,用于启动一次DMA写入(从内存到SPI TXDATA寄存器)。
你可以把这套系统想象成一个拥有多个出口的水管系统。SPI模块内部产生的水流(各种事件)可以流向不同的目的地:流向CPU出口(触发中断服务程序),或者流向DMA泵站(触发自动传输)。CPU_INT、DMA_TRIG_RX、DMA_TRIG_TX就是这三个出口的阀门控制器。
关键点在于:每个“阀门控制器”管理的事件源是独立的、可配置的。并不是所有事件都能触发DMA,也并不是所有事件都会产生CPU中断。这给了我们极大的灵活性。
2.2 核心事件源详解
手册中的Table 23-3、23-4、23-5详细列出了每个事件发布者所能关联的事件源。理解每个事件的含义是精准配置的基础。
CPU中断事件源 (CPU_INT):
- RXFIFO_OVF (0x01):接收FIFO溢出。这是严重的错误事件,意味着数据接收过快,CPU或DMA来不及从FIFO中取走数据,导致新数据被覆盖。必须使能此中断并进行处理(如复位FIFO、上报错误)。
- PER (0x02):奇偶校验错误。当使能了奇偶校验功能(
CTL1.PREN=1)且接收到的数据校验失败时触发。用于保证通信可靠性。 - RTOUT (0x03):接收超时。仅在从机模式下有效。当片选信号有效后,超过
CTL1.RXTIMEOUT个功能时钟周期仍未收到数据时触发。用于检测主机通信异常或从机等待超时。 - RX (0x04):接收FIFO达到预设水位。这是最常用的接收事件。水位通过
IFLS.RXIFLSEL寄存器配置(如1/2满、1/4满)。当FIFO中数据量达到或超过该水位时触发。通常用于通知CPU或触发DMA来批量读取数据。 - TX (0x05):发送FIFO达到预设水位。当发送FIFO中的数据量低于(即空闲空间达到)预设水位时触发。水位通过
IFLS.TXIFLSEL配置(如1/2空、1/4空)。用于在FIFO有空闲时,通知CPU或触发DMA来填充新的待发送数据。 - TXEMPTY (0x06):发送FIFO完全空。当所有数据都已从FIFO移出到移位寄存器并开始发送后触发。这个事件非常有用,它可以精确指示一次数据块(如一帧图像数据)的发送完成,而不是单个字的发送完成。
- IDLE (0x07):SPI总线空闲。当一次传输结束,
STAT.BUSY位由高变低时触发。可以用于判断一次通信事务的结束。 - DMA_DONE1_RX/TX (0x08, 0x09):DMA传输完成。当关联的DMA通道完成一次传输并发出DONE信号时触发。这允许你在DMA搬运完数据后,在SPI模块内部产生一个中断,进行后续处理(如关闭片选、启动下一组数据准备),实现了DMA与中断的协同。
DMA触发事件源:
- 对于DMA_TRIG_RX: 可配置的事件源只有
RTOUT(接收超时)和RX(接收FIFO水位)。这意味着DMA读取操作通常由“有数据可读”或“等待超时”来触发。 - 对于DMA_TRIG_TX: 可配置的事件源只有
TX(发送FIFO水位)。这意味着DMA写入操作由“发送FIFO有空闲位置”来触发。
重要经验: 仔细对比
CPU_INT和DMA_TRIG的事件源列表,你会发现TXEMPTY和IDLE事件不能直接触发DMA。这是因为DMA的触发需要的是一个“请求动作”的信号(如:需要数据/可以取数据),而TXEMPTY和IDLE更多是“状态报告”信号。TXEMPTY中断通常用于在DMA传输完成后,进行最终的收尾工作。
2.3 中断优先级与索引寄存器
IIDX寄存器是理解中断处理的关键。它是一个只读寄存器,硬件会自动将当前已使能且处于挂起状态的最高优先级中断的索引号写入其中。
- 工作原理: 当多个中断事件同时发生时,硬件根据固定的优先级(
Table 23-3中IIDX值越小优先级越高)将最高优先级的中断索引号放入IIDX.STAT。当你读取这个寄存器时,硬件会自动清除该中断在RIS和MIS中的标志位,并立即更新IIDX为下一个最高优先级的挂起中断索引。如果已无挂起中断,则显示0x00。 - 使用模式:
- 查询模式: 在中断服务程序中,可以循环读取
IIDX,直到其值为0x00,从而在一次中断响应中处理所有挂起的、已使能的中断事件。这比逐个查询RIS或MIS的各个位效率更高。 - 自定义优先级: 虽然硬件优先级固定,但你可以通过
IMASK寄存器屏蔽掉某些中断,或者通过软件在中断服务程序中先检查RIS寄存器(它包含所有未屏蔽的原始中断状态),来实现自己的优先级逻辑。
- 查询模式: 在中断服务程序中,可以循环读取
3. 寄存器配置实战与核心细节
理解了架构和事件源,接下来就是动手配置。这里我会结合代码片段和配置步骤,详细说明关键寄存器的作用。
3.1 基础配置流程
在配置中断和DMA之前,必须完成SPI模块的基础初始化,包括时钟、引脚、模式、波特率等。这里假设你已经完成了这些步骤,我们聚焦于事件相关的寄存器。
一个典型的启用中断和DMA触发的配置流程如下:
配置FIFO中断水位 (
IFLS寄存器): 这是决定中断或DMA触发频率的关键。水位设得太高,可能导致FIFO溢出或响应延迟;设得太低���则中断/触发过于频繁,增加系统开销。// 示例:设置接收FIFO在达到1/2满时触发事件,发送FIFO在达到1/2空时触发事件。 SPI0->IFLS = (2 << SPI_IFLS_RXIFLSEL_OFS) | (2 << SPI_IFLS_TXIFLSEL_OFS); // RXIFLSEL = 2 (1/2 full), TXIFLSEL = 2 (1/2 empty)水位选择建议:
- 高吞吐量、大块数据: 使用
1/2或3/4。减少触发次数,让CPU/DMA每次处理更多数据,效率更高。 - 低延迟、实时性要求高: 使用
1/4。数据一到就立刻处理,减少在FIFO中的等待时间。 - 单次传输或精确控制: 可以考虑使用
>=1 frame(RXIFLSEL=7)或>=1 frame free(TXIFLSEL=7),这对于非标准数据帧长度或需要严格帧同步的应用有用。
- 高吞吐量、大块数据: 使用
使能所需的事件源 (
IMASK寄存器组): 你需要分别在三个事件发布者的IMASK寄存器中,使能(置1)你关心的事件位。// 1. 配置CPU中断事件使能:例如,使能接收FIFO事件、发送完成事件和溢出错误事件 SPI0->CPU_INT.IMASK = SPI_IMASK_RX_MASK | SPI_IMASK_TXEMPTY_MASK | SPI_IMASK_RXFIFO_OVF_MASK; // 2. 配置DMA接收触发事件使能:例如,使能接收FIFO事件作为DMA触发源 SPI0->DMA_TRIG_RX.IMASK = SPI_DMA_TRIG_RX_IMASK_RX_MASK; // 3. 配置DMA发送触发事件使能:例如,使能发送FIFO事件作为DMA触发源 SPI0->DMA_TRIG_TX.IMASK = SPI_DMA_TRIG_TX_IMASK_TX_MASK;注意:
DMA_TRIG_RX和DMA_TRIG_TX的IMASK寄存器位宽更少,只包含它们支持的事件(RX和RTOUT,或TX)。配置事件线模式 (
EVT_MODE寄存器): 这个寄存器决定了事件标志(RIS)的清除方式。// INT0_CFG 对应 CPU_INT, INT1_CFG 对应 DMA_TRIG_RX, INT2_CFG 对应 DMA_TRIG_TX // 2 = Hardware mode (硬件自动清除RIS), 1 = Software mode (软件需手动清除RIS) SPI0->EVT_MODE = (2 << SPI_EVT_MODE_INT2_CFG_OFS) | (2 << SPI_EVT_MODE_INT1_CFG_OFS) | (1 << SPI_EVT_MODE_INT0_CFG_OFS);- 硬件模式: 当事件用于触发DMA时,强烈建议设置为硬件模式。这样,当DMA控制器应答了触发信号并启动传输后,硬件会自动清除对应的
RIS标志,无需软件干预,效率最高且不易出错。 - 软件模式: 对于CPU中断,通常设置为软件模式。在中断服务程序中,你需要通过读取
IIDX或向ICLR寄存器相应位写1来清除中断标志,这给了你更灵活的控制权(例如,可以在处理完任务后再清除标志)。
- 硬件模式: 当事件用于触发DMA时,强烈建议设置为硬件模式。这样,当DMA控制器应答了触发信号并启动传输后,硬件会自动清除对应的
配置NVIC(嵌套向量中断控制器): 使能SPI模块对应的全局中断线。这一步是在微控制器的系统层级完成的,与具体外设寄存器无关。
// 假设SPI0的中断号为`SPI0_IRQn` NVIC_EnableIRQ(SPI0_IRQn); NVIC_SetPriority(SPI0_IRQn, 2); // 设置一个合适的中断优先级配置DMA控制器: 这超出了SPI模块本身,但至关重要。你需要配置DMA通道:
- 源/目标地址: 对于RX,源地址是
SPI0->RXDATA,目标地址是你的内存缓冲区。对于TX则相反。 - 传输宽度: 与SPI数据位宽匹配(如8位、16位)。
- 触发源: 选择对应的SPI事件作为DMA硬件请求源(例如,SPI0_RX_DMA_REQ, SPI0_TX_DMA_REQ)。
- 传输数量: 设置你需要传输的数据量。
- 源/目标地址: 对于RX,源地址是
最后,使能SPI模块:
SPI0->CTL1 |= SPI_CTL1_ENABLE_MASK;
3.2 关键寄存器功能对照表
为了更清晰地理解中断/DMA管理相关的寄存器组,我整理了以下表格:
| 寄存器组 (偏移量) | 寄存器名称 | 主要功能 | 所属事件发布者 | 备注 |
|---|---|---|---|---|
| 0x1020 - 0x1048 | IIDX, IMASK, RIS, MIS, ISET, ICLR | CPU中断管理。包含所有9个中断源的索引、使能、状态、设置和清除。 | CPU_INT | 功能最全,管理所有类型的中断。 |
| 0x1050 - 0x1078 | IIDX, IMASK, RIS, MIS, ISET, ICLR | DMA接收触发管理。仅管理RX和RTOUT两个事件源。 | DMA_TRIG_RX | 用于触发DMA从SPI读取数据。 |
| 0x1080 - 0x10A8 | IIDX, IMASK, RIS, MIS, ISET, ICLR | DMA发送触发管理。仅管理TX一个事件源。 | DMA_TRIG_TX | 用于触发DMA向SPI写入数据。 |
| 0x10E0 | EVT_MODE | 事件线模式选择。决定RIS标志是硬件自动清除还是软件手动清除。 | 全局 | 必须为DMA触发通道选择硬件模式(2)。 |
| 0x10E4 | INTCTL | 中断控制。仅INTEVAL位有效,写1可强制重新评估中断源。 | 全局 | 用于特殊调试或软件触发场景。 |
| 0x110C | IFLS | 中断FIFO水位选择。设置RX和TX事件触发的FIFO填充/空闲阈值。 | 全局 | 直接影响中断/DMA触发频率。 |
| 0x1110 | STAT | 状态寄存器。查看BUSY、FIFO空/满状态,不直接用于中断管理。 | 全局 | 用于查询式编程或辅助判断状态。 |
3.3 调试模式行为控制 (PDBGCTL)
在开发阶段,调试器暂停CPU时,外设的行为至关重要。PDBGCTL寄存器控制着SPI模块在调试模式下的行为。
FREE=1: 模块无视CPU暂停,继续运行。这在调试实时通信时非常危险,可能导致数据丢失或状态混乱。FREE=0, SOFT=0: 模块立即停止。可能中断正在进行的传输,导致数据损坏。FREE=0, SOFT=1:(推荐设置)模块在完成当前传输后停止。这是最安全的方式,保证了数据帧的完整性,便于你检查传输过程中的状态。
在初始化时,可以根据需要配置:
// 设置为“完成当前传输后停止”,便于安全调试 SPI0->PDBGCTL = SPI_PDBGCTL_SOFT_MASK; // FREE位默认为1,如果希望调试时停止,需将其清零 // SPI0->PDBGCTL = SPI_PDBGCTL_SOFT_MASK; // FREE=0, SOFT=14. 典型应用场景与配置实例
理论说再多,不如看几个实际例子。下面我以两个常见场景为例,展示具体的配置思路和代码片段。
4.1 场景一:DMA双缓冲连续接收传感器数据
需求: 从一个SPI接口的加速度传感器连续读取数据,采样率1kHz,数据为16位。要求CPU干预最少,数据不能丢失。
设计思路:
- 使用DMA进行数据搬运,采用**双缓冲(Ping-Pong Buffer)**机制。
- DMA接收通道配置为循环模式,由SPI的
RX事件(例如FIFO半满)触发。 - 使能DMA的
DMA_DONE1_RX中断。当DMA填满一个缓冲区时,产生中断,CPU在中断服务程序中处理已满的缓冲区,并切换DMA目标地址到另一个缓冲区。
关键配置步骤:
// 1. 定义双缓冲区 #define BUFFER_SIZE 128 uint16_t rx_buffer_a[BUFFER_SIZE]; uint16_t rx_buffer_b[BUFFER_SIZE]; volatile uint16_t *current_buffer_for_cpu = rx_buffer_a; // CPU当前处理的缓冲区 volatile bool buffer_a_ready = false; // 2. 配置SPI FIFO水位和DMA触发 SPI0->IFLS = (2 << SPI_IFLS_RXIFLSEL_OFS); // RX FIFO达到1/2满时触发 SPI0->DMA_TRIG_RX.IMASK = SPI_DMA_TRIG_RX_IMASK_RX_MASK; // 使能RX事件触发DMA SPI0->EVT_MODE = (2 << SPI_EVT_MODE_INT1_CFG_OFS); // DMA_RX触发设为硬件自动清除 // 3. 配置CPU中断(用于DMA完成) SPI0->CPU_INT.IMASK = SPI_IMASK_DMA_DONE1_RX_MASK; // 只使能DMA接收完成中断 SPI0->EVT_MODE |= (1 << SPI_EVT_MODE_INT0_CFG_OFS); // CPU中��设为软件清除 // 4. 配置DMA通道(以MSPM0的DMA为例,API可能不同) // 配置为:源地址 = SPI0->RXDATA,目标地址 = rx_buffer_a,传输宽度=16位 // 触发源 = SPI0 RX事件,传输模式 = 循环模式,传输数量 = BUFFER_SIZE // 使能DMA完成中断(这会链��到SPI的DMA_DONE1_RX事件) DMA_configureChannel(DMA_CH0, ...); DMA_setTransferCount(DMA_CH0, BUFFER_SIZE); DMA_enableInterrupt(DMA_CH0, DMA_INT_COMPLETE); // 使能传输完成中断 // 5. 启动DMA和SPI DMA_startChannel(DMA_CH0); SPI0->CTL1 |= SPI_CTL1_ENABLE_MASK; // 6. CPU中断服务程序 void SPI0_IRQHandler(void) { uint32_t idx = SPI0->CPU_INT.IIDX; // 读取索引,硬件会自动清除最高优先级中断标志 while(idx != 0) { switch(idx) { case 0x08: // DMA_DONE1_RX // DMA已经填满了一个缓冲区(比如buffer_a) buffer_a_ready = true; // 通知主循环 // 在更复杂的系统中,这里可以立刻切换DMA目标到buffer_b // 但注意,MSPM0的DMA循环模式可能自动处理了双缓冲,需查阅DMA手册确认 // 清除中断标志(如果EVT_MODE设为软件模式) // SPI0->CPU_INT.ICLR = SPI_ICLR_DMA_DONE1_RX_MASK; break; // ... 处理其他中断 } idx = SPI0->CPU_INT.IIDX; // 读取下一个挂起的中断 } }4.2 场景二:中断驱动的不定长命令-响应通信
需求: 作为SPI主机,向从设备发送一个命令字节,然后接收一个不定长的响应数据包。从设备在响应结束后会拉高一个GPIO作为“数据就绪”信号。
设计思路:
- 使用CPU中断处理,因为数据包长度不定,且结束信号是GPIO。
- 发送阶段:使用查询或中断方式发送命令字节。
- 接收阶段:使能
RX中断(水位设为1/4满以降低延迟),在中断服务程序中读取RXDATA。 - 结束判断:在中断服务程序中,检查GPIO状态。如果GPIO变高,则关闭
RX中断,表示一包数据接收完成。
关键配置步骤:
volatile uint8_t rx_data[256]; volatile uint16_t rx_index = 0; volatile bool packet_complete = false; void SPI_InitForCommandResponse(void) { // ... 基础SPI配置(主机模式,波特率等) // 配置FIFO水位:较低水位以快速响应 SPI0->IFLS = (1 << SPI_IFLS_RXIFLSEL_OFS); // RX FIFO >= 1/4满即触发 // 配置CPU中断:使能接收和错误中断 SPI0->CPU_INT.IMASK = SPI_IMASK_RX_MASK | SPI_IMASK_RXFIFO_OVF_MASK | SPI_IMASK_PER_MASK; SPI0->EVT_MODE = (1 << SPI_EVT_MODE_INT0_CFG_OFS); // 软件模式 // 配置GPIO中断,用于检测从设备的“数据就绪”信号(假设在P0.5) GPIO_setAsInputPinWithPullDownResistor(GPIO_PORT_P0, GPIO_PIN5); GPIO_enableInterrupt(GPIO_PORT_P0, GPIO_PIN5); GPIO_interruptEdgeSelect(GPIO_PORT_P0, GPIO_PIN5, GPIO_HIGH_TO_LOW_TRANSITION); // 根据实际信号调整 NVIC_EnableIRQ(SPI0_IRQn); NVIC_EnableIRQ(GPIO_IRQn); } void SendCommandAndStartReceive(uint8_t cmd) { rx_index = 0; packet_complete = false; // 1. 发送命令字节(简单查询方式) while(!(SPI0->STAT & SPI_STAT_TNF_MASK)); // 等待TX FIFO非满 SPI0->TXDATA = cmd; // 2. 使能SPI接收(如果之前关闭了) // 3. 使能RX中断,开始接收数据 // (通常初始化时已使能,这里确保开启) } // SPI中断服务程序 void SPI0_IRQHandler(void) { uint32_t idx = SPI0->CPU_INT.IIDX; while(idx != 0) { switch(idx) { case 0x04: // RX 事件 // 循环读取,直到RX FIFO为空 while(!(SPI0->STAT & SPI_STAT_RFE_MASK)) { if(rx_index < sizeof(rx_data)) { rx_data[rx_index++] = (uint8_t)(SPI0->RXDATA & 0xFF); // 假设8位数据 } else { // 缓冲区溢出处理 HandleRxOverflow(); } } // 检查外部GPIO,判断是否接收完成(此检查也可放在GPIO中断中) // if(GPIO_getInputPinValue(GPIO_PORT_P0, GPIO_PIN5) == READY_SIGNAL_LEVEL) { // SPI0->CPU_INT.IMASK &= ~SPI_IMASK_RX_MASK; // 关闭RX中断 // packet_complete = true; // } break; case 0x01: // RXFIFO_OVF HandleRxFifoOverflow(); SPI0->CPU_INT.ICLR = SPI_ICLR_RXFIFO_OVF_MASK; break; case 0x02: // PER HandleParityError(); SPI0->CPU_INT.ICLR = SPI_ICLR_PER_MASK; break; } idx = SPI0->CPU_INT.IIDX; } } // GPIO中断服务程序(用于检测数据包结束) void GPIO_IRQHandler(void) { if(GPIO_getInterruptStatus(GPIO_PORT_P0, GPIO_PIN5)) { GPIO_clearInterruptFlag(GPIO_PORT_P0, GPIO_PIN5); // 收到结束信号,关闭SPI RX中断,设置完成标志 SPI0->CPU_INT.IMASK &= ~SPI_IMASK_RX_MASK; packet_complete = true; // 可以在这里读取FIFO中剩余的最后几个字节 } }5. 常见问题排查与实战心得
配置中断和DMA时,问题往往隐蔽且令人头疼。下面是我总结的一些常见“坑点”和解决方法。
5.1 DMA传输不触发或只触发一次
这是最常见的问题。
- 检查
EVT_MODE寄存器: 这是最容易被忽略的一步!用于DMA触发的事件线(INT1_CFG和INT2_CFG)必须设置为硬件模式。如果设为软件模式,RIS标志不会自动清除,DMA在第一次触发后,会因为事件标志一直存在而无法识别新的边沿触发(如果DMA配置为边沿触发),或者持续请求导致异常。 - 检查DMA通道配置: 确认DMA的触发源选择正确(例如SPI0_RX_REQ)。确认DMA传输模式是单次触发还是循环模式。如果是单次,传输完成后需要重新使能。
- 检查FIFO水位
IFLS: 如果水位设置不当(例如TX水位设为了“FIFO空”),而你的DMA传输数据量小于FIFO深度,可能无法达到触发条件。对于发送,通常设置为“1/2空”或“1/4空”;对于接收,设置为“1/2满”或“1/4满”。 - 检查SPI和DMA的使能顺序: 正确的顺序是:配置SPI(包括事件) -> 配置DMA -> 使能DMA通道 ->最后使能SPI模块。如果先使能SPI,数据可能已经开始传输并产生事件,但DMA还未就绪,导致事件丢失。
5.2 中断服务程序进入一次后不再进入
- 中断标志未清除: 如果你将
EVT_MODE对应位设为了软件模式,必须在中断服务程序中清除中断标志。可以通过读取IIDX寄存器(推荐,可处理多个挂起中断)或向ICLR寄存器对应位写1来实现。如果标志未清除,该中断将一直处于挂起状态,不会产生新的中断请求。 - NVIC中断未使能或优先级问题: 确保在系统层面通过
NVIC_EnableIRQ()使能了该外设的中断。同时,检查是否有更高优先级的中断长时间占用CPU,导致本中断无法得到响应。 - 中断事件被意外屏蔽: 检查
IMASK寄存器,确保你期望的中断源位是1(使能状态)。在调试过程中,有时会不小心修改了它。
5.3 数据错位或丢失
- 时钟相位和极性:
CTL0寄存器中的SPO和SPH位必须与从设备严格匹配。这是SPI通信的基础,一旦配错,所有数据都是错的。 - 数据大小和打包: 注意
CTL0.DSS设置的数据位宽,以及PACKEN打包使能位。当PACKEN=1时,读写RXDATA/TXDATA寄存器是32位操作,硬件会将其视为两个16位数据。如果你的数据是8位的,务必设置PACKEN=0,并进行8位访问(或16位访问但注意对齐)。 - DMA传输宽度不匹配: DMA的传输数据宽度(字节、半字、字)必须与SPI的数据位宽以及内存缓冲区对齐方式匹配。例如,SPI配置为8位数据,DMA也应配置为8位传输,内存缓冲区应为
uint8_t类型。
5.4 调试技巧
- 善��
STAT寄存器: 在调试时,实时查看STAT寄存器的BUSY、TFE、RFE、TNF、RNF位,可以清晰了解SPI和FIFO的实时状态。 - 使用
ISET寄存器进行软件测试: 你可以通过向ISET寄存器的相应位写1,来软件模拟一个中断事件。这对于测试你的中断服务程序逻辑是否正确,而无需搭建真实硬件通信环境,非常有用。 - 分步调试:
- 先调通查询模式: 不使用中断和DMA,用简单的
while循环读写TXDATA/RXDATA,确保SPI基础通信正常。 - 再测试中断模式: 使能
TXEMPTY或RX中断,在中断服务程序中设置一个标志灯,看能否正常进入。 - 最后集成DMA: 在中断工作正常的基础上,配置DMA触发。可以先配置为单次传输,在DMA完成中断里点亮LED,确保DMA能被正确触发和完成。
- 先调通查询模式: 不使用中断和DMA,用简单的
- 注意电源和时钟管理: 确保在操作SPI和DMA相关寄存器前,该外设的时钟已经使能(通过
PWREN或系统时钟配置寄存器)。在低功耗模式下,唤醒后也需要重新初始化相关配置。
配置MSPM0的SPI中断与DMA,本质上是在理解其“事件-发布者”模型的基础上,进行精准的寄存器编程。关键在于理清三条独立的事件通路,并根据应用场景选择合适的事件源、触发水位和清除模式。对于需要高效数据搬运的场景,优先使用DMA并配以硬件清除模式;对于复杂、不定长的交互协议,灵活使用CPU中断并处理好标志清除。多利用技术手册中的表格和描述,结合调试器观察寄存器值,就能逐步驯服这套强大的机制,让它为你的嵌入式应用带来显著的性能提升。