STM32—— DMA介绍

发布者:郑大大最新更新时间:2022-02-10 来源: eefocus关键字:STM32  DMA  外设 手机看文章 扫描二维码
随时随地手机看文章

1.1 DMA结构框图

DMA 控制器独立于内核,属于一个单独的外设,结构比较简单,从编程的角度来看,我们只需掌握结构框图中的三部分内容即可。


如图:(大家也可以查看《STM32F10x中文参考手册》-10 DMA控制器(DMA)章节

内容)

(1)标号1:DMA请求

如果外设要想通过 DMA 来传输数据,必须先给 DMA 控制器发送 DMA请求, DMA收到请求信号之后,控制器会给外设一个应答信号,当外设应答后且 DMA 控制器收到应答信号之后,就会启动 DMA 的传输,直到传输完毕。


根据前面介绍我们知道,DMA含有DMA1和DMA2两个控制器,其中DMA1含有7个通道,DMA2含有5个通道,不同的 DMA 控制器的通道对应着不同的外设请求,如图

在这里插入图片描述在这里插入图片描述

(2)标号2:DAM通道

DMA 具有 12 个独立可编程的通道,其中 DMA1 有 7 个通道, DMA2有 5 个通道,每个通道对应不同的外设的 DMA 请求。虽然每个通道可以接收多个外设的请求,但是同一时间只能接收一个,不能同时接收多个。


(3)标号3:仲裁器

当发生多个 DMA 通道请求时,就意味着有先后响应处理的顺序问题,这个就由仲裁器也管理。仲裁器管理 DMA 通道请求分为两个阶段。第一阶段属于软件阶段,可以在DMA_CCRx 寄存器中设置,有 4 个等级:非常高、高、中和低四个优先级。第二阶段属于硬件阶段,如果两个或以上的 DMA 通道请求设置的优先级一样,则他们优先级取决于通道编号,编号越低优先权越高,比如通道 0 高于通道 1。在大容量产品和互联型产品中,DMA1 控制器拥有高于 DMA2 控制器的优先级。


1.2 DMA数据配置

(1)外设到存储器

当我们使用从外设到存储器传输时,以 ADC 采集为例。 DMA 外设寄存器的地址对应的就是 ADC 数据寄存器的地址, DMA 存储器的地址就是我们自定义的变量(用来接收存储 AD 采集的数据)的地址。方向我们设置外设为源地址。


(2)存储器到外设

当我们使用从存储器到外设传输时,以串口向电脑端发送数据为例。DMA 外设寄存器的地址对应的就是串口数据寄存器的地址, DMA 存储器的地址就是我们自定义的变量(相当于一个缓冲区,用来存储通过串口发送到电脑的数据)的地址。方向我们设置外设为目标地址。


STM32F1 DMA配置步骤

接下来我们介绍下如何使用库函数对DMA进行配置。这个也是在编写程序中必须要了解的。具体步骤如下:(DMA相关库函数在stm32f10x_dma.c和stm32f10x_dma.h文件中)


1)使能DMA控制器(DMA1或DMA2)时钟

void RCC_AHBPeriphClockCmd(uint32_t RCC_AHBPeriph,

FunctionalState NewState);

RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);

(2)初始化DMA通道,包括配置通道、外设和内存地址、传输数据量等

void DMA_Init(DMA_Channel_TypeDef* DMAy_Channelx,DMA_InitTypeDef* DMA_InitStruct);

typedef struct

{

uint32_t DMA_PeripheralBaseAddr; // 外设地址

uint32_t DMA_MemoryBaseAddr; // 存储器地址

uint32_t DMA_DIR; // 传输方向

uint32_t DMA_BufferSize; // 传输数目

uint32_t DMA_PeripheralInc; // 外设地址增量模式

uint32_t DMA_MemoryInc; // 存储器地址增量模式

uint32_t DMA_PeripheralDataSize; // 外设数据宽度

uint32_t DMA_MemoryDataSize; // 存储器数据宽度

uint32_t DMA_Mode; // 模式选择

uint32_t DMA_Priority; // 通道优先级

uint32_t DMA_M2M; // 存储器到存储器模式

} DMA_InitTypeDef;


DMA_PeripheralBaseAddr:外设地址,外设地址,通过DMA_CPAR寄存器设置,一般设置为外设的数据寄存器地址,比如要进行串口DMA 传输,那么外设基地址为串口接受发送数据存储器USART1->DR 的地址,表示方法为&USART1->DR。如果是存储器到存储器模式则设置为其中一个存储区地址。


