news 2026/6/11 14:28:19

STM32F4 DMA实战:手把手教你用串口DMA发送数据,解放CPU(附完整代码)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
STM32F4 DMA实战:手把手教你用串口DMA发送数据,解放CPU(附完整代码)

STM32F4 DMA实战:手把手教你用串口DMA发送数据,解放CPU(附完整代码)

在嵌入式开发中,串口通信是最基础也最常用的功能之一。但当我们需要传输大量数据时,传统的串口中断方式会频繁打断CPU的正常工作流程,导致系统效率低下。这时,DMA(直接内存访问)技术就能大显身手。本文将带你从零开始,在STM32F4平台上实现串口DMA发送功能,让你的CPU从繁重的数据传输任务中解放出来。

1. DMA基础与硬件准备

1.1 为什么需要DMA

想象一下这样的场景:你的嵌入式设备需要每秒发送数百KB的传感器数据到上位机。如果使用传统的中断方式,每个字节的发送都会产生一次中断,CPU不得不频繁暂停当前任务去处理串口中断。这不仅浪费CPU资源,还可能导致实时任务被延迟。

DMA技术的核心思想是让硬件自己搬运数据。配置好源地址、目标地址和数据量后,DMA控制器会自动完成数据传输,整个过程不需要CPU参与。只有当传输完成时,才会产生一个中断通知CPU。

STM32F4系列有两个DMA控制器,每个控制器有8个数据流(Stream),每个数据流可以映射到不同的通道(Channel)。这种灵活的设计让我们可以同时管理多个外设的DMA传输。

1.2 硬件连接检查

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

  • STM32F4开发板(如STM32F407 Discovery)
  • USB转串口模块(如CH340)
  • 连接线若干

关键引脚检查表

功能STM32F4引脚串口模块引脚
TXDPA9 (USART1_TX)RXD
RXDPA10 (USART1_RX)TXD
GNDGNDGND

提示:不同型号的STM32F4芯片串口引脚可能不同,请查阅对应芯片的数据手册确认。

2. 工程配置与初始化

2.1 创建基础工程

我们使用STM32CubeIDE进行开发,以下是创建工程的步骤:

  1. 打开STM32CubeIDE,选择File > New > STM32 Project
  2. 在MCU/MPU Selector中输入STM32F407VG(根据你的具体芯片选择)
  3. 配置时钟树,确保系统时钟为168MHz(STM32F4的最大主频)
  4. 在Pinout视图中启用USART1和DMA2
// 时钟配置示例(system_stm32f4xx.c中) #define PLL_M 8 #define PLL_N 336 #define PLL_P 2 #define PLL_Q 7

2.2 配置DMA数据流

STM32F4的DMA配置相对复杂,需要特别注意数据流和通道的选择。对于USART1的TX,我们使用DMA2 Stream7 Channel4:

// DMA配置结构体初始化 DMA_HandleTypeDef hdma_usart1_tx; 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_HIGH; hdma_usart1_tx.Init.FIFOMode = DMA_FIFOMODE_DISABLE; if (HAL_DMA_Init(&hdma_usart1_tx) != HAL_OK) { Error_Handler(); } // 将DMA与USART1关联 __HAL_LINKDMA(&huart1, hdmatx, hdma_usart1_tx);

关键参数解析

  • Direction:内存到外设(USART数据寄存器)
  • PeriphInc:外设地址不递增(固定指向USART_DR寄存器)
  • MemInc:内存地址递增(连续发送数组数据)
  • DataAlignment:字节对齐(8位数据)
  • Mode:普通模式(非循环)

3. 实现DMA串口发送

3.1 准备发送数据

DMA传输需要明确三个关键信息:源地址、目标地址和数据长度。我们定义一个全局缓冲区并填充测试数据:

#define BUF_SIZE 1024 uint8_t txBuffer[BUF_SIZE]; void fillTestPattern(void) { for (int i = 0; i < BUF_SIZE; i++) { txBuffer[i] = i % 256; // 填充0-255循环数据 } }

