【WB32库开发】第13章(上)DMA直接存储器访问——存储器到存储器

DMA(Direct Memory Acess),中文译为直接存储器访问。主要用来在不占用CPU的情况下提供在存储器和存储器之间、外设和存储器之间或者外设和外设之间的高速数据传输,可以节约CPU的资源做其它操作。

WB32F10xxx 有两个完全相同的 DMA 控制器 DMAC1 和 DMAC2。每个 DMA 控制器有 3 个通道(一共有 6 个通道),每个通道可以单独配置,管理各种类型 DMA 传输。每个 DMAC 内还有一个仲裁器来协调各个 DMA 请求的优先权。

本节将通过固件库DMA例程中的DMAC_MemoryToMemory工程,来讲解如何配置DMA完成存储器到存储器之间的数据传输。

13.1 DMA存储器到存储器间数据传输配置

本节代码主要功能为,定义一个字符数组,将此数组中存放的字符复制到一个新的地址中。代码从上到下按照结构依次分析与注释,请大家认真学习。

13.1.1 预处理代码及宏定义代码分析

#include "wb32f10x.h"                                  
#include <stdio.h>                                     //使用printf函数时必须包含此头文件。
#include <string.h>                                    //字符串处理函数声明所在头文件,若想输出字符串,必须包含此头文件。
#include "bsp_uart1.h"                                 //用户自定义串口输出函数声明所在头文件。

DMAC_Channel_InitTypeDef DMAC_Channel_InitStruct;      //将DMAC_Channel_InitTypeDef 宏定义为 DMAC_Channel_InitStruct。
NVIC_InitTypeDef NVIC_InitStructure;                   //将NVIC_InitTypeDef 宏定义为 NVIC_InitStructure。
char memSrc[] = "DMAC Memory to Memory Example\r\n";   //申请一个字符数组,并写入"DMAC Memory to Memory Example"
char memDst[sizeof(memSrc)] = {0};                     //另外申请一个字符数组,申请的空间与memSrc[]相同。
uint32_t flag;                                         //申请一个32位无符号的变量flag。

注意:
1)以#include <string.h>这句代码为例再复习一遍:string.h 是个“头文件”,其中包含了字符串处理函数的声明。

C语言中的变量和函数都需要先声明(定义)再使用。我们在使用自己编写的函数或变量之前也要先定义它们,定义本身就是声明。而对于使用系统函数或库函数,也需要先把含有它们声明的文件“包含”进来。这些文件通常在系统的指定目录中,你的编译器(预处理器)会自动找到它们。

#include 是一个预处理指示符,C源码在被编译器编译前会先交由预处理器处理,预处理器就会把 #include <string.h> 替换成string.h文件中的内容,这样这些字符串处理函数的声明就含在源代码中了,编译器才能顺利编译。没有这些声明的话,编译时通常会报“找不到strcmp函数定义…”这样的错误。

2)宏定义又称为宏替换,是C语言提供的三种预处理功能的一种,通过宏定义,可以提高程序的通用性和易读性。

13.1.2 主函数部分代码分析

