文章归档友情连接照片地图

STM32 DMA直接存储器访问简介

分类:电子设计  作者:rming  时间:2012-07-24

DMA简介

DMA,全称为:Direct Memory Access,即直接存储器访问。DMA传输方式无需CPU 直接控制传输,也没有中断处理方式那样保留现场和恢复现场的过程,通过硬件为RAM 与I/O设备开辟一条直接传送数据的通路,能使CPU 的效率大为提高。

stm32中 DMA1有7个通道,DMA2有5个通道(DMA2 仅存在大容量产品中)。DMA挂载的时钟为AHB总线,其时钟为72Mhz,所以可以实现高速数据搬运。

STM32F103RBT6 只有1 个DMA控制器,DMA1 ,下面我们就针对DMA1 进行介绍。

外设(TIMx、ADC、SPIx 、I2Cx 和USARTx )产生的DMA请求,通过逻辑或输入到DMA控制器,这就意味着同时只能有一个请求有效。外设的DMA请求,可以通过设置相应的外设寄存器中的控制位,被独立地开启或关闭。

DMA1 各通道一览表:

这里解释一下上面说的逻辑或,例如通道 1 的几个 DMA1 请求(ADC1、TIM2_CH3、TIM4_CH1),这几个是通过逻辑或到通道 1 的,这样我们在同一时间,就只能使用其中的一个。其他通道也是类似的。

这里我们要使用的是串口 1DMA 传送,也就是要用到通道 4。接下来,我们介绍一下 DMA设置相关的几个寄存器。

寄存器简介

 DMA 中断状态寄存器(DMA_ISR)

我们如果开启了 DMA_ISR 中这些中断,在达到条件后就会跳到中断服务函数里面去,即使没开启,我们也可以通过查询这些位来获得当前 DMA 传输的状态。这里我们常用的是TCIFx,即通道 DMA 传输完成与否的标志

注意此寄存器为只读寄存器,所以在这些位被置位之后,只能通过其他的操作来清除。

 DMA 中断标志清除寄存器(DMA_IFCR)

DMA_IFCR 的各位就是用来清除 DMA_ISR 的对应位的通过写 0 清除在 DMA_ISR 被置位后,我们必须通过向该位寄存器对应的位写入 0 来清除。

 DMA 通道 x 配置寄存器(DMA_CCRx)(x=1~7)

该寄存器控制着 DMA 的很多相关信息,包括数据宽度、外设及存储器的宽度、通道优先级、增量模式、传输方向、中断允许、使能等都是通过该寄存器来设置的。所以 DMA_CCRx 是 DMA 传输的核心控制寄存器。

MEM2MEM:存储器到存储器模式 (Memory to memory mode) 该位由软件设置和清除。 0:非存储器到存储器模式; 1:启动存储器到存储器模式。

PL:通道优先级 (Channel priority level)  这些位由软件设置和清除。 00:低 01:中 10:高 11:最高

MSIZE:存储器数据宽度 (Memory size) 这些位由软件设置和清除。 00:8位 01:16位 10:32位 11:保留

PSIZE:外设数据宽度 (Peripheral size)  这些位由软件设置和清除。 00:8位 01:16位 10:32位 11:保留

MINC:存储器地址增量模式 (Memory increment mode)  该位由软件设置和清除。 0:不执行存储器地址增量操作 1:执行存储器地址增量操作

PINC:外设地址增量模式 (Peripheral increment mode) 该位由软件设置和清除。 0:不执行外设地址增量操作 1:执行外设地址增量操作

CIRC:循环模式 (Circular mode)  该位由软件设置和清除。 0:不执行循环操作 1:执行循环操作

DIR:数据传输方向 (Data transfer direction)   该位由软件设置和清除。 0:从外设读 1:从存储器读

TEIE:允许传输错误中断 (Transfer error interrupt enable)  该位由软件设置和清除。 0:禁止TE中断 0:允许TE中断

HTIE:允许半传输中断 (Half transfer interrupt enable) 该位由软件设置和清除。 0:禁止HT中断 0:允许HT中断

TCIE:允许传输完成中断 (Transfer complete interrupt enable) 该位由软件设置和清除。 0:禁止TC中断 0:允许TC中断

EN:通道开启 (Channel enable) 该位由软件设置和清除。 0:通道不工作 1:通道开启

DMA通道x传输数量寄存器(DMA_CNDTRx)(x = 1…7)

低16位有效。这个寄存器控制通道每次传输的数据量,数据传输数量为0至65535。

该寄存器会随着传输的进行而递减,为0表示已经发送完成。所以可以通过这个寄存器的值来知道当前 DMA 传输的进度。

DMA外设地址寄存器(DMA_CPARx)

32位寄存器。外设数据寄存器的基地址,作为数据传输的源或目标。
 比如我们使用串口 1,那么该寄存器必须写入 0x40013804(其实就是&USART1_DR)。如果使用其他外设,就修改成相应外设的地址就行了。

DMA存储地址寄存器(DMA_CMARx)

存储器地址[31:0],存储器地址作为数据传输的源或目标。
比如我们使用 SendBuf[5200]数组来做存储器,那么我们在DMA_CMARx 中写入&SendBuff 就可以了。

寄存器操作步骤、

1、设置外设地址。

设置外设地址通过DMA1_CPAR4 来设置,我们只要在这个寄存器里面写入&USART1_DR 的值就可以了。该地址将作为 DMA 传输的目标地址。

2、设置存储器地址。

设置存储器地址,我们通过 DMA1_CMAR4 来设置,假设我们要把数组 SendBuf 作为存储器,那么我们在该寄存器写入&SendBuf 就可以了。该地址将作为 DMA 传输的源地址。

3、设置传输数据量。 

通过 DMA1_CNDTR4 来设置 DMA1 通道 4 的数据传输量,这里面写入此次你要传输的数据量就可以了,也就是 SendBuf 的大小。该寄存器的数值将在 DMA 启动后自减,每次新的 DMA 传输,都重新向该寄存器写入要传输的数据量。

4、设置通道 4 的配置信息。

配置信息通过 DMA1_CCR4 来设置。这里我们设置存储器和外设的数据位宽均为 8,且模式是存储器到外设的存储器增量模式。优先级可以随便设置,因为我们只有一个通道被开启了。

假设有多个通道开启(最多 7 个),那么就要设置优先级了,DMA 仲裁器将根据这些优先级的设置来决定先执行那个通道的 DMA。优先级越高的,越早执行,当优先级相同的时候,根据硬件上的编号来决定哪个先执行(编号越小越优先)。

5、使能 DMA1 通道 4,启动传输。

在以上配置都完成了之后,我们就使能 DMA1_CCR4 的最低位开启 DMA 传输,这里注意要设置 USART1 的使能 DMA 传输位,通过USART1->CR3 的第七位设置。

通过以上 5 步设置,我们就可以启动一次 USART1 的 DMA 传输了。

程序设计

MAIN.C

