1. 项目概述与核心价值
在嵌入式开发的日常里,和外设打交道是家常便饭,而SPI(Serial Peripheral Interface)作为最常用的同步串行通信接口之一,其效率直接决定了整个系统的响应速度和吞吐量。很多工程师在初期接触SPI时,往往只关注最基本的四线连接和收发数据,却忽略了其内部一套精密的事件与中断管理机制。这套机制,恰恰是区分“能用”和“好用”的关键。
简单来说,你可以把SPI模块想象成一个忙碌的邮局。数据是信件,FIFO(先入先出队列)是分拣台,而CPU就是邮局的经理。最原始的方式是轮询:经理不停地跑到分拣台前问“有我的信吗?”,这无疑浪费了大量精力。而中断和事件机制,则是在分拣台旁装了几个铃铛——当特定事件发生时(比如“分拣台快满了”、“有一批紧急信件到了”、“分拣台空了”),铃铛响起,经理才需要过来处理。这样,经理就能把大部分时间花在更重要的事情上。
本文要深入探讨的,正是德州仪器(TI)MSPM0系列微控制器中SPI模块的这套“铃铛系统”。我们将聚焦于两个核心的“事件发布器”:CPU中断事件发布器(CPU_INT)和DMA触发发布器(DMA_TRIG_RX/TX)。通过详细拆解其背后的寄存器配置逻辑,你将能理解如何让SPI在后台安静高效地搬运数据,仅在关键时刻“打扰”CPU,或者直接指挥DMA(直接内存访问)这位“专职搬运工”来干活,从而构建出真正高效、实时的嵌入式通信系统。无论你是正在调试SPI通信不稳定性的工程师,还是希望优化系统功耗和性能的开发者,理解这些底层机制都将让你事半功倍。
2. SPI事件与中断机制的整体架构
在深入寄存器细节之前,我们必须先建立起一个清晰的顶层视图。MSPM0的SPI模块将各种状态和错误抽象为“事件”,并通过两套独立的通路进行分发:一路给CPU(引发中断),另一路给DMA控制器(引发DMA传输请求)。
2.1 事件源与发布路径
SPI模块内部监控着多种状态,这些状态就是事件的源头。根据你提供的资料,我们可以整理出主要的事件源:
| 事件源 (IIDX) | 助记符 | 描述 |
|---|---|---|
| 0x01 | RXFIFO_OVF | 接收FIFO溢出 |
| 0x02 | PER | 奇偶校验错误 |
| 0x03 | RTOUT | 外设模式下的接收超时 |
| 0x04 | RX | 接收FIFO达到预设水位 |
| 0x05 | TX | 发送FIFO达到预设水位 |
| 0x06 | TXEMPTY | 发送FIFO完全空(所有数据已移出) |
| 0x07 | IDLE | SPI进入空闲状态(STAT.BUSY变低) |
| 0x08 | DMA_DONE1_RX | RX DMA通道发送完成信号 |
| 0x09 | DMA_DONE1_TX | TX DMA通道发送完成信号 |
| 0x0A | TXFIFO_UNF | 发送FIFO下溢(尝试从空FIFO读取) |
| 0x0B | RXFULL | 接收FIFO已满 |
这些事件产生后,并不是直接冲向CPU或DMA。它们首先进入一个“原始状态寄存器”(RIS)。你可以把RIS看作一个总告示板,上面实时贴着所有发生但尚未处理的事件标签。
关键在于,SPI模块为CPU和DMA分别设立了独立的“管理处”:
- CPU_INT通路:管理所有9个(或更多)中断源,用于通知CPU进行即时处理。
- DMA_TRIG通路:专门管理用于触发DMA传输的事件,主要是
RX(接收FIFO有数据)和TX(发送FIFO有空位),以及RTOUT(接收超时,可用于DMA超时处理)。
这种分离设计非常巧妙。它允许工程师为不同目的的事件配置不同的行为。例如,RX事件既可以配置为触发CPU中断(让CPU来读取数据),也可以配置为触发DMA请求(让DMA自动将数据搬移到内存),甚至两者都不启用(纯轮询模式)。这种灵活性是高效系统设计的基础。
2.2 寄存器组概览:三层管理模型
为了实现上述功能,SPI模块为每条通路都配备了一套完整的寄存器组,形成了清晰的三层管理模型。理解这个模型是进行正确配置的前提。
第一层:状态与索引(反映事实)
- RIS (Raw Interrupt Status):原始中断状态寄存器。任何事件发生,对应的位就会置1。它不受任何屏蔽影响,是硬件状态的真实反映。即使你不关心某个事件,也可以通过查询RIS来了解发生了什么。
- IIDX (Interrupt Index):中断索引寄存器。这是一个非常实用的寄存器。当有多个中断同时挂起时,CPU读取IIDX会直接得到最高优先级的中断编号(即上面表格中的IIDX值)。更重要的是,这次读取操作会自动清除该最高优先级中断在RIS和MIS中的标志位。这简化了中断服务程序(ISR)的编写,你不需要手动去查到底是哪个中断触发的。
第二层:控制与筛选(决定通知谁)
- IMASK (Interrupt Mask):中断屏蔽寄存器。你想让哪个事件能产生有效的中断(或DMA触发),就把对应的位置1(取消屏蔽)。如果某个事件位被屏蔽(置0),即使它在RIS中置1了,也不会传递到下一层。这是你选择关心哪些事件的“过滤器”。
- MIS (Masked Interrupt Status):被屏蔽后的中断状态寄存器。它的值等于
RIS & IMASK。只有被你“放行”(IMASK=1)的事件,才会在这里显示为1。对于CPU_INT通路,MIS的状态直接决定了是否向CPU内核发出中断请求。对于DMA_TRIG通路,则决定了是否向DMA控制器发出触发信号。
第三层:软件干预(主动测试与清除)
- ISET (Interrupt Set):中断置位寄存器。通过软件向某位写1,可以模拟该事件的发生,强制让对应的RIS和MIS位置1。这在系统自检、诊断和安全验证时非常有用,可以测试中断服务程序是否能被正确触发。
- ICLR (Interrupt Clear):中断清除寄存器。在中断服务程序中,处理完一个事件后,必须清除其标志位,以防止同一中断被重复响应。通常的做法是向ICLR对应的位写1。需要注意的是,读取IIDX寄存器会自动清除最高优先级中断的标志,但其他同时发生的中断标志仍需通过ICLR手动清除。
注意:
EVT_MODE寄存器控制着每条事件通路(CPU_INT, DMA_TRIG_RX, DMA_TRIG_TX)的工作模式。例如,可以设置为“硬件自动清除”模式,这样当DMA控制器响应触发并完成一次传输后,硬件会自动清除对应的RIS标志,无需软件干预,非常适合全自动的DMA流水线操作。
3. CPU中断(CPU_INT)配置详解与实战
CPU中断是我们最熟悉的交互方式。当SPI需要CPU立即关注时(比如数据准备好了、发生错误了),就通过CPU_INT通路拉起中断线。配置好这套机制,是保证SPI通信稳定、及时响应异常的关键。
3.1 中断源优先级与处理流程
MSPM0 SPI的CPU中断源有固定的硬件优先级,顺序如下(从高到低):
- RXFIFO_OVF (0x01) - 接收溢出,最高优先级,因为这是严重错误。
- PER (0x02) - 奇偶校验错误。
- RTOUT (0x03) - 接收超时。
- RX (0x04) - 接收FIFO水平触发。
- TX (0x05) - 发送FIFO水平触发。
- TXEMPTY (0x06) - 发送FIFO空。
- IDLE (0x07) - 传输结束空闲。
- DMA_DONE1_RX (0x08) - RX DMA完成。
- DMA_DONE1_TX (0x09) - TX DMA完成。
这个优先级体现在IIDX寄存器中。当多个中断同时挂起时,CPU读取IIDX得到的是优先级最高的那个事件的索引号,并且该事件标志会被自动清除。
一个典型的中断服务程序(ISR)处理流程如下:
- 进入ISR:CPU响应中断,跳转到SPI中断向量对应的服务程序。
- 读取IIDX:获取当前最高优先级的中断原因。
IIDX_STAT字段的值直接对应上表中的事件源。 - 分支处理:根据
IIDX的值,跳转到对应的处理子程序。 - 处理事件:例如,如果是
RX事件,则从RXDATA寄存器读取数据;如果是PER错误,则进行错误恢复或记录。 - 清除标志:如果处理的事件不是通过读取
IIDX自动清除的(例如,你通过查询MIS寄存器来判断多个中断),则需要手动向ICLR寄存器的对应位写1来清除中断标志。 - 退出ISR:中断处理完成,返回主程序。
3.2 关键寄存器配置步骤与代码示例
假设我们需要配置SPI在主模式(Controller)下工作,并使用中断方式接收数据。我们希望当接收FIFO中的数据达到一半(RXIFLSEL = 2)时触发CPU中断,同时使能奇偶校验错误中断以便及时处理通信故障。
以下是基于标准外设库(如果可用)或直接寄存器操作的配置思路:
// 1. 使能SPI模块时钟和电源(假设相关函数已实现) SPI_EnablePowerAndClock(SPI0); // 2. 配置SPI基本参数:主模式、波特率、数据位宽、时钟极性相位等 SPI0->CTL0 = (SPI_CTL0_FRF_MOTOROLA_3WIRE | // Motorola 3线模式 SPI_CTL0_DSS_8BIT | // 8位数据 SPI_CTL0_SPO(0) | // 时钟极性 CPOL=0 SPI_CTL0_SPH(0)); // 时钟相位 CPHA=0 SPI0->CLKCTL = SPI_CLKCTL_SCR(99); // 设置波特率,假设系统时钟100MHz,目标波特率500kHz // 3. 配置FIFO中断触发水平 SPI0->IFLS = (SPI_IFLS_RXIFLSEL_HALF | // RX FIFO >= 1/2满时触发 SPI_IFLS_TXIFLSEL_HALF); // TX FIFO <= 1/2空时触发(如果使用TX中断) // 4. 配置CPU中断事件管理器(CPU_INT组寄存器) // 4.1 首先,清除所有可能挂起的中断标志(写1到ICLR对应位清零) SPI0->CPU_INT.ICLR = 0xFFFF; // 清除所有中断标志位 // 4.2 设置中断屏蔽寄存器(IMASK),使能我们关心的中断 // 使能RX事件中断(当FIFO数据达到一半时)和奇偶校验错误中断 SPI0->CPU_INT.IMASK = SPI_IMASK_RX_MASK | SPI_IMASK_PER_MASK; // 注意:IMASK的位定义通常由头文件提供,例如 SPI_IMASK_RX_MASK 可能对应 (1<<3) // 4.3 配置事件模式(可选,决定标志清除方式) SPI0->EVT_MODE = (SPI_EVT_MODE_INT0_CFG_SOFTWARE); // CPU_INT通路设置为软件清除模式 // 5. 在系统层面,使能SPI模块的全局中断(需要配置NVIC) NVIC_EnableIRQ(SPI0_IRQn); // 使能SPI0的中断向量 NVIC_SetPriority(SPI0_IRQn, 2); // 设置中断优先级(根据系统需求) // 6. 最后,使能SPI模块 SPI0->CTL1 |= SPI_CTL1_ENABLE_MASK;3.3 中断服务程序(ISR)实现示例
void SPI0_IRQHandler(void) { uint32_t intIndex; // 方法1:使用IIDX自动获取并清除最高优先级中断 intIndex = SPI0->CPU_INT.IIDX & 0xFF; // 读取STAT字段 switch(intIndex) { case 0x00: // 无中断挂起(理论上不应进入) break; case 0x04: // RX事件:接收FIFO数据达到阈值 handleSPI_RX_Data(); // 用户函数,读取RXDATA直到FIFO为空或低于阈值 // 注意:读取IIDX已自动清除了此事件的RIS/MIS标志 break; case 0x02: // PER事件:奇偶校验错误 handleSPI_ParityError(); // 用户函数,处理错误,如重发、记录日志等 // 需要手动清除PER标志(如果未通过读IIDX清除) SPI0->CPU_INT.ICLR = SPI_ICLR_PER_MASK; break; case 0x01: // RXFIFO_OVF事件:严重错误 handleSPI_OverflowError(); SPI0->CPU_INT.ICLR = SPI_ICLR_RXFIFO_OVF_MASK; break; // ... 处理其他可能的中断源 default: // 未知中断,可做安全处理 SPI0->CPU_INT.ICLR = 0xFFFF; // 清除所有标志 break; } // 方法2:也可以直接查询MIS寄存器来处理多个同时发生的中断 // uint32_t mis = SPI0->CPU_INT.MIS; // if (mis & SPI_MIS_RX_MASK) { ... } // 但要注意,查询MIS不会自动清除标志,必须在处理后手动写ICLR。 }实操心得:在ISR中,中断标志的清除时机和方式至关重要。使用
IIDX自动清除非常方便,但只针对最高优先级中断。如果使用查询MIS的方式,务必在退出ISR前手动清除所有已处理事件的标志位,否则会导致中断持续触发,CPU陷入死循环。一个常见的调试技巧是,在ISR开头读取并保存RIS寄存器的值,在ISR末尾再读取一次,对比两次的值,可以确认所有预期的标志位是否都被正确清除了。
4. DMA触发(DMA_TRIG)配置详解与实战
当SPI需要进行大量、连续的数据传输时,频繁的中断仍然会给CPU带来负担。此时,DMA(直接内存访问)就是最佳的解决方案。DMA可以在没有CPU干预的情况下,在外设(如SPI的接收数据寄存器RXDATA)和内存之间直接搬运数据。SPI模块的DMA_TRIG_RX和DMA_TRIG_TX事件发布器,就是用来告诉DMA控制器“何时该启动一次传输”的触发器。
4.1 DMA触发机制解析
与CPU_INT管理所有事件不同,DMA_TRIG通路是高度特化的。根据你提供的资料:
DMA_TRIG_RX:专门用于触发接收数据的DMA传输。其可配置的触发源只有两个:RTOUT(接收超时)和RX(接收FIFO达到预设水平)。最常用的就是RX事件,即当接收FIFO中的数据达到你设定的阈值(如半满)时,自动触发DMA将数据搬走。DMA_TRIG_TX:专门用于触发发送数据的DMA传输。其触发源只有TX(发送FIFO达到预设水平)。通常配置为当发送FIFO有空位(如半空)时触发DMA,将内存中待发送的数据填充到FIFO中。
这种设计实现了“流水线”自动化:
- 发送流程:CPU预先将待发送的数据块地址和长度配置给DMA。SPI开始发送,TX FIFO逐渐变空。当空位达到
TXIFLSEL设定的阈值(如半空)时,触发TX事件。DMA_TRIG_TX检测到该事件,立即命令DMA控制器从内存中搬移下一个数据包到TXDATA寄存器,填满FIFO。如此循环,直到整个数据块发送完成,可能再产生一个DMA_DONE1_TX中断通知CPU。 - 接收流程:CPU预先为接收数据分配好内存缓冲区并配置DMA。SPI接收到数据,存入RX FIFO。当数据量达到
RXIFLSEL设定的阈值(如半满)时,触发RX事件。DMA_TRIG_RX检测到该事件,立即命令DMA控制器将FIFO中的数据搬移到内存缓冲区。如此循环,直到接收完指定长度的数据,可能再产生一个DMA_DONE1_RX中断通知CPU。
4.2 DMA触发配置步骤
配置DMA触发比配置CPU中断多了一个环节:需要同时配置SPI的DMA触发条件和DMA控制器本身。以下是一个配置SPI使用DMA进行数据回环(Loopback)发送和接收的示例框架。
// 第一部分:SPI模块的DMA触发配置 // 假设SPI已配置为回环模式(CTL1.LBM=1)进行自测试 // 1. 配置FIFO中断水平(这决定了DMA触发的“灵敏度”) SPI0->IFLS = (SPI_IFLS_RXIFLSEL_QUARTER | // RX FIFO >= 1/4满时触发DMA读取 SPI_IFLS_TXIFLSEL_QUARTER); // TX FIFO <= 3/4满(即1/4空)时触发DMA写入 // 更激进���设置(如1/8)会提高响应频率但增加DMA请求次数,需权衡。 // 2. 配置DMA_TRIG_RX 事件管理器 // 2.1 清除可能存在的旧标志 SPI0->DMA_TRIG_RX.ICLR = SPI_DMATRIGRX_ICLR_RX_MASK | SPI_DMATRIGRX_ICLR_RTOUT_MASK; // 2.2 使能RX事件作为DMA触发源 SPI0->DMA_TRIG_RX.IMASK = SPI_DMATRIGRX_IMASK_RX_MASK; // 2.3 可选:配置事件模式为硬件自动清除,这样DMA传输完成硬件会自动清标志 SPI0->EVT_MODE = (SPI_EVT_MODE_INT1_CFG_HARDWARE); // INT1_CFG对应DMA_TRIG_RX // 3. 配置DMA_TRIG_TX 事件管理器 // 3.1 清除可能存在的旧标志 SPI0->DMA_TRIG_TX.ICLR = SPI_DMATRIGTX_ICLR_TX_MASK; // 3.2 使能TX事件作为DMA触发源 SPI0->DMA_TRIG_TX.IMASK = SPI_DMATRIGTX_IMASK_TX_MASK; // 3.3 可选:配置事件模式为硬件自动清除 // SPI0->EVT_MODE 的INT2_CFG字段已在上一步或此处设置 // 第二部分:DMA控制器的配置(以MSPM0的DMA为例,需参考具体手册) // 这是一个概念性流程,具体寄存器名称随平台变化 // 4. 配置DMA通道(例如,通道0用于SPI TX,通道1用于SPI RX) // 4.1 配置TX DMA通道(内存 -> SPI->TXDATA) DMA_ChannelConfig(0, // 通道号 DMA_TRIGGER_SPI0_TX, // 触发源:SPI0 TX事件 DMA_ADDR_MODE_INCREMENT, // 内存地址自增 DMA_ADDR_MODE_FIXED, // 外设地址固定(TXDATA寄存器) (uint32_t)&myTxBuffer, // 源地址:内存缓冲区 (uint32_t)&(SPI0->TXDATA), // 目标地址:SPI发送寄存器 MY_BUFFER_SIZE, // 传输数据量 DMA_SIZE_16BIT); // 数据宽度(与SPI数据位宽匹配) // 4.2 配置RX DMA通道(SPI->RXDATA -> 内存) DMA_ChannelConfig(1, // 通道号 DMA_TRIGGER_SPI0_RX, // 触发源:SPI0 RX事件 DMA_ADDR_MODE_FIXED, // 外设地址固定(RXDATA寄存器) DMA_ADDR_MODE_INCREMENT, // 内存地址自增 (uint32_t)&(SPI0->RXDATA), // 源地址:SPI接收寄存器 (uint32_t)&myRxBuffer, // 目标地址:内存缓冲区 MY_BUFFER_SIZE, // 传输数据量 DMA_SIZE_16BIT); // 数据宽度 // 5. 使能DMA通道 DMA_ChannelEnable(0); DMA_ChannelEnable(1); // 6. 使能SPI模块(开始传输) SPI0->CTL1 |= SPI_CTL1_ENABLE_MASK; // 7. 启动传输:向SPI TX FIFO写入第一个数据(或由DMA自动触发) // 如果是DMA全自动管理,通常只需要使能SPI和DMA,DMA会在TX FIFO有空位时自动开始搬运。 // 也可以手动写入一个数据来启动时钟。 *(volatile uint16_t *)&(SPI0->TXDATA) = 0xAA55; // 写入初始数据,启动传输4.3 调试模式行为配置(PDBGCTL)
在进行软硬件联合调试时,你肯定不希望在单步执行代码时,SPI通信因为CPU暂停而乱套,导致从设备状态异常。PDBGCTL寄存器就是用来控制调试模式下外设行为的。
FREE位:此位为1时,外设在CPU暂停(调试器中断)时继续运行。为0时,外设随CPU暂停而暂停。SOFT位:仅在FREE=0时有效。为0表示外设立即停止,可能导致不完整的传输和数据损坏。为1表示外设在完成当前传输(到达一个“安全边界”)后才停止,这保证了数据的完整性。
配置建议:
- 实时性要求高的场景(如电机控制、高速ADC采样):设置
FREE=1,确保外设通信不因调试中断。 - 大多数调试场景:设置
FREE=0,SOFT=1。这样你可以在断点处安全地暂停,查看系统状态,而不会破坏SPI的通信事务。 - 排查底层硬件问题时:可以尝试
FREE=0,SOFT=0,让外设立即停止,方便观察精确时刻的信号状态。
// 配置为调试时在完成当前传输后停止 SPI0->PDBGCTL = SPI_PDBGCTL_SOFT_MASK; // FREE=0 (默认), SOFT=1 // 或配置为完全自由运行,不受调试器影响 // SPI0->PDBGCTL = SPI_PDBGCTL_FREE_MASK; // FREE=1, SOFT忽略5. 高级配置与性能优化技巧
掌握了基础配置后,我们可以通过一些高级设置来优化SPI的性能和可靠性。
5.1 中断与DMA的混合使用策略
在实际项目中,纯中断和纯DMA模式并非二选一,完全可以混合使用,发挥各自优势。
场景:大数据流传输附带即时状态监控
- 策略:使用DMA处理
RX和TX的常规数据搬运。同时,使能CPU_INT通路下的RXFIFO_OVF(溢出)、PER(校验错)和RTOUT(超时)中断。这样,DMA保证了数据吞吐效率,而CPU则专注于处理低频率但高优先级的异常事件,实现效率与可靠性的平衡。 - 配置:分别设置
DMA_TRIG_RX/TX.IMASK和CPU_INT.IMASK,并确保在NVIC中使能了SPI的CPU中断。
- 策略:使用DMA处理
场景:命令-响应式通信
- 策略:发送命令阶段数据量小,使用CPU中断模式。等待并接收大量响应数据时,切换到DMA模式。可以通过在中断服务程序中动态修改
IMASK和EVT_MODE寄存器来实现模式切换。 - 注意:切换时需要妥善处理可能挂起的事件标志,避免误触发。
- 策略:发送命令阶段数据量小,使用CPU中断模式。等待并接收大量响应数据时,切换到DMA模式。可以通过在中断服务程序中动态修改
5.2 FIFO水位(IFLS)与DMA块传输的权衡
IFLS寄存器中的RXIFLSEL和TXIFLSEL设置直接影响中断/DMA触发的频率和延迟。
- 高水位/低空位(如
1/4):触发更频繁,DMA请求次数多,但响应延迟低,FIFO溢出的风险小。适合对实时性要求高、数据流不稳定的场景。 - 低水位/高空位(如
1/2或3/4):触发频率低,减少了DMA或中断的上下文切换开销,有利于提升整体吞吐量。适合稳定、高速的连续数据流。
优化建议:
- 测量与调整:在真实负载下,使用逻辑分析仪或调试器测量中断/DMA触发间隔和CPU负载。如果中断太频繁,就提高水位阈值;如果发现FIFO偶尔溢出,就降低阈值。
- 匹配DMA突发大小:如果DMA控制器支持配置突发传输长度(Burst Size),可以尝试将其与SPI FIFO的深度以及水位设置相匹配。例如,FIFO深度为8,水位设为
1/2满(即4个数据)时触发,那么将DMA的突发长度设为4,可以实现一次触发正好搬空或填满FIFO的“和谐”状态,效率最高。
5.3 时钟与功耗配置考量
SPI的时钟配置(CLKDIV,CLKSEL)不仅影响波特率,也影响功耗。
CLKDIV(时钟分频):在CLKCTL.SCR基础上进行额外的预分频,可以产生更低的SPI功能时钟。在通信速率要求不高的场合,降低功能时钟能有效减少动态功耗。CLKSEL(时钟源选择):可以选择系统时钟(SYSCLK)、中频时钟(MFCLK)或低频时钟(LFCLK)。在低速或待机通信时,切换到低频时钟源可以大幅降低功耗。DSAMPLE(延迟采样):这个位于CLKCTL寄存器中的字段非常实用。它通过延迟内部采样点来补偿PCB走线或外设带来的信号延迟,从而放宽了对输入数据建立时间的要求。如果你的SPI通信在高速率下出现数据采样错误,可以尝试逐步增加DSAMPLE的值(从0到F),同时用示波器观察CLKOUT和MISO信号的时序关系,直到找到稳定采样的点。
// 优化示例:在低速模式下选择低频时钟并增加采样延迟 if (lowSpeedMode) { SPI0->CLKSEL = SPI_CLKSEL_LFCLK_SEL_MASK; // 选择低频时钟源 SPI0->CLKDIV = 2; // 额外2分频 SPI0->CLKCTL = (SPI_CLKCTL_SCR(19) | // 设置SCR分频 SPI_CLKCTL_DSAMPLE(2)); // 增加2个功能时钟周期的采样延迟 }6. 常见问题排查与调试实录
即使配置正确,在实际硬件调试中仍会遇到各种问题。下面是我在项目中积累的一些典型问题及其排查思路。
6.1 中断无法触发
- 现象:配置了中断,但程序从未进入中断服务程序。
- 排查清单:
- SPI模块全局使能:确认
CTL1.ENABLE位已置1。 - NVIC配置:确认在微控制器的中断控制器(NVIC)中已使能对应的SPI中断线,并且中断优先级设置正确(非屏蔽)。
- IMASK寄存器:确认你期望的中断源在
CPU_INT.IMASK中对应的位已被置1(取消屏蔽)。 - 事件是否真的发生:读取
CPU_INT.RIS寄存器,查看原始中断标志位是否置1。如果RIS有值而MIS为0,说明IMASK配置错误。如果RIS为0,说明事件根本没产生,需要检查SPI通信本身(如时钟、片选、数据线)。 - 中断标志清除:检查是否在别处意外清除了中断标志(如误操作
ICLR),或者中断服务程序是否清除了标志但未处理完事件导致立即再次触发。 - EVT_MODE设置:确认
EVT_MODE.INT0_CFG(对应CPU_INT)未设置为0(禁用)模式。
- SPI模块全局使能:确认
6.2 DMA传输卡住或数据不完整
- 现象:启动了DMA,但数据没有搬运,或者只搬运了一部分。
- 排查清单:
- DMA触发源配置:确认
DMA_TRIG_RX或DMA_TRIG_TX的IMASK已正确使能(例如,使能了RX位)。 - SPI FIFO水位:确认
IFLS寄存器中的RXIFLSEL/TXIFLSEL设置合理。如果水位设得太高(如FULL),而每次传输的数据量很小,可能永远达不到触发条件。 - DMA控制器配置:
- 触发信号映射:确认DMA控制器的通道触发源正确映射到了
SPI0_RX_EVT或SPI0_TX_EVT(名称可能不同)。 - 传输量(Transfer Size):确认DMA配置的传输数据量(字节数/字数)与实际需求一致。
- 地址与宽度:确认源/目标地址正确,数据宽度(8位/16位)与SPI的
CTL0.DSS设置匹配。例如,SPI配置为8位数据,DMA也应设为8位传输。 - DMA通道使能:确认DMA通道已使能。
- 触发信号映射:确认DMA控制器的通道触发源正确映射到了
- SPI通信是否正常:用逻辑分析仪抓取SPI的CLK、MOSI、MISO信号,确认有数据在传输。没有数据,自然不会有DMA触发事件。
- 事件标志清除模式:如果
EVT_MODE设置为软件清除(SOFTWARE),而你的程序没有在DMA传输完成后(例如在DMA完成中断里)清除RIS标志,那么该事件标志会一直存在,可能干扰后续触发。对于DMA连续传输,建议设置为硬件清除(HARDWARE)模式。
- DMA触发源配置:确认
6.3 接收数据错位或错误
- 现象:接收到的数据与发送端不一致,或出现规律性错位。
- 排查清单:
- 时钟极性与相位(CPOL/CPHA):这是SPI通信中最常见的配置错误。务必确认主从设备的
CTL0.SPO(CPOL)和CTL0.SPH(CPHA)设置完全一致。用示波器观察CLKOUT和MOSI/MISO的时序关系是最直接的验证方法。 - 数据位序(MSB/LSB):检查
CTL1.MSB位,确认主从设备的数据发送/接收位序(最高位先发还是最低位先发)一致。 - FIFO打包(PACKEN):如果使能了
CTL0.PACKEN(打包功能),那么读写RXDATA/TXDATA寄存器时是32位操作(存取两个16位数据)。如果你的DMA或中断服务程序是按16位操作的,就会导致数据错乱。确保操作的数据宽度与PACKEN设置匹配。 - 奇偶校验:如果使能了
CTL1.PTEN和CTL1.PREN,检查CPU_INT.RIS.PER位是否置1。置1表示发生了奇偶校验错误,需要检查硬件连接或通信速率是否过高。
- 时钟极性与相位(CPOL/CPHA):这是SPI通信中最常见的配置错误。务必确认主从设备的
6.4 调试工具与技巧
- 寄存器观察窗口:在IDE的调试模式下,将SPI的所有关键寄存器(
STAT,CPU_INT.RIS/MIS/IIDX,DMA_TRIG_RX.RIS等)添加到观察窗口。实时观察它们的值变化,是判断程序流和硬件状态是否符合预期的最快方法。 - 逻辑分析仪:这是调试SPI硬件通信的终极工具。连接CLK, MOSI, MISO, CS线,可以清晰看到每一位数据的传输时序、内容,以及中断或DMA触发时刻与数据流的关系。
- 软件模拟触发:在怀疑事件触发逻辑有问题时,可以利用
ISET寄存器进行软件模拟。例如,在调试DMA触发逻辑时,可以先不进行实际SPI通信,而是直接写SPI0->DMA_TRIG_RX.ISET = SPI_ISET_RX_MASK;来手动产生一个RX事件,观察DMA是否会响应。这能有效隔离硬件通信问题。 - 利用IDLE和BUSY状态:
STAT.BUSY位和CPU_INT中的IDLE中断,对于判断SPI传输的起止非常有用。可以在传输开始和结束时检查或中断通知,确保没有意外的并发操作。
通过系统性地理解SPI的事件与中断架构,并熟练运用这些配置和调试技巧,你就能让SPI这个“邮局”在你的嵌入式系统中真正高效、可靠地运转起来,将CPU从繁琐的搬运工作中解放出来,去处理更复杂的业务逻辑。