欢迎光临
我们一直在努力

DMA详解:为什么USART、SPI、ADC都喜欢用DMA

一、本文适用场景

DMA 是单片机和嵌入式开发中非常重要的一个外设。无论是 STM32、GD32,还是其他 Cortex-M 系列 MCU,只要涉及大量数据搬运,经常都会用到 DMA。

很多初学者在学习 USART、SPI、ADC 时,经常会遇到这些问题:

  • DMA 到底是什么?
  • DMA 和中断有什么区别?
  • 为什么 USART 接收喜欢用 DMA?
  • 为什么 SPI 刷屏、读写 Flash 喜欢用 DMA?
  • 为什么 ADC 连续采样经常配合 DMA?
  • DMA 是不是可以完全替代 CPU?
  • DMA 的源地址、目标地址、传输长度分别是什么?
  • 普通模式和循环模式有什么区别?
  • 半传输中断和传输完成中断有什么作用?
  • DMA 配置了但不工作,应该怎么排查?

本文将从 DMA 的基本概念开始,详细介绍 DMA 的工作原理、核心参数、常见传输方向,以及 USART、SPI、ADC 为什么都适合配合 DMA 使用。


二、什么是DMA

DMA 的英文全称是:

Direct Memory Access

中文通常称为:

直接存储器访问

可以简单理解为:

DMA 是单片机内部的一个硬件数据搬运工。

在没有 DMA 的情况下,外设和内存之间的数据搬运通常需要 CPU 参与。

例如串口收到一个字节:

USART收到数据

CPU进入中断或轮询

CPU读取USART数据寄存器

CPU把数据存入内存数组

如果数据量很小,这样做没有问题。

但如果数据量很大,或者数据连续不断到来,CPU 就会被频繁打断,系统效率会下降。

有了 DMA 后,流程变成:

CPU提前配置DMA

外设产生DMA请求

DMA自动把数据从外设搬到内存

搬完后通知CPU

也就是说:

CPU负责配置
DMA负责搬运

CPU 不需要每个字节都亲自处理。


三、为什么需要DMA

DMA 的核心作用是:

减少CPU参与数据搬运的次数

它特别适合下面几类场景:

  • 数据量大;
  • 数据连续;
  • 传输频繁;
  • 对实时性要求较高;
  • CPU不希望被频繁中断;
  • 外设和内存之间需要高速搬运数据。

例如:

USART高速接收数据
SPI连续刷屏
ADC连续采样
音频数据流
传感器连续数据
内存块复制

这些场景如果完全靠 CPU 逐字节搬运,会造成较大负担。

DMA 可以让 CPU 从“搬运工”变成“调度员”。


在这里插入图片描述

图1 DMA 基础概念与工作流程

DMA 可以在外设与内存之间,或者内存与内存之间自动搬运数据。CPU 主要负责配置源地址、目标地址、传输长度和触发源。


四、没有DMA时CPU怎么搬数据

以 USART 接收为例。

如果不用 DMA,串口每收到一个字节,CPU 通常需要做这些事情:

1. USART收到1个字节

2. RXNE标志置位

3. CPU进入串口中断

4. CPU读取数据寄存器

5. CPU把数据存入数组或缓冲区

6. 退出中断

7. 下一个字节到来后重复上述流程

如果波特率较低、数据量较小,这种方式是可以接受的。

但如果数据持续不断到来,例如:

115200
460800
921600
甚至更高波特率

CPU 就可能被频繁打断。

问题包括:

  • 中断次数多;
  • CPU占用升高;
  • 业务任务执行变慢;
  • 容易丢数据;
  • 缓冲区处理压力变大;
  • 实时性变差。

这就是 USART 接收经常配合 DMA 的原因。


五、DMA的基本工作流程

DMA 的基本工作流程可以简化为:

1. CPU配置DMA参数

2. 外设产生DMA请求

3. DMA读取源地址数据

4. DMA写入目标地址

5. DMA传输计数递减

