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);
    }
}
相关推荐
2023自学中16 小时前
Cortex-M系列,Cortex-A系列,汇编启动文件的区别
linux·嵌入式硬件
三伏52216 小时前
stm32f103系列手册IIC笔记2
笔记·stm32·嵌入式硬件
changzehai16 小时前
Rust + VSCode + probe-rs搭建stm32-rs嵌入式开发调试环境
vscode·后端·stm32·rust·嵌入式·probe-rs
国科安芯16 小时前
RISC-V架构抗辐照MCU在航天器载荷中的SEU/SEL阈值测试与防护策略
单片机·嵌入式硬件·安全·架构·安全威胁分析·risc-v
猫猫的小茶馆16 小时前
【Linux 驱动开发】三. 应用程序调用驱动过程分析
linux·arm开发·驱动开发·stm32·单片机·嵌入式硬件·pcb工艺
chengpei14717 小时前
Arduino环境下开发STM32
stm32·单片机·嵌入式硬件
三佛科技-1341638421217 小时前
100V8A_HN0801雾化器加湿器MOS管关键特性
单片机·嵌入式硬件·物联网·智能家居·pcb工艺
Embers(余烬矿)18 小时前
STM32 usb 设备描述符失败
stm32·单片机·嵌入式硬件
zd84510150018 小时前
CubeMX H743 lwip ETH初始化流程
网络·stm32·单片机
兆龙电子单片机设计18 小时前
【STM32项目开源】STM32单片机智能温控风扇系统
stm32·单片机·物联网·开源·自动化