一、DMA 是什么

DMA 的全称是 Direct Memory Access,即直接存储器访问。

在 STM32 中,可以把 DMA 理解为一个“数据搬运工”。它能够在不需要 CPU 逐字节参与的情况下,把数据从一个地址搬运到另一个地址。

常见传输方向包括:

传输方向 示例
存储器到存储器 RAM 数组复制到另一个 RAM 数组
存储器到外设 RAM 中的数据通过 USART 发送出去
外设到存储器 USART、ADC 等外设接收或采集的数据写入 RAM

DMA 的核心价值是:通过硬件为 RAM 和 IO 外设之间开辟一条直接传输数据的通道,降低 CPU 搬运数据的负担。

二、为什么需要 DMA

如果不使用 DMA,CPU 通常需要通过循环或中断来搬运数据。

以串口发送一段数据为例,CPU 可能需要:

  1. 读取内存中的一个字节。

  2. 写入串口数据寄存器。

  3. 等待发送缓冲区可用。

  4. 继续发送下一个字节。

数据量较小时,这种方式问题不大;但数据量较大或外设传输频繁时,CPU 会被大量数据搬运任务占用。

使用 DMA 后,CPU 只需要配置好:

  • 源地址

  • 目标地址

  • 数据长度

  • 数据宽度

  • 地址是否自增

  • 传输方向

  • 中断方式

之后由 DMA 控制器自动完成数据传输。CPU 可以继续执行其他任务,只在传输完成、中途错误等事件发生时再处理。

三、DMA 请求与通道

1. DMA 请求

外设想使用 DMA 传输数据时,需要先向 DMA 控制器发送 DMA 请求。

例如:

  • USART 发送数据时,可以请求 DMA 从内存取数据并写入串口发送寄存器。

  • USART 接收数据时,可以请求 DMA 从串口接收寄存器读取数据并写入内存。

  • ADC 转换完成后,可以请求 DMA 把 ADC 数据寄存器中的结果搬运到数组中。

DMA 不是随意连接任意外设的。不同外设的 DMA 请求通常固定映射到不同 DMA 通道,实际使用时必须查阅 STM32F1 参考手册中的 DMA 通道映射表。

2. F1 DMA 通道数量

STM32F1 中 DMA 通道特点如下:

DMA 控制器 通道数量 说明
DMA1 7 个通道 常见 F1 芯片均具备
DMA2 5 个通道 仅部分大容量和互联型芯片具备

通道可以理解为一条独立的数据搬运路径。每个通道有自己的配置寄存器、传输数量寄存器、外设地址寄存器和存储器地址寄存器。

四、F1 DMA 框图理解

STM32F1 DMA 可以简化理解为:

外设DMA请求 -> DMA控制器 -> DMA通道 -> AHB总线 -> 外设/存储器

关键模块包括:

模块 作用
DMA 控制器 管理多个 DMA 通道
DMA 通道 执行具体一次数据传输
仲裁器 决定多个通道同时请求时的响应顺序
AHB 总线接口 访问存储器和外设寄存器

DMA 启动后,通道会按照配置从源地址读取数据,再写入目标地址,直到传输数量减为 0 或发生错误。

五、DMA 优先级

当多个 DMA 通道同时发起请求时,需要决定谁先执行。这个过程由 DMA 仲裁器管理。

STM32F1 DMA 优先级可以分为两个层次。

1. 软件优先级

软件优先级由寄存器配置。

常见优先级包括:

优先级 含义
Very High 最高
High
Medium
Low

HAL 库中通常通过 Priority 字段配置。

2. 硬件优先级

如果多个通道的软件优先级相同,则由硬件通道编号决定优先级。

规则是:

编号较低的通道优先级更高

例如,在同等软件优先级下,DMA1_Channel1 的硬件优先级高于 DMA1_Channel2

实际工程中,如果某个数据流对实时性要求更高,例如 ADC 连续采样或高速串口接收,应给它配置更高的软件优先级。

六、F1 DMA 关键寄存器

STM32F1 DMA 的每个通道都有一组独立寄存器。理解这些寄存器有助于看懂 HAL 配置结构,也方便排查 DMA 不工作的问题。

1. DMA_CCRx 通道配置寄存器

DMA_CCRx 是 DMA 通道的核心控制寄存器,用于配置通道工作方式。

