STM32之DMA(直接存储器存储)

发布者:温柔的心情最新更新时间:2018-07-17 来源: eefocus关键字:STM32  DMA  直接存储器存储 手机看文章 扫描二维码
随时随地手机看文章

DMA简介: 
在硬件系统中,主要由CPU(内核),外设,内存(SRAM),总线等结构组成,数据就经常要在内存与外设之间传输转移,或者是从外设A转移到外设B. 
DMA(Direct Memory Access)直接存储器存储,是一种可以大大减轻CPU工作量的数据存储方式. 
数据转移的一般方式: 
例如当CPU需要处理由ADC外设采集回来的数据时,CPU首先要把数据从ADC外设的寄存器读取到内存中(变量),然后进行运算处理. 
(但是,因为在转移数据的过程中会占用CPU十分宝贵的资源,所以希望CPU更多地被用在数据运算或响应中断之中,而数据转移的工作交由其它部件完成。) 
DMA的方式: 
DMA可以为CPU分担了数据转移的工作。因为DMA的存在,CPU被解放出来,它可以在DMA转移数据的过程中同时进行数据运算,响应中断,大大提高效率.

DMA的工作: 
在STM32中文手册可以找到STM32的系统结构图,可以很清晰的看到STM32内核,存储器,外设以及DMA的连接 
这里写图片描述 
所有这些硬件结构最终都通过各种各样的线连接到总线矩阵之中,硬件结构之间的数据转移都经过总线矩阵的协调,使各个外设都能够和谐地使用总线来传输数据.

例如: 
在不使用DMA的情况下,内核通过DCode经过总线矩阵协调,使用AHB把外设ADC采集的数据读取到内核,然后内核DCode再通过总线矩阵协调,把数据存放到内存SRAM中。 
而在使用DMA之后,由DMA控制器的DMA总线与总线矩阵协调,使用AHB把外设ADC的数据经由DMA通道存放到内存SRAM。在这个数据传输的过程中,不需要内核的全程参与,所以内核可以同时进行数据运算,而且DMA的方式是点到点的数据转移,而不使用DMA的方式还要以内核来作为中转站,显然是DMA的传输方式的效率更高。(所以”直接“是很强的东西!!)

DMA的控制参数: 
要使用DMA,需要确定一系列的控制参数: 
如外设数据的地址,内存地址,传输方向等,在开启DMA传输前还要先发出DMA请求。

DMA实例main函数: 
main函数功能: 
实际上是利用DMA把数据(数组)从内存转移到外设(串口)。外设工作的时候,除了转移数据,实质是不需要内核干预的,而数据转移的工作现在交给了DMA,所以在串口发送数据的时候,内核同时还可以进行其它操作,比如点亮LED灯(类似一个线程动作)。

头文件忽略……….. 
extern uint8_t SendBuff[SENDBUFF_SIZE]; 
uint16_t i;

int main(void) 

/USART1_Config 115200 8-N-1/ 
USART1_Config(); 
DMA_Config(); 
LED_GPIO_Config();

r(i=0;i

{

SendBuff[i] = 0xff;

}

/*串口向DMA发出请求 */

USART_DMACmd(USART1, USART_DMAReq_Tx, ENABLE);//在DMA传送未完成时,CPU会继续执行main函数中的代码


LED1(ON);//先点亮LED,而同时DMA在向串口运送数据,当DMA发送完成时,在中断函数关闭LED

while(1);

}


main函数里面配置好了串口1,DMA,以及LED外设,使能DMA的发送请求.其中串口配置以及LEDGPIO的配置这里就不讲解了,前面的博客都有说,配置的内容都一样.


DMA的配置: 

void DMA_Config(void) 

DMA_InitTypeDef DMA_InitStructure; 

RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);//开启 DMA 时钟

NVIC_Config(); //配置 DMA 中断


/*设置 DMA 源:内存地址&串口数据寄存器地址*/

DMA_InitStructure.DMA_PeripheralBaseAddr = USART1_DR_Base;

/*内存地址(要传输的变量的指针)*/

DMA_InitStructure.DMA_MemoryBaseAddr = (u32)SendBuff;

/*方向:从内存到外设*/

DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;

/*传输大小 DMA_BufferSize=SENDBUFF_SIZE*/

DMA_InitStructure.DMA_BufferSize = SENDBUFF_SIZE;

/*外设地址不增*/

DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;

/*内存地址自增*/

DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;

/*外设数据单位*/

DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;

/*内存数据单位 8bit*/

DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;

/*DMA 模式:一次传输,循环*/

DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;

/*优先级:中*/

DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;

/*禁止内存到内存的传输 */

DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;

/*配置 DMA1 的 4 通道*/

DMA_Init(DMA1_Channel4, &DMA_InitStructure);

DMA_Cmd (DMA1_Channel4,ENABLE); //使能

DMA_ITConfig(DMA1_Channel4,DMA_IT_TC,ENABLE); //配置 DMA 发送完之后产生中断

}

DMA_InitStructure.DMA_PeripheralBaseAddr = USART1_DR_Base; 
保存外设数据寄存器的基地址,这个地址作为传输的源或目标(DMA具有地址自增的功能,地址自增可以方便地读取连续的单元) 
这里面用的USART1_DR_Base宏实际是#define USART1_DR_Base 0x40013804,从《 STM32 参考手册》可知,串口外设会自动地把数据寄存器中的数据,送入它的移位寄存器,然后由硬件按照串口协议把该数据发送出去。 
在这个实例中,把数据寄存器的地址作为外设的地址,那么由DMA通道转移过来的内存数据就会被保护到这个寄存器中,然后串口就会自动进行发送了.

ps:外设数据寄存器的地址可以在《STM32参考手册》中找到一个存储器映射表(部分)(图截的不是很好看!!!) 
这里写图片描述 
这里写图片描述 
可以看到,USART1(串口1)的外设基地址为0x40013800,同时我们在查找《STM32数据手册》手册可以找到USART的数据寄存器,了解到偏移量是0x04,那么USART1的外设基地址加上数据寄存器的地址偏移就是在DMA传输中需要的目标地址了。 
0x4001 3804 = 0x4001 3800 + 0x04;

DMA_InitStructure.DMA_MemoryBaseAddr = (u32)SendBuff; 
保存了内存的基地址,这个地址也可以作为传输源或目标.在使用时通常会给这个成员赋值为某个数组的基地址,然后利用DMA的地址自增功能把数组一个个地填满. 
(在C语言中数组名就是该数组的基地址,而数组(变量)是被保存到内存(SRAM)上的,所以我们实质上给.DMA_MemoryBaseAddr 这个结构体成员赋予了一个内存地址。)

DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; 
DMA数据传输方向,可以选择是外设到内存还是内存到外设。DMA_DIR_PeripheralDST是内存到外设

DMA_InitStructure.DMA_BufferSize = SENDBUFF_SIZE; 
DMA要传输的数据总大小, DMA_BufferSize=SENDBUFF_SIZE=5000,这里要传输5000个数据

DMA_InitStructure.DMA_PeripheralInc=DMA_PeripheralInc_Disable; 
外设地址不增,因为用的是外设地址是固定的.

DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; 
内存地址自增,数组自增把数据一个个都传到数据寄存器.

DMA_InitStructure.DMA_PeripheralDataSize =DMA_PeripheralDataSize_Byte; 
外设传输数据单元大小,可以为字节,半字节,字.

DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; 
内存传输数据单元大小,可以为字节,半字节,字,这里是8bit.

DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; 
DMA的模式,可以为循环模式或者正常模式,在循环模式下传输完一轮数据之后再重新传输,适合ADC不断采集数据等场合,这里是正常模式,也就是一次.

DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; 
配置DMA通道的优先级,总线矩阵根据DMA通道的优先级进行总线协调分配,这里配置为DMA_Priority_Medium(中等优先级). 
(在使用1个DMA通道时,配置任何优先级都没有区别)

DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; 
使能内存到内存的DMA传输。DMA传输可以在外设与内存,外设与外设,还可以在内存与内存之间进行传输。 
这里用的是内存到外设 ,使用DMA_M2M_Disable(禁止内存到内存的传输).

DMA_Init(DMA1_Channel4, &DMA_InitStructure); 
DMA_Cmd (DMA1_Channel4,ENABLE);

填充好结构体后,就使能DMA了,这里用到DMA1_Channel4(DMA1的通道4),这个通道不是随便选择的,是根据DMA的请求映射来选择的,在《STM32参考手册》可以找到。 
(DMA请求是指外设在需要使用DMA前需要向DMA控制器发送请求信息,DMA在接收到请求后才会根据DMA配置进行数据转移。) 
这里写图片描述 
PS: 
从图中可以看到即使同样是外设串口1,串口1的发送数据 DMA 请求和串口1的接收数据DMA请求通道都是不一样的,分别为DMA1通道4和DMA1的通道5。

