news 2026/6/11 6:14:03

STM32F4实战:5分钟搞定串口DMA发送,解放CPU就这么简单

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
STM32F4实战:5分钟搞定串口DMA发送,解放CPU就这么简单

STM32F4实战:5分钟搞定串口DMA发送,解放CPU就这么简单

在嵌入式开发中,串口通信是最基础也最常用的功能之一。但当我们需要频繁发送大量数据时,比如日志记录、传感器数据上传等场景,传统的串口发送方式会严重占用CPU资源。想象一下,你的系统正在处理关键任务,却因为串口发送数据而频繁中断,性能瓶颈就这样产生了。

STM32F4系列微控制器内置的DMA(直接内存访问)功能,正是解决这一痛点的利器。DMA可以在不占用CPU资源的情况下,自动完成数据从内存到外设的传输。今天,我们就以USART1为例,手把手教你如何在5分钟内配置好串口DMA发送,让你的CPU从此摆脱串口发送的负担。

1. 硬件连接与CubeMX配置

1.1 硬件连接检查

在开始之前,确保你的硬件连接正确:

  • USART1_TX引脚(PA9)连接到串口转USB模块的RX
  • 确保共地连接
  • 供电电压符合要求(通常3.3V)

1.2 CubeMX基础配置

使用STM32CubeMX可以大幅简化初始化流程:

  1. 打开CubeMX,选择你的STM32F4型号
  2. 在"Pinout & Configuration"选项卡中启用USART1
    • Mode: Asynchronous
    • Baud Rate: 115200(根据需求调整)
    • Word Length: 8 bits
    • Parity: None
    • Stop Bits: 1
  3. 启用DMA
    • 点击"DMA Settings"添加新配置
    • 选择USART1_TX
    • Direction: Memory To Peripheral
    • Priority: Medium(根据系统需求调整)
    • Mode: Normal(非循环模式)

关键参数对比表

参数推荐值说明
DMA模式Normal单次传输模式
数据宽度Byte与USART数据宽度匹配
内存地址增量Enable发送数组时需要
外设地址增量DisableUSART数据寄存器固定地址
FIFO阈值1/4 Full平衡性能和延迟

2. 代码实现详解

2.1 DMA初始化代码

以下是使用HAL库的DMA初始化代码示例:

// DMA控制器时钟使能 __HAL_RCC_DMA2_CLK_ENABLE(); // DMA句柄配置 hdma_usart1_tx.Instance = DMA2_Stream7; hdma_usart1_tx.Init.Channel = DMA_CHANNEL_4; hdma_usart1_tx.Init.Direction = DMA_MEMORY_TO_PERIPHERAL; hdma_usart1_tx.Init.PeriphInc = DMA_PINC_DISABLE; hdma_usart1_tx.Init.MemInc = DMA_MINC_ENABLE; hdma_usart1_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE; hdma_usart1_tx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE; hdma_usart1_tx.Init.Mode = DMA_NORMAL; hdma_usart1_tx.Init.Priority = DMA_PRIORITY_MEDIUM; hdma_usart1_tx.Init.FIFOMode = DMA_FIFOMODE_ENABLE; hdma_usart1_tx.Init.FIFOThreshold = DMA_FIFO_THRESHOLD_FULL; hdma_usart1_tx.Init.MemBurst = DMA_MBURST_SINGLE; hdma_usart1_tx.Init.PeriphBurst = DMA_PBURST_SINGLE; HAL_DMA_Init(&hdma_usart1_tx); // 关联DMA到USART __HAL_LINKDMA(&huart1, hdmatx, hdma_usart1_tx);

注意:STM32F4的DMA通道与数据流对应关系需要查阅参考手册,USART1_TX通常对应DMA2 Stream7 Channel4。

2.2 数据发送函数

配置好DMA后,发送数据变得非常简单:

