DMA的作用
- 直接存储器访问(DMA)用于在外设于存储器之间以及存储器与存储器之间,提供高速数据传输。可以在无需任何CPU操作的情况下通过DMA快速移动数据。这样节省CPU资源可供其他操作使用。
- 总结:DMA的就是CPU的助手、数据搬运工。
- DMA外设要点概括
- 对于DMA这个器间,它的功能就是建立起一个数据传输的通道。
图表查看
- STM32F207VET6为例
- DMA支持APB1和APB2总线和AHB1、AHB2总线的数据传输
通道概念
- 1、建立(选取)传输通道
- 存储器->存储器
- 外设->存储器
- 存储器->外设
- 2、确定传输对象
- 具体功能
- UART(源)-内存(目标)
- 内存数据(源)-UART(目标)
- 具体功能
- 3、敲定传输细节
- 通道优先级
- 确定传输数据双方的数据格式
- 确定数据传输是否双方的数据格式
- 确定数据是否需要一直采集(循环模式是否使能)
- 是否需要传输标志/中断
实战
- DMA句柄结构体
- DMA_HandleTypeDef
DMA初始化结构体
- DMA_InitTypeDef ```c 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 ( XferM1HalfCpltCallback)( struct __DMA_HandleTypeDef hdma);
void ( XferErrorCallback)( struct __DMA_HandleTypeDef hdma);
void ( XferAbortCallback)( struct __DMA_HandleTypeDef hdma);
__IO uint32_t ErrorCode;
uint32_t StreamBaseAddress;
uint32_t StreamIndex;
}DMA_HandleTypeDef;
![image.png](https://cdn.nlark.com/yuque/0/2022/png/2322648/1658126688681-f5b86b6a-4103-46db-b830-ca822521e703.png#clientId=ubb24075a-1ac0-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=730&id=u9edefd07&margin=%5Bobject%20Object%5D&name=image.png&originHeight=912&originWidth=1040&originalType=binary&ratio=1&rotation=0&showTitle=false&size=159220&status=done&style=none&taskId=u7487727f-6be0-4c83-b69f-dfa4ee2bcd4&title=&width=832)<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/2322648/1658126716407-fef36282-7aaa-4fd1-931c-65b012f7bc24.png#clientId=ubb24075a-1ac0-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=735&id=ufc23f626&margin=%5Bobject%20Object%5D&name=image.png&originHeight=919&originWidth=1044&originalType=binary&ratio=1&rotation=0&showTitle=false&size=162275&status=done&style=none&taskId=ua1e4571b-3c7c-4a91-a649-268635b1aac&title=&width=835.2)<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/2322648/1658126744807-942a4cad-9b64-474d-9635-6323ed4d97b3.png#clientId=ubb24075a-1ac0-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=735&id=u119d6427&margin=%5Bobject%20Object%5D&name=image.png&originHeight=919&originWidth=1044&originalType=binary&ratio=1&rotation=0&showTitle=false&size=138279&status=done&style=none&taskId=u9b3fa603-1179-406b-9ce9-9cd8c41d546&title=&width=835.2)<br />按照之前的步骤生成MDK工程<br />在各自的c文件中(比如usart.c文件中)<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/2322648/1658127095131-65df3fd6-ccb6-430d-b6a7-fdf179de38f1.png#clientId=ubb24075a-1ac0-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=404&id=ue00c597b&margin=%5Bobject%20Object%5D&name=image.png&originHeight=505&originWidth=524&originalType=binary&ratio=1&rotation=0&showTitle=false&size=178545&status=done&style=none&taskId=u3b6cdb0f-88a2-431a-975c-85299c8c369&title=&width=419.2)
- Instance: DMA通道
- Direction
- DMA_MEMORY_TO_MEMORY: 内存到内存的模式
- DMA_MEMORY_TO_PERIPH: 内存到外设的模式
- DMA_PERIPH_TO_MEMORY: 外设到内存的模式
- PeriphInc: 外设地址是否递增
- DMA_PINC_ENABLE: 外设地址递增
- DMA_PINC_DISABLE: 外设地址不递增
- MemInc: 内存地址是否递增
- DMA_MINC_ENABLE: 内存地址递增
- DMA_MINC_DISABLE: 内存地址不递增
- PeriphDataAlignment: 外设数据宽度
- DMA_PDATAALIGN_BYTE: 外设的byte位
- DMA_PDATAALIGN_HALFWORD: 外设的halfword位
- DMA_PDATAALIGN_WORD: 外设的word位
- MemDataAlignment: 内存数据宽度
- DMA_MDATAALIGN_BYTE: 内存的byte位
- DMA_MDATAALIGN_BYTE: 内存的halfword位
- DMA_MDATAALIGN_BYTE: 内存的word位
<a name="yBVQT"></a>
##### 中断配置
![image.png](https://cdn.nlark.com/yuque/0/2022/png/2322648/1658127850846-b8e5aa2d-fd81-4ada-871b-7626cb420c49.png#clientId=ubb24075a-1ac0-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=239&id=ua1f1adaa&margin=%5Bobject%20Object%5D&name=image.png&originHeight=299&originWidth=423&originalType=binary&ratio=1&rotation=0&showTitle=false&size=92054&status=done&style=none&taskId=uf4159655-4644-4464-856a-962946fb248&title=&width=338.4)
- 中断初始化
<a name="Z5fNn"></a>
##### usart-DMA配置
- HAL_DMA_Init(&hdma_usart1_rx) : DMA初始化
- __HAL_LINKDMA(uartHandle,hdmarx,hdma_usart1_rx); : 将串口的外设和DMA连接在了一起
- uartHandle:串口的局柄
- DMA的句柄
- DMA外设初始化句柄
- #define __HAL_LINKDMA(__HANDLE__, __PPP_DMA_FIELD__, __DMA_HANDLE__) \
do{ \<br /> (__HANDLE__)->__PPP_DMA_FIELD__ = &(__DMA_HANDLE__); \<br /> (__DMA_HANDLE__).Parent = (__HANDLE__); \<br /> } while(0U)
- __HANDLE__: 用`->`指向了成员(结构体指针下的成员)
```c
...
//系统时钟初始化: 我设置的是72MHZ
void SystemClock_Config(void);
...
- 自己创建dma初始化(注释原来的)
#ifndef __BSP_DMA_H
#define __BSP_DMA_H
#include "stm32f1xx_hal_gpio.h"
#include "stm32f1xx_hal_dma.h"
void DMA_Config(void);
#endif
#include "bsp_dma.h"
DMA_HandleTypeDef DMA_Handle;
const int32_t TxBuffer[32]={ //发送缓冲区,定义在flash中,只读
0x1000,0x2000,0x3000,0x4000,0x5000,0x6000
};
uint32_t RxBuffer[32]; //接收缓冲区
void DMA_Config(void){
HAL_StatusTypeDef status = HAL_ERROR;
__HAL_RCC_DMA1_CLK_ENABLE(); //开启DMA1时钟(使能)
DMA_Handle.Instance = DMA1_Channel5;//Instance: 指向的是实际的通道
//DMA_Handle.Init = ;//init:指向了外设的初始化的结构体
/*
#define IS_DMA_DIRECTION(DIRECTION) (((DIRECTION) == DMA_PERIPH_TO_MEMORY ) || \
((DIRECTION) == DMA_MEMORY_TO_PERIPH) || \
((DIRECTION) == DMA_MEMORY_TO_MEMORY))
*/
DMA_Handle.Init.Direction = DMA_PERIPH_TO_MEMORY;//外设到内存
/*
#define IS_DMA_PERIPHERAL_INC_STATE(STATE) (((STATE) == DMA_PINC_ENABLE) || \
((STATE) == DMA_PINC_DISABLE))
*/
DMA_Handle.Init.PeriphInc=DMA_PINC_ENABLE; //外设递增使能
/*
#define IS_DMA_MEMORY_INC_STATE(STATE) (((STATE) == DMA_MINC_ENABLE) || \
((STATE) == DMA_MINC_DISABLE))
*/
DMA_Handle.Init.MemInc=DMA_MINC_DISABLE;//内存失能
/*
#define IS_DMA_PERIPHERAL_DATA_SIZE(SIZE) (((SIZE) == DMA_PDATAALIGN_BYTE) || \
((SIZE) == DMA_PDATAALIGN_HALFWORD) || \
((SIZE) == DMA_PDATAALIGN_WORD))
*/
DMA_Handle.Init.PeriphDataAlignment=DMA_PDATAALIGN_BYTE;//外设数据的长度
/*
#define IS_DMA_MEMORY_DATA_SIZE(SIZE) (((SIZE) == DMA_MDATAALIGN_BYTE) || \
((SIZE) == DMA_MDATAALIGN_HALFWORD) || \
((SIZE) == DMA_MDATAALIGN_WORD ))
*/
DMA_Handle.Init.MemDataAlignment=DMA_MDATAALIGN_BYTE;//内存数据的长度;DMA_MDATAALIGN_WORD: 32位=int
/*
#define IS_DMA_MODE(MODE) (((MODE) == DMA_NORMAL ) || \
((MODE) == DMA_CIRCULAR))
*/
DMA_Handle.Init.Mode=DMA_NORMAL;//外设普通模式; DMA_CIRCULAR: 循环传输模式
/*
#define IS_DMA_PRIORITY(PRIORITY) (((PRIORITY) == DMA_PRIORITY_LOW ) || \
((PRIORITY) == DMA_PRIORITY_MEDIUM) || \
((PRIORITY) == DMA_PRIORITY_HIGH) || \
((PRIORITY) == DMA_PRIORITY_VERY_HIGH))
*/
DMA_Handle.Init.Priority=DMA_PRIORITY_MEDIUM;//配置优先级; DMA_PRIORITY_MEDIUM: 中等
HAL_DMA_Init(&DMA_Handle);//传入句柄,进入初始化
}
开始DMA转化
/*
==============================================================================
##### How to use this driver #####
==============================================================================
[..]
(#) Enable and configure the peripheral to be connected to the DMA Channel
(except for internal SRAM / FLASH memories: no initialization is
necessary). Please refer to the Reference manual for connection between peripherals
and DMA requests.
(#) For a given Channel, program the required configuration through the following parameters:
Channel request, Transfer Direction, Source and Destination data formats,
Circular or Normal mode, Channel Priority level, Source and Destination Increment mode
using HAL_DMA_Init() function.
(#) Use HAL_DMA_GetState() function to return the DMA state and HAL_DMA_GetError() in case of error
detection.
(#) Use HAL_DMA_Abort() function to abort the current transfer
-@- In Memory-to-Memory transfer mode, Circular mode is not allowed.
*** Polling mode IO operation ***
=================================
[..]
(+) Use HAL_DMA_Start() to start DMA transfer after the configuration of Source
address and destination address and the Length of data to be transferred
(+) Use HAL_DMA_PollForTransfer() to poll for the end of current transfer, in this
case a fixed Timeout can be configured by User depending from his application.
*** Interrupt mode IO operation ***
===================================
[..]
(+) Configure the DMA interrupt priority using HAL_NVIC_SetPriority()
(+) Enable the DMA IRQ handler using HAL_NVIC_EnableIRQ()
(+) Use HAL_DMA_Start_IT() to start DMA transfer after the configuration of
Source address and destination address and the Length of data to be transferred.
In this case the DMA interrupt is configured
(+) Use HAL_DMA_IRQHandler() called under DMA_IRQHandler() Interrupt subroutine
(+) At the end of data transfer HAL_DMA_IRQHandler() function is executed and user can
add his own function by customization of function pointer XferCpltCallback and
XferErrorCallback (i.e. a member of DMA handle structure).
*** DMA HAL driver macros list ***
=============================================
[..]
Below the list of most used macros in DMA HAL driver.
(+) __HAL_DMA_ENABLE: Enable the specified DMA Channel.
(+) __HAL_DMA_DISABLE: Disable the specified DMA Channel.
(+) __HAL_DMA_GET_FLAG: Get the DMA Channel pending flags.
(+) __HAL_DMA_CLEAR_FLAG: Clear the DMA Channel pending flags.
(+) __HAL_DMA_ENABLE_IT: Enable the specified DMA Channel interrupts.
(+) __HAL_DMA_DISABLE_IT: Disable the specified DMA Channel interrupts.
(+) __HAL_DMA_GET_IT_SOURCE: Check whether the specified DMA Channel interrupt has occurred or not.
[..]
*/
/**
* @brief Start the DMA Transfer.
* @param hdma: pointer to a DMA_HandleTypeDef structure that contains
* the configuration information for the specified DMA Channel.
* @param SrcAddress: The source memory Buffer address
* @param DstAddress: The destination memory Buffer address
* @param DataLength: The length of data to be transferred from source to destination
* @retval HAL status
*/
HAL_StatusTypeDef HAL_DMA_Start(DMA_HandleTypeDef *hdma, uint32_t SrcAddress, uint32_t DstAddress, uint32_t DataLength)
{
HAL_StatusTypeDef status = HAL_OK;
/* Check the parameters */
assert_param(IS_DMA_BUFFER_SIZE(DataLength));
/* Process locked */
__HAL_LOCK(hdma);
if(HAL_DMA_STATE_READY == hdma->State)
{
/* Change DMA peripheral state */
hdma->State = HAL_DMA_STATE_BUSY;
hdma->ErrorCode = HAL_DMA_ERROR_NONE;
/* Disable the peripheral */
__HAL_DMA_DISABLE(hdma);
/* Configure the source, destination address and the data length & clear flags*/
DMA_SetConfig(hdma, SrcAddress, DstAddress, DataLength);
/* Enable the Peripheral */
__HAL_DMA_ENABLE(hdma);
}
else
{
/* Process Unlocked */
__HAL_UNLOCK(hdma);
status = HAL_BUSY;
}
return status;
}
- hdma: 指向了DMA的句柄结构体
- SrcAddress: 原地址(内存地址) 我们文件中就是指
TxBuffer
- DstAddress: 目标的储存器(内存地址),我们的文件中就是指
RxBuffer
- DataLength: 传输的大小
void DMA_Config(void){
....
DMA_Status = HAL_DMA_Start(&DMA_Handle,(uint32_t)TxBuffer,(uint32_t)RxBuffer,32);
/*判断DMA状态: DMA_Status !=HAL_OK */
if(DMA_Status !=HAL_OK){
while(1){
HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_5);
HAL_Delay(1000);
}
}
}
完整函数
#include "bsp_dma.h"
DMA_HandleTypeDef DMA_Handle;
HAL_StatusTypeDef DMA_Status;
const int32_t TxBuffer[32]={ //发送缓冲区,定义在flash中,只读
0x1000,0x2000,0x3000,0x4000,0x5000,0x6000
};
uint32_t RxBuffer[32]; //接收缓冲区
void DMA_Config(void){
HAL_StatusTypeDef status = HAL_ERROR;
__HAL_RCC_DMA1_CLK_ENABLE(); //开启DMA1时钟(使能)
DMA_Handle.Instance = DMA1_Channel5;//Instance: 指向的是实际的通道
//DMA_Handle.Init = ;//init:指向了外设的初始化的结构体
/*
#define IS_DMA_DIRECTION(DIRECTION) (((DIRECTION) == DMA_PERIPH_TO_MEMORY ) || \
((DIRECTION) == DMA_MEMORY_TO_PERIPH) || \
((DIRECTION) == DMA_MEMORY_TO_MEMORY))
*/
DMA_Handle.Init.Direction = DMA_PERIPH_TO_MEMORY;//外设到内存
/*
#define IS_DMA_PERIPHERAL_INC_STATE(STATE) (((STATE) == DMA_PINC_ENABLE) || \
((STATE) == DMA_PINC_DISABLE))
*/
DMA_Handle.Init.PeriphInc=DMA_PINC_ENABLE; //外设递增使能
/*
#define IS_DMA_MEMORY_INC_STATE(STATE) (((STATE) == DMA_MINC_ENABLE) || \
((STATE) == DMA_MINC_DISABLE))
*/
DMA_Handle.Init.MemInc=DMA_MINC_DISABLE;//内存失能
/*
#define IS_DMA_PERIPHERAL_DATA_SIZE(SIZE) (((SIZE) == DMA_PDATAALIGN_BYTE) || \
((SIZE) == DMA_PDATAALIGN_HALFWORD) || \
((SIZE) == DMA_PDATAALIGN_WORD))
*/
DMA_Handle.Init.PeriphDataAlignment=DMA_PDATAALIGN_BYTE;//外设数据的长度
/*
#define IS_DMA_MEMORY_DATA_SIZE(SIZE) (((SIZE) == DMA_MDATAALIGN_BYTE) || \
((SIZE) == DMA_MDATAALIGN_HALFWORD) || \
((SIZE) == DMA_MDATAALIGN_WORD ))
*/
DMA_Handle.Init.MemDataAlignment=DMA_MDATAALIGN_BYTE;//内存数据的长度;DMA_MDATAALIGN_WORD: 32位=int
/*
#define IS_DMA_MODE(MODE) (((MODE) == DMA_NORMAL ) || \
((MODE) == DMA_CIRCULAR))
*/
DMA_Handle.Init.Mode=DMA_NORMAL;//外设普通模式; DMA_CIRCULAR: 循环传输模式
/*
#define IS_DMA_PRIORITY(PRIORITY) (((PRIORITY) == DMA_PRIORITY_LOW ) || \
((PRIORITY) == DMA_PRIORITY_MEDIUM) || \
((PRIORITY) == DMA_PRIORITY_HIGH) || \
((PRIORITY) == DMA_PRIORITY_VERY_HIGH))
*/
DMA_Handle.Init.Priority=DMA_PRIORITY_MEDIUM;//配置优先级; DMA_PRIORITY_MEDIUM: 中等
HAL_DMA_Init(&DMA_Handle);//传入句柄,进入初始化
DMA_Status = HAL_DMA_Start(&DMA_Handle,(uint32_t)TxBuffer,(uint32_t)RxBuffer,32);
/*判断DMA状态: DMA_Status !=HAL_OK */
if(DMA_Status !=HAL_OK){
while(1){
HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_5);
HAL_Delay(1000);
}
}
}