DMA 简介
直接存储器访问 (DMA) 用于在外设与存储器之间以及存储器与存储器之间提供高速数据传输。
可以在无需任何 CPU 操作的情况下通过 DMA 快速移动数据。这样节省的 CPU 资源可 供其它操作使用。
DMA 控制器基于复杂的总线矩阵架构,将功能强大的双 AHB 主总线架构与独立的 FIFO 结 合在一起,优化了系统带宽。 两个 DMA 控制器总共有 16 个数据流(每个控制器 8 个),每一个 DMA 控制器都用于管理 一个或多个外设的存储器访问请求。每个数据流总共可以有多达 8 个通道(或称请求)。每 个通道都有一个仲裁器,用于处理 DMA 请求间的优先级。
- DMA控制器
用于管理DMA的硬件资源,分为DMA1和DMA2,两个DMA控制器基本结构相同,但是DAM2具有存储器到存储区的传输方式,DMA1么有。
- DMA流
进行DMA数据传输的链路,是一个硬件结构。因此,每个DMA流都有一个独立的中断地址,具有多个中断事件源。每个DMA流有独立的4级32位FIFO缓冲区
- DMA请求
外设或者存储器发起的DMA传输请求,DMA通道。一个DMA流可以由8个DMA请求,每个DMA请求一般有两种可选的DMA流。
仲裁器
基于优先级别的DMA请求管理。每个DMA可以设置软件优先级级别。假设两个DMA请求优先级相同,则流编号更小的优先级更高。
DMA传输属性
- DMA流和通道 一个DMA流需要选择一个DMA通道(外设的DMA请求)后,才能组成一个DAM传输链路。
- DMA流的优先级
- 原地址和目标地址
- 源和目标的数据宽度
- 传输数据量的大小
- 源和目标的地址是否自增加
- DMA的工作模式 正常模式(normal 单次模式) 循环模式(circular)
- DMA的传输模式 传输数据方向 存储器到存储器 存储器到外设 外设到存储器
- FIFO是否启动和FIFO的阈值
- 突发传输 突发传输的属性:源 目标 数据大小
- 双缓冲区的使能
- 流量控制
补充说明
原地址和目标地址
32位STM32 MCU 所有寄存器、外设和存储器是在4G范围内统一编写地址的,地址范围位 0x00000000~0xFFFFFFFF。
数据宽度
DMA传输数据宽度有3种方式 字节(byte) 半字(half word)字(word)
源和目标的地址是否自增加
可以设置完成每次DMA传输以后,外设或者存储器的指针是否自增加
FIFO
每个DMA具有4级32位FIFO缓冲区。FIFO 用于在源数据传输到目标之前临时存储这些数据。
每个数据流都有一个独立的 4 字 FIFO,阈值级别可由软件配置为 1/4、1/2、3/4 或满。
例:DAM源数据与目标数据不同,源传输数据位字节数据,而目标位32位数据。可以设置将4个8位的字节数据组合成32位数据进行传输。
DMA函数初始化以及启动函数
HAL_StatusTypeDef HAL_DMA_Init(DMA_HandleTypeDef *hdma) |
初始化 |
HAL_StatusTypeDef HAL_DMA_Start(DMA_HandleTypeDef *hdma, uint32_t SrcAddress, uint32_t DstAddress, uint32_t DataLength) |
开启DMA |
HAL_StatusTypeDef HAL_DMA_PollForTransfer(DMA_HandleTypeDef *hdma, HAL_DMA_LevelCompleteTypeDef CompleteLevel, uint32_t Timeout) |
轮询方式 等待DMA结束 |
HAL_StatusTypeDef HAL_DMA_Abort(DMA_HandleTypeDef *hdma) |
结束轮询方式的DMA |
HAL_StatusTypeDef HAL_DMA_Start_IT(DMA_HandleTypeDef *hdma, uint32_t SrcAddress, uint32_t DstAddress, uint32_t DataLength) |
中断方式启动DMA |
HAL_StatusTypeDef HAL_DMA_Abort_IT(DMA_HandleTypeDef *hdma) |
停止中断的DMA |
HAL_DMA_StateTypeDef HAL_DMA_GetState(DMA_HandleTypeDef *hdma) |
获取DMA的状态 |
uint32_t HAL_DMA_GetError(DMA_HandleTypeDef *hdma) |
获取DMA的错误信息 |
void HAL_DMA_IRQHandler(DMA_HandleTypeDef *hdma) |
DMA中断函数ISR |
双缓冲区模式 |
|
DMA中断跟踪
DMA的函数犹豫需要关联相关外设才能够驱动中断,因此DAM文件中没有固定的回调函数,而是通过函数指针的方式关联到相关的中断回调函数中。在DMA流对象结构体中,DMA_HandleTypeDef 的定义代码中有这些函数的指针
typedef struct __DMA_HandleTypeDef
{
DMA_Stream_TypeDef *Instance; /*!< Register base address */
DMA_InitTypeDef Init; /*!< DMA communication parameters */
HAL_LockTypeDef Lock; /*!< DMA locking object */
__IO HAL_DMA_StateTypeDef State; /*!< DMA transfer state */
void *Parent; /*!< Parent object state */
void (* XferCpltCallback)( struct __DMA_HandleTypeDef * hdma); /*!< DMA transfer complete callback */
void (* XferHalfCpltCallback)( struct __DMA_HandleTypeDef * hdma); /*!< DMA Half transfer complete callback */
void (* XferM1CpltCallback)( struct __DMA_HandleTypeDef * hdma); /*!< DMA transfer complete Memory1 callback */
void (* XferM1HalfCpltCallback)( struct __DMA_HandleTypeDef * hdma); /*!< DMA transfer Half complete Memory1 callback */
void (* XferErrorCallback)( struct __DMA_HandleTypeDef * hdma); /*!< DMA transfer error callback */
void (* XferAbortCallback)( struct __DMA_HandleTypeDef * hdma); /*!< DMA transfer Abort callback */
__IO uint32_t ErrorCode; /*!< DMA Error code */
uint32_t StreamBaseAddress; /*!< DMA Stream Base Address */
uint32_t StreamIndex; /*!< DMA Stream Index */
}DMA_HandleTypeDef;
DAM在触发中断后 会调用DMA的中断通用处理函数HAL_DMA_IRQHandler(DMA_HandleTypeDef *hdma)。DMA_HandleTypeDef 会指定对应外设的中断回调函数。
DMA HAL库使用:USART
串口2 通过DMA的方式进行数据的接受和发送。
- 时钟配置 略
- 串口配置 打开串口2,对串口2的数据传输进行配置,在DMA Setting中添加DAM流
串口传输优势以8位的字位单位。
USART2_TX 是从存储器到外设,存储器的地址自增加(否则会一直传出第一位数据),传输方式为Normal 模式,即发送1次就结束。
USART2_RX 是从外设到寄存器,存储器的地址自增加(否则传入的数据会在第一位不停被覆盖最终保留最后一位的传输数据)。模式为circular模式,需要不停检测外部是否有数据进行传送。
注 在必须打开串口全局中断,否则将无法接收到数据
- 程序代码添加
以DMA方式 发送数据
在完成初始化数据以后,可以直接将数据使用函数进行发送
uint8_t text[]="DEMO_8_0 DMA_UART";
HAL_UART_Transmit_DMA(&huart2, text, sizeof(text));
以DMA方式 接收数据
在DMA触发接收数据后会跳转如串口完成传输中断回调函数void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
重写中断回调函数即可对接收数据进行处理
#define RX_CMD_LEN 5 uint8_t rxBuffer[5]; uint8_t proBuffer[5]; void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { if(huart->Instance==USART2) { for (int i=0;i<RX_CMD_LEN;i++) { proBuffer[i]=rxBuffer[i]; } HAL_UART_Transmit_DMA(&huart2, proBuffer, 5); } }