int main(void)
{
  /* 配置中断优先组为2 */
  NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);                                             

  /* 初始化串口1,此处传入的72000000为WB32使用的主频,115200为波特率 */
  uart1_init(72000000, 115200);
  /* 打印"Enter any key to continue..." */
  printf("Enter any key to continue...\r\n");
  /* 接收串口调试软件发来的字符 */
  getchar();
  /* 若接收到字符,打印"GoGo!!!" */
  printf("GoGo!!!\r\n");

  /* 使能DMAC1时钟,同时还需使能AHB总线上的DMAC1Bridge的时钟 */
  RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMAC1Bridge, ENABLE);
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_BMX1 | RCC_APB1Periph_DMAC1, ENABLE);

  /* 重启DMAC1 模块 */
  DMAC_DeInit(DMAC1);

  /* 配置DMAC1 Channel 0 */
  /* 设置传输源地址为数组memSrc的首地址 */
  DMAC_Channel_InitStruct.DMAC_SourceBaseAddr = (uint32_t)memSrc;
  /* 设置传输目标地址为数组memDst的首地址 */
  DMAC_Channel_InitStruct.DMAC_DestinationBaseAddr = (uint32_t)memDst;
  /* 使能DMA传输全局中断 */
  DMAC_Channel_InitStruct.DMAC_Interrupt = DMAC_Interrupt_Enable;
  /* 指定传输字宽 */
  DMAC_Channel_InitStruct.DMAC_SourceTransferWidth = DMAC_SourceTransferWidth_8b;
  /* 指定接收字宽 */
  DMAC_Channel_InitStruct.DMAC_DestinationTransferWidth = DMAC_DestinationTransferWidth_8b;
  /* 指定源地址增量模式为Increment */
  DMAC_Channel_InitStruct.DMAC_SourceAddrInc = DMAC_SourceAddrInc_Increment;
  /* 指定目标地址增量模式为Increment */
  DMAC_Channel_InitStruct.DMAC_DestinationAddrInc = DMAC_DestinationAddrInc_Increment;
  /* 指定源快速传输字长,对于存储器操作无效 */
  DMAC_Channel_InitStruct.DMAC_SourceTransactionLength = DMAC_SourceTransactionLength_1;
  /* 指定目标快速传输字长,对于存储器操作无效 */
  DMAC_Channel_InitStruct.DMAC_DestinationTransactionLength = DMAC_DestinationTransactionLength_1;
  /* 指定传输类型为存储器到存储器 */
  DMAC_Channel_InitStruct.DMAC_TransferTypeAndFlowControl =DMAC_TransferTypeAndFlowControl_MemoryToMemory_DMAC;
  /* 指定源主接口为AHB */
  DMAC_Channel_InitStruct.DMAC_SourceMasterInterface = DMAC_SourceMasterInterface_AHB;
  /* 指定目标主接口为AHB */
  DMAC_Channel_InitStruct.DMAC_DestinationMasterInterface = DMAC_DestinationMasterInterface_AHB;
  /* 指定块传输长度为memSrc数组的长度 */
  DMAC_Channel_InitStruct.DMAC_BlockTransferSize = sizeof(memSrc);
  /* 源握手接口选择为硬件 */ 
  DMAC_Channel_InitStruct.DMAC_SourceHandshakingInterfaceSelect =DMAC_SourceHandshakingInterfaceSelect_Hardware;
  /* 目标握手接口选择为硬件 */
  DMAC_Channel_InitStruct.DMAC_DestinationHandshakingInterfaceSelect = DMAC_DestinationHandshakingInterfaceSelect_Hardware;
  /* 指定源握手接口优先级 */
  DMAC_Channel_InitStruct.DMAC_SourceHandshakingInterfacePolarity = DMAC_SourceHandshakingInterfacePolarity_High;
  /* 指定目标握手接口优先级 */
  DMAC_Channel_InitStruct.DMAC_DestinationHandshakingInterfacePolarity = DMAC_DestinationHandshakingInterfacePolarity_High;
  /* 失能源自动重装载 */
  DMAC_Channel_InitStruct.DMAC_AutomaticSourceReload = DMAC_AutomaticSourceReload_Disable;
  /* 失能目标自动重装载 */
  DMAC_Channel_InitStruct.DMAC_AutomaticDestinationReload = DMAC_AutomaticDestinationReload_Disable;
  /* 指定流控制模式 */
  DMAC_Channel_InitStruct.DMAC_FlowControlMode = DMAC_FlowControlMode_0;
  /* 指定FIFO(先进先出)模式 */
  DMAC_Channel_InitStruct.DMAC_FIFOMode = DMAC_FIFOMode_0;
  /* 通道优先级选择,与中断相反,数字越大优先级越高 */
  DMAC_Channel_InitStruct.DMAC_ChannelPriority = 0;
  /* 指定保护控制 */
  DMAC_Channel_InitStruct.DMAC_ProtectionControl = 0x1;
  /* 源硬件握手接口分配 */
  DMAC_Channel_InitStruct.DMAC_SourceHardwareHandshakingInterfaceAssign = 0;
  /* 目标硬件握手接口分配 */
  DMAC_Channel_InitStruct.DMAC_DestinationHardwareHandshakingInterfaceAssign = 0;
  /* 指定最大的AMBA快速传输字长 */
  DMAC_Channel_InitStruct.DMAC_MaximumAMBABurstLength = 0;
  /* DMAC通道结构体配置初始化 */
  DMAC_Channel_Init(DMAC1, DMAC_Channel_0, &DMAC_Channel_InitStruct);
  /* DMAC通道中断配置 */
  DMAC_ITConfig(DMAC1, DMAC_Channel_0, DMAC_IT_BLOCK, ENABLE);
  DMAC_ITConfig(DMAC1, DMAC_Channel_0, DMAC_IT_TFR, ENABLE);
  DMAC_ITConfig(DMAC1, DMAC_Channel_0, DMAC_IT_ERR, ENABLE);
	
  /* 中断初始化结构体配置 */
  /* 中断通道选择为DMAC1 */
  NVIC_InitStructure.NVIC_IRQChannel = DMAC1_IRQn;
  /* 指定抢占优先级 */
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
  /* 指定子优先级 */
  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
  /* 使能中断通道 */
  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
  /* 初始化中断初始化结构体 */
  NVIC_Init(&NVIC_InitStructure);
  
  /* 当以上配置好后,打印"DMA transfer enable." */
  printf("DMA transfer enable.\r\n");
  /* 使能DMAC1 */
  DMAC_Cmd(DMAC1, ENABLE);
  /* 使能DMAC1通道0 */
  DMAC_ChannelCmd(DMAC1, DMAC_Channel_0, ENABLE);

  while (1)
  {
  }
}

在这部分代码中,主要配置了DMAC_Channel_InitTypeDef结构体中的各个结构体成员。