DMA_Memory0BaseAddr:存储器地址,通过DMA_CMAR寄存器设置,一般设置为我们自定义存储区的首地址,即我们存放DMA传输数据的内存地址。比如我们定义一个u32类型数组,将数组首地址(直接使用数组名即可)赋值给DMA_Memory0BaseAddr,在DMA传输的时候就可以把数组内的数据发送或接收。


DMA_DIR:数据传输方向选择,可选择外设到存储器、存储器到外设以及存储器到存储器。通过设定DMA_CCR寄存器的DIR[1:0]位的值决定。


比如本章实验是从内存读取数据发送到串口,所以数据传输方向为存储

器到外设,配置为DMA_DIR_MemoryToPeripheral。

DMA_BufferSize:用来设置一次传输数据的大小,通过DMA_CNDTR寄存器设置。

DMA_PeripheralInc:用来设置外设地址是递增还是不变,通过DMA_CCR寄存器的PINC位设置,如果设置为递增,那么下一次传输的时候地址加1。通常外设只有一个数据寄存器,所以一般不会使能该位,即配置为DMA_PeripheralInc_Disable。


DMA_MemoryInc:用来设置内存地址是否递增,通过DMA_CCR寄存器的MINC位设置。我们自定义的存储区一般都是存放多个数据的,所以需要使能存储器地址自动递增功能,即配置为DMA_MemoryInc_Enable。


DMA_PeripheralDataSize:外设数据宽度选择,可以为字节(8位)、半字(16位)、字(32位),通过DMA_CCR寄存器的PSIZE[1:0]位设置。例如本章实验数据是按照8位字节传输,所以配置为DMA_PeripheralDataSize_Byte。


DMA_MemoryDataSize:存储器数据宽度选择,可以为字节(8位)、半字(16位)、字(32位),通过DMA_CCR寄存器的MSIZE[1:0]位设置。本章实验同样设置为8位字节传输,这个要和我们定义的数组对应,所以配置为DMA_MemoryDataSize_Byte。


DMA_Mode:DMA传输模式选择,可选择一次传输或者循环传输,通过DMA_CCR寄存器的CIRC位来设定。比如我们要从内存(存储器)中传输64个字节到串口,如果设置为循环传输,那么它会在64个字节传输完成之后继续从内存的第一个地址传输,如此循环。这里我们设置为一次传输完成之后不循环。所以设置值为DMA_Mode_Normal。


DMA_Priority:用来设置DMA通道的优先级,有低,中,高,超高四种级别,可通过DMA_CCR寄存器的PL[1:0]位来设定。DMA优先级只有在多个DMA数据流同时使用时才有意义,本章实验我们只使用了一个DMA数据流,所以可以任意设置DMA优先级,这里我们就设置为中等优先级,配置参数为DMA_Priority_Medium。


DMA_M2M:用来设置存储器到存储器模式,使用存储器到存储器时用到,设定DMA_CCR 的位 14 MEN2MEN 即可启动存储器到存储器模式。


了解结构体成员功能后,就可以进行配置,本章实验配置代码如下:

DMA_InitTypeDef DMA_InitStructure;

DMA_InitStructure.DMA_PeripheralBaseAddr = par;//DMA外设地址

DMA_InitStructure.DMA_MemoryBaseAddr = mar;//DMA 存储器0地址

DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;//存储器到外设模式

DMA_InitStructure.DMA_BufferSize = ndtr;//数据传输量

DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//外设非增量模式

DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;//存储器增量模式

DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;//外设数据长度:8位

DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;//存储器数据长度:8位

DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;// 使用普通模式

DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;//中等优先级

DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; //DMA通道x没有设置为内存到内存传输

DMA_Init(DMAy_Channelx, &DMA_InitStructure);//初始化DMA


(3)使能外设DMA功能(DMA请求映射图对应的外设)

USART_DMACmd(USART1,USART_DMAReq_Tx,ENABLE); //使能串口1的DMA

发送

(4)开启DMA的通道传输

void DMA_Cmd(DMA_Channel_TypeDef* DMAy_Channelx, FunctionalState NewState);

DMA_Cmd(DMA1_Channel4,ENABLE);

(5)查询DMA传输状态

FlagStatus DMA_GetFlagStatus(uint32_t DMAy_FLAG);

例如我们要查询DMA1通道4传输是否完成,方法是:

DMA_GetFlagStatus(DMA1_FLAG_TC4);

uint16_t DMA_GetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx);

void DMA_SetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx, uint16_t DataNumber);


3.硬件电路

本实验使用到硬件资源如下:

(1)D1和D2指示灯

(2)K_UP按键

(3)串口1

(4)DMA


D1和D2指示灯、K_UP按键、串口1电路在前面章节都介绍过,这里就不多说,至于DMA它属于STM32F1芯片内部的资源,只要通过软件配置好DMA即可使用。D1指示灯用来提示系统运行状态,K_UP按键用来控制DMA发送,每按一次K_UP键,DMA就将内存(自定义的一个数组)内数据发送USART1,并通过串口1将发送的内容打印出来,在DMA数据传输的过程中让D2指示灯不断闪烁,直到数据传输完成。D2指示灯闪烁表示CPU在执行

其他的任务,说明DMA传输是不需要占用CPU的。


4.编写DMA控制程序

本章所要实现的功能是:通过K_UP按键控制DMA串口1数据的传送,在传送过程中让D2指示灯不断闪烁,直到数据传送完成。D1指示灯闪烁提示系统正常运行。程序框架如下:


(1)初始化USART1_TX对应的DMA通道相关参数

(2)编写主函数


dma.h


#ifndef _dma_H

#define _dma_H


#include "system.h"


void DMAx_Init(DMA_Channel_TypeDef* DMAy_Channelx,u32 par,u32 mar,u16 ndtr);//配置DMAx_CHx

void DMAx_Enable(DMA_Channel_TypeDef *DMAy_Channelx,u16 ndtr);//使能一次DMA传输


#endif


dma.c


#include "dma.h"


/*******************************************************************************

* 函 数 名         : DMAx_Init

* 函数功能   : DMA初始化函数

* 输    入         : 

DMAy_Channelx:DMA通道选择,@ref DMA_channel DMA_Channel_0~DMA_Channel_7

par:外设地址

mar:存储器地址

ndtr:数据传输量

* 输    出         : 无

*******************************************************************************/ 

void DMAx_Init(DMA_Channel_TypeDef* DMAy_Channelx,u32 par,u32 mar,u16 ndtr)

{

DMA_InitTypeDef  DMA_InitStructure;

RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);//DMA1时钟使能 

//DMA_DeInit(DMAy_Channelx);

/* 配置 DMA */

DMA_InitStructure.DMA_PeripheralBaseAddr = par;//DMA外设地址

DMA_InitStructure.DMA_MemoryBaseAddr = mar;//DMA 存储器0地址

DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;//存储器到外设模式

DMA_InitStructure.DMA_BufferSize = ndtr;//数据传输量 

DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//外设非增量模式

DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;//存储器增量模式

DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;//外设数据长度:8位

DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;//存储器数据长度:8位

DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;// 使用普通模式 

DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;//中等优先级

DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;  //DMA通道x没有设置为内存到内存传输

DMA_Init(DMAy_Channelx, &DMA_InitStructure);//初始化DMA 

}


/*******************************************************************************

* 函 数 名         : DMAx_Enable

* 函数功能   : 开启一次DMA传输

* 输    入         : DMAy_Channelx:DMA通道选择,@ref DMA_channel DMA_Channel_0~DMA_Channel_7

ndtr:数据传输量

* 输    出         : 无

*******************************************************************************/ 

void DMAx_Enable(DMA_Channel_TypeDef *DMAy_Channelx,u16 ndtr)

{

 

DMA_Cmd(DMAy_Channelx, DISABLE);                      //关闭DMA传输 

DMA_SetCurrDataCounter(DMAy_Channelx,ndtr);          //数据传输量  

 

DMA_Cmd(DMAy_Channelx, ENABLE);                      //开启DMA传输 

}  


main.c


#include "system.h"

#include "SysTick.h"

#include "led.h"

#include "usart.h"

#include "key.h"

#include "dma.h"



#define send_buf_len 5000

u8 send_buf[send_buf_len];

/*******************************************************************************

* 函 数 名         : Send_Data

* 函数功能   : 要发送的数据

* 输    入         : p:指针变量 

* 输    出         : 无

*******************************************************************************/

void Send_Data(u8 *p)

{

u16 i;

for(i=0;i{

*p='5';

p++;

}

}



/*******************************************************************************

* 函 数 名         : main

* 函数功能   : 主函数

* 输    入         : 无

* 输    出         : 无

*******************************************************************************/

int main()

{

u8 i=0;

u8 key;

SysTick_Init(72);

NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);  //中断优先级分组 分2组

LED_Init();

USART1_Init(9600);

KEY_Init();

DMAx_Init(DMA1_Channel4,(u32)&USART1->DR,(u32)send_buf,send_buf_len);

Send_Data(send_buf);

while(1)

{

key=KEY_Scan(0);

if(key==KEY_UP)

{

USART_DMACmd(USART1,USART_DMAReq_Tx,ENABLE);  //使能串口1的DMA发送     

DMAx_Enable(DMA1_Channel4,send_buf_len);     //开始一次DMA传输!


//等待DMA传输完成,此时我们来做另外一些事

//实际应用中,传输数据期间,可以执行另外的任务

while(1)

{

if(DMA_GetFlagStatus(DMA1_FLAG_TC4)!=0)//判断通道4传输完成

{

DMA_ClearFlag(DMA1_FLAG_TC4);

break;

}

led2=!led2;

delay_ms(300);

}

}

i++;

if(i%20==0)

{

led1=!led1;

}

delay_ms(10);

}

}

