关闭

女兔帮

超详细!新手必看STM32-DMA配置与实验(串口使用DMA发送数据)

更新时间:2025-02-19 08:44:57 浏览:

本文目录

下文均以板子进行示例演示! 一、什么是DMA

DMA就是:直接存储器访问。

DMA传输数据从一个地址空间复制到另一个地址空间,提供在外设和存储器或者存储器和存储器之间的高速数据传输。

工作流程:配置DMA–使用函数直接搬数据。

●没有DMA参与的UART数据收发

超详细!新手必看STM32-DMA配置与实验(串口使用DMA发送数据)(图1)

●有DMA参与的UART数据收发:核心配置好DMA控制器即可。

超详细!新手必看STM32-DMA配置与实验(串口使用DMA发送数据)(图2)

二、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 个事件标志逻辑或成为一个单独的中断请求。

超详细!新手必看STM32-DMA配置与实验(串口使用DMA发送数据)(图3)

四、DMA传输的方式

正常模式(DMA Mode )

一次DMA数据传输完后,停止DMA传送,也就是只传输一次。

要开始新的DMA传输,需要在关闭DMA通道的情况下,在DMA 寄存器中重新写入传输数目。

循环传输模式(DMA Mode )

当传输结束时,硬件自动会将传输数据量寄存器进行重装,进行下一轮的数据传输。 也就是多次传输模式。

主要用于处理循环缓冲区和连续的数据传输。

指针增量模式

外设和存储器指针在每次传输后可以自动向后递增或保持常量。当设置为增量模式时,下一个要传输的地址将是前一个地址加上增量值。

一般情况下存储器需要设置递增,而外设设置为保持常量。

五、DMA映射

DMA控制器包含了DMA1和DMA2,其中DMA1和DMA2均有8个数据流,每个数据流有8个通道。这里的通道可以理解为传输数据的一种管道。每个通道都有一个仲裁器,用于处理DMA 请求间的优先级。

如:UART使用DMA传输,就将DMA映射到下面的数据流通道中。

超详细!新手必看STM32-DMA配置与实验(串口使用DMA发送数据)(图4)

超详细!新手必看STM32-DMA配置与实验(串口使用DMA发送数据)(图5)

五、以串口为例,使用DMA1实现框图

超详细!新手必看STM32-DMA配置与实验(串口使用DMA发送数据)(图6)

六、DMA功能 1. 通道选择

每个数据流都与一个DMA请求相关联,此 DMA请求可以从8个可能的通道请求中选出。此选择由寄存器中的CHSEL[2:0] 位控制。芯片不同,DMA映射通道也不同。

超详细!新手必看STM32-DMA配置与实验(串口使用DMA发送数据)(图7)

2. 仲裁器(决定优先级,类似于NVIC控制器)

仲裁器为两个AHB主端口(存储器和外设端口)提供基于请求优先级的8个DMA 数据流请求管理,并启动外设/存储器访问序列。

优先级管理分为软件配置和硬件配置:

软件:每个数据流优先级都可以在 寄存器中的PL[1:0] 位控制配置。分为四个级别:

超详细!新手必看STM32-DMA配置与实验(串口使用DMA发送数据)(图8)

硬件:如果两个请求具有相同的软件优先级,则编号低的数据流优先于编号高的数据流。例如,数据流2的优先级高于数据流4。

3. FIFO直接模式和阈值突发模式 (1)FIFO简介

FIFO用于在源数据传输到目标之前临时存储这些数据。每个数据流都有一个独立的(总容量16字节)FIFO,FIFO临时存储数据最多为16字节,FIFO的存储阈值级别可由软件配置为1/4(4字节)、1/2(8字节)、3/4(12字节)或满(16字节)。

为了使能FIFO阈值级别,必须通过将寄存器中的 DMDIS位置1来禁止直接模式。

超详细!新手必看STM32-DMA配置与实验(串口使用DMA发送数据)(图6)

(2)直接模式(相当于写多少个字节就输出多少个字节)

默认情况下,FIFO 以直接模式操作(将 中的DMDIS位清0,不使用FIFO阈值级别 )。

在直接模式下,不使用FIFO 的阈值级别控制。每完成一次从外设到FIFO 的数据传输后,相应的数据立即就会移出并存储到目标中。

当实现存储器到存储器传输时不得使用直接模式!!!

超详细!新手必看STM32-DMA配置与实验(串口使用DMA发送数据)(图10)