注意:
1)传入变量的地址与传入数组的地址方式不同,若是传入变量,需在变量前加上“&”;传入数组变量时,直接传数组名即可。

2)可以发现在传源地址与目标地址时,数组名前有“(uint32_t)”,此意为强制类型转换。

3)DMAC_Channel_InitTypeDef结构体中的结构体成员很多,初学者可以结合注释和DMA的其他传输方式一起学习,多看多想。

4)uart1_init(72000000, 115200);此部分代码用来配置串口,我们在下面的内容讲解。

13.1.3 DMAC1中断服务函数代码分析

/* 配置DMAC1中断服务函数 */
void DMAC1_IRQHandler(void)
{
  if(DMAC_GetITStatus(DMAC1, DMAC_Channel_0, DMAC_IT_BLOCK) != RESET)    //判断DMAC_IT_BLOCK中断标志位
  {
    DMAC_ClearITPendingBit(DMAC1, DMAC_Channel_0, DMAC_IT_BLOCK);        //若产生DMAC_IT_BLOCK中断标志位,则清除DMAC_IT_BLOCK中断标志位
    printf("DMA block transfer complete.\r\n");                          //打印“DMA block transfer complete.”
  }

  if(DMAC_GetITStatus(DMAC1, DMAC_Channel_0, DMAC_IT_TFR) != RESET)      //判断DMAC_IT_TFR中断标志位
  {
    DMAC_ClearITPendingBit(DMAC1, DMAC_Channel_0, DMAC_IT_TFR);          //若产生DMAC_IT_TFR中断标志位,则清除DMAC_IT_TFR中断标志位
    printf("DMA transfer complete.\r\n");								 //打印“DMA transfer complete.”
    if(memcmp(memDst, memSrc, sizeof(memSrc)) == 0) {           		 //通过“memcmp”比较函数,判断memDst数组与memSrc数组是否相等
      printf("DMA transfer success!!!\r\n");							 //若相等,打印“DMA transfer success!!!”
    }
    else {
      printf("DMA transfer failed!!!\r\n");								 //若不相等,打印"DMA transfer failed!!!”
    }
  }

  if(DMAC_GetITStatus(DMAC1, DMAC_Channel_0, DMAC_IT_ERR) != RESET)		 //判断DMAC_IT_ERR中断标志位
  {
    DMAC_ClearITPendingBit(DMAC1, DMAC_Channel_0, DMAC_IT_ERR);          //若产生DMAC_IT_ERR中断标志位,则清除DMAC_IT_ERR中断标志位
    printf("DMA transfer error!!!\r\n");								 //打印“DMA transfer error!!!”
  }
  
}

此中断服务函数的主要功能为判断DMA是否完成数据传输,以及完成的情况。

注意:
1)请注意这部分代码中出现的中断标识符的名称,“DMAC_IT_BLOCK”,“DMAC_IT_TFR”,“DMAC_IT_ERR”。

我们要学会通过名称(或工程中相关名称的定义)来猜测该中断标识符的作用。

例:
“DMAC_IT_TFR”,当DMA传输(Transfer)完成后,该位由硬件自动置1。我们就可以通过“DMAC_GetITStatus”函数检测该中断标识符的值来判断DMA是否完成了传输。

“DMAC_IT_ERR”,当DMA传输出错后,该位由硬件自动置1。我们就可以通过“DMAC_GetITStatus”函数检测该中断标识符的值来判断DMA的传输是否出错。

2)中断标识位置1后,必须由用户自己清除,这一点在代码中有体现。

13.1.4 串口配置代码分析

在主函数中我们使用到了uart1_init(72000000, 115200);这个函数,鼠标右击进入定义,可以在该文件中看到函数原型:
在这里插入图片描述
此处与我们第八章中所讲串口配置有所不同,这里采用了寄存器操作方式来编写此部分代码,初学者可以忽略此部分,只需要知道void uart1_init(uint32_t apbclk, uint32_t baud)这个函数是用来初始化UART的时钟频率和波特率即可。

13.2 实验现象

将代码编译完成烧录到WB32中,使用串口将开发板与电脑连接好(PA9连串口的RXD、PA10连串口的TXD),打开串口调试软件,复位开发板并使用调试软件发送任意字符给开发板(此例中我发送“1”):
在这里插入图片描述
DMA存储器到存储器数据传输成功。

若将代码中DMAC_Channel_InitTypeDef结构体中的结构体成员DMAC_DestinationAddrInc设置为DMAC_DestinationAddrInc_NoChange,编译烧录后,复位开发板并使用调试软件发送任意字符给开发板(此例中我发送“1”),实验结果如下:

请添加图片描述
DMA存储器到存储器数据传输失败。

注意:
1)请结合数据在计算机中的存储方法,想一想为何将代码中DMAC_Channel_InitTypeDef结构体中的结构体成员DMAC_DestinationAddrInc设置为DMAC_DestinationAddrInc_NoChange后DMA数据传输失败?

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值