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

本文以STM32F4板子为例,介绍了DMA(直接存储器访问)。阐述了其工作流程、作用、传输方向、中断、传输方式等原理,还说明了DMA功能,如通道选择、仲裁器等。并给出寄存器版本和库函数版本的实验示例,包括串口使用DMA收发数据、存储器到存储器传输。


  

一、什么是DMA?

  DMA(Direct Memory Access,直接存储器访问)是一种数据传输方式,可以在不同的地址空间之间快速搬运数据,而无需CPU的直接参与。简单来说,DMA就像是一个“数据搬运工”,CPU只需要告诉它数据从哪里搬到哪里,然后DMA就会自动完成整个传输过程。
  DMA可以用于外设与存储器之间的数据传输,比如从串口接收数据存入内存,或者从内存读取数据发送到外设;也可以用于存储器之间的数据搬运,比如将数据从一个内存区域复制到另一个区域。它的优势在于能够减少CPU的负担,提高数据传输效率,特别适用于大批量数据的高速传输。
  在实际使用中,我们只需要正确配置DMA的参数,比如源地址、目标地址、数据长度等,然后启动使能DMA通道,数据就会自动完成搬运,无需CPU一直干预,大幅提升系统的运行效率。

●没有DMA参与的UART数据收发
  在没有使用DMA的情况下,UART的数据发送和接收通常采用查询或中断方式。对于发送,CPU需要不断检测TXE(发送数据寄存器空)标志位,等待USART准备好后才能写入数据。这种方式不仅占用CPU资源,还可能导致较高的延迟。而在接收数据时,CPU同样需要不断轮询RXNE(接收数据寄存器非空)标志位,或者依赖接收中断,在数据到达时由中断服务程序进行读取。无论是查询还是中断方式,CPU都需要频繁介入数据的搬运,影响系统的实时性,特别是在数据量较大或速率较高的情况下,CPU负担会明显增加。在这里插入图片描述

有DMA参与的UART数据收发:核心配置好DMA控制器即可。
  而使用DMA进行UART收发时,数据搬运的过程可以完全由DMA硬件完成。对于发送数据,CPU只需要将数据缓冲区地址和传输长度配置到DMA控制器,然后启动传输,DMA会自动将数据逐字节搬运到USART数据寄存器,无需CPU干预。接收数据时,DMA同样可以将USART接收到的数据直接存入指定缓冲区,CPU只需在数据传输完成后进行处理,而不需要时刻关注数据到达的情况。这种方式大幅降低了CPU的负担,提高了数据传输的效率,使得系统可以处理更高的数据速率,同时也减少了因中断或查询带来的性能损耗。
在这里插入图片描述

二、DMA的作用?

   DMA的作用就是解决大量数据转移过度消耗CPU资源的问题,有了DMA得CPU可以更加专注的实用的的操作——计算、控制等。
   DMA技术的出现,使得外围设备可以通过DMA控制器直接访问内存,与此同时,CPU可以继续执行程序。
   DMA传输期间,DMA控制器接管了总线的控制权。在DMA传输结束后,DMA控制器将总线的控制权交给CPU。通过硬件为 RAM 与 I/O 设备开辟一条直接传送数据的通路,能使 CPU 的效率大为提高。

三、DMA的传输方向

  1. 外设和存储器
  2. 存储器和外设的传输。
  3. 存储器和存储器间的传输。

四、DMA中断

  每个通道都有 3 个事件标志(DMA 半传输,DMA 传输完成和 DMA 传输出错),这 3 个事件标志逻辑或成为一个单独的中断请求。
在这里插入图片描述

五、DMA传输的模式

  1. 正常模式(DMA Mode Normal)
      一次DMA数据传输完后,停止DMA传送,也就是只传输一次。要开始新的DMA传输,需要在关闭DMA通道的情况下,在DMA CNDTRx寄存器中重新写入传输数目。
  2. 循环传输模式(DMA Mode Circular)
      当传输结束时,硬件自动会将传输数据量寄存器进行重装,进行下一轮的数据传输。 也就是多次传输模式。主要用于处理循环缓冲区和连续的数据传输。
  3. 指针增量模式
      外设和存储器指针在每次传输后可以自动向后递增或保持常量。当设置为增量模式时,下一个要传输的地址将是前一个地址加上增量值。一般情况下存储器需要设置递增,而外设设置为保持常量。

六、DMA映射

  以STM32F4开发板为例(不同型号映射不同)。DMA控制器包含了DMA1和DMA2,其中DMA1和DMA2均有8个数据流,每个数据流有8个通道。这里的通道可以理解为传输数据的一种管道。每个通道都有一个仲裁器,用于处理DMA 请求间的优先级。
  如:UART使用DMA传输,就将DMA映射到下面的数据流通道中。
在这里插入图片描述
在这里插入图片描述

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

