4. UART+DMA硬件设计:串口DMA请求线连接、FIFO深度配置、中断优先级分配
这一章咱们聊点硬核的。UART加上DMA,说白了就是让数据自己跑,CPU别管。但硬件上怎么连、怎么配,坑不少。我刚开始做这个的时候,以为只要把DMA使能位一拉就完事了,结果数据收得七零八落。后来才发现,DMA请求线没接对,FIFO深度没配好,中断优先级还打架。嗯,今天咱们把这些坑一个一个填上。
4.1 串口DMA请求线连接:别让数据迷路
DMA传输的起点,是外设发一个“我要传数据”的请求信号。UART这边,发送和接收各有一条独立的DMA请求线。你得把它们正确连到DMA控制器上。
我习惯把这种连接关系画成一张图,心里才踏实:
这张图里,红色箭头是发送请求线,绿色箭头是接收请求线。它们各自独立,互不干扰。你想想看,如果发送和接收共用一条请求线,那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批量搬运,又不会让延迟大到影响实时性。
4.3 中断优先级分配:别让重要的事等太久
UART+DMA模式下,中断优先级分配是个容易被忽视的细节。你想想看,系统里不止UART一个外设。定时器、GPIO、ADC都在抢CPU。如果UART中断优先级太低,数据可能被其他中断堵住,导致FIFO溢出。
我一般按这个优先级策略来分配:
- 最高优先级: DMA传输完成中断。这个中断意味着数据已经搬完了,CPU需要尽快处理。如果被其他中断打断,可能导致数据覆盖。
- 次高优先级: UART错误中断(溢出、帧错误、奇偶校验错误)。错误必须立即处理,否则后续数据全是错的。
- 中等优先级: UART接收超时中断。这个中断告诉CPU,FIFO里还有数据没被DMA搬走。优先级不能太低,否则数据会卡在FIFO里。
- 较低优先级: 其他外设中断(定时器、GPIO等)。这些中断可以等一等,不影响数据完整性。
为什么会这样?因为DMA传输完成中断是数据流的终点。如果这个中断被延迟,下一个DMA传输可能覆盖还没处理的数据。我遇到过这种情况: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)。为什么?因为接收数据不能丢。发送可以等一等,接收不行。你想想看,如果接收缓冲区满了,新数据来了没地方放,那就直接丢了。发送不一样,发送缓冲区满了,数据还在内存里,等一会儿再发就行。
4.5 硬件调试中的常见问题
最后,我总结几个硬件调试中常见的问题,供你参考:
- DMA请求线没连对: 检查UART的DMA请求输出引脚是否连到了DMA控制器的正确输入引脚。有些MCU内部已经连好了,但有些需要外部连线。
- FIFO溢出: 如果DMA响应太慢,FIFO会溢出。可以增大FIFO深度,或者提高DMA中断优先级。
- 中断优先级冲突: 两个高优先级中断互相抢占,导致死锁。可以用中断优先级分组来避免。
- DMA传输未完成: 检查DMA传输计数是否配置正确。如果计数为0,DMA不会启动传输。
嗯,这一章的内容就这些。硬件设计这块,多动手、多测试,慢慢就能找到感觉。记住,DMA请求线、FIFO深度、中断优先级,这三个点抓住了,UART+DMA的硬件设计就稳了。