参考:【STM32】 DMA原理,步骤超细详解,一文看懂DMA-CSDN博客
为了DMA输出PWM进行的配置,如果不想看前面介绍,可以直接跳转到后面函数配置,然后按需参考自行更改。
1、基本介绍
1.1、定义
全称Direct Memory Access,即直接存储器访问。
- 无须CPU的干预,通过DMA数据可以快速地移动。这就节省了CPU的资源来做其他操作。
- DMA提供通路,将数据从一个地址空间复制到另一个地址空间,在外设↔存储器之间或者存储器↔存储器之间提供高速的数据传输。
1.2、传输方向
- 存储器 ↔ 存储器(仅DMA2可以)
- 外设 ↔ 存储器
- 外设 ↔ 外设
存储器就是数据原地址,外设就是传输的目标地址。
1.3、传输参数
- 数据原地址;
- 传输的目标地址;
- 数据传输量;
- 进行多少次传输的传输模式。
用户参数设置主要涉及源地址、目标地址、传输数据量这三个。只要传输数据量不为0,且DMA是启动状态,就会发送数据传输。
1.4、主要特征
-
每个通道都直接连接专用的硬件DMA请求,但同样支持软件触发。
-
同一个DMA模块上,多请求间的优先权通过软件设置(共四级:很高、高、中等、低),优先权配置相等时由硬件决定(请求0>请求1,以此类推);
-
独立数据源和目标数据区的传输宽度(字节、半字、全字)。源和目标地址的数据必须按传输宽度对齐。
-
支持循环的缓冲器管理。
-
每个通道都有3个事件标志(DMA半传输、DMA传输完成、DMA传输错误),这3个标志可成为一个单独的中断请求。
- 中断相关标志位:
标志位 描述 DMAx_IT_TCx 传输完成中断标志(Transfer Complete) DMAx_IT_HTx 半传输完成中断标志(Half Transfer Complete) DMAx_IT_TEx 传输错误中断标志(Transfer Error) - 状态相关标志位:
标志位 描述 DMAx_FLAG_TCx 传输完成标志(Transfer Complete) DMAx_FLAG_HTx 半传输完成标志(Half Transfer Complete) DMAx_FLAG_TEx 传输错误标志(Transfer Error) DMAx_FLAG_GLx 全局标志(Global Flag,包含上述所有标志的汇总) -
闪存、SRAM、外设的SRAM、APB1、APB2、AHB外设均可作为访问的源和目标。
-
可编程的数据传输数目:最大为65535(2^16 - 1)。
1.5、STM32上的DMA资源
STM32最多有2个DMA控制器(DMA2仅存在大容量产品中),12个独立的可配置的通道(请求),DMA1有7个通道,DMA2有5个通道。每个通道专门用来管理来自于一个或多个外设对存储器访问的请求。还有一个仲裁器来协调各个 DMA 请求的优先权。
- 小容量产品:闪存存储器容量在16K~32K字节之间的微控制器。
- 中容量产品:闪存存储器容量在64K~128K字节之间的微控制器。
- 大容量产品:闪存存储器容量在256K~512K字节之间的微控制器。
1.6、传输模式
- 方法1:DMA_Mode_Normal,正常模式。当一次DMA数据传输完后,停止DMA传送 ,也就是只传输一次。
- 方法2:DMA_Mode_Circular,循环传输模式。但传输结束时。硬件会自动将传输数据量寄存器进行重装,进行下一轮数据传输。
2、基本配置
2.1、结构体
DMA_InitTypeDef 定义于文件“stm32f10x_dma.h”
typedef struct {
uint32_t DMA_PeripheralBaseAddr; // 设置DMA源地址
uint32_t DMA_MemoryBaseAddr; // 设置DMA目的地址
uint32_t DMA_DIR; // 设置数据传输方向,决定数据是从外设→内存还是从内存→外设,也就是外设是源地还是目的地
uint32_t DMA_BufferSize; // 设置传输大小
uint32_t DMA_PeripheralInc; // 设置外设地址是否自增
uint32_t DMA_MemoryInc; // 设置传输数据时内存地址是否递增,需要开启
uint32_t DMA_PeripheralDataSize; // 外设的数据长度是字节传输(8bits)、半字传输(16bits)还是字传输(32bits)
uint32_t DMA_MemoryDataSize; // 设置内存的数据长度
uint32_t DMA_Mode; // 设置DMA的模式,正常模式/循环模式,是否循环发送
uint32_t DMA_Priority; // 设置DMA通道的优先级,有低、中、高、超高四种模式
uint32_t DMA_M2M; // 设置是否是存储器到存储器模式传输
} DMA_InitTypeDef;
2.2、初始化
/**
* @brief 初始化DMA
* @param DMA_CHx DMA通道
* @param peripheral_base_addr 外设地址
* @param memory_base_addr 内存地址
* @param buffer_size 缓冲区大小
*/
void DMA_Config(DMA_Channel_TypeDef *DMA_CHx, uint32_t peripheral_base_addr, uint32_t memory_base_addr, uint32_t buffer_size)
{
DMA_InitTypeDef DMA_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); // 使能DMA1时钟
DMA_DeInit(DMA_CHx); // 将DMA的通道x寄存器重设为缺省值
DMA_InitStructure.DMA_PeripheralBaseAddr = peripheral_base_addr; // 启动传输前装入实际RAM地址
DMA_InitStructure.DMA_MemoryBaseAddr = memory_base_addr; // 接收缓冲区首地址
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; // 数据传输方向,从内存读取到外设。按需更改
DMA_InitStructure.DMA_BufferSize = buffer_size; // DMA缓存的大小(数据量)
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; // 外设地址递增
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; // 内存地址递增
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; // 数据宽度为16位
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; // 数据宽度为16位
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; // 工作在循环模式
DMA_InitStructure.DMA_Priority = DMA_Priority_High; // 高优先级
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; // 没有设置为内存到内存传输
DMA_Init(DMA_CHx, &DMA_InitStructure); // 对DMA通道x进行初始化
DMA_ITConfig(DMA_CHx, DMA_IT_TC, ENABLE); // 开启DMA传输完成中断
// 配置 DMA 中断优先级和使能
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0);
if (DMA_CHx == DMA1_Channel1)
NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel1_IRQn;
else if (DMA_CHx == DMA1_Channel2)
NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel2_IRQn;
else if (DMA_CHx == DMA1_Channel3)
NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel3_IRQn;
else if (DMA_CHx == DMA1_Channel4)
NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel4_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
DMA_Cmd(DMA_CHx, ENABLE); // 使DMA通道开始工作
// 按需开启
TIM_DMACmd(TIM3, TIM_DMA_Update, ENABLE); // 当定时器的计数器达到ARR(自动重装载值)时触发
// TIM_DMACmd(TIM3, TIM_DMA_CC4, ENABLE); // 当定时器的计数器达到CCR4(比较寄存器)时触发
// USART_DMACmd(USART2, USART_DMAReq_Rx, ENABLE); // 开启串口DMA接收
}
- 解析
结构成员 | 参数 | 描述 |
---|---|---|
DMA_DIR | 1. DMA_DIR_PeripheralDST 2. DMA_DIR_PeripheralSRC | 1. 外设作为数据传输的目的地 2. 外设作为数据传输的来源 |
DMA_BufferSize | 1~65535 | DMA 通道的 DMA 缓存的大小。 由 DMA_PeripheralDataSize 或者DMA_MemoryDataSize 的值决定 |
DMA_PeripheralInc | 1. DMA_PeripheralInc_Enable 2. DMA_PeripheralInc_Disable | 1. 外设地址寄存器递增 2. 外设地址寄存器不变 |
DMA_MemoryInc | 1. DMA_PeripheralInc_Enable 2. DMA_PeripheralInc_Disable | 1. 内存地址寄存器递增 2. 内存地址寄存器不变 |
DMA_PeripheralDataSize | 1. DMA_PeripheralDataSize_Byte 2. DMA_PeripheralDataSize_HalfWord 3. DMA_PeripheralDataSize_Word | 1. 数据宽度为 8 位 2. 数据宽度为 16 位 3. 数据宽度为 32 位 |
DMA_MemoryDataSize | 1. DMA_MemoryDataSize_Byte 2. DMA_MemoryDataSize_HalfWord 3. DMA_MemoryDataSize_Word | 1. 数据宽度为 8 位 2. 数据宽度为 16 位 3. 数据宽度为 32 位 |
DMA_Mode | 1. DMA_Mode_Circular 2. DMA_Mode_Normal | 1. 工作在循环缓存模式 2. 工作在正常缓存模式 |
DMA_Priority | 1. DMA_Priority_VeryHigh DMA 2. DMA_Priority_High DMA 3. DMA_Priority_Medium DMA 4. DMA_Priority_Low DMA | 1. 通道 x 拥有非常高优先级 2. 通道 x 拥有高优先级 3. 通道 x 拥有中优先级 4. 通道 x 拥有低优先级 |
DMA_M2M | 1. DMA_M2M_Enable DMA 2. DMA_M2M_Disable DMA | 1. 通道 x 设置为内存到内存传输 2. 通道 x 没有设置为内存到内存传输 |
2.3、中断
void DMA1_Channel3_IRQHandler(void)
{
if (DMA_GetITStatus(DMA1_IT_TC3) != RESET)
{
DMA_ClearITPendingBit(DMA1_IT_TC3);
/* 在此处写处理 */
}
}
知识点补充:
- 下面两个是DMA触发频率控制。这个是控制DMA的发送,当达到ARR/CCR4时触发一次DMA数据发送。
TIM_DMACmd(TIM3, TIM_DMA_Update, ENABLE); // 当定时器的计数器达到ARR(自动重装载值)时触发
TIM_DMACmd(TIM3, TIM_DMA_CC4, ENABLE); // 当定时器的捕获/比较通道 4 达到CCR4(设定的比较值)时触发
- DMA中断都是发送完数据块才进入的。
void DMA1_Channel3_IRQHandler(void){}
- 以F1系列为例,DMA同一个时刻只可以开一个通道进入,因为DMA前面就是通道多路选择器。优先级高的优先响应,然后传输间隙,DMA有空闲时间,低优先级的也可以响应,高低优先级同时来了,优先响应高。
- DMA执行顺序是按照硬件顺序执行的(例如:
DMA1_Channel1
>DMA1_Channel2
>DMA1_Channel3
> ...),当相同硬件优先级的时候(例如DMA1_Channel1
和DMA2_Channel1
可能有相同的优先级),软件优先级高的才起决定作用。 - 使能双缓冲区模式时,自动使能循环模式。使能双缓冲区模式时,不允许配置存储器到存储器模式。