STM32-自学笔记(19.DMA——存储器直接存取,让数据传输更上一层楼)

发布者:ArtisticSoul最新更新时间:2022-04-18 来源: eefocus关键字:STM32  DMA  存储器  直接存取 手机看文章 扫描二维码
随时随地手机看文章

概述:DMA(存储器直接存取)


一个完整的微控制器(处理器)通常由CPU、存储器和外设等组件构成。这些组件一般在结构和功能上都是独立的,即一个组件能持续正常工作并不一定建立在另一个组件正常工作的前提上,而各个组件之间的协调与交互就由CPU来完成。如此一来,CPU作为整个芯片的“大脑”,其职能范围可谓广阔吗,如CPU先从A外设拿到一个数据送给B外设使用,同时C外设又需要D外设提供一个数据……这样的数据搬运工作使得CPU的负荷显得相当繁重。


严格来说,搬运数据只是CPU众多职能中比较不重要的一种。CPU最重要的工作是进行数据的运算,从加减乘除四种基本运算到一些高级运算,包括浮点、积分、微分、FFT等运算。而在一些嵌入式的实时应用场合中,CPU还负责对复杂的中断申请进行响应,以保证主控芯片的实时性能。


理论上,常见的控制器外设,比如USART、I2C、SPI甚至是USB等通信接口,单纯的利用CPU进行协议模拟也是可以实现的,比如51单片机平台经常使用模拟I/O来实现I2C协议通信。但这样即浪费了CPU的资源,同时实现后的性能表现往往和使用专用的硬件模块实现的效果相差甚远。从这个角度来看,各个外设控制器的存在,无疑是降低了CPU的负担,解放了CPU的资源,使其有更多的自由去做数据运算工作。实践表明,“搬运数据"这一工作占用了相当大一部分的CPU资源,成为降低CPU工作效率的主要原因之一。于是需要有一种硬件结构来分担CPU的这一职能,这种硬件结构就是——DMA。


从数据搬运的效果来看,使用DMA也要比使用CPU来执行显得快速而高效得多。先从CPU搬运数据的过程上来分析,如果要把某个存储地址A的数值赋给另外一个地址上B的变量,CPU是这样处理的:首先读出A地址上的数据存储在某个中间变量里(该变量可能位于CPU寄存器里,也有可能位于内存中),然后再转附送到B地址的变量上。在这个过程里,CPU通过一个中间变量扮演了一种“中介”的角色。而若使用DMA传输,则不再需要通过中间变量,而将A地址的数据直接传送到B地址的变量里。在这个过程里,CPU只需要告诉DMA什么时候开始传送,DMA在完成传送之后回馈一个信号通知CPU,而期间的数据搬运过程完全不需要CPU进行干预。这样无疑是一个双赢的局面:既减轻了CPU的负担,又提高了数据搬运的效率,这就是DMA存在的意义。


STM32配备了相当完善的DMA资源:两个DMA控制器共有12个通道(DMA1有7个通道,DMA2有5个通道),每个通道专门用来管理来自于一个或多个外设对存储器访问的DMA请求,还有一个仲裁器来协调各个DMA请求的优先权。DMA单元的特性如下:


拥有12个独立的可配置的通道:DMA1有7个通道,DMA2有5个通道。

每个通道都对应连接专门的硬件DMA请求,每个通道都同样支持软件触发。这些功能可通过软件来配置。

在同一个DMA模块上,多个DMA请求间的优先级关系可以通过软件编程设置(共有4级:最高、高、中、低),优先级相等时由DMA通道号决定(请求0优先于请求1,以此类推)。

独立数据源和目标数据区的传输宽度可为字节、半字和字。源和目标地址必须按数据传输宽度对齐。

支持循环缓冲器管理。

每个DMA通道都有3个事件标志(DMA半传输、DMA传输完成和DMA传输出错中断),这三个事件标志通过逻辑“或”关系合并为一个单独的中断请求

可实现存储器和存储器间的传输。

可实现外设和存储器、存储器和外设之间的传输。

闪存、SRAM、外设的SRAM、APB1、APB2和AHB外设均可作为访问的源和目标。

数据传输数量最多可达65535。


实验设计


目的是为了实现STM32微控制器的DMA数据搬运功能,并在搬运成功的基础上,考察其相对于使用CPU进行数据搬运的做法在效率上的提升。

