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

📅 2026/7/9 10:53:14 👁️ 阅读次数 📝 编程学习
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_tuart_rx_buffer[128];

2. 启动DMA接收

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

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

常见思路是:

启动DMA接收 ↓ 收到一段数据 ↓ 出现IDLE空闲中断 ↓ 停止DMA ↓ 计算已接收长度 ↓ 处理数据 ↓ 重新启动DMA

示例思路:

voidUSART1_IRQHandler(void){if(__HAL_UART_GET_FLAG(&huart1,UART_FLAG_IDLE)!=RESET){__HAL_UART_CLEAR_IDLEFLAG(&huart1);HAL_UART_DMAStop(&huart1);uint16_treceive_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_tadc_buffer[100];

2. 启动ADC DMA

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

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

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

这种方式适合连续采样。


二十七、STM32 HAL SPI DMA示例

1. SPI DMA发送

uint8_tspi_tx_buffer[256];HAL_SPI_Transmit_DMA(&hspi1,spi_tx_buffer,sizeof(spi_tx_buffer));

2. SPI DMA接收

uint8_tspi_rx_buffer[256];HAL_SPI_Receive_DMA(&hspi1,spi_rx_buffer,sizeof(spi_rx_buffer));

3. SPI全双工收发

uint8_tspi_tx_buffer[256];uint8_tspi_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,可以先记住以下结论:

  1. DMA 是直接存储器访问。

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

  3. CPU 负责配置 DMA。

  4. DMA 负责自动搬运数据。

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

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

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

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

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

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

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

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

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

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

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

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

  17. DMA 和中断不是一回事。

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

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

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


二十九、结语

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

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

可以用一句话总结 DMA:

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

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

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

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

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

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