第15章 DMA存储器到存储器模式实验

第十五章 DMA存储器到存储器模式实验

1. 硬件设计

存储器到存储器模式可以实现数据在两个内存的快速拷贝。我们先定义一个静态的源数据,存放在内部FLASH, 然后使用DMA传输把源数据拷贝到目标地址上(内部SRAM),最后对比源数据和目标地址的数据,看看是否传输准确 。

DMA存储器到存储器实验不需要其他硬件要求,只用到RGB彩色灯用于指示程序状态。

2. 软件设计

2.1 编程目的

  1. 使能DMA时钟;

  2. 配置DMA数据参数;

  3. 使能DMA,进行传输;

  4. 等待传输完成,并对源数据和目标地址数据进行比较

2.2 代码分析

  • DMA宏定义及相关变量定义
// 当使用存储器到存储器模式时候,通道可以随便选,没有硬性的规定
#define DMA_CHANNEL     DMA1_Channel6      // 我们这里使用的通道是DMA1_Channel6
#define DMA_CLOCK       RCC_AHBPeriph_DMA1 // DMA1时钟

// 传输完成标志
#define DMA_FLAG_TC     DMA1_FLAG_TC6

// 要发送的数据大小
#define BUFFER_SIZE     32

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

使用宏定义设置外设配置方便程序修改和升级。

存储器到存储器传输通道没有硬性规定,可以随意选择。

aSRC_Const_Buffer[BUFFER_SIZE]定义用来存放源数据,并且使用了const关键字修饰,即常量类型,使得变量是存储在内部flash空间上。

  • DMA配置
// DMA配置函数
void DMA_Config(void)
{
        // 1.DMA初始化结构体
        DMA_InitTypeDef DMA_InitStructure;
        // 2.开启DMA时钟
        RCC_AHBPeriphClockCmd(DMA_CLOCK, ENABLE);
        // 3.源数据地址
        DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)aSRC_Const_Buffer;
        // 4.目标数据地址
        DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)aDST_Buffer;
        // 6.设置传输方向:外设到存储器(这里的外设是内部的FLASH)    
        DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
        // 7.设置传输大小    
        DMA_InitStructure.DMA_BufferSize = BUFFER_SIZE;
        // 8.设置外设(内部的FLASH)地址递增    
        DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Enable;
        // 9.内存地址递增
        DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
        // 10.外设数据单位    
        DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word;
        // 11.内存数据单位
        DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Word;
        // 12.设置DMA模式,一次或者循环模式
        DMA_InitStructure.DMA_Mode = DMA_Mode_Normal ;
        //DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
        // 13.设置通道优先级:高    
        DMA_InitStructure.DMA_Priority = DMA_Priority_High;
        // 14.使能内存到内存的传输
        DMA_InitStructure.DMA_M2M = DMA_M2M_Enable;
        // 15.配置DMA通道           
        DMA_Init(DMA_CHANNEL, &DMA_InitStructure);
       // 16.清除DMA数据流传输完成标志位
        DMA_ClearFlag(DMA_FLAG_TC);
        // 17.使能DMA
        DMA_Cmd(DMA_CHANNEL, ENABLE);
}

使用DMA_InitTypeDef结构体定义一个DMA初始化变量,参考:

typedef struct
{
uint32_t DMA_PeripheralBaseAddr;   // 外设地址
uint32_t DMA_MemoryBaseAddr;       // 存储器地址
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_M2M;                  // 存储器到存储器模式
}DMA_InitTypeDef;

调用RCC_AHBPeriphClockCmd函数开启DMA时钟,使用DMA控制器之前必须开启对应的时钟。

源地址和目标地址使用之前定义的数组首地址,传输的数据量为宏BUFFER_SIZE决定,源和目标地址指针地址递增, 使用一次传输模式不能循环传输,因为只有一个DMA通道,优先级随便设置,最后调用DMA_Init函数完成DMA的初始化配置。

DMA_ClearFlag函数用于清除DMA标志位,代码用到传输完成标志位,使用之前先清除传输完成标志位以免产生不必要干扰。 DMA_ClearFlag函数需要1个形参,即事件标志位,可选有传输完成标志位、半传输标志位、FIFO错误标志位、传输错误标志位等等, 非常多,我们这里选择传输完成标志位,由宏DMA_FLAG_TC定义。

DMA_Cmd函数用于启动或者停止DMA数据传输,它接收两个参数,第一个是DMA通道,另外一个是开启ENABLE或者停止DISABLE。

  • 存储器数据对比
/*
判断指定长度的两个数据源是否完全相等,
如果完全相等返回1,只要其中一对数据不相等返回0
*/
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; // 数据完全相等
}

判断指定长度的两个数据源是否完全相等,如果完全相等返回1;只要其中一对数据不相等返回0。 它需要三个形参,前两个是两个数据源的地址,第三个是要比较数据长度。

  • 主函数
