打印

《Alientek STM32实例手册》28个实例之--DMA实验!--整理后

[复制链接]
9540|6
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
本帖最后由 zhangyang86 于 2011-1-5 23:20 编辑

1.注意我们的教材讲解是基于寄存器操作方便初学者理解透彻,

2.我们另外还提供了该实例的库函数源码
下载链接:https://bbs.21ic.com/icview-210815-1-1.html

3.此实验的教程在《Alientek STM32不完全手册》的 3.8节:


     下载地址:《Alientek STM32不完全手册》
4.源码 :   ALIENTEK MINISTM32 实验15 DMA实验.rar (1.2 MB)
3.15 DMA实验

3.15.1 STM32 DMA简介
DMA,全称为:Direct Memory Access,即直接存储器访问。DMA传输方式无需CPU直接控制传输,也没有中断处理方式那样保留现场和恢复现场的过程,通过硬件为RAM与I/O设备开辟一条直接传送数据的通路,能使CPU的效率大为提高。
STM32最多有2个DMA控制器(DMA2仅存在大容量产品中),DMA1有7个通道。DMA2有5个通道。每个通道专门用来管理来自于一个或多个外设对存储器访问的请求。还有一个仲裁起来协调各个DMA请求的优先权。
STM32的DMA有以下一些特性:
每个通道都直接连接专用的硬件DMA请求,每个通道都同样支持软件触发。这些功能通过软件来配置。
在七个请求间的优先权可以通过软件编程设置(共有四级:很高、高、中等和低),假如在相等优先权时由硬件决定(请求0优先于请求1,依此类推)
独立的源和目标数据区的传输宽度(字节、半字、全字),模拟打包和拆包的过程。源和目标地址必须按数据传输宽度对齐。
支持循环的缓冲器管理
每个通道都有3个事件标志(DMA 半传输,DMA传输完成和DMA传输出错),这3个事件标志逻辑或成为一个单独的中断请求。
存储器和存储器间的传输
外设和存储器,存储器和外设的传输
闪存、SRAM、外设的SRAMAPB1 APB2AHB外设均可作为访问的源和目标。
可编程的数据传输数目:最大为65536
STM32F103RBT6只有1DMA控制器,DMA1,下面我们就针对DMA1进行介绍。
从外设(TIMxADCSPIxI2CxUSARTx)产生的DMA请求,通过逻辑或输入到DMA控制器,这就意味着同时只能有一个请求有效。外设的DMA请求,可以通过设置相应的外设寄存器中的控制位,被独立地开启或关闭。
DMA1各通道一览表:



表3.15.1.1DMA1个通道一览表

这里解释一下上面说的逻辑或,例如通道1的几个DMA1请求(ADC1、TIM2_CH3、TIM4_CH1),这几个是通过逻辑或到通道1的,这样我们在同一时间,就只能使用其中的一个。其他通道也是类似的。
这里我们要使用的是串口1的DMA传送,也就是要用到通道4。接下来,我们介绍一下DMA设置相关的几个寄存器。
第一个是DMA中断状态寄存器(DMA_ISR)。该寄存器的各位描述如下:



图3.15.1.1 寄存器DMA_ISR各位描述

我们如果开启了DMA_ISR中这些中断,在达到条件后就会跳到中断服务函数里面去,即使没开启,我们也可以通过查询这些位来获得当前DMA传输的状态。这里我们常用的是TCIFx,即通道DMA传输完成与否的标志。注意此寄存器为只读寄存器,所以在这些位被置位之后,只能通过其他的操作来清除。
第二个是DMA中断标志清除寄存器(DMA_IFCR)。该寄存器的各位描述如下:



图3.15.1.2寄存器DMA_IFCR各位描述

