DMA控制器原理
一、介绍
- DMA 是一种无需 CPU 干预即可在外设与存储器之间直接传输数据的机制,通过减少 CPU 开销来提升大量数据传输时的系统性能。
- CPU只需要一开始对DMA进行配置:指定从哪个寄存器到哪个外部设备
- DAM搬完后会自动向CPU发送消息,通知CPU搬完了
- STM32F407 有 2 个 DMA 控制器
- 每个控制器有 8 个数据流
- 每个数据流 有 8 个通道
- 1 个数据流每次只能搬运 1 个通道的数据
二、DMA 的仲裁器
| 项目 | 说明 |
|---|---|
| 作用 | 管理多个 DMA 通道对系统总线的竞争访问,协调传输顺序 |
| 软件优先级(4级) | 很高 > 高 > 中 > 低(在 DMA_CCRx 寄存器中设置) |
| 硬件优先级 | 当软件优先级相同时,通道编号越小,优先级越高 |
三、DMA事务
| 操作 | 说明 |
|---|---|
| 从源地址读取数据 | 通过 DMA_SxPAR(外设地址)或 DMA_SxM0AR(存储器地址)寻址,从源端读取数据 |
| 向目的地址写入数据 | 通过 DMA_SxPAR 或 DMA_SxM0AR 寻址,将读取的数据写入目的端 |
| 传输计数器递减 | 每次完整传输完成后,DMA_SxNDTR 计数器减1,记录剩余待传输的数据项数目 |
| 可编程参数 | 说明 |
|---|---|
| 数据项数目 | 由 DMA_SxNDTR 寄存器设置 |
| 数据宽度 | 可配置为 8 位、16 位或 32 位 |
四、DMA传输
4.1 DMA 传输方向配置
| DIR[1:0] | 传输模式 | 源地址 | 目标地址 |
|---|---|---|---|
| 00 | 外设 → 存储器 | DMA_SxPAR(外设地址) | DMA_SxM0AR(存储器地址) |
| 01 | 存储器 → 外设 | DMA_SxM0AR(存储器地址) | DMA_SxPAR(外设地址) |
| 10 | 存储器 → 存储器 | DMA_SxM0AR(源存储器地址) | DMA_SxM0AR(目标存储器地址) |
通过DMA_SxCR 寄存器的 DIR[1:0] 位选择传输方向
三种模式:外设→存储器、存储器→外设、存储器→存储器
存储器范围: 0x2000000 至 0x200200000 共 1 28KB
4.2 FIFO
FIFO 大小:
每个数据流都有一个独立的 4 字(16 字节)FIFO。
工作流程
- 启用 FIFO 后,源地址中的数据不会直接由 DMA 搬运到目标地址。
- 数据先从源地址读取,存入 FIFO 缓冲区中。
- 当 FIFO 中积累的数据达到预设的阈值时,DMA 才会将数据从 FIFO 中取出,写入到目标地址。
核心特点
FIFO 起到了缓冲作用,可以将多个小数据量合并为一次突发传输,提高总线利用效率。
五、指针增量
控制位说明
DMA_SxCR 寄存器中的PINC 位控制外设地址指针是否递增,MINC 位控制存储器地址指针是否递增。
递增模式
- 当设置为增量模式时,每次传输完成后,下一次传输的地址 = 前一次地址 + 增量值。
- 增量值由数据宽度决定:字节(8位)时加1,半字(16位)时加2,字(32位)时加4。
常量模式
当设置为保持常量时,每次传输都使用同一个内存地址,反复从同一位置读取数据或写入同一位置。
典型应用
递增模式适合传输数组或连续内存块。
常量模式适合反复读写同一个外设寄存器(如 ADC 数据寄存器)。
数据宽度
- 数据宽度通过 DMA_SxCR 寄存器的 PSIZE(外设数据宽度)和 MSIZE(存储器数据宽度)位配置。
直接模式(不用FIFO)
- 直接模式下,源和目标的传输数据宽度必须相等。
- 宽度由 DMA_SxCR 中的 PSIZE 位定义,MSIZE 位在此模式下无效。
FIFO 模式
- 使用内部 FIFO 时,FIFO 进行数据对齐和打包;源和目标的数据宽度可以通过 PSIZE 和 MSIZE 位分别编程,支持 8 位、16 位或 32 位,两者可以不同。
宽度不等时的传输计数
- 当 PSIZE 和 MSIZE 不相等时,DMA_SxNDTR 寄存器中配置的传输数据项数目,其数据宽度等于外设总线的宽度(由 PSIZE 位定义)。
- 举例:PSIZE 配置为半字(16位),则实际传输的字节数 = 2 × NDT(NDT 为 DMA_SxNDTR 中的计数值)。
六、循环模式
循环模式的作用
循环模式可用于处理循环缓冲区和连续数据流,例如 ADC 扫描模式。
如何使能
通过 DMA_SxCR 寄存器中的 CIRC 位使能循环模式。
工作流程
当循环模式激活时,每次传输完成且 DMA_SxNDTR 计数器减到 0 后,计数器会自动重新加载为初始设置的数值。
然后 DMA 继续响应新的 DMA 请求,如此往复循环,无需软件重新配置。
典型应用场景
适合需要连续不断传输数据的场景,如 ADC 连续扫描、音频数据流等,避免频繁在中断中重新配置 DMA。
七、双缓冲模式
使能
通过将 DMA_SxCR 寄存器中的 DBM 位置 1 使能双缓冲模式。
注意:存储器到存储器模式不适用双缓冲模式。
地址寄存器协作
在此模式下,DMA_SxM0AR 与 DMA_SxM1AR 两个存储器地址寄存器互相协作。
每次事务结束时,DMA 控制器会自动从一个存储器目标切换到另一个存储器目标。
工作流程
软件在处理一个存储器区域的同时,DMA 传输可以继续填充或使用第二个存储器区域。
两个缓冲区交替使用:一个被 DMA 访问,另一个被 CPU 访问。
八、流控制器
流控制器的定义
控制要传输的数据数目的实体称为流控制器。
通过 DMA_SxCR 寄存器中的 PFCTRL 位,可以针对每个数据流独立配置。
DMA 控制器作为流控制器
适用于要传输的数据项数目确定的情况(也可以通过软件实现不确定的情况)。
在使能 DMA 数据流之前,由软件将要传输的数据项数目编程到 DMA_SxNDTR 寄存器中。
DMA 按照这个计数进行传输,每传一次减 1,减到 0 时传输结束。
外设作为流控制器
适用于要传输的数据项数目未知的情况。
当传输到最后一条数据时,外设通过硬件向 DMA 控制器发出结束指示。
仅限能够发出传输结束信号的外设支持此功能。
外设流控制器时的特殊行为
当外设作为流控制器时,软件写入 DMA_SxNDTR 的值对 DMA 传输没有作用。
一旦使能数据流,硬件会将该值强制置为 0xFFFF。
九、配置步骤
| 配置项 | 相关寄存器/位 | 说明 |
|---|---|---|
| 禁止数据流 | DMA_SxCR.EN | 写0禁止,需等待当前传输完成才生效 |
| 设置外设地址 | DMA_SxPAR | 外设端口寄存器地址(在DR寄存器里面) |
| 设置存储器地址 | DMA_SxM0AR / DMA_SxM1AR | 单缓冲用 M0AR,双缓冲用 M0AR+M1AR |
| 配置传输总数 | DMA_SxNDTR | 要传输的数据项数目 |
| 选择 DMA 通道 | DMA_SxCR.CHSEL[2:0] | 选择外设 DMA 请求通道(存储器到存储器可跳过) |
| 选择流控制器 | DMA_SxCR.PFCTRL | 外设作为流控制器时置1(仅限 SDIO 等) |
| 配置优先级 | DMA_SxCR.PL[1:0] | 很高/高/中/低 四档 |
| 配置 FIFO | 相关 FIFO 控制位 | 存储器到存储器模式必须使能 FIFO |
| 配置传输参数 | DMA_SxCR 各控制位 | 方向、增量模式、数据宽度、循环模式、双缓冲等 |
| 激活数据流 | DMA_SxCR.EN | 置1 使能数据流,开始传输 |
十、DMA 搬运模式
10.1 外设到存储器模式
源地址:外设的数据寄存器地址(DMA_SxPAR)
目标地址:存储器中的一段空间(DMA_SxM0AR)
直接模式:不使用 FIFO,每完成一次从外设到存储器的数据传输,数据立即移出并存储到目标地址。
使能 FIFO:每次产生外设请求,数据从源(外设)传输到 FIFO 中。当 FIFO 达到预设的阈值级别时,FIFO 的内容移出并存储到目标(存储器)中。
10.2 存储器到外设模式
源地址:存储器中的一段空间(DMA_SxM0AR)
目标地址:外设的数据寄存器地址(DMA_SxPAR)
直接模式:不使用 FIFO,每完成一次从存储器到外设的数据传输,数据立即移出并存储到目标地址。
使能 FIFO:
- 数据流使能后立即启动传输,从源(存储器)读取数据将 FIFO 完全填充到阈值级别。每次发生外设请求,FIFO 的内容移出并存储到目标(外设)中。
- 当 FIFO 中剩余数据量小于或等于预设的阈值时,DMA 控制器会自动从源地址(存储器)读取新数据,将 FIFO 重新填满(填到阈值级别或尽可能填满)。这个过程叫做“完全重载 FIFO”,目的是保证 FIFO 中始终有足够的数据等待外设请求,避免外设等待数据。
10.3 存储器到存储器模式
源地址:存储器中的一段空间(DMA_SxM0AR)
目标地址:存储器中的另一段空间(DMA_SxM0AR,注意:存储器到存储器模式下只能使用 M0AR,不能使用双缓冲)
直接模式:不适用。存储器到存储器模式必须使能 FIFO。
使能 FIFO:通过将 DMA_SxCR 寄存器中的使能位(EN)置 1 来使能数据流时,数据流会立即开始从源地址读取数据填充 FIFO,直至达到预设的阈值级别。
达到阈值级别后,FIFO 中的内容便会移出,并存储到目标地址中。
当 FIFO 中剩余数据量低于阈值时,DMA 会自动从源地址读取新数据,将 FIFO 重新填充到阈值级别。
这个过程不断重复,直到 DMA_SxNDTR 计数器减到 0,传输结束。
10.4 DMA 控制器常用状态
• 传输完成中断标志( TCIF )
• 半传输完成中断标志( HTIF )
• 数据传输错误中断标志( TEIF )
• FIFO 状态( FS )
11、DMA相关地址
11.1 DMA控制器基址
| 控制器 | 基址 | 所属总线 |
|---|---|---|
| DMA1 | 0x4002 6000 | AHB1 |
| DMA2 | 0x4002 6400 | AHB1 |
11.2 寄存器映射(偏移地址)
| 寄存器名称 | 缩写 | 偏移地址 | 备注 |
|---|---|---|---|
| DMA 低中断状态寄存器 | DMA_LISR | 0x00 | 数据流 0~3 中断状态 |
| DMA 高中断状态寄存器 | DMA_HISR | 0x04 | 数据流 4~7 中断状态 |
| DMA 低中断标志清零寄存器 | DMA_LIFCR | 0x08 | 清数据流 0~3 中断标志 |
| DMA 高中断标志清零寄存器 | DMA_HIFCR | 0x0C | 清数据流 4~7 中断标志 |
| DMA 数据流 x 配置寄存器 | DMA_SxCR | 0x10 + 0x18×x | x = 0~7 |
| DMA 数据流 x 数据项数寄存器 | DMA_SxNDTR | 0x14 + 0x18×x | |
| DMA 数据流 x 外设地址寄存器 | DMA_SxPAR | 0x18 + 0x18×x | |
| DMA 数据流 x 存储器 0 地址寄存器 | DMA_SxM0AR | 0x1C + 0x18×x | |
| DMA 数据流 x 存储器 1 地址寄存器 | DMA_SxM1AR | 0x20 + 0x18×x | 双缓冲模式用 |
| DMA 数据流 x FIFO 控制寄存器 | DMA_SxFCR | 0x24 + 0x24×x | 注意:偏移增量与上面不同 |
12、HAL接口
12.1 DMA 控制器初始化配置结构体
typedef struct { uint32_t Channel; /* 数据流的通道编号 */ uint32_t Direction; /* 数据传输方向 */ uint32_t PeriphInc; /* 外设地址递增模式 */ uint32_t MemInc; /* 存储器地址递增模式 */ uint32_t PeriphDataAlignment; /* 外设数据宽度 */ uint32_t MemDataAlignment; /* 存储器数据宽度 */ uint32_t Mode; /* DMA 传输模式 */ uint32_t Priority; /* 数据流优先级 */ uint32_t FIFOMode; /* 直接模式/FIFO模式 */ uint32_t FIFOThreshold; /* FIFO阈值级别 */ uint32_t MemBurst; /* 存储器突发模式 */ uint32_t PeriphBurst; /* 外设突发模式 */ } DMA_InitTypeDef;12.2 HAL_DMA_Init() 函数
| 项目 | 内容 |
|---|---|
| 函数功能 | 初始化 DMA 控制器 |
| 函数原型 | HAL_StatusTypeDef HAL_DMA_Init(DMA_HandleTypeDef *hdma) |
| 参数 hdma | DMA 句柄指针,包含 DMA 配置信息 |
| 返回值 | HAL_OK:成功;HAL_ERROR:失败;HAL_BUSY:忙;HAL_TIMEOUT:超时 |
| 作用 | 根据句柄参数配置 DMA 数据流的通道、方向、地址递增、数据宽度、传输模式、优先级及 FIFO 等寄存器参数,并重置句柄状态 |
12.3 HAL_DMA_Start 函数
| 项目 | 内容 |
|---|---|
| 函数功能 | 开始 DMA 传输 |
| 函数原型 | HAL_StatusTypeDef HAL_DMA_Start(DMA_HandleTypeDef *hdma, uint32_t SrcAddress, uint32_t DstAddress, uint32_t DataLength) |
| 参数 hdma | DMA 句柄指针,包含 DMA 配置信息 |
| 参数 SrcAddress | 源地址(外设地址或存储器地址) |
| 参数 DstAddress | 目标地址(存储器地址或外设地址) |
| 参数 DataLength | 待传输的数据项个数 |
| 返回值 | HAL_OK:成功;HAL_ERROR:失败;HAL_BUSY:忙;HAL_TIMEOUT:超时 |
| 作用 | 配置源地址、目标地址和数据长度寄存器后使能 DMA 数据流启动传输,但该函数采用轮询方式会占用 CPU 资源,实际开发中一般用中断方式(HAL_DMA_Start_IT)替代 |
12.4 HAL_DMA_Start_IT 函数
| 项目 | 内容 |
|---|---|
| 函数功能 | 中断方式开始 DMA 传输 |
| 函数原型 | HAL_StatusTypeDef HAL_DMA_Start_IT(DMA_HandleTypeDef *hdma, uint32_t SrcAddress, uint32_t DstAddress, uint32_t DataLength) |
| 参数 hdma | DMA 句柄指针,包含 DMA 配置信息 |
| 参数 SrcAddress | 源地址(外设地址或存储器地址) |
| 参数 DstAddress | 目标地址(存储器地址或外设地址) |
| 参数 DataLength | 待传输的数据项个数 |
| 返回值 | HAL_OK:成功;HAL_ERROR:失败;HAL_BUSY:忙;HAL_TIMEOUT:超时 |
| 作用 | 配置源地址、目标地址和数据长度寄存器并使能中断,然后启动 DMA 传输,传输完成后由中断回调函数处理,不占用 CPU 轮询资源 |
12.5 HAL_UART_Transmit_DMA 函数
| 项目 | 内容 |
|---|---|
| 函数功能 | 使用 DMA 方式发送 UART 数据 |
| 函数原型 | HAL_StatusTypeDef HAL_UART_Transmit_DMA(UART_HandleTypeDef *huart, const uint8_t *pData, uint16_t Size) |
| 参数 huart | UART 句柄指针,包含 UART 配置信息 |
| 参数 pData | 待发送数据的缓冲区首地址 |
| 参数 Size | 待发送的数据字节数 |
| 返回值 | HAL_OK:成功;HAL_ERROR:失败;HAL_BUSY:忙;HAL_TIMEOUT:超时 |
| 作用 | 配置 DMA 将内存中的数据自动传输到 UART 发送寄存器,启动 DMA 传输实现后台发送,发送完成后通过中断回调通知,无需 CPU 逐字节等待 |
12.6 HAL_UART_Receive_DMA 函数
| 项目 | 内容 |
|---|---|
| 函数功能 | 使用 DMA 方式接收 UART 数据 |
| 函数原型 | HAL_StatusTypeDef HAL_UART_Receive_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size) |
| 参数 huart | UART 句柄指针,包含 UART 配置信息 |
| 参数 pData | 存放接收数据的缓冲区首地址 |
| 参数 Size | 待接收的数据字节数 |
| 返回值 | HAL_OK:成功;HAL_ERROR:失败;HAL_BUSY:忙;HAL_TIMEOUT:超时 |
| 作用 | 配置 DMA 将 UART 接收寄存器的数据自动传输到内存缓冲区,启动 DMA 传输实现后台接收,接收完成后通过中断回调通知,无需 CPU 逐字节等待 |