int main(void)
{
  // 1.定义存放比较结果变量
  uint8_t TransferStatus;
  // 2.LED 端口初始化
  LED_GPIO_Config();
  // 3.设置RGB彩色灯为紫色
  LED_PURPLE;  
  // 4.简单延时函数
  Delay(0xFFFFFF);  
  // 5.DMA传输配置
  DMA_Config(); 
  // 6.等待DMA传输完成
  while(DMA_GetFlagStatus(DMA_FLAG_TC) == RESET)
  {

  }   
  // 7.比较源数据与传输后数据
  TransferStatus = Buffercmp(aSRC_Const_Buffer, aDST_Buffer, BUFFER_SIZE);
  // 8.判断源数据与传输后数据比较结果
  if(TransferStatus == 0)  
  {
    // 源数据与传输后数据不相等时RGB彩色灯显示红色
    LED_RED;
  }
  else
  { 
    // 源数据与传输后数据相等时RGB彩色灯显示蓝色
    LED_BLUE;
  }
  while (1)
  {        

  }
}

首先定义一个变量用来保存存储器数据比较结果。

RGB彩色灯用来指示程序进程,使用之前需要初始化它,LED_GPIO_Config定义在bsp_led.c文件中。开始设置RGB彩色灯为紫色, LED_PURPLE是定义在bsp_led.h文件的一个宏定义。

Delay函数只是一个简单的延时函数。

调用DMA_Config函数完成DMA数据流配置并启动DMA数据传输。

DMA_GetFlagStatus函数获取DMA事件标志位的当前状态,这里获取DMA数据传输完成这个标志位,使用循环持续等待直到该标志位被置位, 即DMA传输完成这个事件发生,然后退出循环,运行之后程序。

确定DMA传输完成之后就可以调用Buffercmp函数比较源数据与DMA传输后目标地址的数据是否一一对应。TransferStatus保存比较结果, 如果为1表示两个数据源一一对应相等说明DMA传输成功;相反,如果为0表示两个数据源数据存在不等情况,说明DMA传输出错。

如果DMA传输成功设置RGB彩色灯为蓝色,如果DMA传输出错设置RGB彩色灯为红色。

3. 小节

这一章相比于之前的串口通信就简单多了,主要就是理解一个配置的问题,然后直接调用即可,我们可以再来梳理一下:

  1. 首先宏定义DAM基本参数嘛:选择通道、时钟、传输完成标志,传输数据大小,内部flash数据,目标sram(定义一个空间用来接收)

  2. 各种函数声明,不必多说

  3. 最重要DMA配置:初始化结构体->开启DMA时钟->设置源数据地址(我们的flash中存储的)->设置目标存储地址(sram)->设置传输方向(DMA_DIR_PeripheralSRC,意思是外设到存储器)->设置传输大小(我们已经宏定义过了)->设置外设地址递增( DMA_PeripheralInc_Enable)->设置内存地址递增(DMA_MemoryInc_Enable)->设置外设数据单位(DMA_PeripheralDataSize_Word)->设置内存数据单位(DMA_MemoryDataSize_Word)->设置MDA模式,这里我们选择一次模式->配置通道优先级为高->使能内存到内存传输->接下来三件套(配置结构体,清除标志位,使能)

  4. 判断两个数据长度是否相等:

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; // 数据完全相等
}
  1. 主函数往往最简单了,自行看注释理解吧

当然了出现新的库函数,我们应该介绍一下:

  1. DMA_Init(DMA_CHANNEL, &DMA_InitStructure);
    这个函数用于初始化DMA通道。DMA_CHANNEL是要配置的DMA通道的标识符,例如DMA1_Channel1,&DMA_InitStructure是一个指向DMA_InitTypeDef结构体的指针,包含了所有的配置参数,如数据方向、传输大小、优先级等。通过调用此函数,DMA通道会根据提供的配置结构体进行初始化,设置DMA的各项操作参数。

  2. DMA_ClearFlag(DMA_FLAG_TC);
    这个函数用于清除DMA的传输完成标志位。DMA_FLAG_TC是指传输完成的标志位,表示DMA传输完成时产生的标志。清除这个标志位是为了确保之前的传输完成标志不会干扰当前的DMA传输操作。在开始新的DMA传输前,清除标志位是一个重要步骤,以确保数据传输过程的正确性。

  3. DMA_Cmd(DMA_CHANNEL, ENABLE);
    这个函数用于启用DMA通道。DMA_CHANNEL指定要使能的DMA通道,例如DMA1_Channel1,ENABLE表示要启用该通道。调用此函数后,DMA通道将开始进行数据传输。这个步骤是启动DMA传输的关键,确保DMA能够按照预设的配置开始实际的数据搬运工作。

while(DMA_GetFlagStatus(DMA_FLAG_TC) == RESET)
{
    // 空循环,等待DMA传输完成标志位置位
}
  1. DMA_GetFlagStatus(DMA_FLAG_TC)
    这个函数用于获取DMA标志位的状态。DMA_FLAG_TC代表传输完成标志(Transfer Complete Flag),它指示DMA传输是否已完成。函数返回标志位的当前状态。

  2. == RESET
    RESET是一个常量,用于表示标志位尚未被置位。DMA传输完成标志位在传输完成时会被置位为SET。在标志位为RESET时,表示传输尚未完成。

  3. while循环
    这个while循环持续检查DMA的传输完成标志位是否为RESET。也就是说,它会一直等待,直到DMA_GetFlagStatus(DMA_FLAG_TC)返回SET(传输完成)。在此之前,循环体内部是空的(没有任何操作),只是不断地轮询标志位状态。


2024.8.28 第一次修订,后期不再维护

posted @ 2024-08-28 10:22  hazy1k  阅读(15)  评论(0编辑  收藏  举报