前言

大家好,这里是 Hello_Embed。前面我们通过 “中断 + 环形缓冲区” 解决了 UART 数据丢失问题,但无论是查询还是中断方式,数据在内存与寄存器之间的搬运都需要 CPU 参与,在高速或大数据量场景下仍会占用过多资源。本篇将介绍DMA 方式—— 让 DMA(直接存储器访问)硬件替 CPU 完成数据搬运,彻底解放 CPU,专注于更复杂的任务。本文先聚焦 DMA 发送,下一篇结合 IDLE 中断讲解 DMA 接收的完整方案。

一、DMA 核心原理:为什么能解放 CPU?

DMA(Direct Memory Access)是一种硬件机制,允许外设(如 UART)与内存直接交换数据,无需 CPU 介入。对比查询 / 中断方式,DMA 的优势一目了然:

1. 传统方式(查询 / 中断)的瓶颈

UART 发送流程:
CPU从内存读数据 → 写入TDR寄存器 → 等待TDR空 → 重复上述步骤

  • 每发送 1 字节,CPU 都要参与 “读内存→写寄存器” 的搬运,频繁占用 CPU 资源;
  • 即使是中断方式,每发送 1 字节仍会触发一次中断(除最后 1 字节),中断处理仍消耗资源。
2. DMA 方式的革新

UART DMA 发送流程:
CPU启动DMA → DMA自动从内存读数据→写入TDR → 完成后触发一次中断通知CPU

  • CPU 只需启动 DMA 传输,后续数据搬运由 DMA 硬件自动完成;
  • 无论发送 100 还是 1000 字节,CPU 仅在全部发送完成后处理一次中断,资源占用极低。
二、UART DMA 发送的三要素

DMA 传输需明确 “源、目的地、长度” 三个核心参数,以 UART 发送为例:

要素 具体内容
数据来源 内存中的发送缓冲区(如tx_buf[1000]),地址随传输递增(每次发送 1 字节后地址 + 1)
目的地 UART 的 TDR 寄存器(发送数据寄存器),地址固定(硬件地址,如 USART1 的 TDR 地址)
数据长度 需发送的总字节数(如 1000 字节),DMA 会自动计数,传输完成后停止
三、CubeMX 配置:启用 UART DMA 发送

在之前工程的基础上,只需添加 DMA 配置,步骤如下:

  1. 进入 USART1 的 DMA 设置
    打开 “Connectivity→USART1→DMA Settings”,点击 “Add” 添加发送 DMA:

    • DirectionMemory To Peripheral(内存到外设,符合发送方向);
    • ModeNormal(正常模式,发送完指定长度后停止,区别于循环模式);
    • PriorityLow(中等优先级,可根据需求调整);
      配置后如图:
      请添加图片描述
  2. 确认 NVIC 中断配置
    进入 “NVIC Settings”,确保 USART1 和 DMA 中断已使能(默认生成,无需额外修改):
    请添加图片描述

  3. 生成代码
    配置完成后生成工程,CubeMX 会自动添加 DMA 相关初始化代码(如MX_DMA_Init)。

四、DMA 发送的核心函数与流程

HAL 库中 UART DMA 发送的核心函数与回调如下,使用方式与中断方式高度兼容:

功能 函数 / 回调 作用
启动 DMA 发送 HAL_UART_Transmit_DMA 配置 DMA 参数并启动传输,CPU 立即返回
发送一半回调 HAL_UART_TxHalfCpltCallback 传输完成一半数据时触发(可选,如 1000 字节发送 500 字节时)
发送完成回调 HAL_UART_TxCpltCallback 全部数据发送完成后触发,通知 CPU
1. 启动 DMA 发送:HAL_UART_Transmit_DMA

函数参数与中断发送函数一致,示例:

HAL_StatusTypeDef HAL_UART_Transmit_DMA(UART_HandleTypeDef *huart, const uint8_t *pData, uint16_t Size)
  • *huart:UART 句柄(如&huart1);
  • *pData:发送缓冲区地址(如字符串首地址);
  • Size:发送字节数(如strlen(str1))。
2. DMA 发送的底层流程

