参考文章:STM32 DMA详解
1. 什么是DMA
令人头秃的描述
DMA(Direct Memory Access,直接存储器访问) 提供在外设与内存、存储器和存储器、外设与外设之间的高速数据传输使用。它允许不同速度的硬件装置来沟通,而不需要依赖于CPU,在这个时间中,CPU对于内存的工作来说就无法使用。
简单描述:
就是一个数据搬运工!!
2. DMA的意义
代替 CPU 搬运数据,为 CPU 减负。
(1)数据搬运的工作比较耗时间;
(2) 数据搬运工作时效要求高(有数据来就要搬走);
(3) 没啥技术含量(CPU 节约出来的时间可以处理更重要的事)。
3. 搬运什么数据
存储器: spi、usart、iic、adc 等基于APB1 、APB2或AHB时钟的外设
外设: 自身的闪存(flash)或者内存(SRAM)以及外设的存储设备都可以作为访问地源或者目的
三种搬运方式:
搬运方式 | 用途(案例) |
---|---|
存储器→存储器 | 复制某特别大的数据buf |
存储器→外设 | 将某数据buf写入串口TDR寄存器 |
外设→存储器 | 将串口RDR寄存器写入某数据buf |
模式 | 传输方式 |
---|---|
DMA_Mode_Normal(正常模式) | 一次DMA数据传输完后,停止DMA传送 ,也就是只传输一次 |
DMA_Mode_Circular(循环传输模式) | 当传输结束时,硬件自动会将传输数据量寄存器进行重装,进行下一轮的数据传输。 也就是多次传输模式 |
常用库函数解析
HAL_StatusTypeDef HAL_DMA_Start(DMA_HandleTypeDef *hdma, uint32_t SrcAddress, uint32_t DstAddress, uint32_t DataLength)
参数解析
参数 | 解析 |
---|---|
返回值 | HAL_StatusTypeDef,HAL状态(OK,busy,ERROR,TIMEOUT ) |
DMA_HandleTypeDef *hdma | DMA通道句柄 |
uint32_t SrcAddress | 源内存地址 |
uint32_t DstAddress | 目标内存地址 |
uint32_t DataLength | 传输数据长度。注意:需要乘以sizeof(uint32_t) |
#define __HAL_DMA_GET_FLAG(__HANDLE__, __FLAG__) (DMA1->ISR & (__FLAG__))
参数解析
参数 | 解析 |
---|---|
返回值 | FLAG的值(SET/RESET ) |
__HANDLE__ |
DMA通道句柄 |
__FLAG__ |
数据传输标志。DMA_FLAG_TCx 表示数据传输完成标志,x表通道数 |
Hal库程序设计
// 源数组 放在flash内部
const uint32_t srcBuf[16] = {
0x00000000,0x11111111,0x22222222,0x33333333,
0x44444444,0x55555555,0x66666666,0x77777777,
0x88888888,0x99999999,0xAAAAAAAA,0xBBBBBBBB,
0xCCCCCCCC,0xDDDDDDDD,0xEEEEEEEE,0xFFFFFFFF
};
// 目标数组 放置在SRAM内
uint32_t desBuf[16];
//开启数据传输
HAL_DMA_Start(&hdma_memtomem_dma1_channel1, (uint32_t)srcBuf, (uint32_t)desBuf, 16 * sizeof(uint32_t));
//等待数据传输完成
while( __HAL_DMA_GET_FLAG(&hdma_memtomem_dma1_channel1, DMA_FLAG_TC1) == RESET );
//打印目标数组内容
HAL_UART_Transmit(&huart1,(const uint8_t *)desBuf, 16 * sizeof(uint32_t), 200);
#defien BUF_SIZE 16
//源数组 const表常量类型,数组buf放在flash中
const uint32_t src_buf[BUF_SIZE]={
0x00000000,0x11111111,0x22222222,0x33333333,
0x44444444,0x55555555,0x66666666,0x77777777,
0x88888888,0x99999999,0xAAAAAAAA,0xBBBBBBBB,
0xCCCCCCCC,0xDDDDDDDD,0xEEEEEEEE,0xFFFFFFFF
};
//目标数组 放置在SRAM中
uint32_t des_buf[BUF_SIZE];
void DMA_MTM_Init(void)
{
DMA_InitTypeDef DMA_InitStructure;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); //使能DMA时钟
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)src_buf; //外设地址
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)des_buf; //内存地址
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; //传输方向
DMA_InitStructure.DMA_BufferSize = BUF_SIZE * 4; //传输数量
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Enable; //外设地址增量
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //存储器地址递增
DMA_InitStructure.DMA_PeripheralDataSize= DMA_PeripheralDataSize_Byte; //外设数据宽度 1字节
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; //存储器数据宽度 1字节
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; //正常模式,发送一次
DMA_InitStructure.DMA_Priority = DMA_Priority_Low; //优先级 低
DMA_InitStructure.DMA_M2M = DMA_M2M_Enable; //使能MTM
DMA_Init(DMA1_Channel1, &DMA_InitStructure); //结构体初始化
DMA_ClearFlag(DMA1_FLAG_TC1); //清除对应标志位
DMA_Cmd(DMA1_Channel1, ENABLE); //使能DMA1 通道1
}
uint8_t Buffercmp(const uint32_t *pBuffer1, uint32_t *pBuffer2, uint32_t BufferLen )
{
while(BufferLen--)
{
if( *pBuffer1 != *pBuffer2) //对应的两个源数组不相等,返回0
return 0;
pBuffer1++; //递增两个数据源的地址指针
pBuffer2++;
}
return 1; //完成判断 并且对应数据相对
}
//外部源数组、目标数组变量定义
extern const uint32_t src_buf[BUF_SIZE];
extern uint32_t des_buf[BUF_SIZE];
int main(void)
{
LED_Init();
Usart1_Init(115200); //串口初始化
DMA_MTM_Init(); //M2M初始化
Buffercmp(src_buf, des_buf, BUF_SIZE * 4); //M2M传输数据
//串口打印目标数组数据
Usart_SendString(USART1,(unsigned char *)des_buf, BUF_SIZE * 4);
while(1)
{
}
}
串口DMA发送函数解析
HAL_StatusTypeDef HAL_UART_Transmit_DMA(UART_HandleTypeDef *huart, const uint8_t *pData, uint16_t Size)
参数解析
参数 | 解析 |
---|---|
返回值 | HAL_StatusTypeDef,HAL状态(OK,busy,ERROR,TIMEOUT ) |
UART_HandleTypeDef *huart | 串口句柄 |
const uint8_t *pData | 待发送数据首地址 |
uint16_t Size | 发送数据长度 |
DMA串口发送程序设计
#include
//发送缓存
unsigned char sendBuf[128] = "send by DMA\r\n";
unsigned char sendBuf[128] = "send by DMA\r\n";
//DMA发送数据,此过程不消耗CPU
HAL_UART_Transmit_DMA(&huart1, sendBuf, strlen((const char *)sendBuf));
#define SEND_SIZE 16 //USART_DMA
#define USART1_DR_ADDR (USART1_BASE + 0x04) //串口发送寄存器
//发送数据变量定义
uint8_t send_buf[SEND_SIZE] = "0123456789ABCDEF";
void USART1_DMATx_Init(void)
{
DMA_InitTypeDef DMA_InitStructure;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); //使能DMA时钟
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)send_buf; //内存地址
DMA_InitStructure.DMA_PeripheralBaseAddr= (uint32_t)USART1_DR_ADDR; //外设地址
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; //传输方向
DMA_InitStructure.DMA_BufferSize = SEND_SIZE ; //传输数量
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外设地址不变
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //存储器地址递增
DMA_InitStructure.DMA_PeripheralDataSize= DMA_PeripheralDataSize_Byte; //外设数据宽度 1字节
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; //存储器数据宽度 1字节
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; //正常模式,发送一次
DMA_InitStructure.DMA_Priority = DMA_Priority_Low; //优先级 低
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; //失能MTM
DMA_Init(DMA1_Channel4, &DMA_InitStructure); //结构体初始化
DMA_ClearFlag(DMA1_FLAG_TC4); //清除对应标志位
DMA_Cmd(DMA1_Channel4, ENABLE); //使能DMA1 通道4
}
USART1_DMATx_Init(); //串口DMA发送初始化
USART_DMACmd(USART1, USART_DMAReq_Tx , ENABLE ); //使能串口DMA发送请求
1使能IDLE空闲中断
#define __HAL_UART_ENABLE_IT(__HANDLE__, __INTERRUPT__) ............
参数解析
参数 | 解析 |
---|---|
__HANDLE__ |
串口句柄 |
__INTERRUPT__ |
需要使能的中断 |
返回值 | 无 |
串口DMA接收函数
HAL_StatusTypeDef HAL_UART_Receive_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
参数解析
参数 | 解析 |
---|---|
返回值 | HAL_StatusTypeDef,HAL状态(OK,busy,ERROR,TIMEOUT ) |
UART_HandleTypeDef *huart | 串口句柄 |
uint8_t *pData | 接收缓存首地址 |
uint16_t Size | 接收缓存长度 |
获取串口 Flag的值
#define __HAL_UART_GET_FLAG(__HANDLE__, __FLAG__) (((__HANDLE__)->Instance->SR & (__FLAG__)) == (__FLAG__))
参数解析
参数 | 解析 |
---|---|
返回值 | FLAG的值(SET/RESET ) |
__HANDLE__ |
串口句柄 |
__FLAG__ |
需要查看的FLAG |
清除串口IDLE标识
#define __HAL_UART_CLEAR_IDLEFLAG(__HANDLE__) __HAL_UART_CLEAR_PEFLAG(__HANDLE__)
参数 | 解析 |
---|---|
__HANDLE__ |
串口句柄 |
返回值 | 无 |
关闭串口DMA
HAL_StatusTypeDef HAL_UART_DMAStop(UART_HandleTypeDef *huart)
参数 | 解析 |
---|---|
UART_HandleTypeDef *huart | 串口句柄 |
返回值 | HAL_StatusTypeDef,HAL状态(OK,busy,ERROR,TIMEOUT ) |
获取未传输数据大小
#define __HAL_DMA_GET_COUNTER(__HANDLE__) ((__HANDLE__)->Instance->CNDTR)
参数 | 解析 |
---|---|
__HANDLE__ |
串口句柄 |
返回值 | 未传输数据大小 |
如何判断串口接收是否完成?如何知道串口收到数据的长度?
使用串口空闲中断(IDLE)!
利用串口空闲中断,可以用如下流程实现DMA控制的任意长数据接收:
#define BUF_SIZE 128
//DMA接收缓存变量定义
unsigned char rcvBuf[BUF_SIZE] = {0};
unsigned char rcvLen = 0; //接收字节数
main.c初始化
__HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE); //使能IDLE空闲中断
HAL_UART_Receive_DMA(&huart1, rcvBuf, BUF_SIZE); //使能DMA接收中断
//中断服务函数中,清除中断标志位,关闭DMA传输,计算数据接收长度并操作数据
void USART1_IRQHandler(void)
{
/* USER CODE BEGIN USART1_IRQn 0 */
/* USER CODE END USART1_IRQn 0 */
HAL_UART_IRQHandler(&huart1);
/* USER CODE BEGIN USART1_IRQn 1 */
if( __HAL_UART_GET_FLAG(&huart1, UART_FLAG_IDLE) == SET ) // 判断IDLE标志位是否被置位)
{
__HAL_UART_CLEAR_IDLEFLAG(&huart1); //清除中断标志位
HAL_UART_DMAStop(&huart1); //关闭DMA传输
rcvLen = BUF_SIZE - __HAL_DMA_GET_COUNTER(&hdma_usart1_rx); //计算收到的字节数
//处理缓冲区数据 若程序复杂可立flag 在 while(1) 中处理
HAL_UART_Transmit_DMA(&huart1,rcvBuf,rcvLen);
if(strstr((const char *)rcvBuf,"open") != NULL)
HAL_GPIO_WritePin(LED1_GPIO_Port,LED1_Pin,GPIO_PIN_RESET);
if(strstr((const char *)rcvBuf,"close") != NULL)
HAL_GPIO_WritePin(LED1_GPIO_Port,LED1_Pin,GPIO_PIN_SET);
HAL_UART_Receive_DMA(&huart1, rcvBuf, BUF_SIZE); //开启DMA传输
}
/* USER CODE END USART1_IRQn 1 */
}
//USART_DMA_R 接收缓存
uint8_t receive_buf[16] ={'\0'};
//USART1_DMA_Receive
void USART1_DMARx_Init(void)
{
DMA_InitTypeDef DMA_InitStructure;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); //使能DMA时钟
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)receive_buf; //内存地址
DMA_InitStructure.DMA_PeripheralBaseAddr =(uint32_t)USART1_DR_ADDR; //外设地址
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; //传输方向
DMA_InitStructure.DMA_BufferSize = 16; //传输数量
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外设地址不变
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //存储器地址递增
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;//外设数据宽度 1字节
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; //存储器数据宽度 1字节
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; //循环接收
DMA_InitStructure.DMA_Priority = DMA_Priority_High; //优先级 高
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; //失能MTM
DMA_Init(DMA1_Channel5, &DMA_InitStructure); //结构体初始化
DMA_ClearFlag(DMA1_FLAG_TC5); //清除对应标志位
DMA_Cmd(DMA1_Channel5, ENABLE); //使能DMA1 通道5
}
uint8_t DMARx_flag = 0; //中断标志
extern uint8_t receive_buf[128]; //外部USART_DMA 接收缓存
int main()
{
Usart1_Init(115200); //串口初始化 115200
//USRAT_DMA
USART1_DMARx_Init(); //初始化DMA接收
USART_DMACmd(USART1, USART_DMAReq_Rx , ENABLE);//使能串口DMA接收请求
USART_ITConfig(USART1, USART_IT_IDLE, ENABLE); //使能空闲中断
while(1)
{
}
}
void USART1_IRQHandler ( void )
{
u16 Usart1_Rec_Cnt = 0;
if ( USART_GetITStatus ( USART1, USART_IT_IDLE ) != RESET ) //检查是否发生空闲中断
{
USART1->SR;
USART1->DR; // 清USART_IT_IDLE标志
USART_ClearITPendingBit ( USART1, USART_IT_IDLE ); // 清除中断标志
DMA_Cmd ( DMA1_Channel5, DISABLE ); //关闭DMA传输,进行数据处理,数据处理完再开启
// 计算接收到的数据帧长度
Usart1_Rec_Cnt = 16 - DMA_GetCurrDataCounter ( DMA1_Channel5);
printf ( "The length is %d\r\n", Usart1_Rec_Cnt );
printf ( "The data is %s", receive_buf );
Usart1_Rec_Cnt = 0;
memset(receive_buf,'\0',16);
DMA_SetCurrDataCounter( DMA1_Channel5, 16 ); //清除counter计数器
DMA_Cmd ( DMA1_Channel5, ENABLE );
// DMARx_flag = 1; //中断标志位
}
}