当在直接模式(禁止 FIFO)下将DMA配置为以存储器到外设模式传输数据时,DMA 会将一个数据从存储器预加载到内部 FIFO,从而确保一旦外设触发DMA请求时则立即传输数据。(为了避免 FIFO饱和,建议使用高优先级配置相应的数据流)

该模式仅限以下方式的传输:

●源和目标传输宽度相等,并均由 中的 PSIZE[1:0] 位定义(MSIZE[1:0]位的状态是“无关”)

超详细!新手必看STM32-DMA配置与实验(串口使用DMA发送数据)(图11)

不可能进行突发传输( 中的 [1:0]和[1:0]位的状态是“无关”)。

(3)FIFO阈值突发模式

使能这种模式(将寄存器中的位EN置1)时,每次产生外设请求.数据流都会启动数据源到FIFO的传输。达到FIFO的阈值级别时,FIFO的内容移出并存储到目标中。

如果 寄存器达到零、外设请求传输终止(在使用DMA流控制器的情况下)或 寄存器中的EN位由软件清零,传输即会停止。

选择FIFO阈值(寄存器的位 FTH[1:0] )和存储器突发大小(寄存器的[1:0] 位)时需要小心,FIFO阈值指向的内容必须与整数个存储器业发传输完全匹配。如果不是这样,当使能数据流时将生成一个FIFO错误(或 寄存器的标志FEIEx),然后将自动禁止数据流。允许的和禁止的配置在表41:FIFO阈值配置中介绍。

超详细!新手必看STM32-DMA配置与实验(串口使用DMA发送数据)(图12)

超详细!新手必看STM32-DMA配置与实验(串口使用DMA发送数据)(图13)

超详细!新手必看STM32-DMA配置与实验(串口使用DMA发送数据)(图14)

所有这些情况下,突发大小与数据大小的乘积不得超过FlFO容量大小。如果发生下列情况,会导致 DMA 传输结界出现不完整的冲突传输。如:31个byte,使用4节拍的1次突发,则最多突发7次,还剩3个byte(不完整的冲突传输)。这3个byte就使用单次传输模式进行传输。

4. 源、目标和传输模式

源传输和目标传输在整个4GB区域(地址在 0000和 FFFF 之间)都可以寻址外设和存储器。

传输方向使用寄存器中的 DIR[1:0] 位进行配置,有三种可能的传输方向:存储器到外设、外设到存储器或存储器到存储器。表 37介绍了相应的源和目标地址。

只有DMA2控制器能够执行存储器到存储器的传输。

使用存储器到存储器模式时,不允许循环模式和直接模式。

超详细!新手必看STM32-DMA配置与实验(串口使用DMA发送数据)(图15)

超详细!新手必看STM32-DMA配置与实验(串口使用DMA发送数据)(图16)

超详细!新手必看STM32-DMA配置与实验(串口使用DMA发送数据)(图17)

超详细!新手必看STM32-DMA配置与实验(串口使用DMA发送数据)(图18)

使用示例:如果从UART的DR寄存器(外设)读取到buff[](存储器)中,则需要把寄存器的位DIR[1:0] 位写入00,并把UART的DR寄存器的地址放入中,把buff[]的地址放入中,即可进行DMA传输。

超详细!新手必看STM32-DMA配置与实验(串口使用DMA发送数据)(图19)

5. 指针递增

根据寄存器中 PINC和 MINC位的状态,外设和存储器指针在每次传输后可以自动向后递增或保持常量。

使用示例:如果从UART的DR寄存器(外设)读取到buff[](存储器)中,则需要将buff[](存储器)设置为递增模式,UART的DR寄存器(外设)设置为固定模式。

超详细!新手必看STM32-DMA配置与实验(串口使用DMA发送数据)(图20)

通过单个寄存器访问外设源或目标数据时,禁止递增模式十分有用。

如果使能了递增模式,则根据在DMA.SxCR寄存器PSIZE或 MSIZE位中编程旳数据宽度,下一次传输的地址将是前一次传输的地址递增1(对于字节)、2(对于半字)或4(对于字)。

为了优化封装操作,可以不管AHB 外设端口上传输的数据的大小,将外设地址的增量偏移大小固定下来。DMA.SxCB寄存器中的 位用于将增量偏移大小与外设AHB端口或32位地址(此时地址递增4)上的数据大小对齐。位仅对AHB外设端口有影响。

如果将位置1,则不论 PSIZE值是多少,下一次传输的地址总是前一次传输的地址递增4(自动与32位地址对齐)。但是,AHB存储器端口不受此操作影响。