6. 传输完成后产生中断或标志

7. CPU处理完成事件

其中,CPU主要负责前期配置,例如:

源地址
目标地址
传输方向
传输长度
数据宽度
地址是否递增
传输模式
触发源
中断使能

配置完成后,真正的数据搬运由 DMA 自动完成。


六、DMA的核心参数

学习 DMA,最重要的是理解下面几个参数。

1. 源地址

源地址表示:

数据从哪里来

例如:

USART数据寄存器
SPI数据寄存器
ADC数据寄存器
内存数组

2. 目标地址

目标地址表示:

数据要搬到哪里去

例如:

RAM接收缓冲区
SPI数据寄存器
USART发送寄存器
另一个内存数组

3. 传输方向

DMA 常见传输方向包括:

外设到内存

内存到外设

内存到内存

例如:

USART接收:外设到内存

USART发送:内存到外设

ADC采样:外设到内存

SPI发送:内存到外设

SPI接收:外设到内存

4. 传输长度

传输长度表示:

这次要搬多少个数据单位

例如:

搬100个字节
搬256个半字
搬1024个采样点

DMA 每搬一次,计数器通常会减 1。

当计数器减到 0 时,表示本次传输完成。

5. 数据宽度

数据宽度表示每次搬运的数据单位大小。

常见包括:

8位
16位
32位

例如:

USART通常是8位数据

ADC结果可能是16位

内存拷贝可能是32位

数据宽度配置错误,可能导致数据错位或结果异常。

6. 地址是否递增

DMA 搬运时,源地址和目标地址是否递增非常关键。

例如 USART 接收:

源地址:USART数据寄存器,不递增

目标地址:RAM数组,递增

因为每次都从同一个 USART 数据寄存器读数据,但要依次存到数组不同位置。

ADC 采样也是类似:

源地址:ADC数据寄存器,不递增

目标地址:RAM采样数组,递增

而 USART 发送:

源地址:RAM发送数组,递增

目标地址:USART发送寄存器,不递增


七、DMA常见传输方向

DMA 常见方向可以分为三类。

1. 外设到内存

典型应用:

USART接收
ADC采样
SPI接收
I2S音频接收
传感器数据接收

数据流向:

外设数据寄存器 → DMA → RAM缓冲区

2. 内存到外设

典型应用:

USART发送
SPI发送
屏幕刷图
DAC输出
PWM波形表输出

数据流向:

RAM数组 → DMA → 外设数据寄存器

3. 内存到内存

典型应用:

内存块复制
数组搬运
数据初始化
大块数据转移

数据流向:

内存A → DMA → 内存B


八、DMA普通模式和循环模式

DMA 常见工作模式包括:

普通模式 Normal

循环模式 Circular

1. 普通模式

普通模式下,DMA 完成指定长度传输后停止。

适合:

发送一段串口数据
SPI发送一帧图像数据
接收固定长度数据包
ADC采样固定数量点

例如 USART 发送一段字符串:

RAM中的字符串

DMA搬到USART发送寄存器

发送完成

DMA停止

2. 循环模式

循环模式下,DMA 传输完成后不会停止,而是自动回到缓冲区开头继续传输。

适合:

ADC连续采样
音频数据流
循环接收缓冲区
持续传感器采集

例如 ADC 连续采样:

ADC结果不断产生

DMA不断写入数组

写到数组末尾后回到开头继续写

循环模式很适合持续数据流,但要注意缓冲区覆盖问题。


九、半传输中断和传输完成中断

DMA 常见两个重要中断:

半传输中断

传输完成中断

1. 半传输中断

半传输中断表示:

DMA已经搬完缓冲区前一半

例如缓冲区长度为 100:

搬到第50个数据时产生半传输中断

2. 传输完成中断

传输完成中断表示:

DMA已经搬完整个缓冲区

例如缓冲区长度为 100:

搬完100个数据后产生完成中断

3. 为什么需要半传输中断

半传输中断经常用于连续数据流处理。

