STM32-DMA
一、DMA 是什么
DMA 的全称是 Direct Memory Access,即直接存储器访问。
在 STM32 中,可以把 DMA 理解为一个“数据搬运工”。它能够在不需要 CPU 逐字节参与的情况下,把数据从一个地址搬运到另一个地址。
常见传输方向包括:
| 传输方向 | 示例 |
|---|---|
| 存储器到存储器 | RAM 数组复制到另一个 RAM 数组 |
| 存储器到外设 | RAM 中的数据通过 USART 发送出去 |
| 外设到存储器 | USART、ADC 等外设接收或采集的数据写入 RAM |
DMA 的核心价值是:通过硬件为 RAM 和 IO 外设之间开辟一条直接传输数据的通道,降低 CPU 搬运数据的负担。
二、为什么需要 DMA
如果不使用 DMA,CPU 通常需要通过循环或中断来搬运数据。
以串口发送一段数据为例,CPU 可能需要:
-
读取内存中的一个字节。
-
写入串口数据寄存器。
-
等待发送缓冲区可用。
-
继续发送下一个字节。
数据量较小时,这种方式问题不大;但数据量较大或外设传输频繁时,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 模式。
-
注意采样频率、缓冲区长度和数据处理速度的匹配。
更多推荐


所有评论(0)