如果 AHB外设端口或AHB存储器端口分别请求突发事务,为了满足AMBA 协议(在固定地址模式下不允许突发事务),则需要将PINC或MINC位置1。

6. 流控制器

决定谁可以控制整个数据传输的终点。

控制要传输的数据数目的实体称为流控制器,此流控制器使用寄存器中的 位针对每个数据流独立配置。

超详细!新手必看STM32-DMA配置与实验(串口使用DMA发送数据)(图21)

DMA 控制器: 在这种情况下,要传输的数据项的数目在使能DMA数据流之前由软件编程到寄存器。

外设源或目标:当要传输的数据项的数目未知时属于这种情况。当所传输的是最后的数据时,外设通过硬件向DMA 控制器发出指示。仅限能够发出传输结束信号的外设支持此功能,也就是: SDIO。

七、寄存器版本 实验一:串口使用DMA发送/接收数据 (1)硬件分析

通过的 DMAT使能,相当于将和 连通

剩下的就只需要配置DMA2通道,DMA2初始化。

超详细!新手必看STM32-DMA配置与实验(串口使用DMA发送数据)(图22)

(2)软件分析

1.打开DMA2时钟。

2.关闭数据流。(先关闭才能写进去内容,写完以后使能数据流就可以完成配置)

3.使能 / 的 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打印完第一后,直接跳过()函数,直接打印第二个,与DMA打印过程发生冲突。

超详细!新手必看STM32-DMA配置与实验(串口使用DMA发送数据)(图23)

实验二:存储器到存储器

buff1[]的内容复制给buff2。实验使用数据流1通道0。

超详细!新手必看STM32-DMA配置与实验(串口使用DMA发送数据)(图5)

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 buff[20]="Hello";
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);
          }
      } 
}

八、库函数版本

dma.c

/*
 *==============================================================================
 *函数名称:DMA1_Init
 *函数功能:DMA1初始化
 *输入参数:souAddr:数据源地址;desAddr:数据目的地址
 *返回值:无
 *备  注:数据传输宽度为16位,外设到内存,循环传输,使能了传输完成中断
 *==============================================================================
 */
void DMA1_Init (u32 srcAddr,u32 targetAddr,u32 num)
{
	// 结构体定义
	DMA_InitTypeDef DMA_InitStructure;
	NVIC_InitTypeDef NVIC_InitStructure;
	// 使能DMA时钟
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);
	
	//DMA1初始化
	DMA_DeInit(DMA1_Channel1);
	DMA_InitStructure.DMA_PeripheralBaseAddr = srcAddr;   // 数据源地址
	DMA_InitStructure.DMA_MemoryBaseAddr = targetAddr;   // 目的地址
	DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;   //   传输方向(外设到内存)
	DMA_InitStructure.DMA_BufferSize = num;   // 一次传输数据大小
	DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;   // 外设地址不自增
	DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;   // 内存地址自增
	DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;   // 外设数据宽度选择
	DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;   // 内存数据宽度选择
	DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;   // DMA模式:循环传输
	DMA_InitStructure.DMA_Priority = DMA_Priority_High;   // 优先级:高
	DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;   // 禁止内存到内存的传输
	DMA_Init(DMA1_Channel1, &DMA_InitStructure);   // 配置DMA1
	
	// 使能传输完成中断
	DMA_ITConfig(DMA1_Channel1,DMA_IT_TC, ENABLE);
	
	// NVIC配置
	NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel1_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_Init(&NVIC_InitStructure);
	
	// 使能DMA1通道1
	DMA_Cmd(DMA1_Channel1,ENABLE);
}
// DMA1中断服务函数
void  DMA1_Channel1_IRQHandler(void)
{
	DMA_ClearITPendingBit(DMA1_IT_TC1);// 清除中断标志位
	if(DMA_GetITStatus(DMA1_IT_TC1)!=RESET)
	{
	}
}

main.c

#include "dma.c"
u8 buff[20]="Hello";
void main()
{
    printf("Reset!!!\r\n");
    DMA1_Init ((u32)"Hello", (u32)buff, strlen("Hello"));
    
    while(1){
         if (DMA2->LISR &(0x1<< 11))   //等待传输完成标志
         {
              DMA2->LIFCR |=(0x1 <<11);   //清除传输完成中断
              printf ( "%s\r\n", buff);
          }
      } 
}

你可能感兴趣的