版权归如下公司,禁止非授权转载:

  • 北京西普阳光教育科技股份有限公司(https://www.simpleware.com.cn)
  • 维周机器人科技有限公司(http://www.vejoe.com)


【实验目的】

  • 掌握单片机DMA的工作原理,以及在STM32单片机上实现DMA传输的配置和流程;
  • 了解DMA传输方式相对与传统利用CPU传输方式的优势;
  • 完成以 DMA 的方式给数据赋值并显示在OLED显示屏上的实验。

【实验原理】

DMA (Direct Memory Access),直接存储器存取,是一种可以大大减轻CPU 工作量的数据存取方式,因而被广泛地使用。外设工作的时候,除了转移数据,实质上是不需要内核进行干预的,而如果数据转移的工作现在也交给了另一个外设,那么在该外设进行工作的时候,内核同时还可以进行其它操作。而STM32 的 DMA 模块正是以类似外设的形式,添加到 Cortex 内核之外,进行数据转移工作的。

一、DMA功能简介:

单片机的硬件系统,通常主要由 CPU(内核)、外设、内存(SRAM)、总线等结构组成,在系统工作中,数据经常要在各项设备之间进行转移。通常情况下,在转移数据的过程中会占用 CPU 十分宝贵的资源,所以我们希望 CPU 更多地被用在数据运算或响应中断之中,而数据转移的工作交由其它部件完成。DMA 恰恰为 CPU 分担了这部分数据转移的工作。因为 DMA 的存在, CPU 被解放出来,它可以在 DMA 转移数据的过程中同时进行数据运算,响应中断,大大提高了系统的运行效率。

在 STM32 中,DMA 的传输模式分为3种:

  1. 外设到存储器的传输。
  2. 存储器到存储器的传输。
  3. 存储器到外设的传输。

二、DMA工作过程分析

由 DMA 控制器挂载在AHB(高级高性能总线)上,与内核共享数据总线。DMA控制器在收到一个传输命令后,开始访问源地址寄存器中储存的地址,并加载该地址对应的数据,然后将数据存储到目标地址寄存器中指定的地址上去。在完成一次数据转移后,DMA内记录数据总数的寄存器执行一次减一操作,然后根据预先设定的传输模式决定是否对源/目标地址进行位移操作。例如,若是从外设(GPIO或其他有固定接口的设备)到存储器的传输,则在执行一次操作后,只需目标地址进行位移,源地址不变;而若是从存储器到存储器的传输,则需要同时将源地址和目标地址进行位移。

三、DMA库函数分析

DMA_InitTypeDef结构体参数的配置如下:

typedef struct
{
uint32_t DMA_PeripheralBaseAddr;		// DMA 外设基地址 
uint32_t DMA_MemoryBaseAddr; 		//DMA 内存基地址 
uint32_t DMA_DIR; //规定了外设是作为数据传输的目的地还是来源 	
uint32_t DMA_BufferSize; 				// DMA 缓存的大小
uint32_t DMA_PeripheralInc; 			// 外设地址寄存器递增与否 
uint32_t DMA_MemoryInc;  			// 内存地址寄存器递增与否 		 uint32_t DMA_PeripheralDataSize;		// 外设数据宽度
uint32_t DMA_MemoryDataSize;			// 内存数据宽度
uint32_t DMA_Mode;					// 工作模式
uint32_t DMA_Priority; 					// DMA 软件优先级 
uint32_t DMA_M2M;					//非否内存到内存传输
}DMA_InitTypeDef;
  1. DMA_PeripheralBaseAddr:外设地址,设定 DMA_CPAR 寄存器的值;一般设置为外设的数据寄存器地址,如果是存储器到存储器模式则设置为其中一个存储器地址。
  2. DMA_Memory0BaseAddr:存储器地址,设定 DMA_CMAR 寄存器值;一般设置为我们自定义存储区的首地址。
  3. DMA_DIR:传输方向选择,可选外设到存储器、存储器到外设。它设定DMA_CCR 寄存器的 DIR[1:0]位的值。 这里并没有存储器到存储器的方向选择,当使用存储器到存储器时,只需要把其中一个存储器当作外设使用即可。
  1. DMA_BufferSize:设定待传输数据数目,初始化设定 DMA_CNDTR 寄存器的值。
  2. DMA_PeripheralInc:如果配置为 DMA_PeripheralInc_Enable,使能外设地址自动递增功能,它设定 DMA_CCR 寄存器的 PINC 位的值;一般外设都是只有一个数据寄存器,所以一般不会使能该位。
  3. DMA_MemoryInc:如果配置为 DMA_MemoryInc_Enable,使能存储器地址自动递增功能,它设定 DMA_CCR 寄存器的 MINC 位的值;我们自定义的存储区一般都是存放多个数据的,所以要使能存储器地址自动递增功能。
  4. DMA_PeripheralDataSize: 外设数据宽度,可选字节(8 位)、半字(16位)和字(32位),它设定 DMA_CCR 寄存器的 PSIZE[1:0]位的值。
  5. DMA_MemoryDataSize:存储器数据宽度,可选字节(8 位)、半字(16 位)和字(32位),它设定 DMA_CCR 寄存器的 MSIZE[1:0]位的值。 当外设和存储器之间传数据时,两边的数据宽度应该设置为一致大小。
  6. DMA_Mode: DMA 传输模式选择,可选一次传输或者循环传输,它设定DMA_CCR 寄存器的 CIRC 位的值。 例程我们的 ADC 采集是持续循环进行的,所以使用循环传输模式。
  7. DMA_Priority:软件设置通道的优先级,有 4 个可选优先级分别为非常高、高、中和低,它设定 DMA_CCR 寄存器的 PL[1:0]位的值。 DMA 通道优先级只有在多个 DMA 通道同时使用时才有意义,如果是单个通道,优先级可以随便设置。
  8. DMA_M2M:存储器到存储器模式 ,使用存储器到存储器时用到设定DMA_CCR 的位14 MEN2MEN 即可启动存储器到存储器模式。

四、软件流程图

在这里插入图片描述
图1 实验程序流程示意图

【实验环境】

硬件设备:

双轮自平衡机器人。如图2所示,平衡车所搭载的电路板主控芯片STM32内部有DMA,另外,电路上已经预留接口并安装了OLED显示屏。
ST-Link下载器(包含USB线与下载线)。如图3所示。

操作系统:

Windows7/8/10,32bit/64bit
在这里插入图片描述
图2 双轮自平衡机器人
在这里插入图片描述
图3 ST-Link下载器与下载线

软件环境:

Keil 5,串口助手软件
实验场地:

【实验步骤】

一、 配置工程环境

打开已经建立好的工程模板,在新建立的工程模板中已经添加五个文件夹,分别命名为USER、HARDWARE、SYSTEM、CORE、FWLIB文件夹,如图4所示。其中USER文件夹存放的是主函数,HARDWARE文件夹存放的是本实验对应的硬件设备函数,SYSTEM存放的是本课程所有实验通用的函数,CORE文件夹存放的是启动文件,FWLib文件夹存放的是底层驱动函数。在本实验中,oled模块的设备函数已经在oled.c的文件中给出。
在这里插入图片描述
图4 工程模板对应的文件夹
在HARDWARE文件夹下新建两个文件,分别为dma.c和dma.h。分别存放DMA控制函数与DMA头文件,如图5所示。
在这里插入图片描述
图5 在HARDWARE文件夹下建立dma.c与dma.h文件

二、 完成DMA配置,并开启时钟

2.1 打开程序中的dma.c文件,首先将dma.h文件包含进来。其次对DMA_Config函数进行编写,对DMA的地址通信模式等进行配置。

#include "dma.h"    //包含头文件
void DMA_Config(void)
{
/*定义一个GPIO_ InitTypeDef类型的结构体*/
DMA_InitTypeDef DMA_InitStructure;
/*定义一个GPIO_ InitTypeDef类型的结构体*/
DMA_DeInit(DMA1_Channel1);

//(1)使能DMA时钟
/*开启DMA时钟*/
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);

//(2)初始化DMA通道参数
        /*配置源地址*/
DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)&number_origin;	 
/*配置目标地址*/
DMA_InitStructure.DMA_MemoryBaseAddr = (u32)&number_target;
/*配置DMA的传输方向*/
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
/*配置缓冲区大小*/
DMA_InitStructure.DMA_BufferSize = 1;
/*配置在完成单次转移后是否对源地址进行位移*/
DMA_InitStructure.DMA_PeripheralInc= DMA_PeripheralInc_Disable;
/*配置在完成单次转移后是否对目标地址进行位移*/
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Disable;
/*配置DMA每次访问的源地址长度*/
DMA_InitStructure.DMA_PeripheralDataSize = 															DMA_PeripheralDataSize_HalfWord;
/*配置DMA每次访问的目标地址长度*/
DMA_InitStructure.DMA_MemoryDataSize = 																	DMA_MemoryDataSize_HalfWord;
/*配置DMA的传输模式*/
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
/*配置DMA的优先级*/
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
/*配置DMA是否可以进行内存到内存的转移*/
DMA_InitStructure.DMA_M2M = DMA_M2M_Enable;
/*将以上配置进行部署*/
DMA_Init(DMA1_Channel1, &DMA_InitStructure);

//(3)使能DMA通道,启动传输
/*使能DMA传输*/
DMA_Cmd(DMA1_Channel1, ENABLE);
}