例如 ADC 采样数组长度为 100:

DMA写前50个数据

半传输中断

CPU处理前50个数据

DMA继续写后50个数据

传输完成中断

CPU处理后50个数据

这样可以一边采集,一边处理,提高效率。


十、USART为什么喜欢用DMA

USART 是连续字节流通信。

如果不用 DMA,每收到一个字节就可能打断 CPU 一次。

USART 配合 DMA 后,可以让 DMA 自动把接收到的数据搬到内存缓冲区。

接收流程为:

外部数据进入USART

USART接收数据寄存器

DMA自动搬到RAM缓冲区

CPU在合适时机处理整段数据

这样可以减少 CPU 频繁响应单字节中断的压力。


十一、USART接收DMA

USART 接收 DMA 的方向是:

外设到内存

也就是:

USART_DR → DMA → RAM缓冲区

典型配置:

源地址:USART数据寄存器

目标地址:接收缓冲区

源地址不递增

目标地址递增

传输方向:外设到内存

数据宽度:通常8位

模式:普通模式或循环模式

USART 接收 DMA 常见用法包括:

  • 固定长度接收;
  • 不定长接收;
  • 循环缓冲区接收;
  • DMA + IDLE 空闲中断接收。

十二、USART DMA + IDLE空闲中断

USART 接收很多时候是不定长数据。

例如:

AT指令
串口屏数据
传感器数据帧
上位机命令

数据长度不一定固定。

这时常用:

DMA + IDLE空闲中断

基本思路:

DMA持续接收数据

一段时间没有新字节到来

USART产生IDLE空闲中断

CPU计算DMA已经接收了多少字节

处理这一帧数据

这种方式非常适合串口变长帧。


十三、USART发送DMA

USART 发送 DMA 的方向是:

内存到外设

也就是:

RAM发送缓冲区 → DMA → USART发送数据寄存器

适合发送:

  • 长字符串;
  • 日志数据;
  • 协议帧;
  • 文件数据;
  • 大量连续数据。

不用 DMA 时,CPU 需要不断等待 TXE 标志,再写下一个字节。

使用 DMA 后,CPU 只需要启动一次 DMA 发送,后续搬运由 DMA 完成。


在这里插入图片描述

图2 USART 为什么喜欢用 DMA

USART 是连续字节流。高波特率、批量发送和变长接收场景中,DMA 能明显减少 CPU 逐字节处理的压力。


十四、SPI为什么喜欢用DMA

SPI 的特点是:

速度快
全双工
连续传输字节流

SPI 经常用于:

  • 屏幕刷图;
  • Flash 读写;
  • ADC 数据读取;
  • 传感器高速采样;
  • 外部存储器访问。

这些场景通常数据量比较大。

如果不用 DMA,CPU 需要不断:

等待发送缓冲区为空
写入下一个字节
等待接收缓冲区非空
读取接收数据

数据量一大,CPU 就会很忙。

所以 SPI 非常适合配合 DMA。


十五、SPI发送DMA

SPI 发送 DMA 方向是:

内存到外设

典型场景:

LCD刷屏
OLED刷新
SPI Flash写入
发送传感器命令序列

例如刷屏时,一帧图像数据可能有几 KB、几十 KB,甚至更多。

如果逐字节由 CPU 写 SPI 数据寄存器,效率很低。

使用 DMA 后:

图像数据在RAM中

DMA自动搬到SPI发送寄存器

SPI连续发送

传输完成后通知CPU

CPU 可以去做其他事情。


十六、SPI接收DMA

SPI 接收 DMA 方向是:

外设到内存

典型场景:

SPI Flash读取
外部ADC采样
高速传感器数据读取
IMU数据读取

但要注意,SPI 是全双工通信。

接收数据时,主机通常仍然需要发送 dummy 字节来产生时钟。

所以 SPI 接收 DMA 经常需要:

TX DMA发送dummy数据
RX DMA接收真实数据

或者:

CPU或DMA负责发送dummy
DMA负责接收数据


