DSPI状态寄存器与中断/DMA配置详解:提升嵌入式SPI通信效率
1. DSPI状态寄存器与中断/DMA配置的核心价值
在嵌入式开发里,和SPI外设打交道是家常便饭。从读取传感器数据到驱动显示屏,再到和Flash存储器通信,SPI的身影无处不在。但不知道你有没有遇到过这样的场景:主程序正忙着处理其他任务,突然SPI传输完成了,或者FIFO快空了,你得时不时去“瞅一眼”状态寄存器,这种轮询(Polling)的方式不仅效率低下,还白白消耗了宝贵的CPU周期。更头疼的是,在高速数据流场景下,比如连续采集图像传感器数据,CPU光是搬运数据就够呛了,根本无暇处理其他逻辑。
飞思卡尔(现为NXP)的DSPI模块,正是在这种需求下诞生的“增强版SPI”。它不仅仅是一个通信接口,更是一套完整的通信管理系统。其精髓,就在于**DSPI状态寄存器(DSPI_SR)和DMA/中断请求选择与使能寄存器(DSPI_RSER)**的协同工作。简单来说,DSPI_SR就像是一个24小时不间断的监控面板,实时显示着“传输完成”、“发送FIFO空”、“接收FIFO满”等各种关键事件的状态。而DSPI_RSER则是这个监控面板的报警系统设置菜单,你可以决定哪些事件亮起红灯时,需要立刻“大喊大叫”打断CPU(触发中断),或者直接指挥一个“专职搬运工”DMA控制器来接手数据搬运工作,完全不用CPU操心。
这种机制将开发者从繁琐的轮询检查中解放出来,实现了事件驱动的异步通信。对于追求实时性和低功耗的系统来说,合理配置这些寄存器,意味着你能在数据就绪的瞬间做出响应,或在数据吞吐时让CPU“睡个好觉”,从而整体提升系统的效率和可靠性。接下来,我们就深入PXR40的DSPI模块,把这套机制的里里外外摸个透彻。
2. DSPI状态寄存器(DSPI_SR)深度解析
DSPI_SR寄存器是洞察DSPI模块内部运行状态的窗口。它不是一个简单的标志位集合,而是一个反映了FIFO管理、传输状态和错误监测的综合仪表盘。理解每一位的含义,是进行高效编程和故障排查的基础。
2.1 传输控制与状态标志位
这部分标志位直接关联到一次SPI帧传输的生命周期和队列管理。
TCF (Transfer Complete Flag) - 传输完成标志这是最常用的标志位之一。当一帧数据的所有位都从移位寄存器中移出(对于主机)或移入(对于从机)后,此位被硬件自动置1。它标志着一个物理传输周期的结束。
注意:TCF置位并不意味着数据已经处理完毕。对于主机,它只表示数据已经发送到线上;对于从机,表示数据已接收至移位寄存器。数据从移位寄存器到RX FIFO,或从TX FIFO到移位寄存器的内部搬运,与此标志位无关。软件必须写1清除此位。
TXRXS (TX & RX Status) - 发送接收状态此位反映了DSPI核心的状态机是否处于运行状态。当DSPI正在 actively 进行发送或接收操作时,此位为1(RUNNING);当传输停止或处于空闲时,此位为0(STOPPED)。它的状态变化通常由传输启停逻辑自动控制,例如当设置EOQ(队列结束)标志的帧传输完成时,TXRXS会被自动清零。
EOQF (End of Queue Flag) - 队列结束标志这是一个高级队列管理标志。当你在向TX FIFO写入数据时,可以在命令字中设置EOQ位,来标记这是当前队列的最后一个传输帧。当DSPI执行到这个带有EOQ标记的帧并完成传输后,EOQF标志位会被置1。同时,TXRXS位通常会被自动清零,从而使DSPI停止,等待新的指令。这在需要精确控制一组连续传输的结束时非常有用,软件可以通过查询此标志来得知一个完整的数据包已发送完毕。
2.2 FIFO状态与计数器标志位
DSPI的FIFO是其性能提升的关键,而SR寄存器中的相关标志位和计数器则是管理FIFO的“方向盘”。
TFFF (Transmit FIFO Fill Flag) & RFDF (Receive FIFO Drain Flag)这是两个最核心的流控标志。
- TFFF:当发送FIFO(TX FIFO)未满时,此位为1。这是一个“可以继续写入”的邀请信号。当你的数据写入使得TX FIFO变满,此位会清零。你可以利用此标志来采用中断或DMA方式,在FIFO有空闲时自动填充数据,实现不间断流式发送。
- RFDF:当接收FIFO(RX FIFO)非空(即有数据)时,此位为1。这是一个“有数据待读取”的通知信号。当软件或DMA控制器读取数据使得RX FIFO变空,此位会清零。利用此标志,可以实现数据到达时的即时读取。
TXCTR & RXCTR (FIFO计数器)这两个4位字段是TX和RX FIFO的“水位计”。TXCTR指示TX FIFO中有效条目的数量,RXCTR指示RX FIFO中的数据条目数量。它们为软件提供了更精细的FIFO状态信息。例如,你可以设定当TXCTR小于某个阈值(即FIFO快空了)时再填充数据,或者当RXCTR大于某个阈值(即FIFO快满了)时紧急读取数据,这比单纯的“空/满”标志提供了更大的缓冲管理灵活性。
TXNXTPTR & POPNXTPTR (FIFO指针)这两个指针对于理解FIFO的环形缓冲区机制很有帮助。TXNXTPTR指向下一次传输将从TX FIFO中取出的条目,POPNXTPTR指向下一次从RX FIFO读取将返回的条目。在调试复杂FIFO下溢或上溢问题时,观察这两个指针有助于判断是生产者(写入方)还是消费者(读取方)的节奏出了问题。
2.3 错误标志位
错误标志位是系统稳定性的“守门员”,及时处理错误能防止数据丢失或通信锁死。
TFUF (Transmit FIFO Underflow Flag) - 发送FIFO下溢标志此标志仅在SPI从机模式下有效。当下溢发生时,意味着作为从机的DSPI,其TX FIFO已空,但外部SPI主机却发起了传输请求。此时,从机无数据可发,通常会导致发送无效数据(如旧数据或默认值)。一旦检测到这种情况,TFUF位会被置1。在从机应用中,必须确保在主机发起读取前,TX FIFO中已预先填好数据。
RFOF (Receive FIFO Overflow Flag) - 接收FIFO上溢标志这是一个严重的错误标志。当RX FIFO和接收移位寄存器都已满,且又有新数据从移位寄存器准备存入FIFO时,就会发生上溢,新数据将丢失,RFOF位置1。这通常是因为软件或DMA读取RX FIFO的速度跟不上数据接收的速度。必须优化数据读取逻辑或增加FIFO深度(如果支持)来避免此问题。
3. 中断与DMA请求使能寄存器(DSPI_RSER)配置策略
DSPI_RSER寄存器赋予了开发者将状态事件转化为系统动作的能力。它的每个使能位(*_RE)对应DSPI_SR中的一个标志位,而方向选择位(*_DIRS)则决定了触发的是中断还是DMA请求。
3.1 请求使能(*_RE)位详解
每个*_RE位控制其对应状态标志是否能够产生请求(中断或DMA)。例如:
TCF_RE = 1:使能传输完成中断/DMA请求。TFFF_RE = 1:使能TX FIFO非满(即可写)请求。RFDF_RE = 1:使能RX FIFO非空(即可读)请求。TFUF_RE和RFOF_RE:通常建议使能中断,以便及时处理错误。
配置原则:并非所有标志都需要使能请求。通常,需要CPU及时介入处理的事件(如错误、传输完成)适合配置为中断;而纯粹的数据搬运任务(填充TX FIFO、清空RX FIFO)则更适合配置为DMA,以解放CPU。
3.2 请求方向选择(*_DIRS)位详解
这是DSPI模块灵活性的关键。以TFFF_DIRS和RFDF_DIRS为例:
TFFF_DIRS = 0:当TX FIFO未满(TFFF=1)且使能(TFFF_RE=1)时,产生中断请求。CPU需要响应中断,执行代码向DSPI_PUSHR写入数据。TFFF_DIRS = 1:当TX FIFO未满时,产生DMA请求。DMA控制器会自动响应此请求,根据预先配置的源地址(如内存中的数组)和目的地址(DSPI_PUSHR),自动完成数据搬运,CPU完全不用参与。
典型场景选择:
- 低速、非连续传输:适合使用中断。例如,每隔几百毫秒读取一次温度传感器。配置
TCF_RE=1,在传输完成后产生中断,在中断服务程序(ISR)中读取数据并启动下一次传输。 - 高速、连续、大数据量流传输:必须使用DMA。例如,向LCD屏连续刷新帧缓存,或从ADC连续采集音频数据。配置
TFFF_DIRS=1和RFDF_DIRS=1,并设置好DMA通道。发送时,DMA会在TX FIFO有空闲时自动填充数据;接收时,DMA会在RX FIFO有数据时自动搬走数据。CPU只需在DMA传输完成中断中处理整个数据块即可。
3.3 配置流程与注意事项
配置DSPI_RSER寄存器需要遵循一个严格的顺序,错误的顺序可能导致丢失请求或不可预测的行为。
- 先禁止,再配置:在修改DSPI_RSER之前,最好先通过
DSPI_MCR寄存器暂停DSPI(设置HALT位),或者至少确保DSPI不在RUNNING状态(TXRXS=0)。手册明确指出,不应在DSPI处于运行状态时写入此寄存器。 - 清除待处理标志:在使能任何请求前,先读取
DSPI_SR寄存器并写1清除所有可能已置位的标志位(如TCF、TFFF等)。这可以避免一使能就立即触发一个陈旧的中断或DMA请求。 - 配置DSPI_RSER:按照你的应用需求,设置好各个
*_RE和*_DIRS位。例如,若想用DMA处理发送,则设置TFFF_RE=1且TFFF_DIRS=1。 - 配置外设:如果使能了中断,需要到微控制器的中断控制器(如NVIC)中使能对应的DSPI中断线。如果使能了DMA,则需要配置DMA控制器的相应通道:设置源/目标地址、传输数据宽度、每次请求的传输量(通常为16位或32位对应
DSPI_PUSHR/POPR的一次访问)、并启用该通道。 - 启动传输:最后,再启动DSPI(清除
HALT位)或开始向TX FIFO写入第一个数据以启动传输序列。
重要警告:对于
TFFF和RFDF这类电平敏感(FIFO状态变化)的请求,要特别注意“请求-应答”的握手逻辑。以DMA发送为例:当TX FIFO未满时,DSPI会持续向DMA控制器发出请求信号。DMA每搬运一个数据到DSPI_PUSHR,就会回送一个应答信号给DSPI,DSPI会暂时拉低请求,直到下一次FIFO有空闲再拉高。你必须正确配置DMA为外设流控模式,确保DMA只在收到请求时才传输,否则会导致数据覆盖或丢失。
4. 实战配置:以DMA实现高速SPI数据发送
让我们通过一个具体的例子,将上述理论串联起来。假设我们需要使用DSPI的SPI主机模式,以DMA方式连续发送一个1024字节的数据块到外部DAC。
4.1 初始化步骤
GPIO与DSPI基础配置:首先配置相关引脚为DSPI功能(SCK, MOSI, PCSx)。配置
DSPI_MCR:设置MSTR=1(主机模式),根据需求配置CONT_SCKE、DCONF=0(SPI模式)等。配置DSPI_CTAR0寄存器,设置合适的波特率分频(BR和PBR)、时钟极性与相位(CPOL,CPHA)、帧大小(FMSZ,例如8位或16位)。配置DSPI_RSER寄存器:
- 我们的目标是使用DMA自动填充发送数据,因此需要使能TX FIFO填充请求,并设置为DMA模式。
- 写入
DSPI_RSER = 0x00000080。我们来分解这个值:TFFF_RE(Bit 6) = 1: 使能TX FIFO填充请求。TFFF_DIRS(Bit 7) = 1: 选择该请求为DMA请求。- 其他位(如
TCF_RE,RFDF_RE等)暂时为0。我们可能也会使能TCF_RE用于在DMA传输全部完成后产生中断通知CPU,但这里先专注于DMA发送本身。
配置DMA控制器:
- 假设使用DMA通道0。设置其源地址(
SAR)为内存中数据数组的起始地址(例如&data_buffer[0])。 - 设置其目标地址(
DAR)为DSPI的发送数据寄存器地址((uint32_t)&DSPI0->PUSHR)。 - 配置控制寄存器:
- 设置传输宽度:源和目标都设置为16位(如果帧大小是16位)或32位(如果使用32位访问以同时写入命令和数据)。这需要与
DSPI_PUSHR的访问宽度匹配。 - 设置每次外设请求的传输量(
DSIZE)为1个传输单元(1个16位或32位数据)。 - 关键:使能外设请求(
ERQ位),并设置传输模式为“外设流控模式”。这意味着DMA传输由DSPI发出的TFFFDMA请求来触发和控制。 - 设置总传输次数(
CITER)为数据总量(例如,1024字节 / 2字节每次 = 512次,对于16位帧)。 - 使能DMA通道。
- 设置传输宽度:源和目标都设置为16位(如果帧大小是16位)或32位(如果使用32位访问以同时写入命令和数据)。这需要与
- 假设使用DMA通道0。设置其源地址(
启动传输:
- 确保TX FIFO初始为空。可以向
DSPI_PUSHR手动写入第一个数据,或者直接启动DMA(DMA会在收到第一个TFFF请求后自动开始)。 - 由于TX FIFO初始为空,
TFFF标志位为1,DSPI会立即向DMA控制器发出请求。 - DMA控制器响应请求,从内存读取第一个数据,写入
DSPI_PUSHR。这个写入操作会将数据压入TX FIFO,并使TXCTR加1。 - DSPI模块开始自动进行SPI传输,将TX FIFO中的数据移出。每当TX FIFO中有空间(即
TFFF再次为1),DSPI就会发出新的DMA请求,直到DMA完成所有次数的传输。
- 确保TX FIFO初始为空。可以向
4.2 关键代码片段(概念性伪代码)
// 1. 配置DSPI_RSER,使能TFFF的DMA请求 DSPI0->RSER |= DSPI_RSER_TFFF_RE_MASK | DSPI_RSER_TFFF_DIRS_MASK; // 2. 配置DMA通道 DMA0->TCD[0].SADDR = (uint32_t)data_buffer; // 源地址 DMA0->TCD[0].DADDR = (uint32_t)&(DSPI0->PUSHR); // 目标地址 DMA0->TCD[0].SOFF = 2; // 源地址偏移,每次传输后+2字节(16位数据) DMA0->TCD[0].DOFF = 0; // 目标地址固定 DMA0->TCD[0].ATTR = DMA_ATTR_SSIZE(1) | DMA_ATTR_DSIZE(1); // 源和目标数据大小均为16位 DMA0->TCD[0].NBYTES = 2; // 每次次要循环传输2字节 DMA0->TCD[0].SLAST = -1024; // 主循环结束后,恢复源地址初始值 DMA0->TCD[0].DLAST_SGA = 0; // 目标地址始终不变 DMA0->TCD[0].CITER = DMA_CITER_ELINKNO_ELINK(0) | 512; // 当前主要迭代次数 DMA0->TCD[0].BITER = DMA_BITER_ELINKNO_ELINK(0) | 512; // 起始主要迭代次数 DMA0->TCD[0].CSR = DMA_CSR_INTMAJOR_MASK; // 可选:使能主循环完成中断 // 3. 使能DMA通道请求 DMA0->SERQ = 0; // 使能通道0的请求 // 4. (可选)手动写入第一个数据以启动DSPI,或等待DMA自动开始 // 如果DSPI尚未开始,且TX FIFO为空,TFFF=1会立即触发DMA // 为了确保开始,可以先写一个数据: DSPI0->PUSHR = first_data_word_with_command;5. 常见问题排查与调试技巧
在实际开发中,遇到DSPI通信问题,尤其是涉及中断和DMA时,需要系统性地排查。
5.1 中断不触发
- 检查使能链:这是一个经典的“三件套”检查。
- 外设级:确认
DSPI_RSER中对应的*_RE位已置1。 - 中断控制器级:确认在NVIC中已使能DSPI的中断向量(例如
NVIC_EnableIRQ(DSPI0_IRQn))。 - 全局级:确认CPU的全局中断是开启的(对于ARM Cortex-M,通常通过
__enable_irq()指令)。
- 外设级:确认
- 检查标志位状态:在中断服务程序(ISR)中,第一时间读取
DSPI_SR寄存器,查看是哪个标志位触发了中断。有时可能是未预期的标志位(如错误标志)导致了中断。 - 清除标志位:确保在ISR中,对触发中断的标志位执行了“写1清零”的操作。如果忘记清除,中断会持续触发,导致系统锁死。
- 优先级问题:检查中断优先级是否被其他更高优先级的中断长时间屏蔽。
5.2 DMA传输卡住或数据错误
- 确认请求信号:使用调试器或逻辑分析仪,检查DSPI发给DMA控制器的请求信号(如
DMA_REQ信号)是否在TX FIFO未满时有效拉高。如果没有请求信号,DMA自然不会动作。 - 检查DMA配置:
- 地址对齐:确保源和目标地址的数据宽度对齐(如16位数据应对齐到2字节边界)。
- 传输大小:
NBYTES(每次请求传输的字节数)是否与DSPI_PUSHR的访问宽度匹配?例如,如果你用32位写PUSHR,那么DMA每次传输应是4字节。 - 流控模式:必须配置为外设流控模式,而不是内存到内存的自动模式。
- 检查FIFO指针:在调试时,可以监视
DSPI_SR中的TXCTR和TXNXTPTR。如果TXCTR一直为0或满,而TXNXTPTR不变化,说明数据没有成功从FIFO移出到移位寄存器,可能是SPI时序配置(DSPI_CTAR)或外部设备就绪信号有问题。 - 数据内容检查:对于
DSPI_PUSHR,写入的是一个组合值,包含命令字段(CTAS, PCS等)和数据字段(TXDATA)。确保DMA写入的数据格式正确。一个常见的错误是DMA只写了数据部分,而命令部分为0,导致传输属性错误(如选错了片选线)。
5.3 FIFO上溢/下溢错误
- RFOF (接收上溢):
- 根本原因:数据生产(接收)速度 > 数据消费(读取)速度。
- 排查:检查
RFDF中断/DMA是否使能并正确响应。如果使用中断,ISR执行时间是否过长?如果使用DMA,DMA通道优先级是否过低或被禁用?增大RX FIFO的读取速度或降低SPI波特率。
- TFUF (发送下溢,仅从机):
- 根本原因:主机发起读操作时,从机的TX FIFO为空。
- 解决:在从机应用中,必须采用“预填充”策略。在主机可能发起读取之前,就提前将待发送数据写入从机的TX FIFO。可以利用
TFFF中断或DMA来提前准备数据。
5.4 调试辅助寄存器
别忘了DSPI提供的调试寄存器DSPI_TXFR0-3和DSPI_RXFR0-3。它们允许你直接窥视TX和RX FIFO中的内容,而不会影响FIFO的指针和状态。当通信数据出现错乱时,对比你期望发送的数据(在TXFRn中)和实际接收到的数据(在RXFRn中),是定位问题是在发送端、接收端还是传输线上的有力手段。
最后,一个非常实用的经验是:在复杂DSPI驱动开发初期,先使用轮询模式(查询TFFF和RFDF)让通信跑通,确保基本的时序、数据格式和硬件连接是正确的。然后再逐步引入中断和DMA机制。这样能将问题域隔离,避免同时面对通信协议和异步事件处理两个层面的难题。当你切换到中断/DMA模式后出现问题,就可以更确定地知道问题出在事件配置或DMA设置上,而不是最底层的SPI波形本身。
