前言

做嵌入式开发多年,尤其是工业现场、传感器数据采集这类场景,串口通信绝对是高频刚需。很多朋友用STM32做串口收发,初期用普通中断、查询方式勉强能用,但一旦遇到大数据帧、高速波特率、多设备并发通信,立马出现丢包、卡顿、CPU占用率拉满的问题。

深耕STM32全场景开发以来,我在电机控制、数据采集项目里反复验证:串口DMA+空闲中断是解决这类问题的最优解,既能解放CPU,又能保证数据完整性,适配绝大多数工业严苛环境。

本篇先深挖底层理论,把DMA、空闲中断的工作逻辑、寄存器机制、优劣对比讲透,再上STM32F103 HAL库实战代码,手把手教大家配置,解决分包、丢包痛点,代码可直接移植到STM32CubeIDE/CubeMX工程,新手也能吃透上手。

适用场景:工业串口数据采集、传感器上报、多机串口通信、高速波特率(115200及以上)收发,拒绝丢包、拒绝CPU阻塞。

一、串口通信常规方案痛点剖析(理论深挖)

在STM32嵌入式开发中,串口(UART/USART)属于异步串行通信,收发双方仅依靠波特率同步数据,无时钟线协同传输,这一特性决定了数据帧边界判定是通信稳定的核心。新手常用的两种方案,底层机制存在先天缺陷,完全无法适配工业场景的严苛要求。

1.1 查询式收发(轮询模式)

查询模式是最原始的串口处理方式,CPU需要在死循环中持续读取串口状态寄存器(SR),判断RXNE位是否置位(数据就绪),再读取数据寄存器(DR)获取数据。

  • 底层逻辑:CPU独占式等待,无数据时空转消耗资源,有数据时逐字节搬运,全程占用CPU内核;

  • 致命缺陷:CPU利用率接近100%,无法处理电机驱动、ADC采集、逻辑运算等实时任务,工业多任务系统中直接宕机;

  • 适用场景:极简裸机测试、无其他任务的单点调试,无工业实用价值。

1.2 单字节中断收发(传统中断模式)

传统串口中断采用字节触发机制,UART每接收/发送1个字节,硬件自动置位RXNE/TXE中断标志,触发中断服务函数,CPU负责单个字节的缓存搬运。

  • 底层逻辑:硬件触发中断,CPU逐字节处理,中断触发频率与数据长度成正比,数据越长中断越频繁;

  • 致命缺陷:高速波特率、大数据帧下,中断频繁触发导致线程切换开销剧增,引发中断嵌套冲突、数据漏读、系统抖动;且无帧结束标志,无法判定数据边界,极易出现分包、粘包问题。

1.3 三种串口方案核心对比表

方案类型 CPU占用率 中断频率 帧完整性 工业场景适配
查询轮询 极高(≈100%) 无中断 不适配
单字节中断 极高(逐字节触发) 极差(分包粘包) 不适配
DMA+空闲中断 极低(仅处理帧数据) 极低(整帧触发) 极佳(整帧处理) 完美适配

二、DMA+空闲中断方案核心理论(底层吃透)

想要把DMA+空闲中断用稳、用透,必须先理解两大核心模块的硬件工作原理、寄存器机制、协同逻辑,而非单纯照搬代码。

2.1 DMA控制器深度原理

DMA全称Direct Memory Access(直接存储器访问),是STM32内置的独立硬件控制器,拥有独立的总线矩阵访问权限,无需CPU干预,即可完成外设与内存、内存与内存之间的高速数据传输,相当于系统的“专职数据搬运工”。

2.1.1 DMA核心工作机制
  • 总线控制权:DMA通过总线仲裁机制,临时获取系统总线控制权,避开CPU直接读写外设和内存;

  • 传输三要素:外设基地址、内存基地址、传输长度,三大参数配置完成后,DMA自动完成批量搬运;

  • 地址递增特性:支持外设地址固定、内存地址自增,适配串口这类单寄存器收发场景;

  • 传输模式:正常模式(传输完成后停止)、循环模式(连续传输),串口接收推荐正常模式。

2.1.2 STM32F103 DMA硬件特性
  • 内置2个DMA控制器:DMA1(7个通道)、DMA2(5个通道),串口1接收固定映射DMA1 Channel5,硬件绑定不可更改;

  • 支持3种传输方向:外设→内存(串口/ADC接收)、内存→外设(串口发送)、内存→内存(数据拷贝);

  • 数据宽度支持:字节(8bit)、半字(16bit)、字(32bit),串口收发统一配置为字节宽度;

  • HAL库中通过Handle句柄管理DMA,无需直接操作寄存器,降低开发门槛。

2.2 串口空闲中断(IDLE)深度原理

空闲中断是UART外设独有的帧结束中断,并非针对单个字节触发,而是针对一整帧数据传输完毕触发,是解决串口分包、粘包问题的核心。

2.2.1 空闲中断触发条件

当UART接收完最后一个字节后,串口总线持续一个字节传输时间的空闲状态(无数据传输),硬件自动置位IDLE中断标志,触发中断服务函数。

2.2.2 关键寄存器操作
  • 标志置位:USART_SR寄存器的IDLE位,帧结束空闲时自动置1;

  • 标志清除:硬件强制要求,先读USART_SR寄存器,再读USART_DR寄存器,缺一不可,否则标志无法清除,系统会卡死在中断;

  • 中断使能:通过USART_CR1寄存器的IDLEIE位使能空闲中断,HAL库需单独开启。

2.2.3 空闲中断核心价值

精准判定数据帧结束时机,配合DMA批量搬运,实现“整帧接收、整帧处理”,彻底解决异步串口无法识别数据边界的行业痛点。

2.3 DMA+空闲中断协同工作逻辑

两大模块各司其职、无缝协同,形成完整的接收闭环:

  • DMA负责搬运:串口收到字节时,DMA自动将数据从USART_DR寄存器搬运到内存缓存,全程CPU不参与;

  • 空闲中断负责收尾:一帧数据传输完毕,总线空闲触发IDLE中断,通知CPU处理完整数据帧;

  • CPU负责运算:仅在整帧数据就绪后参与解析,其余时间可处理电机、PID、传感器等核心任务。

三、DMA+空闲中断工作流程导图

为了让大家直观掌握整套执行逻辑,绘制标准化流程图,吃透流程再写代码,调试少走90%弯路:

系统上电初始化

CubeMX配置GPIO/串口/DMA/NVIC

HAL库初始化+使能IDLE空闲中断

启动DMA接收,CPU执行主任务

串口收到数据?

DMA自动搬运数据至内存缓存

帧传输完毕+总线空闲?

硬件置位IDLE中断标志

进入USART1中断服务函数

清除IDLE中断标志(读SR+DR)

关闭DMA,计算实际接收长度

置位接收完成标志

CPU处理完整数据帧

重启DMA接收,等待下一帧

整套流程硬件自动化执行,无CPU干预,既保证传输效率,又兼顾实时性,完全契合工业场景需求。

四、STM32F103 HAL库实战配置(直接移植)

基于STM32CubeIDE + HAL库,以USART1为例,波特率115200,DMA1 Channel5接收,配合空闲中断实现整帧数据接收,可直接移植到F103全系工程。

前置说明:需先在CubeMX中开启USART1、对应DMA接收通道、NVIC中断分组,生成基础初始化代码。

4.1 全局变量定义(.h/.c文件声明)

/* ------------------- main.h 中添加 ------------------- */
#include "stm32f1xx_hal.h"

#define USART1_RECV_LEN  200    // 最大接收帧长度
extern uint8_t USART1_RECV_BUF[USART1_RECV_LEN];  // 接收缓存
extern uint16_t USART1_RECV_CNT;                  // 实际接收长度
extern uint8_t USART1_RECV_OVER;                  // 接收完成标志

/* ------------------- main.c 中定义 ------------------- */
uint8_t USART1_RECV_BUF[USART1_RECV_LEN] = {0};
uint16_t USART1_RECV_CNT = 0;
uint8_t USART1_RECV_OVER = 0;

// 串口句柄外部声明
extern UART_HandleTypeDef huart1;

4.2 空闲中断使能函数

/**
  * @brief  使能串口空闲中断(HAL库默认不开启,需手动调用)
  * @param  huart: 串口句柄
  * @retval 无
  */
void HAL_UART_EnableIdleInterrupt(UART_HandleTypeDef *huart)
{
  if(huart->Instance == USART1)
  {
    __HAL_UART_CLEAR_IDLEFLAG(huart);  // 清除空闲标志
    __HAL_UART_ENABLE_IT(huart, UART_IT_IDLE); // 使能IDLE中断
  }
}

4.3 串口中断服务函数(HAL库适配)

/**
  * @brief  USART1中断服务函数
  * @param  无
  * @retval 无
  */
void USART1_IRQHandler(void)
{
  // 调用HAL库公共中断处理函数
  HAL_UART_IRQHandler(&huart1);

  // 判断是否为空闲中断触发
  if(__HAL_UART_GET_FLAG(&huart1, UART_FLAG_IDLE) != RESET)
  {
    // 清除空闲中断标志(硬件要求:先读SR,再读DR)
    __HAL_UART_CLEAR_IDLEFLAG(&huart1);

    // 暂停DMA,防止数据覆盖
    HAL_UART_DMAStop(&huart1);

    // 计算实际接收长度:总长度 - DMA剩余计数
    USART1_RECV_CNT = USART1_RECV_LEN - __HAL_DMA_GET_COUNTER(huart1.hdmarx);
    // 置位接收完成标志
    USART1_RECV_OVER = 1;
  }
}

4.4 主函数初始化与业务逻辑

int main(void)
{
  // HAL库初始化(CubeMX自动生成)
  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init();
  MX_DMA_Init();
  MX_USART1_UART_Init();

  // 使能串口空闲中断(关键步骤)
  HAL_UART_EnableIdleInterrupt(&huart1);
  // 启动DMA接收
  HAL_UART_Receive_DMA(&huart1, USART1_RECV_BUF, USART1_RECV_LEN);

  while (1)
  {
    // 判断一帧数据是否接收完成
    if(USART1_RECV_OVER)
    {
      /* ------------------- 数据处理逻辑 ------------------- */
      // 示例:回显测试
      HAL_UART_Transmit_DMA(&huart1, USART1_RECV_BUF, USART1_RECV_CNT);
      // 工业场景:添加帧解析、校验、指令执行逻辑

      /* ------------------- 重置接收 ------------------- */
      USART1_RECV_CNT = 0;
      USART1_RECV_OVER = 0;
      // 重启DMA,等待下一帧数据
      HAL_UART_Receive_DMA(&huart1, USART1_RECV_BUF, USART1_RECV_LEN);
    }
  }
}

4.5 CubeMX关键配置步骤

  1. Pinout配置:PA9=USART1_TX,PA10=USART1_RX;

  2. USART1配置:Mode选择Asynchronous,波特率115200,8N1;

  3. DMA Settings:Add添加DMA1 Channel5,Direction选择Peripheral To Memory,Memory Increment使能;

  4. NVIC配置:开启USART1全局中断,配置抢占优先级、子优先级;

  5. Clock Configuration配置系统时钟,生成代码即可。

五、工程调试避坑指南(HAL库专属)

  1. 必须手动使能空闲中断:HAL库默认不开启IDLE中断,需调用**__HAL_UART_ENABLE_IT**;

  2. DMA句柄必须关联:CubeMX生成代码时,确保huart1.hdmarx正确关联DMA1 Channel5;

  3. 中断优先级合理配置:工业场景串口优先级低于急停、电机、故障中断;

  4. 禁止在中断内耗时操作:中断服务函数仅做标志位和长度计算,数据处理放主循环;

  5. 缓存区防止溢出:根据实际帧长度设置USART1_RECV_LEN,避免越界;

  6. DMA重启逻辑:正常模式下,每处理完一帧必须重启HAL_UART_Receive_DMA。

六、写在最后

串口DMA+空闲中断是STM32工业开发的核心技能,相比标准库,HAL库封装更完善、移植性更强,适配CubeMX可视化配置,大幅降低开发门槛。我在电机控制、环境监测、工业网关等多个项目中批量应用,长时间高温、强电磁干扰环境下运行零丢包,完全满足工业场景可靠性要求。

本篇代码基于HAL库编写,兼容STM32F103全系核心板,移植至其他串口只需修改句柄、DMA通道和引脚即可。后续会继续分享TMC驱动、PID算法、C语言底层调试、Bug排查等实战干货,欢迎点赞收藏,有问题评论区交流。


🎁欢迎关注,获取更多技术干货!

公众号:BackCatK Chen,文章末尾可以扫码关注

🎁资料包亮点

这份资料包涵盖了从硬件电路设计STM32单片机开发,再到Linux系统学习的全链路内容,适合不同阶段的学习者:

  • 硬件基础:包含硬件电路合集、硬件设计开发工具包,帮你打牢底层基础。
  • STM32专项:从环境搭建、开发工具、传感器模块到项目实战,还有书籍和芯片手册,一站式搞定STM32学习。
  • C语言进阶:C语言学习资料包,助你掌握嵌入式开发的核心语言。
  • 面试求职:嵌入式面试题合集,提前备战技术面试。
  • Linux拓展:Linux相关学习资料包,拓宽技术视野。
📂资料包目录
  • 00-STM32单片机环境搭建
  • 01-硬件电路合集
  • 02-硬件设计开发工具包
  • 03-C语言学习资料包
  • 04-STM32单片机开发工具包
  • 05-STM32传感器模块合集
  • 06-STM32项目合集
  • 07-STM32单片机书籍&芯片手册
  • 08-Linux相关学习资料包
    在这里插入图片描述
Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