STM32—— DMA介绍

2023-11-01 80浏览
百检网是一家专业的第三方检测平台,汇聚众多拥有权威资质的第三方检测机构为你提供一站式的检测服务,做检测就上百检网。百检网让检测从此检测,一份报告全国通用,专业值得信赖。

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);

}

}

百检网秉承“客户至上,服务为先,精诚合作,以人为本”的经营理念,始终站在用户的角度解决问题,为客户提供“一站购物式”的新奇检测体验,打开网站,像挑选商品一样简单,方便。打破行业信息壁垒,建构消费和检测机构之间高效的沟通平台