3.2 启动DMA传输

准备好数据后,使用HAL库函数启动DMA传输:

void startDMATransmission(void) { if (HAL_UART_Transmit_DMA(&huart1, txBuffer, BUF_SIZE) != HAL_OK) { // 错误处理 Error_Handler(); } // 可以在这里执行其他任务,DMA会在后台传输数据 processSensorData(); // 示例:处理传感器数据 }

传输过程监控

// 在主循环中检查传输状态 while (1) { if (__HAL_DMA_GET_FLAG(&hdma_usart1_tx, DMA_FLAG_TCIF7)) { // 传输完成 __HAL_DMA_CLEAR_FLAG(&hdma_usart1_tx, DMA_FLAG_TCIF7); LED_Toggle(); // 用LED指示传输完成 break; } }

3.3 中断处理

虽然DMA不需要CPU参与数据传输,但我们通常需要知道传输何时完成:

// 在stm32f4xx_it.c中添加中断处理 void DMA2_Stream7_IRQHandler(void) { HAL_DMA_IRQHandler(&hdma_usart1_tx); } // 回调函数(在main.c中实现) void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart) { if (huart->Instance == USART1) { // 传输完成处理 printf("DMA transmission complete!\r\n"); } }

4. 高级技巧与性能优化

4.1 双缓冲技术

对于连续数据流传输,双缓冲技术可以避免数据覆盖问题:

uint8_t txBuffer1[BUF_SIZE], txBuffer2[BUF_SIZE]; volatile uint8_t activeBuffer = 0; // 在传输完成回调中切换缓冲区 void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart) { if (huart->Instance == USART1) { activeBuffer ^= 1; // 切换缓冲区 if (activeBuffer) { HAL_UART_Transmit_DMA(&huart1, txBuffer1, BUF_SIZE); } else { HAL_UART_Transmit_DMA(&huart1, txBuffer2, BUF_SIZE); } } }

4.2 内存与DMA性能优化

关键优化点

  1. 内存对齐:确保DMA缓冲区地址对齐到4字节边界

    __attribute__((aligned(4))) uint8_t txBuffer[BUF_SIZE];
  2. Cache一致性:如果使用带Cache的STM32型号(如STM32F7/H7),需要维护Cache一致性

    SCB_CleanDCache_by_Addr((uint32_t*)txBuffer, BUF_SIZE);
  3. 突发传输:配置DMA使用突发传输模式提高效率

    hdma_usart1_tx.Init.MemBurst = DMA_MBURST_INC4; hdma_usart1_tx.Init.PeriphBurst = DMA_PBURST_SINGLE;

4.3 常见问题排查

DMA传输不工作的检查清单

  1. 检查DMA和USART时钟是否使能
  2. 确认DMA数据流和通道选择正确
  3. 验证源和目标地址是否正确
  4. 检查缓冲区是否在有效内存区域
  5. 确认DMA中断优先级配置合理
  6. 检查硬件连接,特别是串口TX线

调试技巧

// 在调试时检查DMA寄存器状态 printf("DMA S7CR: 0x%08X\r\n", DMA2_Stream7->CR); printf("DMA S7NDTR: %d\r\n", DMA2_Stream7->NDTR);

5. 完整代码示例

以下是核心代码的完整实现:

/* main.c */ #include "stm32f4xx_hal.h" #define BUF_SIZE 1024 __attribute__((aligned(4))) uint8_t txBuffer[BUF_SIZE]; UART_HandleTypeDef huart1; DMA_HandleTypeDef hdma_usart1_tx; void SystemClock_Config(void); static void MX_GPIO_Init(void); static void MX_DMA_Init(void); static void MX_USART1_UART_Init(void); void fillTestPattern(void) { for (int i = 0; i < BUF_SIZE; i++) { txBuffer[i] = i % 256; } } int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_DMA_Init(); MX_USART1_UART_Init(); fillTestPattern(); HAL_UART_Transmit_DMA(&huart1, txBuffer, BUF_SIZE); while (1) { // 主循环可以执行其他任务 } } /* DMA配置 */ void MX_DMA_Init(void) { __HAL_RCC_DMA2_CLK_ENABLE(); 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_HIGH; hdma_usart1_tx.Init.FIFOMode = DMA_FIFOMODE_DISABLE; HAL_DMA_Init(&hdma_usart1_tx); __HAL_LINKDMA(&huart1, hdmatx, hdma_usart1_tx); HAL_NVIC_SetPriority(DMA2_Stream7_IRQn, 0, 0); HAL_NVIC_EnableIRQ(DMA2_Stream7_IRQn); } /* USART1初始化 */ void MX_USART1_UART_Init(void) { huart1.Instance = USART1; huart1.Init.BaudRate = 115200; huart1.Init.WordLength = UART_WORDLENGTH_8B; huart1.Init.StopBits = UART_STOPBITS_1; huart1.Init.Parity = UART_PARITY_NONE; huart1.Init.Mode = UART_MODE_TX_RX; huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE; huart1.Init.OverSampling = UART_OVERSAMPLING_16; HAL_UART_Init(&huart1); }

6. 实际应用中的经验分享

在多个项目中应用DMA串口发送后,我总结出几点实用经验:

  1. 缓冲区大小选择:不是越大越好,一般1-4KB比较合适。太大会增加内存占用,太小则频繁触发传输完成中断。

  2. 错误恢复:DMA传输可能因各种原因失败,好的做法是加入超时检测和自动重试机制。

  3. 性能测试:在实际项目中,我测得使用DMA的串口传输可以让CPU利用率从70%降低到15%以下(在115200bps持续发送数据时)。

  4. 与RTOS配合:在FreeRTOS等实时操作系统中使用DMA时,要注意任务优先级和DMA中断优先级的协调,避免优先级反转问题。

  5. 电源管理:在低功耗应用中,DMA传输期间CPU可以进入睡眠模式,进一步节省能耗。

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

中文微博情感分类实战:朴素贝叶斯+预训练字词向量一键运行

本文还有配套的精品资源&#xff0c;点击获取 简介&#xff1a;直接跑通中文短文本情感判断任务&#xff0c;用朴素贝叶斯模型处理微博风格文本。内置两个标注好的CSV数据集&#xff08;data1.csv、data2.csv&#xff09;&#xff0c;覆盖正面、负面、中性等常见情感表达&am…

作者头像 李华
网站建设 2026/6/11 14:27:06

中大型组织全流程人事管理软件系统推荐:泛微・聚才林选育用留全覆盖

一、中大型组织人事管理的核心选型标准中大型组织具有人员规模大、组织架构复杂、跨区域运营、管理流程规范、人才需求精细化等特点&#xff0c;选型人事管理软件系统的核心标准聚焦四大方面&#xff1a;一是全流程功能覆盖&#xff0c;满足选育用留全环节管理需求&#xff1b;…

作者头像 李华
网站建设 2026/6/11 14:26:38

AI搜索获客实战分享:亲测有效的方法

行业痛点分析当前&#xff0c;AI搜素领域面临诸多技术挑战。一方面&#xff0c;随着用户从传统搜索引擎转向更智能的AI助手&#xff0c;企业如何在新的搜索环境中获得曝光变得愈发困难。另一方面&#xff0c;数据表明&#xff0c;超过70%的企业在AI搜索中几乎“隐形”&#xff…

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

深入解析PCA9955A:16通道LED恒流驱动与硬件渐变控制实战

1. 项目概述与核心价值在嵌入式开发&#xff0c;尤其是涉及人机交互界面、状态指示或者氛围营造的项目里&#xff0c;控制LED是再基础不过的需求。但当你需要同时驱动十几个甚至几十个LED&#xff0c;并且要求它们能独立调光、平滑渐变&#xff0c;甚至同步闪烁时&#xff0c;事…

作者头像 李华