启动后,DMA 硬件自动执行以下步骤,无需 CPU 干预:

  1. DMA 从内存pData读取 1 字节,写入 UART 的 TDR 寄存器;
  2. TDR 将数据转移到移位寄存器发送后,触发 “DMA 请求”;
  3. DMA 响应请求,地址递增(pData+1),长度减 1,重复步骤 1;
  4. 当长度减为 0(全部发送完成),DMA 触发 “传输完成中断”,调用HAL_UART_TxCpltCallback
3. 复用回调函数:与中断方式兼容

DMA 发送完成后,最终会调用HAL_UART_TxCpltCallback(与中断方式的发送完成回调相同)。我们在之前的笔记中已重定义该函数,设置了g_tx_cplt标志,因此可直接复用:

// 在usart.c中已定义的发送完成回调(无需修改)
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{
    if (huart == &huart1)
        g_tx_cplt = 1;  // 置位发送完成标志
}

// 等待发送完成的函数(同样复用)
void Wait_Tx_Complete(void)
{
    while (g_tx_cplt == 0);  // 等待标志置位
    g_tx_cplt = 0;           // 复位标志
}
五、代码实现:DMA 发送 + 中断接收(缓冲区)

结合上一篇的 “中断接收 + 环形缓冲区”,只需将发送部分改为 DMA 方式,即可实现高效收发。

1. main 函数核心代码
/* USER CODE BEGIN PV */
// 声明外部函数:启动接收中断、等待发送完成、读取缓冲区数据
extern void StartUART1Recv(void);
extern void Wait_Tx_Complete(void);
extern int UART1GetChar(uint8_t *pVal);
/* USER CODE END PV */

/* USER CODE BEGIN 2 */
// 1. 启动UART1接收(中断+环形缓冲区,沿用前一篇方案)
StartUART1Recv();

// 2. 定义发送的提示信息和接收变量
char *str1 = "Please enter a char : \r\n";
uint8_t c;  // 存储从缓冲区读取的字节
/* USER CODE END 2 */

/* Infinite loop */
while (1)
{
    /* USER CODE BEGIN 3 */
    // 步骤1:用DMA发送提示信息(仅启动DMA,CPU不等待)
    HAL_UART_Transmit_DMA(&huart1, (uint8_t *)str1, strlen(str1));
    
    // 步骤2:等待DMA发送完成(可选,根据需求决定是否等待)
    Wait_Tx_Complete();
    
    // 步骤3:从环形缓冲区读取数据(沿用中断接收方案)
    while (0 != UART1GetChar(&c));  // 等待读取到数据
    
    // 步骤4:数据加1后返回(简单起见用查询方式发送)
    c += 1;
    HAL_UART_Transmit(&huart1, &c, 1, 1000);
    HAL_UART_Transmit(&huart1, (uint8_t *)"\r\n", 2, 1000);
}
2. 实验结果:DMA 发送高效可靠

烧录程序后,用串口工具发送 “12345” 和 “a”,单片机正确返回 “23456” 和 “b”:
请添加图片描述

关键优势:相比中断发送,DMA 方式在发送大数据时(如 1000 字节),不会频繁触发中断,CPU 可在传输期间处理其他任务(如 LCD 显示、传感器读取)。

六、DMA 接收的挑战与后续

DMA 发送简单易用,但 DMA 接收存在一个核心问题:需要预先知道接收数据的长度(配置 DMA 时需指定Size)。而实际场景中,电脑发送的数据长度往往是不确定的(如用户随机输入),直接使用 DMA 接收会因长度不匹配导致数据丢失或错误。
解决这一问题的方案是 “DMA 接收 + IDLE 中断”:用 DMA 持续接收数据,当检测到 “空闲状态”(一段时间无数据)时,触发 IDLE 中断,通知 CPU 读取已接收的数据。这将是下一篇笔记的核心内容。

结尾

本文介绍了 UART DMA 发送的原理、配置和代码实现,通过 DMA 硬件自动搬运数据,大幅降低了 CPU 的资源占用,适合高速或大数据量发送场景。下一篇我们将结合 IDLE 中断,彻底解决 DMA 接收的 “长度不确定” 问题,实现高效可靠的全双工通信。
Hello_Embed 继续带你深入 UART 的高级应用,敬请期待~

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