硬件电路


因为DMA和Flash都属于STM32的内部设备,所以本节程序只需要一个USART电平转换电路供给显示实验结果即可。


程序设计(软件设计)


注意要点:


配置RCC寄存器组,打开DMA时钟

配置NVIC,给予DMA传输完成中断0级先占优先级

配置SysTick定时器,产生1us时间间隔的中断请求用以计时

配置DMA寄存器组各参数,这是本次实验成功与否的关键

主函数 main.c


#include"stm32f10x_lib.h"

 

#include"stdio.h"

 

#include"string.h"

 

#define BufferSize   32

 

vu16 CurrDataCounter=0;                 //定义DMA传输数目变量

 

vu32 Tick=0;                            //计时变量

 

uc32 SRC_Const_Buffer[BufferSize]=    //定义外设数据,注意此处数据定义在FLASH中

 

{

 

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;

 

}

 

u32 DST_Buffer[BufferSize];        //在RAM中开辟一片空间用作DMA目的空间

 

void RCC_Configuration(void);      

 

void NVIC_Configuration(void);

 

void GPIO_Configuration(void);

 

void USART_Configuration(void);

 

void DMA_Configuration(void);

 

void SysTick_Configuration(void);

 

int main(void)

 

{

 

u8 i=0;

 

u8 TickCntCPU=0;

 

u8 TickCntDMA=0;

 

RCC_Configuration();                 //设置系统时钟

 

NVIC_Configuration();                //设置NVIC

 

GPIO_Configuration();                //设置GPIO端口

 

USART_Configuration();               //设置USART

 

DMA_Configuration();                 //DMA初始化

 

SysTick_Configuration();             //SysTick初始化

 

//开始使用CPU搬运数据并计时

 

//计时变量清零

 

Tick=0;

 

for(i=0;i 

{

 

DST_Buffer[i]=SRC_Const_Buffer[i];

 

}

 

TickCntCPU=Tick;          //保存计时数据

 

//  CPU搬运数据完成

 

//缓冲清空

 

for(i=0;i 

{

 

DST_Buffer[i]=0;

 

}

 

//开始使用DMA搬运数据并计时

 

Tick=0;              //计时变量清零

 

DMA_Cmd(DMA1_Channel6,ENABLE);    //开启DMA6通道传输

 

while(CurrDataCounter!=0);        //等待传输完成

 

TickCntDMA=Tick;                  //保存计时数据

 

//DMA搬运数据完成

 

//实验结果

 

if(strncmp((const char*)SRC_Const_Buffer,(const char*)DST_Buffer,BufferSize)==0)

 

{

 

printf("rn Transmit Success!rn");

 

}

 

else

 

{

 

printf("rn Transmit Fault!rn");

 

}

 

printf("rn The CPU transfer,time consume:%dus!nr",TickCntCPU);

 

printf("rn The CPU transfer,time consume:%dus!nr",TickCntDMA);

 

while(1);

 

}

 

 

 

 

 

 

设置系统各部分时钟      RCC_Configuration


void RCC_Configuration(void)

 

{

 

ErrorStatus HSEStartUpStatus;      //定义枚举类型变量 HSEStartUpStatus

 

RCC_DeInit();                     //复位系统时钟设置

 

RCC_HSEConfig(RCC_HSE_ON);         //开启HSE

 

HSEStatrtUpStatus=RCC_WaitForHSEStartUp();   //等待HSE起振并稳定

 

if(HSEStatrtUpStatus==SUCCESS)     //判断HSE是否起振成功,是则进入if()内部

 

{

 

RCC_HCLKConfig(RCC_SYSCLK_Div1);   //选择HCLK(AHB)时钟源为SYSCLK分频

 

RCC_PCLK2Config(RCC_HCLK_Div1);    //选择PCLK2时钟源为HCLK(AHB)1分频

 

RCC_PCLK1Config(RCC_HCLK_Div2);    //选择PCLK1时钟源为HCLK(AHB)2分频

 

FLASH_SetLatency(FLASH_Latency_2);  //设置Flash延时周期数为2

 

FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable);   //使能Flash预取缓存

 

//选择PLL时钟源为 HSE 1 分频,倍频数为9,则PLL=8MHz *9=72MHz

 

RCC_PLLConfig(RCC_PLLSource_HSE_Div1,RCC_PLLMul_9);

 

RCC_PLLCmd(ENABLE);                  //使能PLL

 

while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY)==RESET);  //等待PLL输出稳定

 

RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);    //选择SYSCLK时钟源为PLL

 