十七、SPI使用DMA的注意点

SPI 使用 DMA 时要注意:

1. TX和RX长度要匹配

SPI 全双工通信中,发送多少个字节,通常就会接收多少个字节。

如果 TX 和 RX 长度不匹配,可能导致接收错位或溢出。

2. 片选CS时序要正确

很多 SPI 设备要求:

CS拉低

完成整段SPI传输

CS拉高

如果 DMA 传输还没真正结束就拉高 CS,设备可能认为通信提前结束。

3. 区分DMA完成和SPI真正发送完成

DMA 完成表示:

DMA已经把数据写入SPI数据寄存器或缓冲区

但不一定表示最后一个 bit 已经从 SPI 引脚发出。

有些场景还需要等待 SPI 的 BUSY 标志清除后,再拉高 CS。


在这里插入图片描述

图3 SPI 为什么喜欢用 DMA

SPI 适合高速连续传输。屏幕刷图、Flash 读写、外部 ADC 和高速传感器数据搬运,配合 DMA 可以明显减少 CPU 等待和逐字节操作。


十八、ADC为什么喜欢用DMA

ADC 的作用是把模拟量转换成数字量。

例如:

电压
电流
温度
传感器模拟信号
音频波形

ADC 每完成一次转换,结果通常存放在 ADC 数据寄存器中。

如果采样频率较低,CPU 可以通过中断或轮询读取结果。

但如果采样频率较高,例如连续采样波形,CPU 每次都读取 ADC 结果就会很忙。

这时使用 DMA 非常合适。


十九、ADC + DMA工作流程

ADC + DMA 的典型流程为:

定时器触发ADC采样

ADC转换完成

ADC结果写入ADC数据寄存器

DMA自动把结果搬到RAM数组

达到半缓冲或满缓冲后中断通知CPU

CPU处理一批采样数据

这样 ADC 专心采样,DMA 专心搬运,CPU 专心处理数据。


二十、ADC DMA常见模式

ADC 配合 DMA 常见模式包括:

1. 单次采样DMA

采样一次,DMA 搬运一次。

适合简单场景。

2. 连续转换DMA

ADC 连续转换,DMA 连续搬运。

适合持续采样。

3. 循环DMA

DMA 缓冲区写满后自动回到开头继续写。

适合持续数据流,例如:

波形采集
音频采样
传感器连续数据

4. 多通道扫描 + DMA

ADC 依次采样多个通道,DMA 按顺序把结果放入数组。

例如:

CH0, CH1, CH2
CH0, CH1, CH2
CH0, CH1, CH2

对应数组可能是:

adc_buffer[0] = CH0第1次结果
adc_buffer[1] = CH1第1次结果
adc_buffer[2] = CH2第1次结果
adc_buffer[3] = CH0第2次结果

多通道 DMA 时,数组长度必须和通道数、采样次数对应好。


二十一、ADC为什么特别适合DMA

ADC 特别适合 DMA 的原因:

采样结果连续产生
数据格式固定
每次都从同一个ADC数据寄存器读取
结果要批量存入RAM

这正好符合 DMA 的优势。

如果用 CPU 每次读取 ADC:

ADC转换完成一次
CPU响应一次
CPU读一次ADC_DR
CPU存一次数组

而用 DMA 后:

ADC负责采样
DMA负责搬运
CPU处理整批数据

效率更高,数据也更稳定。


在这里插入图片描述

图4 ADC 为什么喜欢用 DMA

ADC 连续采样时,DMA 可以自动把 ADC 数据寄存器中的结果搬到 RAM 数组,特别适合电压采样、电流采样、波形采集和传感器数据流。


二十二、DMA和中断的区别

很多初学者容易把 DMA 和中断混在一起。

其实它们不是一类东西。

1. 中断是什么

中断是:

事件通知机制

它告诉 CPU:

某件事发生了,需要处理

例如:

串口收到数据
定时器溢出
ADC转换完成
DMA传输完成

2. DMA是什么

DMA 是:

数据搬运机制

它负责把数据从一个地址搬到另一个地址。

例如:

USART_DR → RAM
RAM → SPI_DR
ADC_DR → RAM

3. DMA也可以产生中断

DMA 完成搬运后,可以产生中断通知 CPU:

我搬完了

所以 DMA 和中断经常配合使用。

可以这样记:

DMA负责搬数据
中断负责通知CPU


二十三、DMA常见调试问题

1. DMA完全不工作

可能原因:

  • DMA时钟没有开启;
  • 外设DMA请求没有使能;
  • DMA通道或请求映射错误;
  • 源地址配置错误;
  • 目标地址配置错误;
  • 传输方向配置错误;
  • 传输长度为0;
  • DMA没有启动;
  • 外设没有真正产生请求。

2. USART DMA收不到数据

可能原因:

  • USART接收没有使能;
  • USART DMA接收请求没开;
  • DMA目标缓冲区地址错误;
  • 接收长度配置不对;
  • IDLE中断没有处理;
  • 变长帧逻辑错误;
  • 缓冲区被覆盖;
  • 没有重新启动DMA。

3. SPI DMA发送异常

可能原因:

  • CS时序不对;
  • TX DMA和RX DMA长度不匹配;
  • 只开了TX没有处理RX溢出;
  • DMA完成后过早拉高CS;
  • SPI BUSY标志未清;
  • DMA通道映射错误。

4. ADC DMA结果异常

可能原因:

  • ADC通道数和DMA数组长度不匹配;
  • DMA数据宽度设置错误;
  • ADC数据对齐方式理解错;
  • Circular模式没有开启;
  • 采样时间太短;
  • 采样频率超过处理能力;
  • 半传输和传输完成回调逻辑写反。

二十四、DMA调试推荐顺序

遇到 DMA 不工作,建议按照下面顺序排查:

第1步:确认外设本身能否正常工作

第2步:确认DMA时钟是否开启

第3步:确认外设DMA请求是否使能

第4步:确认DMA通道或请求映射是否正确

第5步:确认源地址和目标地址是否正确

第6步:确认传输方向是否正确

第7步:确认数据宽度是否正确

第8步:确认地址递增配置是否正确

第9步:确认传输长度是否正确

第10步:确认DMA是否真正启动

第11步:确认半传输或完成中断是否触发

第12步:最后再看业务逻辑

排查 DMA 时,建议同时查看:

外设状态寄存器
DMA状态寄存器
中断标志
内存缓冲区内容
实际波形


在这里插入图片描述

图5 DMA 调试与常见问题排查

DMA 不工作时,通常不是“DMA搬不动”,而是源地址、目标地址、长度、触发源、通道映射或完成处理配置不正确。


二十五、STM32 HAL中USART DMA接收示例

下面代码只作为理解思路,不同芯片和工程配置可能不同。

1. 定义接收缓冲区

uint8_t uart_rx_buffer[128];

2. 启动DMA接收

HAL_UART_Receive_DMA(&huart1, uart_rx_buffer, sizeof(uart_rx_buffer));

3. 使用IDLE中断处理变长数据

常见思路是:

启动DMA接收

收到一段数据

出现IDLE空闲中断

停止DMA

计算已接收长度

处理数据

重新启动DMA

示例思路:

void USART1_IRQHandler(void)
{
if (__HAL_UART_GET_FLAG(&huart1, UART_FLAG_IDLE) != RESET)
{
__HAL_UART_CLEAR_IDLEFLAG(&huart1);

HAL_UART_DMAStop(&huart1);

uint16_t receive_len =
sizeof(uart_rx_buffer) __HAL_DMA_GET_COUNTER(huart1.hdmarx);

/* 处理 uart_rx_buffer 中 receive_len 长度的数据 */

HAL_UART_Receive_DMA(&huart1, uart_rx_buffer, sizeof(uart_rx_buffer));
}

HAL_UART_IRQHandler(&huart1);
}

实际工程中还要注意缓冲区保护、数据解析和多任务同步。


二十六、STM32 HAL ADC DMA示例

1. 定义ADC采样数组

uint16_t adc_buffer[100];

2. 启动ADC DMA

HAL_ADC_Start_DMA(&hadc1, (uint32_t *)adc_buffer, 100);

3. 半传输和传输完成回调

void HAL_ADC_ConvHalfCpltCallback(ADC_HandleTypeDef *hadc)
{
if (hadc->Instance == ADC1)
{
/* 处理 adc_buffer 前半部分数据 */
}
}

void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc)
{
if (hadc->Instance == ADC1)
{
/* 处理 adc_buffer 后半部分数据 */
}
}

这种方式适合连续采样。


二十七、STM32 HAL SPI DMA示例

1. SPI DMA发送

uint8_t spi_tx_buffer[256];

HAL_SPI_Transmit_DMA(&hspi1, spi_tx_buffer, sizeof(spi_tx_buffer));

2. SPI DMA接收

uint8_t spi_rx_buffer[256];

HAL_SPI_Receive_DMA(&hspi1, spi_rx_buffer, sizeof(spi_rx_buffer));

3. SPI全双工收发

uint8_t spi_tx_buffer[256];
uint8_t spi_rx_buffer[256];

HAL_SPI_TransmitReceive_DMA(&hspi1,
spi_tx_buffer,
spi_rx_buffer,
sizeof(spi_tx_buffer));

对于 SPI 设备,还要重点控制 CS 时序。

有些场景需要:

CS拉低
启动DMA传输
等待DMA完成
等待SPI BUSY清除
CS拉高


二十八、学习DMA要记住的核心结论

学习 DMA,可以先记住以下结论:

  • DMA 是直接存储器访问。

  • DMA 可以理解为硬件数据搬运工。

  • CPU 负责配置 DMA。

  • DMA 负责自动搬运数据。

  • DMA 常用于外设和内存之间搬运数据。

  • USART 接收适合 DMA,因为串口数据可能连续到来。

  • USART 发送适合 DMA,因为可以一次发送整段数据。

  • SPI 适合 DMA,因为 SPI 常用于高速连续传输。

  • ADC 适合 DMA,因为 ADC 连续采样结果需要自动入内存。

  • DMA 的关键参数是源地址、目标地址、长度和方向。

  • 外设寄存器地址通常不递增。

  • RAM数组地址通常需要递增。

  • 普通模式传输完成后停止。

  • 循环模式传输完成后自动重新开始。

  • 半传输中断适合处理前半段缓冲区。

  • 传输完成中断适合处理后半段或整段数据。

  • DMA 和中断不是一回事。

  • DMA 负责搬数据,中断负责通知 CPU。

  • DMA 不工作时要先看外设是否真的产生请求。

  • 数据量越大、越连续,DMA 优势越明显。


  • 二十九、结语

    DMA 是嵌入式开发中非常重要的效率工具。

    它不能替代 CPU 的逻辑判断和控制决策,但可以帮助 CPU 从大量重复的数据搬运工作中解放出来。

    可以用一句话总结 DMA:

    CPU擅长计算和控制,
    DMA擅长搬运数据;
    数据量越大、越连续,
    DMA价值越明显。

    USART、SPI、ADC 都喜欢用 DMA,本质原因都是:

    它们经常产生连续数据流,
    而这些数据需要在外设和内存之间高效搬运。

    真正掌握 DMA,需要把下面这些内容串起来:

    源地址
    +
    目标地址
    +
    传输方向
    +
    传输长度
    +
    数据宽度
    +
    地址递增
    +
    触发源
    +
    普通/循环模式
    +
    半传输/完成中断

    当这些配置理解清楚后,再去看 USART DMA、SPI DMA、ADC DMA,就会顺很多。


    赞(0)
    未经允许不得转载:171主机测评 » DMA详解:为什么USART、SPI、ADC都喜欢用DMA
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址