STM32F429入门(十八):DMA

DMA(Direct Memory Access,直接存储区访问)为实现数据高速在外设寄存器与存储器之 间或者存储器与存储器之间传输提供了高效的方法。它不占CPU,可以节省很多资源。有时候当我们需要在两个存储器之间传输数据时,我们需要通过CPU,从A传送到B,也就是说它可以减少CPU的负担。

  • DMA1:P->M,M->P

  • DMA2:P->M,M->P,M->M

    (P为外设,M为存储器)

存储区到外设传输就是把特定存储区内容转移至外设的数据寄存器中,这种多用于外设的发送通信

存储器到存储器传输就是把一个指定的存储区内容拷贝到另一个存储区空间。

一、DMA功能框图

 

 (一)通道+流

  • 流:是数据传输的一条链路,每个DMA控制器有8条独立的数据流,每次传输的数据最大为65535,如果数据的单位为字的话,那一次可以传输256KB。(2部分)

  • 通道:每个数据流有8个通道选择,每个通道对应不同的DMA请求。(1部分)

DMA1请求映射:

 DMA2请求映射:

 具体要怎么选择,可以使用寄存器:DMA_SxCR:CHSEL

 如果多个DMA请求一起来,如何处理?这个时候就需要仲裁器。

(二)仲裁器

  1. 软件阶段:DMA_SxCR:PL

 

 

        2.硬件阶段:数据流编号小的优先级大

同一个数据流只能使用一个通道,同一个DMA控制器可以使用多个数据流(需要优先级)。

(三)FIFO

  • 源和目标之间的一个数据中转站

  • 每个数据流都独立拥有四级 32 位 FIFO(先进先出存储器缓冲区)。DMA 传输具有 FIFO 模式直接模式

  • 每个数据流有4字FIFO,阈值级别有1/4,2/4,3/4,4/4。具体配置的寄存器为DMA_SxFCR:FTH

  • 在开启FIFO时,直接模式要禁止,MA_SxFCR:DMDIS

直接模式和FIFO模式有什么区别呢:

  • 直接模式在每个外设请求都立即启动对存储器传输。在直接模式下,如果 DMA 配置为存储器到外设传输那 DMA 会见一个数据存放在 FIFO 内,如果外设启动 DMA 传输请求就可以马上将数据传输过去。它不需要等待是否达到了所设定的阈值级别。直接模式要求源地址和目标地址的数据宽度必须一致,直接模式不能用于存储器到存储器传输

  • FIFO 用于在源数据传输到目标地址之前临时存放这些数据。可以通过 DMA 数据流 xFIFO 控制寄存器 DMA_SxFCR 的 FTH[1:0]位来控制 FIFO 的阈值,分别为 1/4、1/2、3/4 和满。如果数据存储量达到阈值级别时,FIFO 内容将传输到目标中。

那FIFO有什么优势呢?

FIFO 对于要求源地址和目标地址数据宽度不同时非常有用,比如源数据是源源不断的字节数据,而目标地址要求输出字宽度的数据,即在实现数据传输时同时把原来 4 个 8 位 字节的数据拼凑成一个 32 位字数据。此时使用 FIFO 功能先把数据缓存起来,分别根据需要输出数据。

  • FIFO 另外一个作用使用于突发(burst)传输。(下面详解)

那FIFO阈值与突发配置的关系如下表:

讲到这一点,我们需要理清计算机的知识,关于字和字节的关系,我一直懵懵懂懂。

  • 位:计算机处理的最小单元,它不随CPU的处理能力的变化而变化。

  • 字节:就是8位数据的大小,它也不随CPU的处理能力的变化而变化。

  • 字:计算机进行数据处理时,一次存取、加工、传送的数据长度陈为字(word),一个字通常是由一个或多个字节(一般是字节的整数倍)构成。

  • 字长:计算机的每个字所包含的位数称为字长。一般我们所说的8位机、16位机、32位机、64位机指的就是计算机的字长。

如上的总结可得如下:

  • 在16位CPU中,一个字表示16位(两个字节)

  • 在32位CPU中,一个字表示32位(四个字节):STM32

  • 在64位CPU中,一个字表示64位 (八个字节)

  • ...

也就是说,在上面的表中,如果你的阈值级别设置为1/4,那么它就只能发一个字节(四个节拍),如果是1/2,那么它就只能发两个字节,可以为一个字节两次突发,或者是两个字节一次突发....

我也很好奇的去了解了FIFO如何设置以及满阈值与FIFO深度的关系:

FIFO阈值包含将满阈值afull_cnt和将空阈值aempty_cnt,当FIFO内包含的数据data_cnt大于等于afull_cnt时,将满信号有效(afull为1),afull传输给上游模块A,通知上游模块停止发送数据,防止FIFO发送溢出,NOTE:将满阈值afull_cnt的作用是防止FIFO发送溢出导致数据丢失。将空阈值的作用时防止FIFO空的,即FIFO中没有有效数据了还会产生读数据操作。

img

原文链接:FIFO阈值如何设置?将满阈值与FIFO深度的关系?_IC小鸽的博客-CSDN博客

那么如何设置满阈值?

1)当FIFO中的数据为afull_cnt时,产生afull=1。

2)Afull=1经过M拍到达模块A,此时FIFO中应该有(afull_cnt+M)个数据。

3)Afull=1到达模块A时,模块A立即停止发送数据,此时电路中还存在N拍数据将陆续送到FIFO中,所以最后FIFO中应该为(afull_cnt+M+N)个数据。

4)为了保证数据不会溢出,所以应该满足公式depth_fifo>= afull_cnt+M+N,因此,将满阈值应该至少为depth_fif-(M+N)

FIFO深度depth应该为多少?

若depth_fifo过小,afull有效之后,fifo中存储的数据将很快被下游数据读取,而新的数据又无法及时到达FIFO,因此会造成流水气泡,影响电路性能

(四、五、六)M接口、P接口、编程接口

 由图可得,DMA2可以访问Flash、SRAM、外设AHB、APB。它无法实现从外设到外设。而DMA1只能访问APB1的外设。

DMA 控制器的功能是快速转移内存数据,需要一个连接至源数据地址的端口和一个连接至目标地址的端口

  • DMA2(DMA 控制器2)的存储器端口和外设端口都是连接到 AHB 总线矩阵,可以使用 AHB 总线矩阵功能。DMA2 存储器和外设端口可以访问相关的内存地址,包括有内部 Flash、内部 SRAM、AHB1 外设、AHB2 外设、APB2 外设和外部存储器空间。

  • DMA1 的存储区端口相比 DMA2 的要减少 AHB2 外设的访问权,同时 DMA1 外设端 口是没有连接至总线矩阵的,只有连接到 APB1 外设,所以 DMA1不能实现存储器到存储 器传输。

编程端口:AHB 从器件编程端口是连接至 AHB2 外设的。AHB2 外设在使用 DMA 传输时需要相关控制信号

二、DMA数据配置

DMA工作模式有多样,具有多种可能的工作模式:

(一)DMA传输模式:DMA2三种(外设到存储器,存储器到外设,存储器到存储器)、DMA1两种(外设到存储器,存储器到外设)

模式选择可以通过 DMA_SxCR 寄存器的 DIR[1:0]位控制,进而将 DMA_SxCR 寄存器的 EN 位置 1 就可以使能 DMA传输。

在 DMA_SxCR 寄存器的 PSIZE[1:0]和 MSIZE[1:0]位分别指定外设和存储器数据宽度大小,可以指定为字节(8 位)、半字(16 位)和字(32 位),我们可以根据实际情况设置。直接模式要求外设和存储器数据宽度大小一样,实际上在这种模式下 DMA 数据流直接使用 PSIZE,MSIZE 不被使用。

(二)源地址和目标地址

DMA 数据流 x 外设地址 DMA_SxPAR(x 为 0~7)寄存器用来指定外设地址,它是一个 32 位数据有效寄存器。DMA 数据流 x 存储器 0 地址 DMA_SxM0AR(x 为 0~7) 寄存器和 DMA 数据流 x 存储器 1 地址 DMA_SxM1AR(x 为 0~7) 寄存器用来存放存储器地址,其中 DMA_SxM1AR 只用于双缓冲模式,DMA_SxM0AR 和 DMA_SxM1AR 都是 32 位数据有效 的。

当选择外设到存储器模式时,即设置 DMA_SxCR 寄存器的 DIR[1:0] 位为“00”DMA_SxPAR 寄存器为外设地址,也是传输的源地址,DMA_SxM0AR 寄存器为存储器地址,也是传输的目标地址。对于存储器到存储器传输模式,即设置 DIR[1:0] 位为“10”时, 采用与外设到存储器模式相同配置。而对于存储器到外设,即设置 DIR[1:0]位为“01”时, DMA_SxM0AR 寄存器作为为源地址DMA_SxPAR 寄存器作为目标地址

 

  • 双缓冲模式

    设置 DMA_SxCR 寄存器的 DBM 位为 1 可启动双缓冲传输模式,并自动激活循环模式。(不应用于存储器搭到存储器的传输)

    双缓冲模式下,两个存储器地址指针都有效,即 DMA_SxM1AR 寄存器将被激活使用。开始传输使用 DMA_SxM0AR 寄存器的地址指针所对应的存储区,当这个存储区数据传输完 DMA 控制器会自动切换至 DMA_SxM1AR 寄存 器的地址指针所对应的另一块存储区,如果这一块也传输完成就再切换至 DMA_SxM0AR 寄存器的地址指针所对应的存储区,这样循环调用。(这样的传输模式适用于传输较大信息的情况下)

    当其中一个存储区传输完成时都会把传输完成中断标志 TCIF 位置 1,如果我们使能了 DMA_SxCR 寄存器的传输完成中断,则可以产生中断信号。

    敲重点:!!!

    DMA_SxCR 寄存器的 CT 位,当 DMA 控制器是在访问使用 DMA_SxM0ARCT=0,此时 CPU 不能访问 DMA_SxM0AR,但可以向 DMA_SxM1AR 填充或者读取数据;当 DMA 控制器是在访问使用 DMA_SxM1ARCT=1,此时 CPU 不能访问 DMA_SxM1AR,但可以向 DMA_SxM0AR 填充或者读取数据。在未使能 DMA 数据流传输时,可以直接写 CT 位,改变开始传输的目标存储区。

    双缓冲模式应用在需要解码程序的地方是非常有效的。比如MP3,它需要特定的解码库程序来解码文件才能得到可以播放的PCM信号,先读取一段原始数据到缓冲区,然后对缓冲区的内容进行解码,解码后才可以输出到音频播放电路,这个流程对CPU的要求极高,容易出现播放不流畅的现姓。使用双缓冲就可以解决这个问题,达到解码和输出音频数据到音频电路同步进行的效果。

(三)流控制器

主要是控制DMA传输停止的作用。如果我们得知我们需要传输多少数据,那我们可以在传输之前设置DMA_SxNDTR 寄存器为要传输数目值,DMA控制器传输完设置的数目后,就可以控制DMA停止传输。

DMA 数据流 x 数据项数 DMA_SxNDTR(x 为 0~7)寄存器用来记录当前仍需要传输数目,它是一个 16 位数据有效寄存器,即最大值为 65535。在编程时一般都会明确指定一个传输数量,在完成一次数目传输后 DMA_SxNDTR 计数值就会自减,当达到零时就说明传输完成。

如果我们不知道我们需要传输多少数值,那就无法调用寄存器使其自动控制传输,我们可以用外设通过硬件通信向DMA控制器发送停止传输信号。这里的前提是外设必须可以发出这个停止的传输信号,只有SDIO才有这个功能。

(四)循环模式

循环模式在传输一次后会自动按照相同配置重新传输,周而复始直至被控制停止或传输发生错误。

使用寄存器:DMA_SxCR 寄存器的 CIRC 位。

(五)传输类型

DMA传输类型:单次传输突发传输

  • 突发传输:在非常短时间内结合非常高数据信号传输数据。在传输阶段把速度提高,实现高速传输,在数据传输完成后恢复正常速度。这个过程要占用AHB总线,保证要求每个数据项在传输过程中不被分割,这样一次性把数据传输完才释放AHB总线。

  • 单次传输:必须通过AHB总线仲裁多次控制才传输完成。

(六)直接模式

在直接模式下,如果 DMA 配置为存储器到外设传输那 DMA 会见一个数据存放在 FIFO 内,如果外设启动 DMA 传输请求就可以马上将数据传输过去。

(七)DMA中断

每个 DMA 数据流可以在发送以下事件时产生中断:

  1. 达到半传输:DMA 数据传输达到一半时 HTIF 标志位被置 1,如果使能 HTIE 中 断控制位将产生达到半传输中断;

  2. 传输完成:DMA 数据传输完成时 TCIF 标志位被置 1,如果使能 TCIE 中断控制 位将产生传输完成中断;

  3. 传输错误:DMA 访问总线发生错误或者在双缓冲模式下试图访问“受限”存储器 地址寄存器时 TEIF 标志位被置 1,如果使能 TEIE 中断控制位将产生传输错误中 断;

  4. FIFO 错误:发生 FIFO 下溢或者上溢时 FEIF 标志位被置 1,如果使能 FEIE 中断 控制位将产生 FIFO 错误中断;

  5. 直接模式错误:在外设到存储器的直接模式下,因为存储器总线没得到授权,使 得先前数据没有完成被传输到存储器空间上,此时 DMEIF 标志位被置 1,如果使 能 DMEIE 中断控制位将产生直接模式错误中断。

三、DMA初始化结构体

typedef struct {
 	uint32_t DMA_Channel; 					//通道选择
	uint32_t DMA_PeripheralBaseAddr; 		//外设地址
 	uint32_t DMA_Memory0BaseAddr; 			//存储器 0 地址
 	uint32_t DMA_DIR; 						//传输方向
 	uint32_t DMA_BufferSize; 				//数据数目
 	uint32_t DMA_PeripheralInc;				//外设递增
 	uint32_t DMA_MemoryInc; 				//存储器递增
 	uint32_t DMA_PeripheralDataSize; 		//外设数据宽度
 	uint32_t DMA_MemoryDataSize; 			//存储器数据宽度
 	uint32_t DMA_Mode; 						//模式选择
 	uint32_t DMA_Priority; 					//优先级
 	uint32_t DMA_FIFOMode;		 			//FIFO 模式
 	uint32_t DMA_FIFOThreshold; 			//FIFO 阈值
 	uint32_t DMA_MemoryBurst; 				//存储器突发传输
	uint32_t DMA_PeripheralBurst; 			//外设突发传输
} DMA_InitTypeDef;
  • DMA_Channel: DMA 请求通道选择,可选通道 0 至通道 7,每个外 设对应固定的通道,DMA_SxCR :CHSEL[2:0]。

  • DMA_PeripheralBaseAddr: 外设地址, DMA_SxPAR。

  • DMA_Memory0BaseAddr:存储器 0 地址,DMA_SxM0AR。

  • DMA_DIR:传输方向选择,可选外设到存储器、存储器到外设以及存储 器到存储器,DMA_SxCR :DIR[1:0]。

  • DMA_BufferSize:设定一次传输的数据个数,DMA_SxNDTR 。

  • DMA_PeripheralInc:外设地址是否递增, DMA_SxCR :PINC 。

  • DMA_MemoryInc:存储器地址是否递增,DMA_SxCR :MINC。

  • DMA_PeripheralDataSize:外设数据宽度,可选字节(8 位)、半 字(16 位)和字(32位),DMA_SxCR :PSIZE[1:0]。

  • DMA_BufferSize:设定一次传输的数据个数,DMA_SxNDTR 。

  • DMA_MemoryDataSize:存储器数据宽度,可选字节(8 位)、半字 (16 位)和字(32位),DMA_SxCR :MSIZE[1:0]。

  • DMA_Mode :DMA传输模式选择,可选一次传输或者循环传输 ,DMA_SxCR :CIRC 位的值。

  • DMA_Priority:优先级,非常高、高、中和低,DMA_SxCR :PL[1:0]

  • DMA_FIFOMode: FIFO 模式使能,DMA_SxFCR :DMDIS 。

  • DMA_FIFOThreshold: FIFO 阈值选择,1/4、1/2、 3/4 和满, DMA_SxFCR :FTH[1:0]。

  • DMA_MemoryBurst:存储器突发模式选择,单次模式、 4 节拍、 8 节拍、16 节拍,DMA_SxCR :MBURST[1:0] 。

  • DMA_PeripheralBurst:外设突发模式选择,单次模式、 4 节拍、 8 节拍、16 节拍,DMA_SxCR :PBURST[1:0] 。