while(RCC_GetSYSCLKSource()!=0x08);      //等待PLL成为SYSCLK时钟源

 

}

 

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_USART1,ENABLE);    //打开APB2总线上的GPIOA和USART1时钟

 

RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);

 

}

设置各GPIO端口功能      GPIO_Configuration


void GPIO_Configuration(void)

 

{

 

GPIO_InitTypeDef GPIO_InitStructure;

 

//设置USART1的Tx脚(PA.9)为第2功能推挽输出功能

 

GPIO_InitStructure.GPIO_Pin=GPIO_Pin_9;

 

GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;

 

GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;

 

GPIO_Init(GPIOA,&GPIO_InitStructure);

 

//设置USART1的Rx脚(PA.10)为浮空输入脚

 

GPIO_InitStructure.GPIO_Pin=GPIO_Pin_10;

 

GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IN_FLOATING;

 

GPIO_Init(GPIOA,&GPIO_InitStructure);

 

}

设置NVIC参数     NVIC_Configuration


void NVIC_Configuration(void)

 

{

 

//定义NVIC初始化结构体NVIC_InitStructure

 

NVIC_InitTypeDef NVIC_InitStructure;

 

// #ifdef...#else...#endif结构的作用是根据预编译条件决定中断向量表起始地址

 

#ifdef  

 

//中断向量表起始地址从0x20000000开始

 

NVIC_SetVectorTable(NVIC_VectTab_RAM,0x0);

 

#else

 

//中断向量表起始地址从0x80000000开始

 

NVIC_SetVectorTable(NVIC_VectTab_FLASH,0x0);

 

endif

 

//选择优先级分组0

 

NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0);

 

//开启DMA6通道中断控制,0级先占优先级,0级次占优先级

 

NVIC_InitStructure.NVIC_IRQChannel=DMA1_Channel6_IRQChannel;

 

NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0;

 

NVIC_InitStructure.NVIC_IRQChannelSubPriority=0;

 

NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;

 

NVIC_Init(&NVIC_InitStructure);

 

}

设置DMA参数      DMA_Configuration


void DMA_Configuration(void)

 

{

 

//定义DMA初始化结构体DMA_InitStructure

 

DMA_InitTypeDef DMA_InitStructure;

 

//将DMA6通道的寄存器重设为默认值

 

DMA_DeInit(DMA1_Channel6);

 

// 外设地址:(u32)SRC_Const_Buffer;

   内存地址:(u32)DST_Buffer;

   外设作为数据传输的来源;

   DMA缓存大小:BufferSize;

   外设地址寄存器递增;

   内存地址寄存器递增;

   外设数据宽度为32位;

   内存数据宽度为32位;

   CAN工作在正常缓存模式;

   设置DMA通道优先级为高;

   DMA通道设置为内存到内存传输;

   

DMA_InitStructure.DMA_PeripheralBaseAddr=(u32)SRC_Const_Buffer;

 

DMA_InitStructure.DMA_MemoryBaseAddr=(u32)DST_Buffer;

 

DMA_InitStructure.DMA_DIR=DMA_DIR_PeripheralSRC;

 

DMA_InitStructure.DMA_BufferSize=BufferSize;

 

DMA_InitStructure.DMA_PeripheralInc=DMA_PeripheralInc_Enable;

 

DMA_InitStructure.DMA_MemoryInc=DMA_MemoryInc_Enable;

 

DMA_InitStructure.DMA_PeripheralDataSize=DMA_PeripheralDataSize_word;

 

DMA_InitStructure.DMA_MemoryDataSize=DMA_MemoryDataSize_word;

 

DMA_InitStructure.DMA_Mode=DMA_Mode_Normal;

 

DMA_InitStructure.DMA_Priority=DMA_Priority_High;

 

DMA_InitStructure.DMA_M2M=DMA_M2M_Enable;

 

DMA_Init(DMA1_Channel6,&DMA_InitStructure);

 

DMA_ITConfig(DMA1_Channl6,DMA_IT_TC,ENABLE);     //开启DMA传输完成中断

 

CurrDataCounter=DMA_GetCurrDataCounter(DMA_Channl6);    //读出当前数据量计数值

 

}

