新手必看!超详细的STM32-DMA配置与实验教程(包含串口使用DMA发送数据)
下文均以STM32F4板子进行示例演示!
一、什么是DMA?
DMA就是:直接存储器访问。
DMA传输数据从一个地址空间复制到另一个地址空间,提供在外设和存储器或者存储器和存储器之间的高速数据传输。
●没有DMA参与的UART数据收发
●有DMA参与的UART数据收发:核心配置好DMA控制器即可。
二、DMA的作用?
DMA的作用就是解决大量数据转移过度消耗CPU资源的问题,有了DMA得CPU可以更加专注的实用的的操作——计算、控制等。
DMA技术的出现,使得外围设备可以通过DMA控制器直接访问内存,与此同时,CPU可以继续执行程序。
DMA传输期间,DMA控制器接管了总线的控制权。在DMA传输结束后,DMA控制器将总线的控制权交给CPU。通过硬件为 RAM 与 I/O 设备开辟一条直接传送数据的通路,能使 CPU 的效率大为提高。
三、DMA的传输方向
- 外设和存储器
- 存储器和外设的传输。
- 存储器和存储器间的传输。(只有DMA2控制器可以,DMA1不行。且不允许循环模式和直接模式)
三、DMA中断
每个通道都有 3 个事件标志(DMA 半传输,DMA 传输完成和 DMA 传输出错),这 3 个事件标志逻辑或成为一个单独的中断请求。
四、DMA传输的方式
-
正常模式(DMA Mode Normal)
一次DMA数据传输完后,停止DMA传送,也就是只传输一次。
要开始新的DMA传输,需要在关闭DMA通道的情况下,在DMA CNDTRx寄存器中重新写入传输数目。 -
循环传输模式(DMA Mode Circular)
当传输结束时,硬件自动会将传输数据量寄存器进行重装,进行下一轮的数据传输。 也就是多次传输模式。
主要用于处理循环缓冲区和连续的数据传输。 -
指针增量模式
外设和存储器指针在每次传输后可以自动向后递增或保持常量。当设置为增量模式时,下一个要传输的地址将是前一个地址加上增量值。
一般情况下存储器需要设置递增,而外设设置为保持常量。
五、DMA映射
DMA控制器包含了DMA1和DMA2,其中DMA1和DMA2均有8个数据流,每个数据流有8个通道。这里的通道可以理解为传输数据的一种管道。每个通道都有一个仲裁器,用于处理DMA 请求间的优先级。
如:UART使用DMA传输,就将DMA映射到下面的数据流通道中。
五、以串口为例,使用DMA1实现框图
六、DMA功能
1. 通道选择
每个数据流都与一个DMA请求相关联,此 DMA请求可以从8个可能的通道请求中选出。此选择由DMA_SxCR寄存器中的CHSEL[2:0] 位控制。芯片不同,DMA映射通道也不同。
2. 仲裁器(决定优先级,类似于NVIC控制器)
仲裁器为两个AHB主端口(存储器和外设端口)提供基于请求优先级的8个DMA 数据流请求管理,并启动外设/存储器访问序列。
优先级管理分为软件配置和硬件配置:
软件:每个数据流优先级都可以在 DMA_SxCR寄存器中的PL[1:0] 位控制配置。分为四个级别:
硬件:如果两个请求具有相同的软件优先级,则编号低的数据流优先于编号高的数据流。例如,数据流2的优先级高于数据流4。
3. FIFO直接模式和阈值突发模式
(1)FIFO简介
FIFO用于在源数据传输到目标之前临时存储这些数据。每个数据流都有一个独立的(总容量16字节)FIFO,FIFO临时存储数据最多为16字节,FIFO的存储阈值级别可由软件配置为1/4(4字节)、1/2(8字节)、3/4(12字节)或满(16字节)。
为了使能FIFO阈值级别,必须通过将DMA_SxFCR寄存器中的 DMDIS位置1来禁止直接模式。
(2)直接模式(相当于写多少个字节就输出多少个字节)
默认情况下,FIFO 以直接模式操作(将 DMA_SxFCR中的DMDIS位清0,不使用FIFO阈值级别 )。
在直接模式下,不使用FIFO 的阈值级别控制。每完成一次从外设到FIFO 的数据传输后,相应的数据立即就会移出并存储到目标中。
当实现存储器到存储器传输时不得使用直接模式!!!
当在直接模式(禁止 FIFO)下将DMA配置为以存储器到外设模式传输数据时,DMA 会将一个数据从存储器预加载到内部 FIFO,从而确保一旦外设触发DMA请求时则立即传输数据。(为了避免 FIFO饱和,建议使用高优先级配置相应的数据流)
该模式仅限以下方式的传输:
●源和目标传输宽度相等,并均由 DMA_SxCR中的 PSIZE[1:0] 位定义(MSIZE[1:0]位的状态是“无关”)
不可能进行突发传输(DMA_SxCB 中的 PBURST[1:0]和MBURST[1:0]位的状态是“无关”)。
(3)FIFO阈值突发模式
使能这种模式(将DMA_SxCR寄存器中的位EN置1)时,每次产生外设请求.数据流都会启动数据源到FIFO的传输。达到FIFO的阈值级别时,FIFO的内容移出并存储到目标中。
如果DMA_SxNDTR 寄存器达到零、外设请求传输终止(在使用DMA流控制器的情况下)或 DMA_SxCR寄存器中的EN位由软件清零,传输即会停止。
选择FIFO阈值(DMA_SxFCB寄存器的位 FTH[1:0] )和存储器突发大小(DMA_SxCR寄存器的MBURST[1:0] 位)时需要小心,FIFO阈值指向的内容必须与整数个存储器业发传输完全匹配。如果不是这样,当使能数据流时将生成一个FIFO错误(DMA_HISR或 DMA_LISR寄存器的标志FEIEx),然后将自动禁止数据流。允许的和禁止的配置在表41:FIFO阈值配置中介绍。
所有这些情况下,突发大小与数据大小的乘积不得超过FlFO容量大小。如果发生下列情况,会导致 DMA 传输结界出现不完整的冲突传输。如:31个byte,使用4节拍的1次突发,则最多突发7次,还剩3个byte(不完整的冲突传输)。这3个byte就使用单次传输模式进行传输。
4. 源、目标和传输模式
源传输和目标传输在整个4GB区域(地址在0x0000 0000和OxFFFF FFFF 之间)都可以寻址外设和存储器。
传输方向使用DMA_SxCR寄存器中的 DIR[1:0] 位进行配置,有三种可能的传输方向:存储器到外设、外设到存储器或存储器到存储器。表 37介绍了相应的源和目标地址。
只有DMA2控制器能够执行存储器到存储器的传输。
使用存储器到存储器模式时,不允许循环模式和直接模式。
使用示例:如果从UART的DR寄存器(外设)读取到buff[](存储器)中,则需要把DMA_SxCR寄存器的位DIR[1:0] 位写入00,并把UART的DR寄存器的地址放入DMA_SxPAR中,把buff[]的地址放入DMA_SxMOAR中,即可进行DMA传输。
5. 指针递增
根据DMA_SxCR寄存器中 PINC和 MINC位的状态,外设和存储器指针在每次传输后可以自动向后递增或保持常量。
使用示例:如果从UART的DR寄存器(外设)读取到buff[](存储器)中,则需要将buff[](存储器)设置为递增模式,UART的DR寄存器(外设)设置为固定模式。
通过单个寄存器访问外设源或目标数据时,禁止递增模式十分有用。
如果使能了递增模式,则根据在DMA.SxCR寄存器PSIZE或 MSIZE位中编程旳数据宽度,下一次传输的地址将是前一次传输的地址递增1(对于字节)、2(对于半字)或4(对于字)。
为了优化封装操作,可以不管AHB 外设端口上传输的数据的大小,将外设地址的增量偏移大小固定下来。DMA.SxCB寄存器中的 PINCOS位用于将增量偏移大小与外设AHB端口或32位地址(此时地址递增4)上的数据大小对齐。PINCOS位仅对AHB外设端口有影响。
如果将PINCOS位置1,则不论 PSIZE值是多少,下一次传输的地址总是前一次传输的地址递增4(自动与32位地址对齐)。但是,AHB存储器端口不受此操作影响。
如果 AHB外设端口或AHB存储器端口分别请求突发事务,为了满足AMBA 协议(在固定地址模式下不允许突发事务),则需要将PINC或MINC位置1。
6. 流控制器
决定谁可以控制整个数据传输的终点。
控制要传输的数据数目的实体称为流控制器,此流控制器使用DMA_SxCR寄存器中的 PFCTRL位针对每个数据流独立配置。
DMA 控制器: 在这种情况下,要传输的数据项的数目在使能DMA数据流之前由软件编程到DMA_SxNDTR寄存器。
外设源或目标:当要传输的数据项的数目未知时属于这种情况。当所传输的是最后的数据时,外设通过硬件向DMA 控制器发出指示。仅限能够发出传输结束信号的外设支持此功能,也就是: SDIO。
七、实验
实验一:串口使用DMA发送/接收数据
(1)硬件分析:
通过USART1_CR3的 DMAT使能DMA2_Stream7_CH4,相当于将USART1_DR和DMA2_Stream7_CH4 连通
剩下的就只需要配置DMA2通道,DMA2初始化。
(2)软件分析:
1.打开DMA2时钟。
2.关闭数据流。(先关闭才能写进去内容,写完以后使能数据流就可以完成配置)
3.使能USART1_TX / USART1_RX的 DMA传输。
4.使能直接模式。
5.配置CR。
6.设置传输项数。
7.设置源地址。
8.设置目标地址。
9.使能数据流。
(3)发送数据代码:
void USART1_DMAT_Init (u32 sAddr,u32 rAddr,u32 num) // sAddr:源地址(发送方) rAddr:目标地址(接收方),num:数量
{
RCC->AHB1ENR|=(0x1 <<22); //1.打开DMA2时钟
DMA2_Stream7->CR &= ~(0x1<<0);//2.关闭数据流7
USART1->CR3 |=(0x1<< 7); //3.使能USART1_TX的DMA传输
DMA2_Stream7->FCR &= ~(0x1<<2);//4.使能直接模式
//5.配置CR
DMA2_Stream7->CR = 0;//整体清零
DMA2_Stream7->CR |=(0x4 <<25); //选择通道4
DMA2_Stream7->CR |=(0x2<<16); //高优先级
DMA2_Stream7->CR |=(0x1 <<10);//存储器地址递增 1byte
DMA2_Stream7->CR |=(0x1<<6);//存储器到外设方向
/*
*PSIZE=MSIZE =8bit = lbyte
*外设地址固定
*禁止循环模式
*DMA作为流控制器 */
DMA2_Stream7->NDTR = num;//6.设置传输项数
DMA2_Stream7->MOAR = sAddr;//7.设置源地址
DMA2_Stream7->PAR= rAddr;//8.设置目标地址
DMA2_Stream7->CR |=(0x1<<0);//9.使能数据流
}
//---------------------------------------以下为主函数main.c使用内容
u8 buff[20]="hello world";
void mian()
{
printf("Reset!!!\r\n");
USART1_DMAT_Init((u32)buff, (u32)&USART1->DR ,strlen(buff));
//printf("DMA!!!\r\n");
}
(4)接收数据代码:
void USART1_DMAR_Init (u32 sAddr,u32 rAddr,u32 num) // sAddr:源地址(发送方) rAddr:目标地址(接收方),num:数量
{
RCC->AHB1ENR|=(0x1 <<22); //1.打开DMA2时钟
DMA2_Stream2->CR &= ~(0x1<<0);//2.关闭数据流2
USART1->CR3 |=(0x1<< 6); //3.使能USART1_RX的DMA传输
DMA2_Stream2->FCR &= ~(0x1<<2);//4.使能直接模式
//5.配置CR
DMA2_Stream2->CR = 0;//整体清零
DMA2_Stream2->CR |=(0x4 <<25); //选择通道4
DMA2_Stream2->CR |=(0x2<<16); //高优先级
DMA2_Stream2->CR |=(0x1 <<10);//存储器地址递增 1byte
/*
*PSIZE=MSIZE =8bit = lbyte
*外设地址固定
*禁止循环模式
*DMA作为流控制器
*外设到存储器
*/
DMA2_Stream2->NDTR = num;//6.设置传输项数
DMA2_Stream2->PAR= sAddr;//7.设置源地址
DMA2_Stream2->MOAR = rAddr; //8.设置目标地址
DMA2_Stream2->CR |=(0x1<<0);//9.使能数据流
}
//---------------------------------------以下为主函数main.c使用内容
u8 buff[20]="0";
void mian()
{
printf("Reset!!!\r\n");
USART1_DMAT_Init((u32)&USART1->DR, (u32)buff, 20);
while(1){
if (DMA2->LISR &(0x1<<21)) //等待传输完成标志
{
DMA2->LIFCR |=(0x1 <<21); //清除传输完成中断
printf ( "RX:%s\r\n", buff);
DMA2_stream2->CR &=~(0x1<< 0); //关闭数据流
DMA2_stream2->NDTR =20; //重传传输项数
DMA2_stream2->CR |=(0x1 <<0);//使能数据流
}
}
}
(5)分析: 发送时 为什么会出现下面这种情况呢?
答:因为DMA传输不需要CPU参与。当CPU打印完第一printf后,直接跳过USART1_DMAT_Init()函数,直接打印第二个printf,与DMA打印过程发生冲突。
实验二:存储器到存储器
buff1[]的内容复制给buff2。实验使用数据流1通道0。
void DMA2_Stream1_CH0_Init(u32 sAddr,u32 rAddr,u32 num)
{
RCC->AHB1ENR |= (0x1 <<22);//1.打开DMA2时钟
DMA2_Stream1->CR &= ~(0x1<<0);//2.关闭数据流1
DMA2_Stream1->FCR |=(0x1<< 2);//3.禁止直接模式
DMA2_Stream1->FCR L= (0x3 <<0);//4.设置FIFo满容量
//5.配置CR
DMA2_Stream1->CR = 0;//整体清零
DMA2_Stream1->CR |=(0x2<<16); //高优先级
DMA2_Stream1->CR |=(0x1 <<10);//存储器2地址递增 1byte
DMA2_Stream1->CR |=(0x1 <<9);//存储器1地址递增 1byte
DMA2_Stream1->CR |=(0x2 <<6);//存储器到存储器方向
/*
*选择通道0
*禁止循环模式
*DMA作为流控制器 */
DMA2_Stream7->NDTR = num;//6.设置传输项数
DMA2_Stream7->PAR= sAddr;;//7.设置源地址
DMA2_Stream7->MOAR =rAddr//8.设置目标地址
DMA2_Stream7->CR |=(0x1<<0);//9.使能数据流
}
//---------------------------------------以下为主函数main.c使用内容
u8 buff1[20]="Hello";
u8 buff2[20]="0";
void mian()
{
printf("Reset!!!\r\n");
DMA2_Stream1_CH0_Init((u32)"Hello", (u32)buff, strlen("Hello"));
while(1){
if (DMA2->LISR &(0x1<< 11)) //等待传输完成标志
{
DMA2->LIFCR |=(0x1 <<11); //清除传输完成中断
printf ( "%s\r\n", buff);
}
}
}
作者:小仇学长