四、编程时需要用到的固件库函数

1-初始化DMA的寄存器到复位状态

DMA_DeInit(DMA_Stream_TypeDef* DMAy_Streamx); 

2-DMA初始化函数

void DMA_Init(DMA_Stream_TypeDef* DMAy_Streamx,  DMA_InitTypeDef* DMA_InitStr

3-DMA使能函数

DMA_Cmd(DMA_Stream_TypeDef* DMAy_Streamx,  FunctionalState NewState);

五、实践

(1)存储器到存储器:FLASH to SRAM,把内部Flash的数据传输到内部的SRAM。

  1. 在Flash中定义好要传输的数据,在SRAM中定义好用来接收Flash数据的变量。

  2. 确定使用DMA2,哪个数据流,哪个通道,然后定义为宏,方便修改。

  3. 初始化DMA,主要是配置DMA初始化结构体。

如何配置要参考《STM32F429手册》流的配置过程:

 

4.编写数据比较函数

5.编写main函数

初始化相关宏定义:

/* 相关宏定义,使用存储器到存储器传输必须使用DMA2 */
#define DMA_STREAM               DMA2_Stream0
#define DMA_CHANNEL              DMA_Channel_0
#define DMA_STREAM_CLOCK         RCC_AHB1Periph_DMA2 
#define DMA_FLAG_TCIF            DMA_FLAG_TCIF0

#define BUFFER_SIZE              32
#define TIMEOUT_MAX              10000 /* Maximum timeout value */

定义想要传输的数据源以及目标存储器:

/* 定义aSRC_Const_Buffer数组作为DMA传输数据源
  const关键字将aSRC_Const_Buffer数组变量定义为常量类型 */
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传输目标存储器 */
uint32_t aDST_Buffer[BUFFER_SIZE];

DMA传输配置:

static void DMA_Config(void)
{
  DMA_InitTypeDef  DMA_InitStructure;
  __IO uint32_t    Timeout = TIMEOUT_MAX;
    
  /* 使能DMA时钟 */
  RCC_AHB1PeriphClockCmd(DMA_STREAM_CLOCK, ENABLE);
  
  /* 复位初始化DMA数据流 */
  DMA_DeInit(DMA_STREAM);

  /* 确保DMA数据流复位完成 */
  while (DMA_GetCmdStatus(DMA_STREAM) != ENABLE)
  {
  }
  
  /* DMA数据流通道选择 */
  DMA_InitStructure.DMA_Channel = DMA_CHANNEL;  
  /* 源数据地址 */
  DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)aSRC_Const_Buffer;
  /* 目标地址 */
  DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)aDST_Buffer;
  /* 存储器到存储器模式 */
  DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToMemory;
  /* 数据数目 */
  DMA_InitStructure.DMA_BufferSize = (uint32_t)BUFFER_SIZE;
  /* 使能自动递增功能 */
  DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Enable;
  /* 使能自动递增功能 */
  DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
  /* 源数据是字大小(32位) */
  DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word;
  /* 目标数据也是字大小(32位) */
  DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Word;
  /* 一次传输模式,存储器到存储器模式不能使用循环传输 */
  DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
  /* DMA数据流优先级为高 */
  DMA_InitStructure.DMA_Priority = DMA_Priority_High;
  /* 禁用FIFO模式 */
  DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;     
  DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full;
  /* 单次模式 */
  DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;
  /* 单次模式 */
  DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
  /* 完成DMA数据流参数配置 */
  DMA_Init(DMA_STREAM, &DMA_InitStructure);
  
  /* 清除DMA数据流传输完成标志位 */
  DMA_ClearFlag(DMA_STREAM,DMA_FLAG_TCIF);
  
  /* 使能DMA数据流,开始DMA数据传输 */
  DMA_Cmd(DMA_STREAM, ENABLE);

  /* 检测DMA数据流是否有效并带有超时检测功能 */
  Timeout = TIMEOUT_MAX;
  while ((DMA_GetCmdStatus(DMA_STREAM) != ENABLE) && (Timeout-- > 0))
  {
  }
   
  /* 判断是否超时 */
  if (Timeout == 0)
  {
    /* 超时就让程序运行下面循环:RGB彩色灯闪烁 */
    while (1)
    {      
      LED_RED;
      Delay(0xFFFFFF);
      LED_RGBOFF;
      Delay(0xFFFFFF);
    }
  } 
}