DMA_IFCR的各位就是用来清除DMA_ISR的对应位的,通过写0清除。在DMA_ISR被置位后,我们必须通过向该位寄存器对应的位写入0来清除。
第三个是DMA通道x配置寄存器(DMA_CCRx)(x=1~7,下同)。该寄存器的我们在这里就不贴出来了,见《STM32参考手册》第108页9.4.3一节。该寄存器控制着DMA的很多相关信息,包括数据宽度、外设及存储器的宽度、通道优先级、增量模式、传输方向、中断允许、使能等都是通过该寄存器来设置的。所以DMA_CCRx是DMA传输的核心控制寄存器。
第四个是DMA通道x传输数据量寄存器(DMA_CNDTRx)。这个寄存器控制DMA通道x的每次传输所要传输的数据量。其设置范围为0~65535。并且该寄存器的值会随着传输的进行而减少,当该寄存器的值为0的时候就代表此次数据传输已经全部发送完成了。所以可以通过这个寄存器的值来知道当前DMA传输的进度。
第五个是DMA通道x的外设地址寄存器(DMA_CPARx)。该寄存器用来存储STM32外设的地址,比如我们使用串口1,那么该寄存器必须写入0x40013804(其实就是&USART1_DR)。如果使用其他外设,就修改成相应外设的地址就行了。
最后一个是DMA通道x的存储器地址寄存器(DMA_CMARx),该寄存器和DMA_CPARx差不多,但是是用来放存储器的地址的。比如我们使用SendBuf[5200]数组来做存储器,那么我们在DMA_CMARx中写入&SendBuff就可以了。
DMA相关寄存器就为大家介绍到这里,此节我们要用到串口1的发送,属于DMA1的通道4,接下来我们就介绍下DMA1通道4的配置步骤:
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传输了。
3.15.2 硬件设计
这一节我们将利用外部按键KEY0来控制DMA的传送,每按一次KEY0,DMA就传送一次数据到USART1,然后在TFTLCD模块上显示进度等信息。DS0还是用来做为程序运行的指示灯。
这里我们使用到的硬件资源如下:
1)按键KEY0。2)指示灯DS0。3)TFTLCD液晶显示模块。
3.15.3 软件设计
打开上一节的工程,首先在HARDWARE文件夹下新建一个DMA的文件夹。然后新建一个dma.c的文件和dma.h的头文件,保存在DMA文件夹下,并将DMA文件夹加入头文件包含路径。
打开dma.c文件,输入如下代码:
#include"dma.h"
//MiniSTM32开发板
//DMA驱动代码
//正点原子@ALIENTEK
//2010/6/7
u16DMA1_MEM_LEN;//保存DMA每次数据传送的长度
//DMA1的各通道配置
//这里的传输形式是固定的,这点要根据不同的情况来修改
//从存储器->外设模式/8位数据宽度/存储器增量模式
//DMA_CHx:DMA通道CHx
//cpar:外设地址
//cmar:存储器地址
//cndtr:数据传输量
voidMYDMA_Config(DMA_Channel_TypeDef*DMA_CHx,u32 cpar,u32 cmar,u16 cndtr)
{
u32 DR_Base;
//做缓冲用,不知道为什么.非要不可

RCC->AHBENR|=1<<0;//开启DMA1时钟

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传输
voidMYDMA_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传输

}
该部分代码仅仅2个函数,MYDMA_Config函数,基本上就是按照我们上面介绍的步骤来初始化DMA的,该函数在外部只能修改通道、源地址、目标地址和传输数据量等几个参数,跟多的其他设置只能在该函数内部修改。
MYDMA_Enable函数用来产生一次DMA传输,该函数每执行一次,DMA就发送一次。
保存dma.c,并把dma.c加入到HARDWARE组下,接下来打开dma.h,输入如下内容:
#ifndef__DMA_H
#define
__DMA_H

#include"sys.h"
//MiniSTM32开发板
//DMA驱动代码
//正点原子@ALIENTEK
//2010/6/7
voidMYDMA_Config(DMA_Channel_TypeDef*DMA_CHx,u32 cpar,u32 cmar,u16 cndtr);//配置DMA1_CHx
voidMYDMA_Enable(DMA_Channel_TypeDef*DMA_CHx);//使能DMA1_CHx
#endif
保存dma.h,最后我们在test.c里面修改main函数如下:
u8SendBuff[5200];
constu8 TEXT_TO_SEND[]={"ALIENTEK MiniSTM32 DMA 串口实验"};
intmain(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,50,"Mini STM32");

