stm32F407下DMA
STM32F4 最多有 2 个 DMA 控制器(DMA1 和 DMA2),共 16 个数据流(每个控制器 8 个),每一个 DMA 控制器都用于管理一个或多个外设的存储器访问请求。
每个数据流总共可以有多达 8个通道(或称请求)。每个数据流通道都有一个仲裁器,用于处理 DMA 请求间的优先级。
一、任务
- 使用DMA实现从内存至内存传输数据
- 使用DMA实现从内存至外设传输数据
二、几个概念
- 控制器:就是DMA1和DMA2,他们就是控制器
- 请求:每个控制器可以发送的
- 数据流:每个控制器 8 个
- 模式:Normal表单次传输,传输一次后终止传输,Circular表示循环传输,传输完成后又重新开始继续传输,不断循环永不停止
- 方向:3种,内存到内存,内存到外设,外设到内存
- 地址自增:串口发送数据是将数据不断存进串口的发送数据寄存器(USARTx_TDR)。所以外接的地址是不递增。而内存储器存储的是要发送的数据,所以地址指针要递增才能将所以的数据发送出去。
- 传输字宽:每次传输的数据长度,可以一个字节,两个字节(半字),四个字节(字),串口数据发送寄存器只能存储8bit,每次发送一个字节,所以数据长度选择Byte
三、配置
(一)usart串口配置
启用usart1串口,显示传输数据的内容
以下三项为基本配置,两种传输模式均使用此共同配置
- PA10<-->USART1.RX
- PA9<-->USART1.TX
- RCC和sys配置与之前一致,无需特别调整
四、实现
(一)内存至内存传输数据
存储器到存储器需要外设接口可以访问存储器,而仅 DMA2 的外设接口可以访问存储器,所以仅 DMA2 控制器支持存储器到存储器的传输,DMA1 不支持
1.DMA配置
- System Core->DMA->DMA2,新增(add)一个DMA
- DMA Request(请求):MEMTOMEM(内存到内存)
- Stream(通道):DMA2 Steam0-7均可,8个通道可用
- Direction(方向):Memory to Memory
- Mode(模式):normal(正常模式),即单次传输,发送完即停止
- Increment Address(指针自增模式):src memory和dst memory,都选择自增(内存至内存传输数据,都选择自增)
- Data width:byte(以字节长度传输数据),串口数据发送寄存器只能存储8bit,每次发送一个字节,所以数据长度选择Byte
2.实现
定义两个int数组变量,用于存储源和目标数组,分别命名为:src_buf和des_buf
/* * 定义两个数组,实现DMA从内存到内存进行传输数据 * src_buf:源数组,保存在内存中; * des_buf:目标数组,DMA将源数组拷贝至本数组,实现内�?-->内存复制功能 * */ const uint8_t src_buf[16]={ 1,2,3,4, 5,6,7,8, 9,10,11,12, 13,14,15,16 }; uint8_t des_buf[16];
传输数据
使用HAL_DMA_Start函数,将源数据内容传输至目标数组,并使用printf通过串口将目标数组发送至串口调试工具
/*
* 1. 内存到内存传输数据
* HAL_DMA_Start函数实现内存至内存数据传�?
*/
HAL_DMA_Start(&hdma_memtomem_dma2_stream0, (uint32_t)src_buf, (uint32_t)des_buf, 16);
/*
* 将源数组和目标数据,通过串口输出至串口调试工�?
*/
for(int i=0;i<16;i++) { printf("src_buf[%d]=%d,des_buf[%d]+1=%d\r\n",i,src_buf[i],i,des_buf[i]+1);
}
- 显示结果如下
src_buf[0]=1,des_buf[0]+1=1 src_buf[1]=2,des_buf[1]+1=3 src_buf[2]=3,des_buf[2]+1=4 src_buf[3]=4,des_buf[3]+1=5 src_buf[4]=5,des_buf[4]+1=6 src_buf[5]=6,des_buf[5]+1=7 src_buf[6]=7,des_buf[6]+1=8 src_buf[7]=8,des_buf[7]+1=9 src_buf[8]=9,des_buf[8]+1=10 src_buf[9]=10,des_buf[9]+1=11 src_buf[10]=11,des_buf[10]+1=12 src_buf[11]=12,des_buf[11]+1=13 src_buf[12]=13,des_buf[12]+1=14 src_buf[13]=14,des_buf[13]+1=15 src_buf[14]=15,des_buf[14]+1=16 src_buf[15]=16,des_buf[15]+1=17
(二)内存至外设传输数据
通过DMA将内存中的内容,发送至usart串口外设,这里的发送,不再使用printf函数,而是使用函数通过串口直接发送
1.DMA配置
- Connectivity->usart1->DMA Settings,新增(add)一个DMA
- DMA Request(请求):USART1_TX,使用usart1的发送端口
- Stream(通道):DMA2_stream7,这里只能选择7通道,可以查询DMA2控制器的8个通道功能表看不同接口可以使用的通道
- Direction(方向):memory to pheriperal(内存至外设)
- Mode:nomarl
- Increment Address(指针自增模式):src选择自增,dst不选,从内存到外设,读取内存,地址在变化,而外设地址是不变的。
- Data width:byte
2.实现
DMA从内存向外设(usart1)传输数据
定义一个字符串,用于存储向外发送的内容
const uint8_t txt_buf[]="welcome to bytetoy.cn!\r\n";
传输数据
使用HAL_UART_Transmit_DMA函数发送数据
/* * 2. DMA从内存向外设(usart1)传输数据 * 这里需要注意,HAL_UART_Transmit_DMA传输数据后,需要关闭端口,否则串口会一直busy,后面的无法使用串口传输数据 */ HAL_UART_Transmit_DMA(&huart1,(uint8_t *)txt_buf, sizeof(txt_buf));
显示结果如下
welcome to bytetoy.cn!
五、注意事项
(一)HAL_DMA_Start函数
此函数的第二、三参数是源地址和目标内存地址,地址是32位长度,使用时需要强制转换
而HAL_UART_Transmit_DMA函数的源内存地址是8位长度
HAL_DMA_Start(&hdma_memtomem_dma2_stream0, (uint32_t)src_buf, (uint32_t)des_buf, 16);
(二)HAL_UART_Transmit_DMA函数
此函数如果不开启串口中断,则程序只能发送一次数据,程序不能判断DMA传输是否完成,USART一直处于busy状态,此配置在usart1的NVIC Settings配置种,选择使能interupt
需要手动关闭DMA通道(HAL_UART_DMAStop),串口方可进行后面的传输。
其次是,串口传输数据比较慢,使用此函数传输数据较长时,传输后,需要delay一段时间,否则数据无法传输完成。
HAL_UART_Transmit_DMA(&huart1,(uint8_t *)txt_buf, sizeof(txt_buf));
HAL_Delay(5000);
HAL_UART_DMAStop(&huart1);
for(int i=0;i<16;i++) {
printf("src_buf[%d]=%d,des_buf[%d]+1=%d\r\n",i,src_buf[i],i,des_buf[i]+1);
}