DMA实验
此文章仅限于记录个人知识盲区,没有指导作用,如果有任何疑问,欢迎在评论区提问讨论~
DMA
DMA,全称为:Direct Memory Access,即直接存储器访问。DMA 传输方式无需 CPU 直接 控制传输,也没有中断处理方式那样保留现场和恢复现场的过程,通过硬件为 RAM 与 I/O 设备 开辟一条直接传送数据的通路,能使 CPU 的效率大为提高。
STM32L475 最多有 2 个 DMA 控制器(DMA1 和 DMA2) ,共 14 个通道(每个控制器 7 个), 每一个 DMA 控制器都用于管理一个或多个外设的存储器访问请求。每个通道总共可以有多达 7 个请求。每个数据流通道都有一个仲裁器,用于处理 DMA 请求间的优先级。
STM32L475 的 DMA 有以下一些特性:
● 单 AHB 主总线架构
● 支持外设到内存、内存到外设、内存到内存和外设到外设的数据传输
● 所有的 DMA 通道均可独立配置
● 每个通道产生一个中断请求,每个中断请求都是由三个DMA事件中的任何一个引起的: 传输完成、半传输完成和传输错误
STM32L475有两个DMA控制器,DMA1和DMA2
DMA 控制器执行直接存储器传输:因为采用 AHB 主总线,它可以控制 AHB 总线矩阵来 启动 AHB 事务。它可以执行下列事务:
- 外设到存储器的传输
- 存储器到外设的传输
- 存储器到存储器的传输
DMA1和DMA2的通道以及Requst number一览
仲裁器
仲裁器根据通道请求的优先级来启动外设/存储器的访问。
优先权管理分2个阶段:
软件:每个通道的优先权可以在DMA_CCRx寄存器中设置,有4个等级:最高优先级、高优先级、中等优先级、低优先级;
硬件:如果2个请求有相同的软件优先级,则较低编号的通道比较高编号的通道有较高的优先权。比如:如果软件优先级相同,通道2优先于通道4。
注意: 在大容量产品和互联型产品中,DMA1控制器拥有高于DMA2控制器的优先级。
实验流程
1)使能 DMA1时钟。
- 初始化 DMA1 通道 4,包括配置通道,外设地址,存储器地址,传输数据量等。
3)使能串口 1 的 DMA 发送
4)使能 DMA1 通道 4,启动传输。
5)查询 DMA 传输状态
实验用到的函数
1)使能DMA1时钟
__HAL_RCC_DMA1_CLK_ENABLE();
2)初始化 DMA1 通道 4,包括配置通道,外设地址,存储器地址,传输数据量等。
DMA_HandleTypeDef UART1TxDMA_Handler; //DMA 句柄
UART1TxDMA_Handler.Instance= DMA1_Channel4; //数据流选择
UART1TxDMA_Handler.Init.Request=DMA_REQUEST_2; //通道选择
UART1TxDMA_Handler.Init.Direction=DMA_MEMORY_TO_PERIPH; //存储器到外设
UART1TxDMA_Handler.Init.PeriphInc=DMA_PINC_DISABLE; //外设非增量模式
UART1TxDMA_Handler.Init.MemInc=DMA_MINC_ENABLE; //存储器增量模式
UART1TxDMA_Handler.Init.PeriphDataAlignment=DMA_PDATAALIGN_BYTE;//外设:8 位
UART1TxDMA_Handler.Init.MemDataAlignment=DMA_MDATAALIGN_BYTE; //存储器:8 位
UART1TxDMA_Handler.Init.Mode=DMA_NORMAL; //普通模式
UART1TxDMA_Handler.Init.Priority=DMA_PRIORITY_MEDIUM; //中等优先级
HAL 库为了处理各类外设的 DMA 请求,在调用相关函数之前,需要调用一个宏定义标识符,来连接 DMA 和外设句柄。例如要使用串口 DMA 发送,所以方式为:
__HAL_LINKDMA(&UART1_Handler,hdmatx,UART1TxDMA_Handler);
其中 UART1_Handler 是串口初始化句柄,我们在 usart.c 中定义过了。UART1TxDMA_Handler 是 DMA 初始化句柄。hdmatx 是外设句柄结构体的成员变量,在这里实际就是 UART1_Handler 的 成员变量。在 HAL 库中,任何一个可以使用 DMA 的外设,它的初始化结构体句柄都会有一个 DMA_HandleTypeDef 指针类型的成员变量,是 HAL 库用来做相关指向的。Hdmatx 就是 DMA_HandleTypeDef 结构体指针类型。 这句话的含义就是把 UART1_Handler 句柄的成员变量 hdmatx 和 DMA 句柄 UART1TxDMA_Handler 连接起来,是纯软件处理,没有任何硬件操作。
进入函数的definition可以看到
#define __HAL_LINKDMA(__HANDLE__, __PPP_DMA_FIELD__, __DMA_HANDLE__)
do{
(__HANDLE__)->__PPP_DMA_FIELD__ = &(__DMA_HANDLE__); (__DMA_HANDLE__).Parent = (__HANDLE__);
} while(0)
其中parent来自于DMA的成员变量
typedef struct __DMA_HandleTypeDef {
DMA_Stream_TypeDef *Instance;
DMA_InitTypeDef Init;
HAL_LockTypeDef Lock;
__IO HAL_DMA_StateTypeDef State;
void *Parent;
void (* XferCpltCallback)( struct __DMA_HandleTypeDef * hdma);
void (* XferHalfCpltCallback)( struct __DMA_HandleTypeDef * hdma);
void (* XferM1CpltCallback)( struct __DMA_HandleTypeDef * hdma);
void (* XferErrorCallback)( struct __DMA_HandleTypeDef * hdma);
__IO uint32_t ErrorCode;
uint32_t StreamBaseAddress;
uint32_t StreamIndex;
}DMA_HandleTypeDef;
-
成员变量 Instance 是用来设置寄存器基地址,例如要设置为 DMA2 的数据流 7,那么取值 为 DMA2_Stream7。
-
成员变量 Parent 是 HAL 库处理中间变量,用来指向 DMA 通道外设句柄。
-
成员变量 XferCpltCallback(传输完成回调函数), XferHalfCpltCallback(半传输完成 回调函数), XferM1CpltCallback(Memory1 传输完成回调函数)和 XferErrorCallback(传输 错误回调函数)是四个函数指针,用来指向回调函数入口地址。
-
成员变量 StreamBaseAddress 和 StreamIndex 是数据流基地址和索引号,这个是 HAL 库处 理的时候会自动计算,用户无需设置。
所以该语句就是把HAL库处理中间变量指向DMA通道外设句柄: USART1,把DMA句柄指向USART1成员变量,即把两者联系起来。
3)使能串口 1 的 DMA 发送
HAL_DMA_Init(&hdma_usart1_tx)
4)使能 DMA1 通道 4,启动传输。
HAL_UART_Transmit_DMA(&UART1_Handler, (u8*)SendBuff, SEND_BUF_SIZE); //启动传输
这个函数比较好理解,第一个参数是 DMA 句柄,第二个是传输源地址,第三个是传输的数据长度。
5)查询 DMA 传输状态
在 DMA 传输过程中,我们要查询 DMA 传输通道的状态,使用的方法是:等待DMA传输完成,完成后清除FLAG
__HAL_DMA_GET_FLAG(&UART1TxDMA_Handler,DMA_FLAG_TC4);
__HAL_DMA_CLEAR_FLAG(&hdma_usart1_tx, DMA_FLAG_TC4);
获取当前传输剩余数据量:
__HAL_DMA_GET_COUNTER(&UART1TxDMA_Handler);