DMA_ITConfig(DMA1_Channel4,DMA_IT_TC,ENABLE); 
把DMA配置成DMA_IT_TC(DMA发送完成标志)中断.DMA_ITConfig是用于外设中断的函数.

DMA的中断: 
中断配置: 
在DMA_Config()中调用了NVIC_Config()进行DMA中断配置 
static void NVIC_Config(void) 

NVIC_InitTypeDef NVIC_InitStructure;

NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1); //配置DMA通道的优先级组1


NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel4_IRQn;//中断通道

NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;

NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;

NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;

NVIC_Init(&NVIC_InitStructure);


这里的是调用了NVIC_PriorityGroupConfig()DMA的中断优先级为组1,中断通道配置为DMA1_Channel4_IRQn.


中断服务函数: 

void DMA1_Channel4_IRQHandler(void) 

{


if(DMA_GetFlagStatus(DMA1_FLAG_TC4)==SET)//判断是否为 DMA 发送完成中断

{


LED1(OFF);//LED 关闭

DMA_ClearFlag(DMA1_FLAG_TC4); //清除DMA中断标志位

}


这个中断服务函数名在启动文件startup_stm32f10x_hd.s中找到(以前说过,这里再说一次),这里就是检查中断标志位,关闭LED灯,清除中断标志位


在main函数里USART_DMACmd(USART1, USART_DMAReq_Tx, ENABLE); 

使能或者关闭串口的DMA接口,这里配置的是串口1的USART_DMAReq_Tx(串口发送请求),也可以是USART_DMAReq_Rx(串口接收请求). 

调用这个库函数允许串口外设向 DMA 发出请求,请求DMA传输数据。调用了这个函数之后,DMA开始响应串口的请求,根据DMA配置,把数组中的数据一个个地转移到串口数据寄存器,并由串口向外发送这些数据。在调用了USART_DMACmd()函数之后,接下来在main函数就把LED点亮了.


PS: 

实际上,在DMA还没传输完成数据的时候,因为内核并不参与DMA数据传输的全过程,所以内核在这个时候执行了点亮LED1的代码。而当DMA传输完成时,进入了中断,再把LED1关闭.


关键字:STM32  DMA  直接存储器存储 引用地址:STM32之DMA(直接存储器存储)

上一篇:STM32之SPI_FLASH
下一篇:STM32之USART(串口通信)

推荐阅读最新更新时间:2024-03-16 16:09

STM32用SysTick来做定时器
1 硬件电路配置 这里还是借用前面LED电路我就不贴图片。 2 时钟说明 SysTick和HCK的时钟频率是一样的库函数代码如下 /** * @brief Initialize and start the SysTick counter and its interrupt. * * @param ticks number of ticks between two interrupts * @return 1 = failed, 0 = successful * * Initialise the system tick timer and its interrupt and start the
[单片机]
STM32 双向GPIO设置
STM32 GPIO 输出漏开,外部上拉,既可以输出,也可以读取输入电平 这个是STM32 GPIO内部结构图, 可以看到GPIO输出部分在设置漏开输出的时候,如果我们设置输出高电平,那么将输出一个高阻状态,如果外部接上拉电阻,就可以得到高电平,此时外部如果给一个低电平那么,这时候我们读输出寄存器也会读到低电平。 注意不要去读输出寄存器。
[单片机]
<font color='red'>STM32</font> 双向GPIO设置
什么是中断 stm32中断服务函数
中断概念: 中断是指在计算机执行期间,系统内发生任何非寻常的或非预期的急需处理事件,使得CPU暂时中断当前正在执行的程序而转去执行相应的时间处理程序。待处理完毕后又返回原来被中断处继续执行或调度新的进程执行的过程。 中断作用: 计算机与外设之间的联系一般通过两种方法:一是通过CPU控制来进行数据的传送;二是在专门的芯片控制下进行数据的传送。我们所说的DMA,就是不用CPU控制,外设同内存之间相互传送数据的通道,在这种方式下,外设利用DMA通道直接将数据写入存储器或将数据从存储器中读出,而不用CPU参与,系统的速度会大大增加。 你打开stm32的启动文件,例如startup_stm32f10x_hd.s 里面有很多中断跳转的
[单片机]
关于STM32的基本知识
STM32简介 STM32是“意法半导体”生产的基于“ARM公司Cortex-M3内核”的32位高性能MCU。 ST——芯片制造商意法半导体,SOC厂商 ARM——IP厂商,负责芯片内核设计的公司 M——Microelectronics的缩写,指微控制器 32——指它是一个32位的微控制器 注意:51单片机是5V工作电压,而STM32是3.3V工作电压。STM32芯片结构,如下图所示。 STM32和ARM7的关系 ARM7和STM32的内核都是由ARM公司设计的。ARM7内核采用的是冯诺依曼结构(也就是计算机CPU采用的结构)而STM32采用的是哈佛结构。STM32是ARM公司设计出来取代ARM7的,所以它的性能优于ARM7。
[单片机]
关于<font color='red'>STM32</font>的基本知识
STM32 关于外部中断线、中断源和中断服务函数的问题
中断线问题: 上图可以看出,PA0、PB0...PG0共用的EXTI0中断线,PA1、PB1...PG1共用的EXTI1中断线,也就是 编程里面的(如下图库文件),对应16个中断线,后面三个是专用的中断线,不做讨论。 有人要问,假设:我设置的引脚是PB9和PE9都开启外部中断怎么办?小编告诉你,这还真办不了,我实测过。配置函数的时候后覆盖之前的函数的(如下图所示) 如果这样配置的话,会出现出货光电1的初始化覆盖掉编码盘的初始化,说白了就是编码盘的设置被覆盖掉了,结果是外部中断 出货光电1管脚会触发而编码盘就不起作用了,这也是STM32其中一个问题所在吧! 反过来,结果是外部中断 编码盘 管脚会触发 而 出货光电1管脚
[单片机]
<font color='red'>STM32</font> 关于外部中断线、中断源和中断服务函数的问题
在MDK上建立一个C++的STM32开发工程
最近,我惊讶地发现居然可以用C++进行嵌入式程序开发,甚至是裸系统程序。之前,做单片机开发、STM32上程序开发,想到的都是C语言、汇编语言,根本就没有考虑过还可以用C++语言做开发。近日,在学习公司的项目之后,发现C++有其独道之处。开发起来比C语言更方便架构搭建与程序管理。 我之前对C++有曲解,总认为C++只适合做上层应用软件开发,不适用于单片机开发。一方面是因为C++喜欢来不不就new一个对象。对于内存空间非常紧张的单片机,只能望海兴叹。二是C++生成的代码量较C庞大,效率上可能没有C高。对于主频较低的单片机,也用一点勉强。但是,这并不能掩盖C++的优势。C++是面向对象的程序语言,能非常灵活地进行继承与派生
[单片机]
STM32笔记(四)---配置系统时钟实验
配置系统时钟实验 1 使用 HSE 一般情况下,我们都是使用 HSE,然后 HSE 经过 PLL 倍频之后作为系统时钟。 通常的配置是: HSE=8M, PLL 的倍频因子为: 9,系统时钟就设置成:SYSCLK = 8M * 9 = 72M。使用 HSE,系统时钟 SYSCLK 最高是128M(16倍频)。 当程序来到 main 函数之前,启动文件: statup_stm32f10x_hd.s 已经调用 SystemInit()函数把系统时钟初始化成 72MHZ, SystemInit()在库文件: system_stm32f10x.c 中定义。如果我们想把系统时钟设置低一点或者超频的话,可以修改底层的库文件,但是为了维
[单片机]
电源管理(STM32
一、低功耗模式 STM32F1xx 待机模式 电源控制寄存器——PWR_CR 位 1 PDDS:掉电深睡眠 与LPDS位协同操作 0:当CPU进入深睡眠时进入停机模式,调压器的状态由LPDS位控制。 1:CPU进入深睡眠时进入待机模式。 电源控制/状态寄存——PWR_CSR 位 0 WUF:唤醒标志 该位由硬件设置,并只能由POR/PDR(上电/掉电复位)或设置电源控制寄存器(PWR_CR)的 CWUF位清除。 0:没有发生唤醒事件 1:在WKUP引脚上发生唤醒事件或出现RTC闹钟事件。 注:当WKUP引脚已经是高电平时,在(通过设置EWUP位)使能WKUP引脚时,会检测到一个额外的 事件。 位 8
[单片机]
电源管理(<font color='red'>STM32</font>)
小广播
添点儿料...
无论热点新闻、行业分析、技术干货……
设计资源 培训 开发板 精华推荐

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

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

换一换 更多 相关热搜器件
随便看看
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved