4. UART+DMA硬件设计:串口DMA请求线连接、FIFO深度配置、中断优先级分配

这一章咱们聊点硬核的。UART加上DMA,说白了就是让数据自己跑,CPU别管。但硬件上怎么连、怎么配,坑不少。我刚开始做这个的时候,以为只要把DMA使能位一拉就完事了,结果数据收得七零八落。后来才发现,DMA请求线没接对,FIFO深度没配好,中断优先级还打架。嗯,今天咱们把这些坑一个一个填上。

4.1 串口DMA请求线连接:别让数据迷路

DMA传输的起点,是外设发一个“我要传数据”的请求信号。UART这边,发送和接收各有一条独立的DMA请求线。你得把它们正确连到DMA控制器上。

我习惯把这种连接关系画成一张图,心里才踏实:

UART + DMA 请求线连接结构 UART 外设 发送数据寄存器 (TDR) 接收数据寄存器 (RDR) 发送移位寄存器 接收移位寄存器 DMA 控制器 通道 0 (UART_TX) 通道 1 (UART_RX) 源地址 / 目的地址 传输计数 / 优先级 UART_TX_DMA_REQ UART_RX_DMA_REQ 系统内存 发送缓冲区 (SRAM) 系统内存 接收缓冲区 (SRAM) 系统数据总线 (AHB/APB) 关键点:UART 的 TX 和 RX 各有一条独立的 DMA 请求线,必须分别连接到 DMA 控制器的不同通道

这张图里,红色箭头是发送请求线,绿色箭头是接收请求线。它们各自独立,互不干扰。你想想看,如果发送和接收共用一条请求线,那DMA控制器根本分不清该往哪个方向传数据。

我曾经踩过的坑: 某次调试,UART接收一直没数据。查了半天,发现DMA请求线接到了发送通道上。接收数据当然不会触发发送请求。这种低级错误,一旦焊上去,改起来就麻烦了。

4.2 FIFO深度配置:平衡的艺术

FIFO(先进先出缓冲区)是UART内部的一个小缓存。它能在CPU或DMA来不及处理时,暂存几个字节的数据。FIFO深度怎么配?这其实是个平衡问题。

我一般按这个思路来选:

FIFO深度 适用场景 优点 缺点
1字节(无FIFO) 低速、单字节交互 延迟最低 CPU频繁中断
4字节 中低速、简单协议 减少中断次数 可能丢数据
8字节 高速、DMA配合 批量传输效率高 延迟稍大
16字节 极高速、大数据流 几乎不丢数据 响应延迟明显

我个人习惯,如果用了DMA,FIFO深度至少配到8字节。为什么呢?因为DMA传输有启动开销。如果FIFO太浅,DMA刚启动就要处理中断,效率反而低。8字节是个甜点,既能让DMA批量搬运,又不会让延迟大到影响实时性。

一个小技巧: 配置FIFO触发阈值时,我通常设为FIFO深度的一半。比如8字节的FIFO,触发阈值设4字节。这样DMA有足够时间响应,又不会让FIFO溢出。

4.3 中断优先级分配:别让重要的事等太久

UART+DMA模式下,中断优先级分配是个容易被忽视的细节。你想想看,系统里不止UART一个外设。定时器、GPIO、ADC都在抢CPU。如果UART中断优先级太低,数据可能被其他中断堵住,导致FIFO溢出。

我一般按这个优先级策略来分配:

  • 最高优先级: DMA传输完成中断。这个中断意味着数据已经搬完了,CPU需要尽快处理。如果被其他中断打断,可能导致数据覆盖。
  • 次高优先级: UART错误中断(溢出、帧错误、奇偶校验错误)。错误必须立即处理,否则后续数据全是错的。
  • 中等优先级: UART接收超时中断。这个中断告诉CPU,FIFO里还有数据没被DMA搬走。优先级不能太低,否则数据会卡在FIFO里。
  • 较低优先级: 其他外设中断(定时器、GPIO等)。这些中断可以等一等,不影响数据完整性。