LCD_ShowString(60,70,"DMA USARTTEST");

LCD_ShowString(60,90,"ATOM@ALIENTEK");

LCD_ShowString(60,110,"2010/6/7");

LCD_ShowString(60,130,"Press KEY0 ToStart");

j=sizeof(TEXT_TO_SEND);

for(i=0;i<5200;i++)//填充ASCII字符集数据

{

if(t>=j)//加入换行符

{

if(mask)

{

SendBuff=0x0a;

t=0;

}else

{

SendBuff=0x0d;

mask++;

}

}else//复制TEXT_TO_SEND语句

{

mask=0;

SendBuff=TEXT_TO_SEND[t];

t++;

}

}

POINT_COLOR=BLUE;//设置字体为蓝色

i=0;

while(1)

{

t=KEY_Scan();

if(t==1)//KEY0按下

{

LCD_ShowString(60,150,"StartTransimit....");

LCD_ShowString(60,170,"
%");//显示百分号

printf("\n\nDMADATA:\n");

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,"TransimitFinished!");//提示传送完成

}

i++;

delay_ms(1);

if(i==200)

{

LED0=!LED0;//提示系统正在运行

i=0;

}

}

}
至此,DMA串口传输的软件设计就完成了。


3.15.4
下载与测试

在代码编译成功之后,我们通过下载代码到ALIENTEKMiniSTM32开发板上,可以看到LCD显示如下内容:

13.15.4.1DMA串口实验实物测试图

伴随DS0的不停闪烁,提示程序在运行。我们打开串口调试助手,然后按KEY0,可以看到串口显示如下内容:


13.15.4.2
串口收到的数据内容

同时可以看到TFTLCD上显示了进度等信息,如下图所示:
13.15.4.3 DMA串口数据传输中


技术支持论坛:www.openedv.com
QQ讨论群:95288038(验证:21ic)
QQ:389063473/ 497610476



至此,我们整个DMA串口实验就结束了,希望大家通过这节的学习,掌握STM32DMA的使用。
沙发
A7_COOL| | 2011-4-1 11:14 | 只看该作者
voidMYDMA_Config(DMA_Channel_TypeDef*DMA_CHx,u32 cpar,u32 cmar,u16 cndtr)
{
u32 DR_Base;
//做缓冲用,不知道为什么.非要不可RCC->AHBENR|=1<<0;//开启DMA1时钟
DR_Base=cpar;

我在使用DMA2的通道5传递ADC3的采样数据时也遇到楼主的这个问题,只不过我用的是一条__nop();指令替换了楼主的DR_Base=cpar;就可以正常操作了,似乎原因是,在使能DMA的时钟信号以后,需要等待几个若干周期,再进行相关寄存器操作就ok。但是另外一个让我困惑的就是,我同样在用DMA1传输ADC12的同步采样数据时,确没有这个问题,甚是不解!不知道,楼主找到真正的原因没?

使用特权

评论回复
板凳
gavin_li| | 2011-5-9 13:17 | 只看该作者
DMA能直接传数据给TFT显存,然后显示吗

使用特权

评论回复
地板
dfsa| | 2011-5-9 16:09 | 只看该作者
很好的一些资料

使用特权

评论回复
5
muyueye| | 2011-8-16 19:39 | 只看该作者
请问,TIM1怎么用DMA读取它的CNT中的值,TIM1本来是用于码盘模式计数的,现在用其它的定时器计时,想每隔一定时间读取TIM1 的CNT的值,求指点!

使用特权

评论回复
6
gxgclg| | 2011-8-17 09:38 | 只看该作者
很好的资料,收藏了

使用特权

评论回复
7
muyueye| | 2011-8-17 11:15 | 只看该作者
已经知道怎么办了,原来当初理解错了,:D

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

个人签名:正点原子STM32开发板购买单击这里

80

主题

916

帖子

51

粉丝