超详细!新手必看STM32-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;//整体清零
	DMA2_Stream2->CR |=(0x5 <<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);//使能数据流
 
          }
      } 
}

分析: 发送时 为什么会出现下面这种情况呢?

答:因为DMA传输不需要CPU参与。当CPU打印完第一printf后,直接跳过USART1_DMAT_Init()函数,直接打印第二个printf,与DMA打印过程发生冲突。通俗来说:在CPU打印时,DMA也发生了打印。
在这里插入图片描述

2. 库函数版本(STM32F1)

在这里插入图片描述

功能:串口助手发送到开发板什么内容,就回显什么内容。注意:我们只需要配置好源地址和目标地址后,使能通道即可,DMA会自动去进行搬运数据。

usart.c

#include "usart.h"
#include "delay.h"
#include "stdio.h"
#include "stdlib.h"
#include "string.h"
#include "stm32f10x.h"

#define USART_MAX_LEN 100
volatile uint16_t recv_length = 0;              // 接收帧数据的长度
uint8_t DMA_USART1_RX_BUF[USART_MAX_LEN] = {0}; // 接收数据缓存
uint8_t DMA_USART1_TX_BUF[400];                 // 发送数据缓存

void USART1_Config(u32 bound) // 同时配置接收和发送
{
    GPIO_InitTypeDef GPIO_InitStructure;
    USART_InitTypeDef USART_InitStructure;
    NVIC_InitTypeDef NVIC_InitStructure;
    // 1时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE);

    // 2 GPIO USART1_TX   GPIOA.9
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; // PA.9
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // 复用推挽输出
    GPIO_Init(GPIOA, &GPIO_InitStructure);          // 初始化GPIOA.9

    // USART1_RX	  GPIOA.10初始化
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;            // PA10
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; // 浮空输入
    GPIO_Init(GPIOA, &GPIO_InitStructure);                // 初始化GPIOA.10
    USART_DeInit(USART1);

    // 3 中断  NVIC 配置
    NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; // 抢占优先级2
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;        // 子优先级1
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;           // IRQ通道使能
    NVIC_Init(&NVIC_InitStructure);                           // 根据指定的参数初始化VIC寄存器

    NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel4_IRQn;  // 嵌套通道为DMA1_Channel4_IRQn
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; // 抢占优先级为 2
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 4 + 3;    // 响应优先级为 7
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;           // 通道中断使能
    NVIC_Init(&NVIC_InitStructure);

    NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel5_IRQn;  // 串口1发送中断通道
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; // 抢占优先级
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 5 + 3;    // 子优先级
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;           // IRQ通道使能
    NVIC_Init(&NVIC_InitStructure);

    // 4配置 USART设置
    USART_InitStructure.USART_BaudRate = bound;                                     // 串口波特率
    USART_InitStructure.USART_WordLength = USART_WordLength_8b;                     // 字长为8位数据格式
    USART_InitStructure.USART_StopBits = USART_StopBits_1;                          // 一个停止位
    USART_InitStructure.USART_Parity = USART_Parity_No;                             // 无奇偶校验位
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; // 无硬件数据流控制
    USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;                 // 收发模式
    USART_Init(USART1, &USART_InitStructure);                                       // 初始化串口1

    USART_ITConfig(USART1, USART_IT_IDLE, ENABLE);
    USART_DMACmd(USART1, USART_DMAReq_Rx, ENABLE);
    USART_DMACmd(USART1, USART_DMAReq_Tx, ENABLE);

    USART_Cmd(USART1, ENABLE);
}

void USART_DMA_Init(void)
{
    DMA_InitTypeDef DMA_InitStructure;
    /* 开启DMA时钟 */
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);

    /* 配置DMA1通道5(接收) */
    DMA_DeInit(DMA1_Channel5);
    DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)(&USART1->DR);
    DMA_InitStructure.DMA_MemoryBaseAddr = (u32)DMA_USART1_RX_BUF;
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
    DMA_InitStructure.DMA_BufferSize = USART_MAX_LEN;
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
    DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
    DMA_InitStructure.DMA_Priority = DMA_Priority_High;
    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
    DMA_Init(DMA1_Channel5, &DMA_InitStructure);

    /* 配置DMA1通道4(发送) */
    DMA_DeInit(DMA1_Channel4);
    DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)(&USART1->DR);
    DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)DMA_USART1_TX_BUF;
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;
    DMA_InitStructure.DMA_BufferSize = 0; // 发送时,BufferSize由实际数据大小决定
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
    DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
    DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh;
    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
    DMA_Init(DMA1_Channel4, &DMA_InitStructure);

    // 启动DMA接收通道
    DMA_Cmd(DMA1_Channel5, ENABLE);

    // 开启DMA发送完成中断
    DMA_ITConfig(DMA1_Channel4, DMA_IT_TC, ENABLE);
    DMA_ITConfig(DMA1_Channel5, DMA_IT_TC, ENABLE);

    // 启用USART接收和发送DMA请求
    USART_DMACmd(USART1, USART_DMAReq_Rx, ENABLE);
    USART_DMACmd(USART1, USART_DMAReq_Tx, ENABLE);
}

void USART1_IRQHandler(void) // 串口1中断服务程序
{
    /* 检查是否触发了空闲中断 (IDLE) */
    if (USART_GetITStatus(USART1, USART_IT_IDLE) != RESET) // 空闲中断触发
    {
        DMA_Cmd(DMA1_Channel5, DISABLE); /* 暂时关闭DMA接收,避免数据还未处理时进行错误的操作 */

        // 获取接收到的数据长度,单位为字节
        recv_length = USART_MAX_LEN - DMA_GetCurrDataCounter(DMA1_Channel5);
        USART_ClearITPendingBit(USART1, USART_IT_IDLE); /* 清除空闲中断标志 */

        // 将接收到的数据复制到发送缓冲区并启动发送
        DMA_USART1_Send(DMA_USART1_RX_BUF, recv_length);

        // 重新设置DMA的计数器,确保下次DMA能够正确接收最大长度的数据
        DMA_SetCurrDataCounter(DMA1_Channel5, USART_MAX_LEN);

        DMA_Cmd(DMA1_Channel5, ENABLE); /* 重新启用DMA接收功能,继续接收数据 */
        USART_ReceiveData(USART1);      // 清除空闲中断标志位(此函数有清除标志位的作用)
    }
    /* 检查是否完成了串口数据发送 */
    if (USART_GetFlagStatus(USART1, USART_IT_TXE) == RESET) // 判断发送完成标志位(TXE)
    {
        USART_ITConfig(USART1, USART_IT_TC, DISABLE); /* 禁用发送完成中断(TC中断),表示发送已经完成 */
    }
}

void DMA1_Channel4_IRQHandler(void)
{
    if (DMA_GetITStatus(DMA1_IT_TC4))
    {
        DMA_ClearITPendingBit(DMA1_IT_TC4); // 清除传输完成中断标志位
        DMA_Cmd(DMA1_Channel4, DISABLE);
        DMA1_Channel4->CNDTR = 0;                    // 清除数据长度
        USART_ITConfig(USART1, USART_IT_TC, ENABLE); // 打开串口发送完成中断
    }
}

void DMA_USART1_Send(uint8_t *data, u16 size) // 串口1DMA发送函数
{
    // 禁用DMA通道
    DMA_Cmd(DMA1_Channel4, DISABLE);
    // 清除所有标志位
    DMA_ClearFlag(DMA1_FLAG_TC4);
    DMA_ClearFlag(DMA1_FLAG_HT4);
    DMA_ClearFlag(DMA1_FLAG_TE4);
    USART_ClearFlag(USART1, USART_FLAG_TC);
    // 复制数据到发送缓冲区
    memcpy(DMA_USART1_TX_BUF, data, size);
    // 设置传输数据长度
    DMA_SetCurrDataCounter(DMA1_Channel4, size);
    // 使能DMA通道
    DMA_Cmd(DMA1_Channel4, ENABLE);
}

main.c

#include "stm32f10x.h"
#include "usart.h"
#include "delay.h"
#include "led.h"

int main(void)
{
	// 设置NVIC中断优先级分组为2
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	delay_init();
	// 初始化USART和DMA
	USART1_Config(115200);
	USART_DMA_Init();
	DMA_USART1_Send("Hello World!\r\n", sizeof("Hello World!\r\n") - 1); 
	while (1)
	{

	}
}

在这里插入图片描述

上面程序的数据流走向:
接收数据流
在这里插入图片描述
发送数据流
在这里插入图片描述

实验二:存储器到存储器

要求:buff1 的内容复制给buff2 。

1. 寄存器版本(STM32F4)

在这里插入图片描述
实验使用数据流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 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);
          }
      } 
}

2. 库函数版本(STM32F1)

在这里插入图片描述

dma.c

#include "dma.h"
#include "delay.h"
#include "stdio.h"
#include "stdlib.h"
#include "string.h"
#include "stm32f10x.h"
#include "led.h"

void MyDMA_Init(uint32_t addrA, uint32_t addrB, uint16_t Size)
{
	// 开启DMA1的时钟控制
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);

	// 清除所有标志位
	DMA_ClearFlag(DMA1_FLAG_TC7);
	DMA_ClearFlag(DMA1_FLAG_HT7);
	DMA_ClearFlag(DMA1_FLAG_TE7);

	DMA_InitTypeDef DMA_InitStruct;
	DMA_StructInit(&DMA_InitStruct);

	// 配置DMA
	DMA_InitStruct.DMA_PeripheralBaseAddr = addrA;						 // 外设地址(源地址)
	DMA_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; // 字节传输
	DMA_InitStruct.DMA_PeripheralInc = DMA_PeripheralInc_Enable;		 // 地址自增
	DMA_InitStruct.DMA_Priority = DMA_Priority_VeryHigh;				 // 最高优先级

	DMA_InitStruct.DMA_MemoryBaseAddr = addrB;					 // 存储器的地址(目标地址)
	DMA_InitStruct.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; // 字节传输
	DMA_InitStruct.DMA_MemoryInc = DMA_MemoryInc_Enable;		 // 地址自增
	DMA_InitStruct.DMA_Mode = DMA_Mode_Normal;					 // 单次传输

	DMA_InitStruct.DMA_BufferSize = Size;			// 传输计数器的大小
	DMA_InitStruct.DMA_DIR = DMA_DIR_PeripheralSRC; // 从外设到内存
	DMA_InitStruct.DMA_M2M = DMA_M2M_Enable;		// 软件触发

	// 初始化DMA
	DMA_Init(DMA1_Channel7, &DMA_InitStruct);

	// 使能DMA,立即触发
	DMA_Cmd(DMA1_Channel7, ENABLE);
}

usart.c

#include "stm32f10x.h"
#include "usart.h"
#include <stdio.h>

void USART_Config(u32 bound)
{
	//GPIO端口设置
	GPIO_InitTypeDef GPIO_InitStructure;
	USART_InitTypeDef USART_InitStructure;
	NVIC_InitTypeDef NVIC_InitStructure;

	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA, ENABLE);	//使能USART1,GPIOA时钟

	//USART1_TX   GPIOA.9
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //PA.9
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;	//复用推挽输出
	GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.9

	//USART1_RX	  GPIOA.10初始化
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;//PA10
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入
	GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.10  

	//Usart1 NVIC 配置
	NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority= 3;//抢占优先级3
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;		//子优先级3
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;			//IRQ通道使能
	NVIC_Init(&NVIC_InitStructure);	//根据指定的参数初始化VIC寄存器

	//USART 初始化设置

	USART_InitStructure.USART_BaudRate = bound;//串口波特率
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为8位数据格式
	USART_InitStructure.USART_StopBits = USART_StopBits_1;//一个停止位
	USART_InitStructure.USART_Parity = USART_Parity_No;//无奇偶校验位
	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//无硬件数据流控制
	USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;	//收发模式

	USART_Init(USART1, &USART_InitStructure); //初始化串口1
	USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启串口接受中断
	USART_Cmd(USART1, ENABLE);                    //使能串口1 

}

void USART1_IRQHandler(void)                	
{
	if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)  //接收中断(接收到的数据必须是0x0d 0x0a结尾)
	{
		USART_ClearITPendingBit(USART1,USART_IT_RXNE);//清除USART2接收中断待处理位
	}	
}

/******************************************************************************************/

/* 加入以下代码, 支持printf函数, 而不需要选择use MicroLIB */

#if 1
#if (__ARMCC_VERSION >= 6010050)                    /* 使用AC6编译器时 */
__asm(".global __use_no_semihosting\n\t");          /* 声明不使用半主机模式 */
__asm(".global __ARM_use_no_argv \n\t");            /* AC6下需要声明main函数为无参数格式,否则部分例程可能出现半主机模式 */

#else
/* 使用AC5编译器时, 要在这里定义__FILE 和 不使用半主机模式 */
#pragma import(__use_no_semihosting)

struct __FILE
{
    int handle;
    /* Whatever you require here. If the only file you are using is */
    /* standard output using printf() for debugging, no file handling */
    /* is required. */
};

#endif

/* 不使用半主机模式,至少需要重定义_ttywrch\_sys_exit\_sys_command_string函数,以同时兼容AC6和AC5模式 */
int _ttywrch(int ch)
{
    ch = ch;
    return ch;
}

/* 定义_sys_exit()以避免使用半主机模式 */
void _sys_exit(int x)
{
    x = x;
}

char *_sys_command_string(char *cmd, int len)
{
    return NULL;
}

/* FILE 在 stdio.h里面定义. */
FILE __stdout;

/* 重定义fputc函数, printf函数最终会通过调用fputc输出字符串到串口 
其中串口可根据实际使用情况调整 */
int fputc(int ch, FILE *f)
{
    while ((USART1->SR & 0X40) == 0);             /* 等待上一个字符发送完成 */

    USART1->DR = (uint8_t)ch;                     /* 将要发送的字符 ch 写入到DR寄存器 */
    return ch;
}
#endif

main.c

#include "stm32f10x.h"
#include "usart1.h"
#include "delay.h"
#include "string.h"
#include "dma.h"

uint8_t Source[]="0123456789";
uint8_t Target[]="ABCDEFGHIJ";

int main(void)
{
	// 设置NVIC中断优先级分组为2
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	// 初始化延时和LED
	delay_init();
	USART_Config(115200);
	
	printf("\r\nbefore:\n");
	printf("\r\nSource:%s\n",Source);
	printf("\r\nTarget:%s\n",Target);
	delay_ms(1000);
	printf("\r\n---------------------------------------\n");
	MyDMA_Init((uint32_t)Source,(uint32_t)Target,10);  //数据转运
	
	printf("\r\nafter:\n");
	printf("\r\nSource:%s\n",Source);
	printf("\r\nTarget:%s\n",Target);
	printf("\r\n---------------------------------------\n");

	while (1)
	{
		delay_ms(100);
	}
}

在这里插入图片描述

STM32使用DMA(直接内存访问)来实现串口数据的收发可以大幅度提高数据传输效率,减少CPU的负担。以下是一个基本的示例: ```c #include "stm32f10x.h" #define USART_RX_BUFFER_SIZE 256 volatile uint8_t usart_rx_buffer[USART_RX_BUFFER_SIZE]; volatile uint16_t usart_rx_write_index = 0; volatile uint16_t usart_rx_read_index = 0; void usart_init(void) { USART_InitTypeDef USART_InitStructure; GPIO_InitTypeDef GPIO_InitStructure; DMA_InitTypeDef DMA_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE); RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); // 配置USART1的GPIO GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, &GPIO_InitStructure); // 配置USART1 USART_InitStructure.USART_BaudRate = 115200; USART_InitStructure.USART_WordLength = USART_WordLength_8b; USART_InitStructure.USART_StopBits = USART_StopBits_1; USART_InitStructure.USART_Parity = USART_Parity_No; USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; USART_Init(USART1, &USART_InitStructure); USART_Cmd(USART1, ENABLE); // 配置DMA DMA_DeInit(DMA1_Channel5); DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&USART1->DR; DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)usart_rx_buffer; DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; DMA_InitStructure.DMA_BufferSize = USART_RX_BUFFER_SIZE; DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh; DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; DMA_Init(DMA1_Channel5, &DMA_InitStructure); DMA_Cmd(DMA1_Channel5, ENABLE); // 配置USART1的DMA接收 USART_DMACmd(USART1, USART_DMAReq_Rx, ENABLE); } void usart_rx_handler(void) { uint16_t rx_write_index = USART_RX_BUFFER_SIZE - DMA_GetCurrDataCounter(DMA1_Channel5); while (rx_write_index != usart_rx_write_index) { uint8_t data = usart_rx_buffer[usart_rx_read_index]; usart_rx_read_index = (usart_rx_read_index + 1) % USART_RX_BUFFER_SIZE; // 处理接收到的数据 // ... if (usart_rx_read_index == usart_rx_write_index) { break; } } } int main(void) { usart_init(); while (1) { usart_rx_handler(); } } ``` 在上述代码中,我们使用DMA1通道5来实现USART1的接收,使用了循环模式,这意味着当DMA传输完成时,它将从USART1的数据寄存器中读取数据,并将其存储到usart_rx_buffer中。同时,我们使用了一个循环缓冲区来存储接收到的数据,然后在usart_rx_handler中处理接收到的数据。需要注意的是,在处理数据时,我们只能处理已经接收到的数据,而不能处理正在接收的数据。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值