设置Systick定时器,重装载时间为250ms     Systick_Configuration


void Systick_Configuration(void)

 

{

 

SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8);     //选择HCLK/8为SysTick时钟源

 

SysTick_SetReload(9);                     //主频为72/8MHz,配置计数值为9可以得到1us定时间隔

 

SysTick_CounterCmd(SysTick_Counter_Enable);  //启动SysTick计数

 

SysTick_ITConfig(ENABLE);                  //使能SysTick中断

 

}

设置USART1            USART_Configuration


void USART_Configuration(void)

 

{

 

USART_InitTypeDef USART_InitStructure;       //定义USART初始化结构体USART_InitStructure

 

USART_ClockInitTypeDef USART_ClockInitStructure;  //定义USART初始化结构体USART_ClockInitStructure

 

//波特率为9600bps;8位数据长度,1个停止位,无检验位;禁用硬件流控制;禁止USART时钟;时钟极性低;在第2个边沿捕获数据;最后一位数据的时钟脉冲不从SCLK输出

 

USART_InitStructure.USART_BaudRate=9600;

 

USART_InitStructure.USART_WordLength=USART_WordLength_8b;

 

USART_InitStructure.USART_StopBits=USART_StopBits_1;

[1] [2]
关键字:STM32  DMA  存储器  直接存取 引用地址:STM32-自学笔记(19.DMA——存储器直接存取,让数据传输更上一层楼)

上一篇:STM32-自学笔记(15.窗口看门狗)
下一篇:STM32-自学笔记(16.窗口看门狗,程序用到的库函数介绍)

推荐阅读最新更新时间:2024-11-09 19:09

存储器大厂美光在台投资千亿
  全球DRAM大厂 美光 预计在台投资新台币1,300亿元,绝大部分DRAM基地都在台湾地区,成为生产重镇,未来也将持续在台扩充产能。 美光 锁定中科进行新的3D封测制程,预估2-3年可量产,要打造成为亚太区营运中心。下面就随网络通信小编一起来了解一下相关内容吧。   台湾也就 美光 扩大投资所需土地、厂房、资金等提供协助,未来规划中科扩厂计划,将可增加2,000个以上就业机会,将台湾塑造为高效完整的DRAM全球生产基地。   据了解,美光于2012年并购尔必达与瑞晶之后,刚好瑞晶于中科已有2厂,后面还有2个厂还没盖,解决土地、厂房、水电等问题后,美光未来将与台积电、友达为邻,预计落脚中科的后里园区,未来该地也将形成台湾地区最
[网络通信]
STM32 Nucleo Shield显示板卡,简化超低功耗设备GUI设计
意法半导体新推出的STM32 * Nucleo Shield显示板卡开创物联网产品人机界面之先河。新SPI Shield显示板卡X-NUCLEO- GFX01M1利用STM32G0微控制器(MCU)的经济性,支持引入低成本非内存映射SPI闪存IC支持等新功能的最新版TouchGFX软件(4.15.0版)。 如果采用STM32G0和TouchGFX开发项目,开发人员可用仅5美元的物料清单成本,给任何项目添加一个小图形界面屏幕,这样,定时器、控制器、家用电器等简单设备也能为用户带来类似智能手机的使用体验。 新的X-Nucleo-GFX01M1 Shield显示板卡支持新的X-cube-display软件包,该软件包提供了
[单片机]
<font color='red'>STM32</font> Nucleo Shield显示板卡,简化超低功耗设备GUI设计
STM32数模转换器(DAC)简析
STM32F4xx系列提供的DAC模块是12 位电压输出数模转换器。DAC可以按 8 位或 12 位模式进行配置,并且可与DMA控制器配合使用。在 12 位模式下,数据可以采用左对齐或右对齐。DAC有两个输出通道,每个通道各有一个转换器。在DAC双通道模式下,每个通道可以单独进行转换;当两个通道组合在一起同步执行更新操作时,也可以同时进行转换。可通过一个输入参考电压引脚VREF+(与ADC共享)来提高分辨率。 DAC通道框图 DAC引脚 DAC通道使能 将 DAC_CR 寄存器中的相应 ENx 位置 1,即可接通对应 DAC 通道。经过一段启动时间tWAKEUP 后,DAC 通道被真正使能。 注意:ENx 位只会使能模
[单片机]
<font color='red'>STM32</font>数模转换器(DAC)简析
STM32的中断操作(一)——EXTI
前言 在单片机的编程中,中断都是很重要的一个概念。在stm32中,中断有两种,一种是外部中断(EXTI),另一种是定时器中断(SysTick),本篇文章从中断的概念入手,再对外部中断简要的做一些介绍。 一、中断是什么? 相信大家在初学中断这个概念时都听过这样一个例子:如果你一个人在家里正在做饭,这时门铃响了,你选择先去开门,然后再回来做饭,这就是一个浅显易懂的中断的例子,做饭为主程序,而去开门就是中断程序,门铃响起就是中断请求。而如果门铃响起同时电话也响起,那么你决定先去处理哪一件事的过程就是中断优先级的判别过程。 中断的定义如下: 中断是指计算机运行过程中,出现某些意外情况需主机干预时,机器能自动停止正在运行的程序并
[单片机]
<font color='red'>STM32</font>的中断操作(一)——EXTI
用STM32CubeIDE软件实现STM32外部中断实例
本文用STM32CubeIDE软件实现STM32外部中断实例。 新建工程 “File”-“New”-“STM32 Project”。 输入芯片型号STM32F103ZE。 选择相应封装,下一步。 填写项目名,选择工程位置,下一步。 配置时钟、调试模式 选择高速外部时钟。 HCLK总线时钟处输入72,回车,配置使用最高时钟频率。 调试模式选择串行,方便下载程序。 配置管脚外设 本实例中用到了LED1与KEY_LEFT,找到相应电路图。 可见LED1对应PC0管脚,低电平点亮。按键K1对应PE2管脚,低电平有效。 PC0配置为GPIO_Output,GPIO输出模式 PE2配置为GPIO_EX
[单片机]
用STM32CubeIDE软件实现<font color='red'>STM32</font>外部中断实例
基于STM32单片机和EM310的无线终端设计
0引言 在监测监控及数据采集系统(SCADA)系统中,采集数据的实时性和准确性对系统有很大的影响,合适的通信网络可以有效提高SCADA系统的效益,本文设计的GPRS无线终端在SCADA系统中的应用解决了上述问题,除满足数据实时性和准确性外,还具有建设成本低.结构简单.传输容量大.可远程控制等优势. 1 终端简介 1.1终端结构 无线终端由EM310GSM模块和STM32F103单片机构成,以STM32F103单片机为核心,STM32F103单片机有丰富的通信接口,用于连接EM310和现场仪表;还有丰富的I/O资源,可以扩展较大容量的RAM,一方面存储指令集,用于解读指令并作出相应的动作;另一方面用于暂时存储监测点采
[单片机]
基于<font color='red'>STM32</font>单片机和EM310的无线终端设计
STM32入门系列-STM32时钟系统,时钟初始化配置函数
在前面推文的介绍中,我们知道STM32系统复位后首先进入SystemInit函数进行时钟的设置,然后进入主函数main。那么我们就来看下SystemInit()函数到底做了哪些操作,首先打开我们前面使用库函数编写的LED程序,在system_stm32f10x.c文件中可以找到SystemInit()函数,SystemInit()代码如下: void SystemInit (void) { /* Reset the RCC clock configuration to the default reset state(for debug purpose) */ /* Set HSION bit */ RCC- CR |= (ui
[单片机]
stm32学习笔记---计数器定时中断(1s)
#include tim_driver.h //tim2³õʼ»¯:1ÃëÖÓ¸üÐÂÊý¾Ý void tim2_init(void) { TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct; //ʹÄÜʱÖÓ RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); TIM_TimeBaseInitStruct.TIM_ClockDivision = TIM_CKD_DIV1; TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up;
[单片机]
<font color='red'>stm32</font>学习笔记---计数器定时中断(1s)
小广播
设计资源 培训 开发板 精华推荐

最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

换一换 更多 相关热搜器件
随便看看

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved