news 2026/4/3 7:51:59

从零到一:STM32串口通信中的DMA技术实战与性能优化

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
从零到一:STM32串口通信中的DMA技术实战与性能优化

从零到一:STM32串口通信中的DMA技术实战与性能优化

在嵌入式系统开发中,串口通信是最基础也最常用的外设接口之一。无论是调试信息输出、设备间数据交换,还是与上位机通信,串口都扮演着重要角色。然而,传统的轮询和中断方式在处理大量数据时往往效率低下,这时DMA(直接内存访问)技术就能大显身手。

1. DMA技术基础与串口通信

DMA(Direct Memory Access)是一种允许外设直接与内存交换数据而不需要CPU干预的技术。在STM32微控制器中,DMA控制器可以自动完成数据在内存和外设之间的传输,从而解放CPU去处理其他任务。

DMA在串口通信中的优势

  • 降低CPU负载:CPU只需初始化DMA传输,之后可以处理其他任务
  • 提高传输效率:DMA可以连续传输大量数据而不需要频繁中断
  • 减少延迟:避免了中断处理带来的上下文切换开销

STM32的DMA控制器主要特性:

typedef struct { __IO uint32_t CCR; // 通道配置寄存器 __IO uint32_t CNDTR; // 数据传输数量寄存器 __IO uint32_t CPAR; // 外设地址寄存器 __IO uint32_t CMAR; // 内存地址寄存器 } DMA_Channel_TypeDef;

2. STM32 DMA串口通信配置实战

2.1 硬件连接与CubeMX配置

以STM32F4系列为例,配置USART2使用DMA的步骤如下:

  1. 在CubeMX中启用USART2异步模式
  2. 配置波特率、数据位、停止位等参数
  3. 在DMA Settings标签页添加USART2_TX和USART2_RX的DMA通道
  4. 配置DMA参数:
    • 方向:外设到内存或内存到外设
    • 优先级:根据需求选择
    • 内存地址递增:使能
    • 外设地址不递增
    • 数据宽度:字节
    • 模式:普通模式或循环模式

关键配置参数对比

参数TX配置RX配置
方向内存到外设外设到内存
地址递增内存递增内存递增
模式普通普通
数据宽度字节字节
FIFO阈值1/41/4

2.2 代码实现

初始化完成后,生成代码并添加以下关键部分:

// DMA发送函数 void UART_DMA_Send(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size) { HAL_UART_Transmit_DMA(huart, pData, Size); // 可以通过HAL_UART_TxCpltCallback回调函数获知发送完成 } // DMA接收函数 void UART_DMA_Receive(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size) { HAL_UART_Receive_DMA(huart, pData, Size); // 可以通过HAL_UART_RxCpltCallback回调函数获知接收完成 } // 发送完成回调函数 void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart) { if(huart->Instance == USART2) { // 发送完成处理 } } // 接收完成回调函数 void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { if(huart->Instance == USART2) { // 接收完成处理 } }

3. DMA性能优化技巧

3.1 双缓冲技术

双缓冲技术可以有效避免数据处理和传输的冲突:

#define BUF_SIZE 256 uint8_t rx_buf1[BUF_SIZE]; uint8_t rx_buf2[BUF_SIZE]; uint8_t *current_buf = rx_buf1; void Start_Double_Buffer_Receive(void) { HAL_UART_Receive_DMA(&huart2, rx_buf1, BUF_SIZE); } void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { if(huart->Instance == USART2) { if(current_buf == rx_buf1) { // 处理rx_buf1数据 HAL_UART_Receive_DMA(&huart2, rx_buf2, BUF_SIZE); current_buf = rx_buf2; } else { // 处理rx_buf2数据 HAL_UART_Receive_DMA(&huart2, rx_buf1, BUF_SIZE); current_buf = rx_buf1; } } }

3.2 空闲中断结合DMA

不定长数据接收是串口通信中的常见需求,可以通过空闲中断实现:

void UART_Init_IDLE_Receive(void) { __HAL_UART_ENABLE_IT(&huart2, UART_IT_IDLE); HAL_UART_Receive_DMA(&huart2, rx_buffer, BUFFER_SIZE); } void USART2_IRQHandler(void) { if(__HAL_UART_GET_FLAG(&huart2, UART_FLAG_IDLE)) { __HAL_UART_CLEAR_IDLEFLAG(&huart2); uint32_t received = BUFFER_SIZE - __HAL_DMA_GET_COUNTER(huart2.hdmarx); // 处理接收到的received字节数据 HAL_UART_Receive_DMA(&huart2, rx_buffer, BUFFER_SIZE); } HAL_UART_IRQHandler(&huart2); }

3.3 内存对齐优化

DMA传输对内存对齐有要求,优化内存布局可以提高性能:

__attribute__((aligned(4))) uint8_t dma_buffer[1024]; // 4字节对齐

4. 常见问题与解决方案

4.1 DMA传输不启动

可能原因

  • DMA时钟未使能
  • DMA通道配置错误
  • 外设未正确初始化

解决方案

// 检查DMA时钟 __HAL_RCC_DMA1_CLK_ENABLE(); // 检查DMA初始化 hdma_usart2_tx.Instance = DMA1_Stream6; hdma_usart2_tx.Init.Channel = DMA_CHANNEL_4; // ...其他参数配置

4.2 数据丢失或错位

可能原因

  • 缓冲区溢出
  • 传输速度不匹配
  • 中断优先级冲突

解决方案

  • 增大缓冲区大小
  • 调整波特率或使用硬件流控
  • 合理设置中断优先级:
HAL_NVIC_SetPriority(DMA1_Stream6_IRQn, 0, 0); HAL_NVIC_EnableIRQ(DMA1_Stream6_IRQn);

4.3 低功耗模式下的DMA问题

在低功耗模式下,DMA可能无法正常工作。解决方案:

// 进入低功耗前 HAL_UART_DMAStop(&huart2); // 唤醒后重新初始化 MX_USART2_UART_Init(); MX_DMA_Init(); HAL_UART_Receive_DMA(&huart2, rx_buffer, BUFFER_SIZE);

5. 实战案例:物联网数据采集系统

以一个典型的物联网数据采集系统为例,展示DMA在实际项目中的应用:

系统架构

  • STM32F407作为主控
  • 多个传感器通过UART接口连接
  • LoRa模块用于无线传输
  • DMA处理所有串口通信

关键代码实现

// 传感器数据结构 typedef struct { float temperature; float humidity; uint32_t timestamp; } SensorData; // DMA接收缓冲区 __attribute__((aligned(4))) uint8_t sensor_rx_buf[2][sizeof(SensorData)]; volatile uint8_t current_rx_buf = 0; void Init_Sensor_Communication(void) { // 初始化UART和DMA MX_USART1_UART_Init(); MX_DMA_Init(); // 启用空闲中断 __HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE); // 启动双缓冲DMA接收 HAL_UART_Receive_DMA(&huart1, sensor_rx_buf[0], sizeof(SensorData)); } void USART1_IRQHandler(void) { // 处理空闲中断 if(__HAL_UART_GET_FLAG(&huart1, UART_FLAG_IDLE)) { __HAL_UART_CLEAR_IDLEFLAG(&huart1); // 计算接收到的数据长度 uint32_t received = sizeof(SensorData) - __HAL_DMA_GET_COUNTER(huart1.hdmarx); if(received == sizeof(SensorData)) { // 处理完整数据帧 Process_Sensor_Data((SensorData*)sensor_rx_buf[current_rx_buf]); // 切换缓冲区 current_rx_buf ^= 1; HAL_UART_Receive_DMA(&huart1, sensor_rx_buf[current_rx_buf], sizeof(SensorData)); } } HAL_UART_IRQHandler(&huart1); } void Process_Sensor_Data(SensorData* data) { // 数据校验和处理 if(data->temperature > 100.0f ||>
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/3/20 2:29:22

Phi-4-mini-reasoning实战:5步搭建你的AI数学老师

Phi-4-mini-reasoning实战:5步搭建你的AI数学老师 你是否曾为孩子一道几何题反复讲解却收效甚微?是否在自学高等数学时卡在证明步骤,找不到清晰的推导路径?是否希望有个随时在线、耐心细致、逻辑严密的“一对一数学导师”&#x…

作者头像 李华
网站建设 2026/4/1 22:42:36

基于74LS160与Multisim的12小时制数字时钟仿真设计

1. 74LS160芯片在数字时钟中的核心作用 74LS160是一款经典的4位二进制同步计数器芯片,在数字时钟设计中扮演着核心角色。我第一次用这个芯片做时钟项目时,发现它最大的优势就是同步计数特性——所有触发器在同一个时钟脉冲下同时翻转,避免了异…

作者头像 李华
网站建设 2026/3/28 1:02:32

动手实操:基于科哥UNet镜像的AI抠图全流程记录

动手实操:基于科哥UNet镜像的AI抠图全流程记录 1. 为什么这次抠图体验让我停不下来 上周给客户做电商主图,三张人像图手动抠图花了两小时——发丝边缘反复调整、背景残留白边、导出后在手机上一看又漏了半缕头发。直到我点开科哥这个 cv_unet_image-ma…

作者头像 李华
网站建设 2026/3/23 22:57:03

MGeo模型加载慢?FP16模式开启提速建议

MGeo模型加载慢?FP16模式开启提速建议 在实际部署 MGeo 地址相似度匹配实体对齐-中文-地址领域 镜像时,不少开发者反馈:模型首次加载耗时过长,单次推理启动延迟明显,影响本地调试效率与服务冷启动体验。尤其在 RTX 40…

作者头像 李华
网站建设 2026/3/14 18:49:21

BBDown:B站视频下载的开源解决方案

BBDown:B站视频下载的开源解决方案 【免费下载链接】BBDown Bilibili Downloader. 一款命令行式哔哩哔哩下载器. 项目地址: https://gitcode.com/gh_mirrors/bb/BBDown 副标题:如何高效解决B站视频离线保存难题? 一、识别视频下载核心…

作者头像 李华
网站建设 2026/3/28 0:54:51

小白也能用!Qwen3-VL镜像一键搭建图文问答系统

小白也能用!Qwen3-VL镜像一键搭建图文问答系统 标签:#多模态 #Qwen3-VL #图文问答 #CPU部署 #WebUI #零代码部署 你有没有试过这样的情景: 手头有一张商品截图,想快速知道图里写了什么; 孩子交来一张数学题照片&#x…

作者头像 李华