#include <stm32f10x_lib.h>
#include "sys.h"
#include "usart.h"
#include "delay.h"
#include "led.h"
#include "key.h"
#include "exti.h"
#include "wdg.h"
#include "timer.h"
#include "lcd.h"
#include "rtc.h"
#include "wkup.h"
#include "adc.h"
#include "dma.h"
const u8 *COMPILED_DATE=__DATE__;//获得编译日期
const u8 *COMPILED_TIME=__TIME__;//获得编译时间
//DMA 实验
u8 SendBuff[5200];
const u8 TEXT_TO_SEND[]={"DMA 串口实验"};
int main(void)
{
u16 i;
u8 t=0;
u8 j,mask=0;
float pro=0;//进度
Stm32_Clock_Init(9);//系统时钟设置
delay_init(72);	//延时初始化
uart_init(72,9600); //串口1初始化
led_init();
key_init();
LCD_Init();
MYDMA_Config(DMA1_Channel4,(u32)&USART1->DR,(u32)SendBuff,5200);//DMA1通道4,外设为串口1,存储器为SendBuff,长度5200.
POINT_COLOR=RED;//设置字体为蓝色
LCD_ShowString(60,110,"DMA USART TEST");
//显示提示信息
LCD_ShowString(60,130,"Press KEY0 To Start!");
j=sizeof(TEXT_TO_SEND);
for(i=0;i<5200;i++)//填充ASCII字符集数据
{
if(t>=j)//加入换行符
{
if(mask)
{
SendBuff[i]=0x0a;
t=0;
}else
{
SendBuff[i]=0x0d;
mask++;
}
}else//复制TEXT_TO_SEND语句
{
mask=0;
SendBuff[i]=TEXT_TO_SEND[t];
t++;
}
}
POINT_COLOR=BLUE;//设置字体为蓝色
i=0;
while(1)
{
t=key_scan();
if(t==1)//KEY0按下
{
LCD_ShowString(60,150,"Start Transimit....");
LCD_ShowString(60,170," %");//显示百分号
printf("
DMA DATA:
");
USART1->CR3=1<<7; //使能串口1的DMA发送
MYDMA_Enable(DMA1_Channel4);//开始一次DMA传输!
//等待DMA传输完成,此时我们来做另外一些事,点灯
//实际应用中,传输数据期间,可以执行另外的任务
while(1)
{
if(DMA1->ISR&(1<<13))//等待通道4传输完成
{
DMA1->IFCR|=1<<13;//清除通道4传输完成标志
break;
}
pro=DMA1_Channel4->CNDTR;//得到当前还剩余多少个数据
pro=1-pro/5200;//得到百分比
pro*=100; //扩大100倍
LCD_ShowNum(60,170,pro,3,16);
}
LCD_ShowNum(60,170,100,3,16);//显示100%
LCD_ShowString(60,150,"Transimit Finished!");//提示传送完成
}
i++;
delay_ms(10);
if(i==20)
{
LED0=!LED0;//提示系统正在运行
i=0;
}
}
}

DMA.C

#include "dma.h"
//DMA 驱动代码
u16 DMA1_MEM_LEN;//保存DMA每次数据传送的长度
//DMA1的各通道配置
//这里的传输形式是固定的,这点要根据不同的情况来修改
//从存储器->外设模式/8位数据宽度/存储器增量模式
//DMA_CHx:DMA通道CHx
//cpar:外设地址
//cmar:存储器地址
//cndtr:数据传输量
void MYDMA_Config(DMA_Channel_TypeDef*DMA_CHx,u32 cpar,u32 cmar,u16 cndtr)
{
u32 DR_Base; //做缓冲用,不知道为什么.非要不可
RCC->AHBENR|=1<<0;//开启DMA1时钟
__nop(); //等待 DMA1 时钟稳定
__nop(); //经测试最少 2 个 nop
__nop();
DR_Base=cpar;
DMA_CHx->CPAR=DR_Base; //DMA1 外设地址
DMA_CHx->CMAR=(u32)cmar; //DMA1,存储器地址
DMA1_MEM_LEN=cndtr; //保存DMA传输数据量
DMA_CHx->CNDTR=cndtr; //DMA1,传输数据量
DMA_CHx->CCR=0X00000000;//复位
DMA_CHx->CCR|=1<<4; //从存储器读
DMA_CHx->CCR|=0<<5; //普通模式
DMA_CHx->CCR|=0<<6; //外设地址非增量模式
DMA_CHx->CCR|=1<<7; //存储器增量模式
DMA_CHx->CCR|=0<<8; //外设数据宽度为8位
DMA_CHx->CCR|=0<<10; //存储器数据宽度8位
DMA_CHx->CCR|=1<<12; //中等优先级
DMA_CHx->CCR|=0<<14; //非存储器到存储器模式
}
//开启一次DMA传输
void MYDMA_Enable(DMA_Channel_TypeDef*DMA_CHx)
{
DMA_CHx->CCR&=~(1<<0); //关闭DMA传输
DMA_CHx->CNDTR=DMA1_MEM_LEN; //DMA1,传输数据量
DMA_CHx->CCR|=1<<0; //开启DMA传输
}

DMA.H

#ifndef __DMA_H
#define	__DMA_H
#include "sys.h"
void MYDMA_Config(DMA_Channel_TypeDef*DMA_CHx,u32 cpar,u32 cmar,u16 cndtr);//配置DMA1_CHx
void MYDMA_Enable(DMA_Channel_TypeDef*DMA_CHx);//使能DMA1_CHx
#endif


提交评论