要注意的是,上面使用的是单次模式,第一次配置的时候以为是直接模式,就很纳闷,存储器到存储器不应该不可以是直接模式吗,其实使用存储器到存储器模式时,不允许使用循环模式和直接模式,只能使用单次传输和FIFO缓冲。而且只有DMA2控制器能够执行存储器到存储器的传输。

之后再加入一个传输完成的检测函数:

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

之后我们通过led的亮灯情况来判断是否传输成功:

int main(void)
{
  /* 定义存放比较结果变量 */
  uint8_t TransferStatus;
  
	/* LED 端口初始化 */
	LED_GPIO_Config();
    
  /* 设置RGB彩色灯为紫色 */
  LED_PURPLE;  
  
  /* 简单延时函数 */
  Delay(0xFFFFFF);  
  
  /* DMA传输配置 */
  DMA_Config(); 
  
  /* 等待DMA传输完成 */
  while(DMA_GetFlagStatus(DMA_STREAM,DMA_FLAG_TCIF)==DISABLE)
  {
    
  }   
  
  /* 比较源数据与传输后数据 */
  TransferStatus=Buffercmp(aSRC_Const_Buffer, aDST_Buffer, BUFFER_SIZE);
  
  /* 判断源数据与传输后数据比较结果*/
  if(TransferStatus==0)  
  {
    /* 源数据与传输后数据不相等时RGB彩色灯显示红色 */
    LED_RED;
  }
  else
  { 
    /* 源数据与传输后数据相等时RGB彩色灯显示蓝色 */
    LED_BLUE;
  }

	while (1)
	{		
	}
}

(2)存储器到外设:SRAM to 串口,同时LED灯闪烁,演示DMA传数据不需要占用CPU。

初始化对应串口:

void Debug_USART_Config(void)
{
  GPIO_InitTypeDef GPIO_InitStructure;
  USART_InitTypeDef USART_InitStructure;
		
  RCC_AHB1PeriphClockCmd(DEBUG_USART_RX_GPIO_CLK|DEBUG_USART_TX_GPIO_CLK,ENABLE);

  /* 使能 USART 时钟 */
  RCC_APB2PeriphClockCmd(DEBUG_USART_CLK, ENABLE);
  
  /* GPIO初始化 */
  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;  
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  
  /* 配置Tx引脚为复用功能  */
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
  GPIO_InitStructure.GPIO_Pin = DEBUG_USART_TX_PIN  ;  
  GPIO_Init(DEBUG_USART_TX_GPIO_PORT, &GPIO_InitStructure);

  /* 配置Rx引脚为复用功能 */
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
  GPIO_InitStructure.GPIO_Pin = DEBUG_USART_RX_PIN;
  GPIO_Init(DEBUG_USART_RX_GPIO_PORT, &GPIO_InitStructure);
  
 /* 连接 PXx 到 USARTx_Tx*/
  GPIO_PinAFConfig(DEBUG_USART_RX_GPIO_PORT,DEBUG_USART_RX_SOURCE,DEBUG_USART_RX_AF);

  /*  连接 PXx 到 USARTx__Rx*/
  GPIO_PinAFConfig(DEBUG_USART_TX_GPIO_PORT,DEBUG_USART_TX_SOURCE,DEBUG_USART_TX_AF);
  
  /* 配置串DEBUG_USART 模式 */
  /* 波特率设置:DEBUG_USART_BAUDRATE */
  USART_InitStructure.USART_BaudRate = DEBUG_USART_BAUDRATE;
  /* 字长(数据位+校验位):8 */
  USART_InitStructure.USART_WordLength = USART_WordLength_8b;
  /* 停止位:1个停止位 */
  USART_InitStructure.USART_StopBits = USART_StopBits_1;
  /* 校验位选择:不使用校验 */
  USART_InitStructure.USART_Parity = USART_Parity_No;
  /* 硬件流控制:不使用硬件流 */
  USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
  /* USART模式控制:同时使能接收和发送 */
  USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
  /* 完成USART初始化配置 */
  USART_Init(DEBUG_USART, &USART_InitStructure);
	
  /* 使能串口 */
  USART_Cmd(DEBUG_USART, ENABLE);
}