常见配置项包括:

配置项 作用
通道使能 启动或关闭当前 DMA 通道
传输方向 设置外设到存储器、存储器到外设等方向
循环模式 传输完成后是否自动重新开始
外设地址自增 外设地址是否递增
存储器地址自增 存储器地址是否递增
外设数据宽度 8 位、16 位或 32 位
存储器数据宽度 8 位、16 位或 32 位
通道优先级 设置软件优先级
传输完成中断 使能传输完成中断
半传输中断 使能半传输中断
传输错误中断 使能传输错误中断

2. DMA_CNDTRx 传输数量寄存器

DMA_CNDTRx 用于设置当前通道要传输的数据数量。

DMA 每传输一个数据单位,该寄存器的值就会递减。传输完成后,它通常减到 0。

HAL 中也可以通过宏查询剩余传输数量:

__HAL_DMA_GET_COUNTER();

3. DMA_CPARx 外设地址寄存器

DMA_CPARx 用于保存外设地址。

例如串口发送时,外设地址通常是 USART 的数据寄存器地址;ADC 采集时,外设地址通常是 ADC 数据寄存器地址。

外设寄存器地址一般不需要自增,因为同一个外设数据寄存器会反复读写。

4. DMA_CMARx 存储器地址寄存器

DMA_CMARx 用于保存存储器地址。

例如:

  • 串口发送时,存储器地址是待发送数组地址。

  • 串口接收时,存储器地址是接收缓冲区地址。

  • ADC 采集时,存储器地址是采样结果数组地址。

多数情况下,存储器地址需要开启自增,这样 DMA 才能连续访问数组中的多个元素。

5. DMA_ISR 中断状态寄存器

DMA_ISR 用于查询 DMA 传输状态。

常见状态包括:

状态 含义
传输完成 当前传输全部完成
半传输 传输完成一半
传输错误 传输过程中出现错误

6. DMA_IFCR 中断标志清除寄存器

DMA_IFCR 用于清除 DMA_ISR 中的对应标志位。

处理中断后应及时清除标志,否则可能导致中断反复进入或状态判断异常。

七、DMA 传输配置要点

1. 传输方向

F1 DMA 常见传输方向包括:

方向 说明
外设到存储器 外设数据写入 RAM,例如 ADC 采集、USART 接收
存储器到外设 RAM 数据写入外设,例如 USART 发送
存储器到存储器 RAM 数据复制

配置方向时,要明确谁是源地址,谁是目标地址。

2. 地址是否自增

地址自增是 DMA 配置中很容易出错的地方。

场景 外设地址 存储器地址
USART 发送 不自增 自增
USART 接收 不自增 自增
ADC 多次采样 不自增 自增
内存数组复制 自增或按需求配置 自增

如果存储器地址没有开启自增,多个数据会反复覆盖同一个内存位置。

3. 数据宽度

DMA 的外设数据宽度和存储器数据宽度应与实际数据匹配。

常见选择:

数据类型 建议宽度
uint8_t Byte
uint16_t Half Word
uint32_t Word

例如 ADC 采样值通常是 12 位数据,常用 uint16_t 数组接收,因此 DMA 存储器宽度通常配置为 Half Word。

4. 正常模式与循环模式

DMA 有两种常见工作模式:

模式 说明 适合场景
Normal 传输指定数量后停止 一次性发送、一次性内存复制
Circular 传输完成后自动重新开始 ADC 连续采样、循环接收缓冲

循环模式下,DMA 传输不会在完成一次后停止,适合持续采集或持续接收。

八、DMA 与外设联动

外设使用 DMA 时,除了初始化 DMA 本身,还需要使能外设侧的 DMA 请求。

以 USART DMA 发送为例,除了配置 DMA 通道,还需要配置 USART 允许 DMA 发送请求。

USART 相关寄存器中,USART_CR3 可用于使能 DMA 发送。

使用 HAL 库时,通常不直接操作该位,而是调用:

HAL_UART_Transmit_DMA();

该函数会使能串口 DMA 发送,并启动传输。

同时还需要将 DMA 句柄与外设句柄关联:

__HAL_LINKDMA();

如果没有完成句柄关联,HAL 的 DMA 中断回调和外设状态管理可能无法正常工作。