关键字:STM32  DMA  外设 引用地址:STM32—— DMA介绍

上一篇:STM32学习—外部中断EXTI
下一篇:STM32——pwm控制LED

推荐阅读最新更新时间:2024-11-07 22:08

计算机外设的电源管理
标准化的要求 通过附加外设来扩展计算机的功能时,需要使用标准接口才能实现不同厂商应用的全部功能。使用无线通信增加台式机的功能,或者通过使用更多的内存条来增加笔记本电脑的内存,这些方法使得低价的入门级计算机可以升级或根据个人需要进行添减。20世纪90年代初,个人电脑附加卡标准的出现使得不同供应商的内存条都可以用到笔记本电脑上。PCMCIA(个人计算机存储卡国际协会)的成立规范了接口标准,允许使用闪存或硬盘驱动器等插入式附加卡来扩展笔记本电脑内存。许多其他厂商自然很快就意识到,他们的专用功能也可以通过PCMCIA卡添加到这些设备上。 存储、通信和游戏应用等方面制造商加入了PCMCIA,理解这个接口标准或对这个标准施加影响,使笔
[电源管理]
计算机<font color='red'>外设</font>的电源管理
STM32 GPIO的工作模式
一、前言 在之前围绕STM32的GPIO的基本结构进行了介绍,图1为STM32的5V容忍的GPIO口内部基本结构图,图2为GPIO的基本结构中各个模块部分的概述。 阅读GPIO基本结构的内容能够对GPIO的工作模式有更深的了解。 正是由于GPIO的结构中包含了多样性的电路和模块,因此进行合理的配置组合,就可以使得GPIO应用在不同的工作模式下进行工作。 图1 STM32的5V容忍的GPIO内部基本结构 图2 GPIO基本结构包含的功能概述 二、工作模式概述 图3为STM32的GPIO工作模式概述图,从图中可以看出,GPIO端口的静态特征就是指芯片可供你选择的该GPIO的配置,只有通过对使用的GPIO端口进行合理的配置,
[单片机]
<font color='red'>STM32</font> GPIO的工作模式
ADSP-21535 Blackfin的Mem DMA高速通信
  ADSP-21535 Blackfin 是美国AD公司和Intel 公司于2001年底联合推出的一款定点DSP, RISC指令结构,运作高效,具有十分优异的性能。该DSP具有300MHz的主频,2个40bit的MAC(乘加器)和2个32bit的ALU(算术逻辑单元),4个8bit的视频处理单元,16个地址寻址单元。该DSP内部集成了308KB的RAM,并具有丰富的外部接口,如PCI、USB、SPI、同步和异步串口等。同时,芯片内部设计了看门狗和多种定时器,充分满足软件工程稳定性的设计要求。值得一提的是,21535可以动态地控制电压输入,调整运行频率,减少芯片功耗,十分适用于移动产品的设计。   2002年底,AD公司在中国
[嵌入式]
stm32 中断服务函数是如何进入的
今天在看stm32的中断,一时间不理解stm32主函数是如何进入中断函数的,按C编程的理解,会有个特定的入口之类的,但是看demo过程中没有发现入口。 以串口中断服务函数void USART1_IRQHandler(void) 为例,首先用到串口中断,需要先设定串口中断初始化以及串口初始化,另外void USART1_IRQHandler(void) 中断服务函数也应该写好。 发现在stm32的启动文件startup_stm32f10x_md.s中写到 DCD USART1_IRQHandler 其中DCD是一条数据定义伪指令,用于分配一片连续的字存储单元并用指定的数据初始化。 库里定义 #define USART1 ((
[单片机]
STM32学习14:EXTI(外部中断事件控制器)
EXTI管理了控制器的23个中断/事件线。每个中断/事件线都对应有一个边沿检测器,可以实现输入信号的上升沿检测和下降沿的检测。EXTI可以实现对每个中断/事件线进行单独配置,可以单独配置为中断或者事件,以及触发事件的属性。 编程思路: 1、配置NVIC。初始化NVIC(实现过程:先初始化NVIC结构体,再写NVICInit()函数)。 2、配置按键中断。在这个函数中,因为我们要使用IO口作为中断输入, 所以第一步我们要使能相应的IO时钟。(因为GPIO 和中断线映射关系是在寄存器 SYSCFG_EXTICR1~ SYSCFG_EXTICR4 中配置的。所以我们要配置外部中断,还需要打开 SYSCFG 时钟
[单片机]
<font color='red'>STM32</font>学习14:EXTI(外部中断事件控制器)
NIOS II的SOPC中存储器型外设接口的设计
0  引言   随着微电子设计技术与工艺的发展,数字集成电路由最初的电子管、晶体管逐步发展成专用集成电路(ASIC,Application Specific IntegratedCircuit),同时可编程逻辑器件也取得了长足进步。   如今,可完成超大规模的复杂组合逻辑与时序逻辑的FPGA器件不断推陈出新,从而为实现片上可编程系统(SOPC)提供了强大的硬件支持。SOPC是Ahera公司提出的一种灵活、高效的片上系统(SOC)解决方案,它将处理器、存储器、I/O口等系统设计所需要的功能模块集成到一个可编程器件上,从而构成一个可编程的片上系统。   同时,Altera公司也提供了完整的开发套件(QuaItus II、SOPC
[嵌入式]
使用STM32CubeMx配置STM32输入捕获功能
输入捕获原理 在输入捕获模式下,当检测到ICx信号上相应的边沿后,计数器的当前值被锁存到捕获/比较寄存器(TIMx_CCRx)中。当发生捕获事件时,相应的CCxIF标志(TIMx_SR寄存器)被置1,如果开放了中断或者DMA操作,则将产生中断或者DMA请求。如果发生捕获事件时CCxIF标志已经为高,那么重复捕获标志CCxOF(TIMx_SR寄存器)被置1。写CCxIF=0可清除CCxIF,或读取存储在TIMx_CCRx寄存器中的捕获数据也可清除CCxIF。写CCxOF=0可清除CCxOF。 摘自《STM32参考手册中文》 简单解释:定时器一直在计数,如果检测到设置的极性边沿,会把当前的计数值存下来,并触发中断; 比如,
[单片机]
使用STM32CubeMx配置<font color='red'>STM32</font>输入捕获功能
STM32串口DMA超时接收方法,可大大节约CPU时间
本办法使用定时器定时查询DMA接收到的数据,如果超过设定的周期则认为本次数据包结束,将数据拷贝到缓冲区,交由其他程序处理。可以接收任意大小的数据包,尤其适用于MODBUS等协议,曾经用于GPS、GPRS等接收,很实用。本方法占用CPU时间极少,尤其是波特率很高时,效果更加明显。 当某一个串口的数据接收超时以后,定时器中断中将数据拷贝到缓冲区,在主程序中可以判断数据标志UART1_Flag,大于0的时候即代表有数据接收到,可以处理,处理完后将此变量清零即可。 两个数据包间隔较小时,可以将定时器的周期调短些。 //超时时间定义 #define UART1_TimeoutComp 2 //20ms #define UART2
[单片机]
小广播
设计资源 培训 开发板 精华推荐

最新单片机文章
  • 学习ARM开发(16)
    ARM有很多东西要学习,那么中断,就肯定是需要学习的东西。自从CPU引入中断以来,才真正地进入多任务系统工作,并且大大提高了工作效率。采 ...
  • 学习ARM开发(17)
    因为嵌入式系统里全部要使用中断的,那么我的S3C44B0怎么样中断流程呢?那我就需要了解整个流程了。要深入了解,最好的方法,就是去写程序 ...
  • 学习ARM开发(18)
    上一次已经了解ARM的中断处理过程,并且可以设置中断函数,那么它这样就可以工作了吗?答案是否定的。因为S3C44B0还有好几个寄存器是控制中 ...
  • 嵌入式系统调试仿真工具
    嵌入式硬件系统设计出来后就要进行调试,不管是硬件调试还是软件调试或者程序固化,都需要用到调试仿真工具。 随着处理器新品种、新 ...
  • 最近困扰在心中的一个小疑问终于解惑了~~
    最近在驱动方面一直在概念上不能很好的理解 有时候结合别人写的一点usb的例子能有点感觉,但是因为arm体系里面没有像单片机那样直接讲解引脚 ...
  • 学习ARM开发(1)
  • 学习ARM开发(2)
  • 学习ARM开发(4)
  • 学习ARM开发(6)
何立民专栏 单片机及嵌入式宝典

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

换一换 更多 相关热搜器件

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

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