初始化DMA:

//DMA
#define DEBUG_USART_DR_BASE               (USART1_BASE+0x04)		
#define SENDBUFF_SIZE                     5000				//发送的数据量

#define DEBUG_USART_DMA_CLK               RCC_AHB1Periph_DMA2	
#define DEBUG_USART_DMA_CHANNEL           DMA_Channel_4
#define DEBUG_USART_DMA_STREAM            DMA2_Stream7

void MtoP_DMA_Config(void)
{
  DMA_InitTypeDef DMA_InitStructure;

  /*开启DMA时钟*/
  RCC_AHB1PeriphClockCmd(DEBUG_USART_DMA_CLK, ENABLE);
  
  /* 复位初始化DMA数据流 */
  DMA_DeInit(DEBUG_USART_DMA_STREAM);

  /* 确保DMA数据流复位完成 */
  while (DMA_GetCmdStatus(DEBUG_USART_DMA_STREAM) != DISABLE)  {
  }

  /*usart1 tx对应dma2,通道4,数据流7*/	
  DMA_InitStructure.DMA_Channel = DEBUG_USART_DMA_CHANNEL;  
  /*设置DMA源:串口数据寄存器地址*/
  DMA_InitStructure.DMA_PeripheralBaseAddr = DEBUG_USART_DR_BASE;	 
  /*内存地址(要传输的变量的指针)*/
  DMA_InitStructure.DMA_Memory0BaseAddr = (u32)SendBuff;
  /*方向:从内存到外设*/		
  DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToPeripheral;	
  /*传输大小DMA_BufferSize=SENDBUFF_SIZE*/	
  DMA_InitStructure.DMA_BufferSize = SENDBUFF_SIZE;
  /*外设地址不增*/	    
  DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; 
  /*内存地址自增*/
  DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;	
  /*外设数据单位*/	
  DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
  /*内存数据单位 8bit*/
  DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;	
  /*DMA模式:不断循环*/
  //DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
	DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;	
  /*优先级:中*/	
  DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; 
	
  /*禁用FIFO*/
  DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;        
  DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full;    
  /*存储器突发传输 单次传输*/
  DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;    
  /*外设突发传输 单次传输*/
  DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
	
  /*配置DMA2的数据流7*/		   
  DMA_Init(DEBUG_USART_DMA_STREAM, &DMA_InitStructure);
  
  /*使能DMA*/
  DMA_Cmd(DEBUG_USART_DMA_STREAM, ENABLE);
  
  /* 等待DMA数据流有效*/
  while(DMA_GetCmdStatus(DEBUG_USART_DMA_STREAM) != ENABLE)
  {
  }
}

之后,我们在main函数中实现边点灯边从dma发送数据的功能

int main(void)
{	
  uint16_t i;
	
	LED_GPIO_Config();
	
	/*初始化USART 配置模式为 115200 8-N-1,中断接收*/
  Debug_USART_Config();
	
	  /*填充将要发送的数据*/
  for(i=0;i<SENDBUFF_SIZE;i++)
  {
    SendBuff[i]	 = 'A';
    
  }
	
	MtoP_DMA_Config();
	
	USART_DMACmd(DEBUG_USART, USART_DMAReq_Tx, ENABLE);

  while(1)
	{	
		LED1_TOGGLE
    	Delay(0xFFFFF);
	}	
}

DMA就总结到这啦,明天就国庆了,那就国庆节快乐!!在假期多做一些有意义的事情8!!

  • 3
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

郑烯烃快去学习

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值