九、F1 DMA 相关 HAL 函数

常用 HAL 函数和宏如下:

函数或宏 作用
__HAL_RCC_DMAx_CLK_ENABLE() 使能 DMA 控制器时钟
HAL_DMA_Init() 初始化 DMA 通道参数
HAL_DMA_Start_IT() 以中断方式启动 DMA 传输
__HAL_LINKDMA() 连接 DMA 句柄和外设句柄
HAL_UART_Transmit_DMA() 启动串口 DMA 发送
__HAL_DMA_GET_FLAG() 查询 DMA 传输状态标志
__HAL_DMA_GET_COUNTER() 查询剩余传输数量
__HAL_DMA_ENABLE() 使能 DMA 通道
__HAL_DMA_DISABLE() 失能 DMA 通道
HAL_UART_DMAStop() 停止串口 DMA 传输

常见结构体包括:

结构体 作用
DMA_HandleTypeDef DMA 句柄
DMA_InitTypeDef DMA 初始化参数结构体

十、DMA 基本配置步骤

使用 HAL 库配置 DMA 时,可以按以下流程理解。

1. 使能 DMA 时钟

先使能 DMA 控制器时钟:

__HAL_RCC_DMAx_CLK_ENABLE();

例如使用 DMA1 时,需要使能 DMA1 时钟。

2. 初始化 DMA

调用:

HAL_DMA_Init();

初始化内容通常包括:

  • DMA 通道实例

  • 传输方向

  • 外设地址是否自增

  • 存储器地址是否自增

  • 外设数据宽度

  • 存储器数据宽度

  • 工作模式

  • 优先级

3. 连接 DMA 和外设句柄

如果 DMA 由外设触发,例如 USART、ADC、SPI 等,需要将 DMA 句柄链接到外设句柄:

__HAL_LINKDMA();

这样 HAL 库才能在外设驱动中正确使用对应 DMA 通道。

4. 启动 DMA 传输

普通 DMA 传输可以使用:

HAL_DMA_Start_IT();

启动时需要确定:

  • 源地址

  • 目标地址

  • 传输数量

串口发送 DMA 可使用:

HAL_UART_Transmit_DMA();

该函数会同时处理串口侧 DMA 请求和 DMA 传输启动。

5. 查询 DMA 传输状态

可以通过宏查询 DMA 状态和剩余数量:

__HAL_DMA_GET_FLAG();
__HAL_DMA_GET_COUNTER();

常用于判断传输是否完成、还剩多少数据未搬运。

6. 使用 DMA 中断

如果使用 DMA 中断,需要配置 NVIC:

HAL_NVIC_SetPriority();
HAL_NVIC_EnableIRQ();

并编写对应的中断服务函数:

void DMAx_Channelx_IRQHandler(void)
{
    HAL_DMA_IRQHandler(&hdma_xxx);
}

中断中通常处理传输完成、半传输和传输错误等事件。

十一、典型应用场景

1. 内存到内存

内存到内存传输适合大块数据复制。

配置重点:

  • 源地址自增。

  • 目标地址自增。

  • 数据宽度与数组类型匹配。

  • 传输方向配置为存储器到存储器。

2. 串口 DMA 发送

串口 DMA 发送适合发送较长数据帧,避免 CPU 逐字节等待发送。

配置重点:

  • 存储器地址指向发送缓冲区。

  • 外设地址指向 USART 数据寄存器。

  • 存储器地址自增。

  • 外设地址不自增。

  • 使能 USART 的 DMA 发送请求。

3. 串口 DMA 接收

串口 DMA 接收适合接收固定长度数据或配合空闲中断实现不定长接收。

配置重点:

  • 外设地址指向 USART 数据寄存器。

  • 存储器地址指向接收缓冲区。

  • 外设地址不自增。

  • 存储器地址自增。

  • 根据应用选择 Normal 或 Circular 模式。

4. ADC DMA 采集

ADC DMA 采集适合连续采集多个通道或连续采集单通道数据。

配置重点:

  • 外设地址指向 ADC 数据寄存器。

  • 存储器地址指向采样数组。

  • 数据宽度通常选择 Half Word。

  • 常使用 Circular 模式。

  • 注意采样频率、缓冲区长度和数据处理速度的匹配。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