STM32-DMA数据传输(USART-ADC-数组)
DMA结构体初始化DMA功能框图DMA数据配置DMA传输数据的思路存储器到外设传输数据存储器到存储器传输数据
文章目录
一、DMA简介
DMA:直接存储器访问
-
主要功能:
把数据从一个位置搬移到另外一个位置,而且不占用CPU,即在传输数据的时候, CPU 可以干其他的事情。
数据传输支持从外设到存储器或者存储器到存储器,这里的存储器可以是 SRAM 或者是 FLASH。存储器到外设典型应用是:USART
外设到存储器典型应用是:ADC
DMA 控制器包含了 DMA1 和 DMA2,其中 DMA1 有 7 个通道, DMA2 有 5 个通道,这里的通道可以理解为传输数据的一种管道。 -
使用USART传输数据时
数组的内容首先发送给CPU的CR1,CR2,CR3然后再发到串口
-
使用DMA传输数据
只需要CPU给DMA下达一个指令,数组内容即可通过串口发送出去
二、DMA 功能框图
(一)DMA请求
如果外设要想通过 DMA 来传输数据,必须先给 DMA 控制器发送 DMA 请求, DMA 收到请求信号之后,控制器会给外设一个应答信号,当外设应答后且 DMA 控制器收到应答信号之后,就会启动 DMA 的传输,直到传输完毕
(二)DMA通道
-
DM1请求映射
如果使用串口,则对应通道2,3,4,5,6,7
USART1的TX和RX分别对应DMA1的通道4和通道5(并不是随便使用) -
DMA2请求映射
当多个DMA请求同时来的时候,则需要用到仲裁器来决定谁先执行
(三)DMA仲裁
仲裁器根据通道请求的优先级来启动外设/存储器的访问。
优先权管理分2个阶段:
● 软件:每个通道的优先权可以在DMA_CCRx寄存器中设置,有4个等级:
─ 最高优先级
─ 高优先级
─ 中等优先级
─ 低优先级
● 硬件:如果2个请求有相同的软件优先级,则较低编号的通道比较高编号的通道有较高的优先权。举个例子,通道2优先于通道4。
三、数据配置
-
DMA初始化结构体
-
DMA数据配置
使用 DMA,最核心就是配置要传输的数据,包括数据从哪里来,要到哪里去,传输的数据的单位是什么,要传多少数据,是一次传输还是循环传输等等。
(一)数据从哪里来到哪里去?
-
DMA_PeripheralBaseAddr
外设地址,设定 DMA_CPAR 寄存器的值;一般设置为外设的数据寄存器地址,如果是存储器到存储器模式则设置为其中一个存储器地址。
-
DMA_Memory0BaseAddr
存储器地址,设定DMA_CMAR 寄存器值;一般设置为我们自定义存储区的首地址。—如果定义了一个数组,数组名即为首地址
-
DMA_DIR
传输方向选择,可选外设到存储器、存储器到外设。它设定 DMA_CCR 寄存器的DIR[1:0] 位的值。这里并没有存储器到存储器的方向选择,当使用存储器到存储器时,只需要把其中一个存储器当作外设使用即可。
DMA_DIR:规定外设是作为数据传输的目的地还是来源(数据传输方向)
DMA_DIR_PeripheralDST 外设作为数据传输的目的地 DMA_DIR_PeripheralSRC 外设作为数据传输的来源
(二)数据要传多少,传输的单位是什么?
-
DMA_BufferSize
要传输的数据个数
-
DMA_PeripheralInc
如果配置为 DMA_PeripheralInc_Enable,使能外设地址自动递增功能,(比如说外设地址定义了一个数组,地址需要递增),一般外设都是只有一个数据寄存器,所以一般不会使能该位。
-
DMA_MemoryInc
如果配置为 DMA_MemoryInc_Enable,使能存储器地址自动递增功能(同样定义了一个数组,地址需要递增),我们自定义的存储区一般都是存放多个数据的,所以要使能存储器地址自动递增功能。
-
DMA_PeripheralDataSize
外设的数据宽度,可选字节 (8 位)、半字 (16 位) 和字 (32 位)—这是根据定义的外设变量位数来决定
-
DMA_MemoryDataSize
存储器数据宽度,可选字节 (8 位)、半字 (16 位) 和字 (32 位)–根据定义的存储器变量位数决定
当外设和存储器之间传数据时,两边的数据宽度应该设置为一致大小
(三)什么时候传输结束?
-
DMA_Mode
DMA 传输模式选择,可选一次传输或者循环传输
例如串口发送一个数组内容的数据就可以设置位单次传输
例如我们的 ADC 采集是持续循环进行的,所以使用循环传输模式。
四、DMA三种应用
(一)从存储器到存储器(M-to-M)
当我们使用从存储器到存储器传输时,以内部 FLASH 向内部 SRAM 复制数据为例。
DMA 外设寄存器的地址对应的就是内部 FLASH(我们这里把内部 FALSH 当作一个外设来看)的地址, DMA存储器的地址就是我们自定义的变量(相当于一个缓冲区,用来存储来自内部 FLASH 的数据)的地址。
实验目的:
我们先定义一个静态的源数据,存放在内部 FLASH,然后使用 DMA 传输把源数据拷贝到目标地址上(内部 SRAM),最后对比源数据和目标地址的数据,看看是否传输准确
-
首先定义两个数组:一个作为外设,一个作为存储器
内部FLASH是存放代码code
内部SRAM是定义的变量/* 定义aSRC_Const_Buffer数组作为DMA传输数据源 * const关键字将aSRC_Const_Buffer数组变量定义为常量类型 * 表示数据存储在内部的FLASH中 */ const uint32_t aSRC_Const_Buffer[BUFFER_SIZE]= { 0x01020304,0x05060708,0x090A0B0C,0x0D0E0F10, 0x11121314,0x15161718,0x191A1B1C,0x1D1E1F20, 0x21222324,0x25262728,0x292A2B2C,0x2D2E2F30, 0x31323334,0x35363738,0x393A3B3C,0x3D3E3F40, 0x41424344,0x45464748,0x494A4B4C,0x4D4E4F50, 0x51525354,0x55565758,0x595A5B5C,0x5D5E5F60, 0x61626364,0x65666768,0x696A6B6C,0x6D6E6F70, 0x71727374,0x75767778,0x797A7B7C,0x7D7E7F80}; /* 定义DMA传输目标存储器 * 存储在内部的SRAM中 */ uint32_t aDST_Buffer[BUFFER_SIZE];
aSRC_Const_Buffer[BUFFER_SIZE] 定义用来存放源数据,并且使用了 const 关键字修饰,即常量类型,使得变量是存储在内部 flash 空间上
-
DMA初始化部分
源地址和目标地址使用之前定义的数组首地址,传输的数据量为宏 BUFFER_SIZE 决定,源和目标地址指针地址递增,使用一次传输模式不能循环传输,因为只有一个 DMA 通道,优先级随便设置,最后调用 DMA_Init 函数完成 DMA 的初始化配置
//存储器到存储器 void DMA_mtm_config(void) { //1-要初始化结构体肯定要定义一个结构体变量 DMA_InitTypeDef DMA_InitStruct; //2、配置DMA时钟 RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);//使能DMA1时钟 //数据从哪里来到哪里去(配置3个) //源数据地址(把此数组看做外设) DMA_InitStruct.DMA_PeripheralBaseAddr=(uint32_t)aSRC_Const_Buffer;//外设地址---是一个数组,数组名称就是首地址 //目标地址 DMA_InitStruct.DMA_MemoryBaseAddr=(uint32_t)aDST_Buffer;//存储器地址 --是一个空数组 //方向:外设作为源地址 DMA_InitStruct.DMA_DIR=DMA_DIR_PeripheralSRC;//传输方式 //传多少,单位是多少 DMA_InitStruct.DMA_BufferSize= BUFFER_SIZE;//传输数目(要传输多少个) DMA_InitStruct.DMA_PeripheralInc=DMA_PeripheralInc_Enable ;//外设地址递增 DMA_InitStruct.DMA_PeripheralDataSize= DMA_PeripheralDataSize_Word ;//数据宽度,word是一个字32位 //配置memory DMA_InitStruct.DMA_MemoryInc=DMA_MemoryInc_Enable ;//内存地址递增 DMA_InitStruct.DMA_MemoryDataSize=DMA_MemoryDataSize_Word ; //配置模式和优先级 DMA_InitStruct.DMA_Mode=DMA_Mode_Normal ;//正常模式或者是循环模式 DMA_InitStruct.DMA_Priority=DMA_Priority_High ;//共有四种优先级 DMA_InitStruct.DMA_M2M=DMA_M2M_Enable;//刚好是M-M模式 DMA_Init(DMA1_Channel6, &DMA_InitStruct); //M-T-M模式可以是任意的通道 DMA_ClearFlag(DMA1_FLAG_TC6);//先将这个标志位清楚 //3、使能DMA DMA_Cmd(DMA1_Channel6, ENABLE); }
-
数据是否传输完成且无误,两个数组中的内容要进行比较
/** * 判断指定长度的两个数据源是否完全相等, * 如果完全相等返回1,只要其中一对数据不相等返回0 *参数1是常量指针,是不变的 *参数2也是一个指针 *参数3是要比较多长 */ uint8_t Buffercmp(const uint32_t* pBuffer, uint32_t* pBuffer1, uint16_t BufferLength) { /* 数据长度递减 */ while(BufferLength--) { /* 判断两个数据源是否对应相等 */ if(*pBuffer != *pBuffer1) { /* 对应数据源不相等马上退出函数,并返回0 */ return 0; } /* 递增两个数据源的地址指针 */ pBuffer++; pBuffer1++; } /* 完成判断并且对应数据相对 */ return 1; }
-
主函数部分的内容:
int main(void) { uint8_t status=0; delay_init(); //延时函数初始化 LED_Init(); //初始化与LED连接的硬件接口 DMA_mtm_config();//DMA初始化 //在比较之前最后先检测数据是否传送完毕:使用 DMA_GetFlagStatus(uint32_t DMAy_FLAG);函数 //使用的DMA1通道6 while(DMA_GetFlagStatus(DMA1_FLAG_TC6)==RESET);//如果返回值位reset表示还没哟传输成功 status=Buffercmp(aSRC_Const_Buffer,aDST_Buffer,BUFFER_SIZE);//返回值是一个8位的 if(status==0)//表示失败 { LED0=0; } else//表示成功 { LED1=0; } while(1) { } }
如果两个数组中的内容一样、则点亮LED1灯作为信号,反之传输失败则点亮LED0灯作为信号。
STM32-DMA(存储器到存储器传输数据)–代码链接
(二)从存储器到外设(M-to-P)
当我们使用从存储器到外设传输时,以串口向电脑端发送数据为例。 DMA 外设寄存器的地址对应的就是串口数据寄存器的地址, DMA 存储器的地址就是我们自定义的变量(相当于一个缓冲区,用来存储通过串口发送到电脑的数据)的地址。方向我们设置外设为目标地址
实验目的:
我们先定义一个数据变量,存于 SRAM 中,然后通过 DMA 的方式传输到串口的数据寄存器,然后通过串口把这些数据发送到电脑的上位机显示出来。
串口初始化部分不再赘述
stm32串口自定义协议接收一串十六进制数据
DMA代码部分:
-
首先需要定义一个发送数据的数组
#define SendBuff_Size 5000 u8 SendBuffe[SendBuff_Size];//定义一个发送数组
-
DMA初始化工作
因为数据是从存储器到串口,所以设置存储器为源地址,串口的数据寄存器为目标地址,要发送的数据有很多且都先存储在存储器中,则存储器地址指针递增,串口数据寄存器只有一个,则外设地址地址不变,两边数据单位设置成一致,传输模式可选一次或者循环传输,只有一个 DMA 请求,优先级随便设
//Memory->p(USART->DR) //通过手册查询串口的DR寄存器偏移地址是:0x04则外设地址是USART1_BASE+0x04 void USART1_DMA_mtp_config(void) { //1-要初始化结构体肯定要定义一个结构体变量 DMA_InitTypeDef DMA_InitStruct; //2、配置DMA时钟(通过查看手册可以知道USART1_TX使用DMA1的通道4) RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);//使能DMA1通道4的时钟 //3、数据从哪里来到哪里去(配置3个) //外设地址(串口数据寄存器)--目标地址 DMA_InitStruct.DMA_PeripheralBaseAddr=(u32)&USART1->DR;//外设地址(或者USART1_BASE+0x04) //存储器地址---源地址 DMA_InitStruct.DMA_MemoryBaseAddr=(uint32_t)SendBuffe;//存储器地址(指向了数组首地址) //方向:存储器到外设(外设串口作为目的地) DMA_InitStruct.DMA_DIR=DMA_DIR_PeripheralDST;//传输方式(MTP) //规定外设是作为数据传输的目的地还是来源(数据传输方向) //DMA_DIR_PeripheralDST 外设作为数据传输的目的地 //DMA_DIR_PeripheralSRC 外设作为数据传输的来源 //4、传多少,单位是多少 DMA_InitStruct.DMA_BufferSize= SendBuff_Size;//传输数目(数组的长度) //只有一个串口数据寄存器不需要递增,数组是U8类型,所以一次传输一个字节 DMA_InitStruct.DMA_PeripheralInc=DMA_PeripheralInc_Disable ;//外设地址b不需要递增 DMA_InitStruct.DMA_PeripheralDataSize= DMA_PeripheralDataSize_Byte ;//数据宽度,u8类型,一个字节 //配置memory(定义了一个数组,发送一个会继续下一个,所以地址是递增) DMA_InitStruct.DMA_MemoryInc=DMA_MemoryInc_Enable ;//内存地址递增 DMA_InitStruct.DMA_MemoryDataSize=DMA_MemoryDataSize_Byte ;//一个字节 //5、配置模式和优先级 DMA_InitStruct.DMA_Mode=DMA_Mode_Normal ;//正常模式或者是循环模式 DMA_InitStruct.DMA_Priority=DMA_Priority_High ;//共有四种优先级 DMA_InitStruct.DMA_M2M=DMA_M2M_Disable ;//不使用M-M模式 DMA_Init(DMA1_Channel4, &DMA_InitStruct);//串口1TX是DMA通道4 // DMA_ClearFlag(DMA1_FLAG_TC4);//先将这个标志位清除 //6、使能DMA DMA_Cmd(DMA1_Channel4, ENABLE); }
-
主函数部分内容:
int main(void) { u16 i; for(i=0;i<SendBuff_Size;i++)//对数组填充数据 { SendBuffe[i]='b'; } delay_init(); //延时函数初始化 LED_Init(); //初始化与LED连接的硬件接口 uart_init(9600); USART1_DMA_mtp_config(); //等待串口发送数据(去usart头文件中找有关DMA的函数) USART_DMACmd(USART1,USART_DMAReq_Tx,ENABLE); //把定义的变量发送到串口,串口通过TX引脚发送出去 while(1) { //为了证明dma不占用CPU此处让led闪烁 LED0=0; delay_ms(100); LED0=1; delay_ms(100); } }
实验现象:串口助手可以接收到5000个数据–数据均为字符b,与此同时LED0在不停的闪烁
STM32+DMA+串口发送数据(存储器到外设数据传输)-代码链接
(三)外设到存储器(P-to-M)
当我们使用从外设到存储器传输时,以 ADC 采集为例。 DMA 外设寄存器的地址对应的就是 ADC数据寄存器的地址ADC_DR, DMA 存储器的地址就是我们自定义的变量(用来接收存储 AD 采集的数据)的地址,方向我们设置外设为源地址。
STM32-ADC单通道采集数据(中断形式和DMA形式)–代码
代码参考ADC学习笔记整理部分
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)