void USART1_Send_DMA(uint8_t *data, uint16_t length) { // 等待上一次传输完成 while(HAL_DMA_GetState(&hdma_usart1_tx) == HAL_DMA_STATE_BUSY); // 启动DMA传输 HAL_UART_Transmit_DMA(&huart1, data, length); }

实际调用时只需:

uint8_t buffer[] = "Hello DMA!"; USART1_Send_DMA(buffer, sizeof(buffer)-1); // 减1去除结尾的'\0'

3. 性能对比与优化

3.1 CPU占用率测试

我们通过简单的测试来对比有无DMA时的CPU占用情况:

测试条件

  • 发送1KB数据
  • 系统时钟168MHz
  • 串口波特率115200

测试结果

发送方式CPU占用率发送耗时
轮询发送~85%90ms
中断发送~30%90ms
DMA发送<1%90ms

提示:虽然DMA不会减少传输时间(受限于串口波特率),但能极大释放CPU资源。

3.2 常见性能优化技巧

  • 双缓冲技术:准备两个缓冲区,当一个缓冲区通过DMA发送时,CPU可以填充另一个缓冲区
  • 循环DMA模式:适用于持续发送数据的场景,如波形输出
  • 内存对齐:确保数据缓冲区地址对齐到4字节边界,可提升DMA效率

4. 实战中的坑与解决方案

4.1 传输完成标志问题

很多开发者会遇到DMA发送不完整或重复发送的问题,通常是因为:

  1. 未正确检查传输完成标志
  2. 未清除传输完成标志
  3. 在传输完成前修改了缓冲区内容

正确的中断处理方式

void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart) { if(huart->Instance == USART1) { // 传输完成处理逻辑 // 可以在这里启动下一次传输或设置标志位 } }

4.2 缓冲区对齐问题

DMA对内存访问有对齐要求,不当的对齐会导致数据错误。解决方案:

  • 使用编译器指令强制对齐:
__attribute__((aligned(4))) uint8_t buffer[1024];
  • 或者使用标准库提供的对齐分配:
uint8_t *buffer = (uint8_t*)memalign(4, 1024);

4.3 DMA与Cache一致性问题

在启用Cache的系统中(如STM32F4带有CCM),需要特别注意:

  1. 发送前确保数据已写入物理内存:
SCB_CleanDCache_by_Addr((uint32_t*)buffer, length);
  1. 接收时无效化Cache区域:
SCB_InvalidateDCache_by_Addr((uint32_t*)buffer, length);

5. 进阶应用:高效日志系统实现

结合DMA和串口,我们可以构建一个高效的日志输出系统:

5.1 环形缓冲区实现

#define LOG_BUF_SIZE 2048 typedef struct { uint8_t buffer[LOG_BUF_SIZE]; volatile uint16_t head; volatile uint16_t tail; volatile uint8_t dma_busy; } log_buffer_t; log_buffer_t log_buf; void log_putc(uint8_t c) { uint16_t next = (log_buf.head + 1) % LOG_BUF_SIZE; while(next == log_buf.tail); // 缓冲区满时等待 log_buf.buffer[log_buf.head] = c; log_buf.head = next; if(!log_buf.dma_busy) { log_buf.dma_busy = 1; uint16_t len = (log_buf.head >= log_buf.tail) ? (log_buf.head - log_buf.tail) : (LOG_BUF_SIZE - log_buf.tail); USART1_Send_DMA(&log_buf.buffer[log_buf.tail], len); } }

5.2 DMA传输完成回调

void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart) { if(huart->Instance == USART1) { log_buf.tail = (log_buf.tail + hdma_usart1_tx.Instance->NDTR) % LOG_BUF_SIZE; if(log_buf.head != log_buf.tail) { uint16_t len = (log_buf.head >= log_buf.tail) ? (log_buf.head - log_buf.tail) : (LOG_BUF_SIZE - log_buf.tail); USART1_Send_DMA(&log_buf.buffer[log_buf.tail], len); } else { log_buf.dma_busy = 0; } } }

这种实现方式可以确保日志输出几乎不占用CPU时间,同时不会丢失任何日志信息。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/11 6:14:03

2025终极指南:8大网盘直链下载助手,告别限速烦恼

2025终极指南&#xff1a;8大网盘直链下载助手&#xff0c;告别限速烦恼 【免费下载链接】Online-disk-direct-link-download-assistant 一个基于 JavaScript 的网盘文件下载地址获取工具。基于【网盘直链下载助手】修改 &#xff0c;支持 百度网盘 / 阿里云盘 / 中国移动云盘 …

作者头像 李华
网站建设 2026/6/11 6:13:02

实体店要不要做小程序?不盲目跟风,看懂 4 大实用价值再投入

很多实体店老板很迷茫&#xff1a;身边同行都在做小程序&#xff0c;自己不做怕落后&#xff0c;做了又不知道有什么用、能不能赚钱。我见过很多实体店老板跟风做了小程序&#xff0c;结果放在那里不用&#xff0c;白白浪费钱&#xff1b;也见过很多老板用小程序&#xff0c;把…

作者头像 李华
网站建设 2026/6/11 6:02:55

Qt Quick 05|QML 与 C++ 交互:C++ 暴露属性 / 方法、QML 调用 C++

正文 Qt Quick 标准架构&#xff1a;QML 负责界面展示&#xff0c;C 负责业务逻辑、底层运算、硬件 / 网络操作。二者交互是混合开发核心。 一、交互前置规则 暴露给 QML 的 C 类必须继承 QObject&#xff1b;属性、方法、信号槽需要使用 Qt 元对象系统&#xff08;Q_OBJECT …

作者头像 李华
网站建设 2026/6/11 5:59:54

嵌入式通信实战:用C语言把浮点数拆成HEX-ASCII码发送(附完整代码)

嵌入式通信实战&#xff1a;用C语言实现浮点数到HEX-ASCII的高效转换在物联网设备与嵌入式系统开发中&#xff0c;数据通信的效率和可靠性往往是项目成败的关键。当我们面对温度传感器输出的23.78℃或压力传感器传回的1013.25hPa时&#xff0c;这些浮点数如何穿越UART、CAN或Lo…

作者头像 李华