STM32串口通信DMA接收 + 空闲中断IDLE详解

文章目录

在嵌入式开发中,如何高效接收不定长度的串口数据是一个经典问题。传统的按字节中断接收不仅浪费 CPU 资源,还容易在大数据量时丢包。

为什么选择 DMA + IDLE?

传统的按字节中断接收(RXNE)就像是快递员每送一个包裹都要敲一次你的门,当你忙碌(CPU 负载高)时,包裹就会堆积甚至丢失。我们通过利用了 STM32 硬件层面的两个核心特性:

  1. DMA (搬运工)
    • 串口寄存器(DR/RDR)每收到一个字节,DMA 硬件直接通过总线将其搬运到指定的内存缓冲区(RAM)。
    • 可以完全解放 CPU。在整个传输过程中,CPU 不需要进入任何中断服务程序(ISR),可以全力处理业务逻辑。
  2. IDLE 信号 (断句器)
    • 触发机制是硬件检测到 RX 引脚在出现起始位后,保持高电平(空闲态)超过 1 个字符时间,自动触发空闲中断
    • 完美解决"不定长"难题。它能自动识别一帧数据的结束,无需在协议中预设长度字段,也不需要复杂的超时判断逻辑。

核心原理

这种方案结合了 DMA(直接存储器访问) 和 IDLE(空闲中断) 的各自优势:

技术点 作用 意义
DMA 自动搬运:串口每收到一个字节,由硬件自动搬运到内存。 解放 CPU,传输过程中不需要 CPU 干预。
IDLE 中断 帧结束判断:当串口总线连续一个字节时间没有数据时触发。 自动断句,完美解决不定长数据的边界识别。
Normal 模式 单次触发:完成一次接收事件(满或空闲)后 DMA 停止。 数据安全,防止处理过程中新数据覆盖旧数据。

下图直观地展示了 UART 总线在空闲帧触发时的逻辑状态变化

CubeMX配置

  1. USART 设置:模式选择 Asynchronous,并在 NVIC 选项卡中勾选 USARTx global interrupt。
  2. DMA 设置:添加 USARTx_RX 通道,Mode 选择 Normal(单次模式),其余保持默认。

建议将串口中断优先级设置得稍高,以保证空闲信号能被及时捕获。

代码实现

在CubeMX配置完成之后,我们生成,在程序初始化阶段,需要执行以下代码启动接收。

c 复制代码
// 1. 启动接收
// rx_buffer: 缓冲区; sizeof(rx_buffer): 最大期望接收长度
HAL_UARTEx_ReceiveToIdle_DMA(&huart1, rx_buffer, sizeof(rx_buffer));

// 2. 屏蔽半满中断 (HT)
// 防止缓冲区搬运到一半时误触发回调,只在"发完"或"收满"时进回调
__HAL_DMA_DISABLE_IT(huart1.hdmarx, DMA_IT_HT);

之后我们需要开始编写事件回调函数,当硬件检测到"空闲"或"缓冲区满"时,会自动跳入此函数。

c 复制代码
/* * 说明:Size 参数是由 HAL 库自动计算的,代表本次实际收到的字节数。
 */
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{
    if (huart->Instance == USART1)
    {
        // --- 业务处理开始 ---
        // 此时数据已完整存放在 rx_buffer 中,长度为 Size
        HAL_UART_Transmit(&huart1, rx_buffer, Size, 100); // 示例:回传数据
        // --- 业务处理结束 ---

        // --- 关键:手动重启接收 ---
        // 在 Normal 模式下,回调结束后接收会停止。必须再次调用以启动下一包的监听。
        HAL_UARTEx_ReceiveToIdle_DMA(&huart1, rx_buffer, sizeof(rx_buffer));
        __HAL_DMA_DISABLE_IT(huart1.hdmarx, DMA_IT_HT); // 记得再次关闭半满中断
    }
}

建议在空闲中断中,将数据通过 memcpy 函数拷贝到 FIFO 或另一个缓冲区,然后立即重启 DMA。不要在中断处理。

注意事项

如果你发送的数据长度超过了 rx_buffer 的设定值(例如发送 20 字节,Buffer 只有 10 字节),你会发现回调函数进入了两次。

  1. 第一次触发(DMA 满中断):前 10 字节填满缓冲区,DMA 停止并触发回调,Size = 10。
  2. 第二次触发(空闲中断):你在回调里重启了接收,后 10 字节继续进入,发完后总线空闲,再次触发回调,Size = 10。

解决方案:请确保 rx_buffer 的长度大于你预期单包数据的最大长度。

健壮性优化

在工业现场,电磁干扰可能触发 Overrun (ORE) 错误,导致 DMA 接收永久停止。必须重写错误回调函数:

c 复制代码
void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart)
{
    if (huart->Instance == USART1)
    {
        // 清除溢出错误标志(HAL库内部通常已处理,但显式重启是必要的)
        // 重启接收以恢复通信
        HAL_UARTEx_ReceiveToIdle_DMA(&huart1, rx_buffer, sizeof(rx_buffer));
        __HAL_DMA_DISABLE_IT(huart1.hdmarx, DMA_IT_HT);
    }
}
相关推荐
d111111111d14 小时前
STM32如何通过寄存器直接禁止EXTI0中断
笔记·stm32·单片机·嵌入式硬件·学习
PN杰15 小时前
通过matlab处理Tek示波器导出的.tss波形文件
stm32·单片机·matlab
cpp_250115 小时前
P8597 [蓝桥杯 2013 省 B] 翻硬币
数据结构·c++·算法·蓝桥杯·题解
Sumerking16 小时前
DMM 高精度采样部分
单片机·mcu
点灯小铭16 小时前
基于单片机的蔬菜大棚温湿度远程测报系统设计
单片机·嵌入式硬件·毕业设计·课程设计·期末大作业
v先v关v住v获v取16 小时前
自动搬运车结构设计9张cad+三维图+设计说明书
科技·单片机·51单片机
别了,李亚普诺夫16 小时前
ADC-学习笔记
笔记·stm32·学习
Zeku17 小时前
20251231 - Linux 字符设备驱动开发笔记:分层设计
stm32·freertos·linux驱动开发·linux应用开发
一路往蓝-Anbo17 小时前
STM32单线串口通讯实战(二):链路层核心 —— DMA环形缓冲与收发切换时序
c语言·开发语言·stm32·单片机·嵌入式硬件·物联网