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数据传输失败?