2.2 打开dma.h文件,添加DMA_Config 函数声明及外部变量。

#ifndef __DMA_H
#define __DMA_H
#include "sys.h"  
extern uint16_t number_origin;    //引用已经定义的外部变量
extern uint16_t number_target;    //引用已经定义的外部变量
void DMA_Config(void);        //声明函数
#endif

三、 编写main.c文件

将工程编译需要用到的头文件包含进来。

#include "stm32f10x.h"    //包含系统寄存器定义声明的头文件
#include "sys.h"          //包含系统文件
#include "delay.h"        //包含延时函数头文件
#include "dma.h"          //包含DMA头文件

定义待转移的源地址及目标地址。

uint16_t number_origin=123;        //待转移的源地址
uint16_t number_target=0;          //待转移的目标地址

预定义oled屏幕显示函数。

void oled_show(void);      //oled屏幕显示函数

在主函数中调用延时函数与OLED初始化函数。

delay_init();	  //调用延时函数
OLED_Init();    //调用OLED初始化函数

在主函数中,更新源地址内容并调用显示函数。

int main(void)
{ 
delay_init();         //延时函数初始化
OLED_Init();        //OLED屏幕显示初始化
DMA_Config();        //DMA初始化

while(1)
{	
	DMA_Cmd(DMA1_Channel1, ENABLE);   //调用DMA传输 
	number_origin++;
	delay_ms(500);
	oled_show();
} 
}