在这里插入图片描述

(1)配置阶段:通过AHB从器件编程接口配置DMA控制器,设定源地址、目标地址、优先级等参数。
(2)请求阶段:数据源通过数据流请求通道向DMA控制器发送请求信号。
(3)传输阶段:DMA控制器根据优先级和仲裁结果,从源地址读取数据,经过FIFO缓冲区暂存,然后传输到目标外设的数据寄存器。
(4)处理阶段:外设接收到数据后进行进一步处理或发送。

八、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通道并使能它,DMA会自动在源地址和目标地址之间传输数据。 你不需要在每次传输时手动调用数据操作。DMA的优势就在于它可以极大地减少CPU的负担,允许你在传输过程中继续执行其他任务。

实验一:串口使用DMA发送/接收数据

软件分析

流程:
(1)配置串口功能,并打开串口空闲中断,使能串口DMA发送和接收功能。
(2)配置DMA参数,如源地址、目标地址、大小、方向等。
(3)DMA发送和接收
① 使用DMA发送数据时,要先关闭发送通道(数据流),然后才能写入数据,写完以后使能通道就可以自动完成内容搬运。
在这里插入图片描述
② 使用DMA接收数据时,要先关闭接收通道,防止还没处理完,新的数据就又来了,处理完后再打开通道。
在这里插入图片描述

1. 寄存器版本(STM32F4)

在这里插入图片描述

发送数据代码如下,这里我们假设串口已经被配置好了,且该代码使用的是F4开发板。

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");
}

接收数据代码

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
### STM32 使用 DMA 进行 UART 数据收发 #### 初始化设置 为了实现基于DMA的UART通信,在初始化阶段需完成几个重要步骤。首先,要确保正确配置串口参数以及DMA控制器的相关属性。这通常涉及到指定波特率、数据位数、停止位等基本通讯参数[^1]。 对于DMA的具体启用操作,则是在相应的寄存器中设定特定标志来激活该功能。例如,通过设置`USART1_CR3`中的`DMAT`位可以开启发送方向上的DMA支持;而对于接收端来说,同样需要在相同位置找到对应的控制选项并加以设置[^2]。 ```c // 配置USART1_CR3使能DMA传输模式 USART1->CR3 |= USART_CR3_DMAT; ``` #### 开启DMA服务函数 一旦硬件层面准备完毕之后,就需要调用HAL库提供的API接口来进行实际的数据交换过程管理。比如,利用`HAL_UART_Receive_DMA()`方法启动一次性的DMA读取动作,它接受三个参数——指向UART句柄结构体指针、目标缓冲区地址以及预期接收到的最大字节数量: ```c HAL_StatusTypeDef HAL_UART_Receive_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size); ``` 此命令执行后,DMA引擎将会自动处理后续所有的数据搬运工作而无需CPU频繁介入,直到预设数量的信息被成功获取为止。 另外,如果希望采用DMA方式发送数据出去的话,也可以借助类似的API如`HAL_UART_Transmit_DMA()`, 它们的工作原理相似,只是作用对象变成了输出流而非输入流。 #### 中断回调机制 尽管DMA本身能够减少处理器负担,但在某些情况下仍然可能需要用到中断机制来响应特殊事件的发生,比如当整个传输完成后触发通知以便应用程序知晓当前状态变化。因此,在项目开发过程中往往还需要适当调整中断优先级,并编写合适的ISR(Interrupt Service Routine)或者注册自定义的回调函数用于捕捉这些信号[^3]。 #### 实际应用案例分析 考虑到不同应用场景下需求各异,这里给出一段简单的代码片段作为参考实例,展示了如何在一个典型的嵌入式系统环境中运用上述理论知识构建起完整的DMA-UART交互流程: ```c #include "main.h" UART_HandleTypeDef huart1; void SystemClock_Config(void); static void MX_GPIO_Init(void); static void MX_USART1_UART_Init(void); int main(void){ /* Reset of all peripherals, Initializes the Flash interface and the Systick. */ HAL_Init(); /* Configure the system clock */ SystemClock_Config(); /* Initialize all configured peripherals */ MX_GPIO_Init(); MX_USART1_UART_Init(); // 设置接收缓存数组大小为12个字符长度 uint8_t buff_receive[12]; // 打开DMA接收数据的功能 HAL_UART_Receive_DMA(&huart1,buff_receive,sizeof(buff_receive)); while (1){} } /** * @brief This function handles DMA stream transfer complete interrupt. */ void DMA2_Stream7_IRQHandler(void){ HAL_DMA_IRQHandler(huart1.hdmarx); } ``` 这段程序实现了STM32单片机上通过DMA技术高效地从外部设备那里抓取消息到内部存储空间内的目的,同时也体现了良好的编程实践风格,包括但不限于合理的模块划分、清晰的任务分工等方面[^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值