为什么会这样?因为DMA传输完成中断是数据流的终点。如果这个中断被延迟,下一个DMA传输可能覆盖还没处理的数据。我遇到过这种情况:DMA完成中断被一个高频率的定时器中断抢占,结果接收缓冲区里的数据被覆盖了两次,最后解析出来的数据全是乱的。

核心原则: 数据完整性 > 实时响应 > 其他任务。UART和DMA相关的中断,优先级应该高于普通外设中断,但低于系统时钟中断(如果有时钟同步需求)。

4.4 实际配置示例:以STM32为例

说了这么多理论,咱们看个实际例子。以STM32F4系列为例,配置UART+DMA的硬件连接:

// 1. 使能UART和DMA时钟
RCC->AHB1ENR |= RCC_AHB1ENR_DMA2EN;
RCC->APB1ENR |= RCC_APB1ENR_USART3EN;

// 2. 配置UART的DMA请求
USART3->CR3 |= USART_CR3_DMAT;   // 使能发送DMA
USART3->CR3 |= USART_CR3_DMAR;   // 使能接收DMA

// 3. 配置DMA通道
// 发送通道:内存到外设
DMA2_Stream2->CR = DMA_SxCR_CHSEL_2 |      // 通道选择
                    DMA_SxCR_DIR_1 |         // 内存到外设
                    DMA_SxCR_MINC |          // 内存地址递增
                    DMA_SxCR_TCIE;           // 传输完成中断使能

// 接收通道:外设到内存
DMA2_Stream5->CR = DMA_SxCR_CHSEL_2 |      // 通道选择
                    DMA_SxCR_DIR_0 |         // 外设到内存
                    DMA_SxCR_MINC |          // 内存地址递增
                    DMA_SxCR_TCIE;           // 传输完成中断使能

// 4. 配置FIFO深度(UART内部)
USART3->CR1 |= USART_CR1_FIFOEN;            // 使能FIFO
USART3->CR1 |= USART_CR1_RXFFIE;            // 接收FIFO满中断
// FIFO深度由硬件决定,STM32F4的USART FIFO深度为8字节

// 5. 配置中断优先级
NVIC_SetPriority(DMA2_Stream2_IRQn, 1);     // 发送完成中断,优先级1
NVIC_SetPriority(DMA2_Stream5_IRQn, 0);     // 接收完成中断,优先级0(最高)
NVIC_SetPriority(USART3_IRQn, 2);           // UART错误中断,优先级2

这段代码里,我把接收DMA完成中断设为了最高优先级(0)。为什么?因为接收数据不能丢。发送可以等一等,接收不行。你想想看,如果接收缓冲区满了,新数据来了没地方放,那就直接丢了。发送不一样,发送缓冲区满了,数据还在内存里,等一会儿再发就行。

注意: 中断优先级数值越小,优先级越高。不同MCU的优先级配置方式可能不同,有的用数值表示优先级,有的用分组。一定要查手册确认。

4.5 硬件调试中的常见问题

最后,我总结几个硬件调试中常见的问题,供你参考:

  • DMA请求线没连对: 检查UART的DMA请求输出引脚是否连到了DMA控制器的正确输入引脚。有些MCU内部已经连好了,但有些需要外部连线。
  • FIFO溢出: 如果DMA响应太慢,FIFO会溢出。可以增大FIFO深度,或者提高DMA中断优先级。
  • 中断优先级冲突: 两个高优先级中断互相抢占,导致死锁。可以用中断优先级分组来避免。
  • DMA传输未完成: 检查DMA传输计数是否配置正确。如果计数为0,DMA不会启动传输。

嗯,这一章的内容就这些。硬件设计这块,多动手、多测试,慢慢就能找到感觉。记住,DMA请求线、FIFO深度、中断优先级,这三个点抓住了,UART+DMA的硬件设计就稳了。