在主函数下面新建oled_show函数,并编写显示函数。

void oled_show(void)
{
OLED_ShowString(0,50,"DMA_NUM_O");  //在指定位置显示文字信息
OLED_ShowNumber(85,50,number_origin,3,12); //在指定位置显示数字信息
OLED_ShowString(0,30,"DMA_NUM_T");
OLED_ShowNumber(85,30,number_target,3,12);
/*=============刷新=======================*/
OLED_Refresh_Gram();	
}
}

四、 编译并下载,观察实验现象

本实验采用仿真器为STLink V2,将仿真器与小车相连,注意正负极不要接反,如图6所示。
在这里插入图片描述
图6 仿真器与下载线连接图
编译程序:点击如图7所示的编译按键。
在这里插入图片描述
图7 Keil编译环境下的编译按键
当编译完成后,如果没有问题,Build Output栏会出现无错误、无警告的提示,如图8所示。
在这里插入图片描述
图8 编译通过后Build Output栏提示信息
下载程序:点击如图所示的下载按键,程序就会下载到STM32的芯片中。下载按键如图9所示。
在这里插入图片描述
图9 Keil编译环境下的下载按键
观察实验现象,如图12所示。重新上电以后,OLED显示屏上显示的DMA_NUM_O后数字从初始设定的123开始,每隔0.5秒加一。DMA_NUM_T紧跟着DMA_NUM_O变化。
在这里插入图片描述
图10 平衡车上的小灯LED2

【思考题】

1、选择题

题目1: 下面哪条语句是设置串口数据寄存地址?(A)
A:DMA_InitStructure.DMA_PeripheralBaseAddr = 0x40004C04;
B:DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;
C:DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
D:DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;

题目2: 在DMA进行数据转移时,内核可以进行其它操作吗?(A)
A:可以
B:不可以

2、简答题

题目1:DMA的工作意义是什么?
在转移数据的过程中会占用 CPU 十分宝贵的资源,所以我们希望 CPU 更多地被用在数据运算或响应中断之中,而数据转移的工作交由其它部件完成。DMA 正为 CPU 分担了数据转移的工作。因为 DMA 的存在, CPU 被解放出来,它可以在 DMA 转移数据的过程中同时进行数据运算,响应中断,大大提高效率。

附录:DMA 库函数

在这里插入图片描述


Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