STM32学习笔记一一DMA传输

发布者:电子科技爱好者最新更新时间:2019-01-09 来源: eefocus关键字:STM32  DMA传输 手机看文章 扫描二维码
随时随地手机看文章

1.简介

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


STM32 最多有 2 个 DMA 控制器(DMA2 仅存在大容量产品中), DMA1 有 7 个通道。 DMA2 有 5个通道。每个通道专门用来管理来自于一个或多个外设对存储器访问的请求。还有一个仲裁起来协调各个 DMA 请求的优先权。


2.STM32 DMA特性

●每个通道都直接连接专用的硬件 DMA 请求,每个通道都同样支持软件触发。这些功能通过软件来配置。


●在七个请求间的优先权可以通过软件编程设置(共有四级:很高、高、中等和低),假如在相等优先权时由硬件决定(请求 0 优先于请求 1,依此类推) 。


●独立的源和目标数据区的传输宽度(字节、半字、全字),模拟打包和拆包的过程,源和目标地址必须按数据传输宽度对齐。


●支持循环的缓冲器管理


●每个通道都有 3 个事件标志(DMA 半传输, DMA 传输完成和 DMA 传输出错),这 3 个事件标志逻辑或成为一个单独的中断请求。


●存储器和存储器间的传输


●外设和存储器,存储器和外设的传输


●闪存、 SRAM、外设的 SRAM、 APB1 APB2 和 AHB 外设均可作为访问的源和目标。


●可编程的数据传输数目:最大为 65536


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


这里写图片描述


3.相关寄存器

DMA 中断状态寄存器(DMA_ISR)


这里写图片描述 
这里写图片描述


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


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


该寄存器的各位描述如下图:


这里写图片描述


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


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


该寄存器控制着 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 就可以了。


4.库函数下 DMA1 通道 4 的配置步骤


(1)使能 DMA 时钟

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


(2) 初始化 DMA 通道 4 参数

DMA 通道配置参数种类比较繁多,包括内存地址,外设地址,传输数据长度,数据宽度,通道优先级等等。这些参数的配置在库函数中都是在函数 DMA_Init 中完成.


函数定义:


void DMA_Init(DMA_Channel_TypeDef* DMAy_Channelx,DMA_InitTypeDef* DMA_InitStruct)


第一个参数:指定初始化的 DMA 通道号; 


第二个参数: 跟其他外设一样,同样是通过初始化结构体成员变量值来达到初始化的目的。


DMA_InitTypeDef 结构体的定义:


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 传输的外设基地址,比如要进行串口 DMA 传输,那么外设基地址为串口接受发送数据存储器 USART1->DR 的地址,表示方法为 &USART1->DR。


第二个参数 :DMA_MemoryBaseAddr为内存基地址,也就是我们存放 DMA传输数据的内存地址。


第三个参数: DMA_DIR 设置数据传输方向,决定是从外设读取数据到内存还送从内存读取数据发送到外设,也就是外设是源地还是目的地,这里我们设置为从内存读取数据发送到串口, 所以外设自然就是目的地了,所以选择值为 DMA_DIR_PeripheralDST。


第四个参数: DMA_BufferSize 设置一次传输数据量的大小。


第五个参数 :DMA_PeripheralInc 设置传输数据的时候外设地址是不变还是递增。如果设置 为递增,那么下一次传输的时候地址加 1,这里因为我们是一直往固定外设地址&USART1->DR   发送数据,所以地址不递增,值为 DMA_PeripheralInc_Disable;


第六个参数 :DMA_MemoryInc 设置传输数据时候内存地址是否递增。 这个参数和 

DMA_PeripheralInc 意思接近,只不过针对的是内存。这里我们的场景是将内存中连续存储单元的数据发送到串口,毫无疑问内存地址是需要递增的,所以值为DMA_MemoryInc_Enable。


第七个参数 :DMA_PeripheralDataSize 用来设置外设的的数据长度是为字节传输(8bits),半字传输 (16bits)还是字传输 (32bits),这里我们是 8 位字节传输,所以值设置为DMA_PeripheralDataSize_Byte。


第八个参数 :DMA_MemoryDataSize 是用来设置内存的数据长度,和第七个参数意思接近,这里我们同样设置为字节传输 DMA_MemoryDataSize_Byte。


第九个参数: DMA_Mode 用来设置 DMA 模式是否循环采集,也就是说,比如我们要从内存中采集 64 个字节发送到串口,如果设置为重复采集,那么它会在 64 个字节采集完成之后继续从内存的第一个地址采集,如此循环。这里我们设置为一次连续采集完成之后不循环。所以设置值为 DMA_Mode_Normal。在我们下面的实验中,如果设置此参数为循环采集,那么你会看到串口不停的打印数据,不会中断。


第十个参数:是设置 DMA 通道的优先级,有低,中,高,超高三种模式,这里设置优先级别为中级,所以值为 DMA_Priority_Medium。如果要开启多个通道,那么这个值就非常有意义。


第 十 一 个 参 数 :DMA_M2M 设 置 是 否 是 存 储 器 到 存 储 器 模 式 传 输 , 这 里 我 们 选 择DMA_M2M_Disable。


(3)使能串口 DMA 发送


进行 DMA 配置之后,我们就要开启串口的 DMA 发送功能,使用的函数是:USART_DMACmd(USART1,USART_DMAReq_Tx,ENABLE);

如果是要使能串口 DMA 接受,那么第二个参数修改为 USART_DMAReq_Rx 即可。


(4)使能 DMA1 通道 4,启动传输。


使能串口 DMA 发送之后,我们接着就要使能 DMA 传输通道:DMA_Cmd(DMA_CHx, ENABLE);

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


(5)查询 DMA 传输状态


在 DMA 传输过程中,我们要查询 DMA 传输通道的状态,使用的函数是:FlagStatus DMA_GetFlagStatus(uint32_t DMAy_FLAG)

比如我们要查询 DMA 通道 4 传输是否完成,方法是:DMA_GetFlagStatus(DMA2_FLAG_TC4);

这里还有一个比较重要的函数就是获取当前剩余数据量大小的函数:uint16_t DMA_GetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx)

比如我们要获取 DMA 通道 4 还有多少个数据没有传输,方法是:DMA_GetCurrDataCounter(DMA1_Channel4);


5.软件实现

(1)DMA实现


#include "dma.h"


DMA_InitTypeDef DMA_InitStructure;

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)

{

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

    DMA_DeInit(DMA_CHx); //将 DMA 的通道 1 寄存器重设为缺省值

    DMA1_MEM_LEN=cndtr;

    DMA_InitStructure.DMA_PeripheralBaseAddr = cpar; //DMA 外设 ADC 基地址

    DMA_InitStructure.DMA_MemoryBaseAddr = cmar; //DMA 内存基地址

    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; //数据传输方向内存到外设

    DMA_InitStructure.DMA_BufferSize = cndtr; //DMA 通道的 DMA 缓存的大小

    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; //DM 通道拥有中优先级

    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; //非内存到内存传输

    DMA_Init(DMA_CHx, &DMA_InitStructure); //初始化 DMA 的通道

}


//开启一次 DMA 传输

void MYDMA_Enable(DMA_Channel_TypeDef*DMA_CHx)

{

    DMA_Cmd(DMA_CHx, DISABLE ); //关闭 USART1 TX DMA1 所指示的通道

    DMA_SetCurrDataCounter(DMA1_Channel4,DMA1_MEM_LEN);//设置 DMA 缓存的大小

    DMA_Cmd(DMA_CHx, ENABLE); //使能 USART1 TX DMA1 所指示的通道

}


(2)主函数实现


const u8 TEXT_TO_SEND[]={"ALIENTEK Mini STM32 DMA 串口实验"};

#define TEXT_LENTH  sizeof(TEXT_TO_SEND)-1          //TEXT_TO_SEND字符串长度(不包含结束符)

u8 SendBuff[(TEXT_LENTH+2)*100];


 int main(void)

 { 

    u16 i;

    u8 t=0; 

    float pro=0;            //进度 

    delay_init();            //延时函数初始化    

    uart_init(9600);        //串口初始化为9600

    LED_Init();             //初始化与LED连接的硬件接口

    LCD_Init();             //初始化LCD 

    KEY_Init();             //按键初始化         

    MYDMA_Config(DMA1_Channel4,(u32)&USART1->DR,(u32)SendBuff,(TEXT_LENTH+2)*100);//DMA1通道4,外设为串口1,存储器为SendBuff,长(TEXT_LENTH+2)*100.

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

    LCD_ShowString(60,50,200,16,16,"Mini STM32");   

    LCD_ShowString(60,70,200,16,16,"DMA TEST"); 

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

    LCD_ShowString(60,110,200,16,16,"2018/3/26");   

    LCD_ShowString(60,130,200,16,16,"KEY0:Start");

    //显示提示信息       

    for(i=0;i<(TEXT_LENTH+2)*100;i++)//填充ASCII字符集数据

    {

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

        { 

            SendBuff[i++]=0x0d; 

            SendBuff[i]=0x0a; 

            t=0;

        }else 

            SendBuff[i]=TEXT_TO_SEND[t++];//复制TEXT_TO_SEND语句    

    }        

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

    i=0;

    while(1)

    {

        t=KEY_Scan(0);

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

        {

            LCD_ShowString(60,150,200,16,16,"Start Transimit....");

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

            printf("\r\nDMA DATA:\r\n ");       

            USART_DMACmd(USART1,USART_DMAReq_Tx,ENABLE); //????1?DMA??        

            MYDMA_Enable(DMA1_Channel4);//开始一次DMA传输!      

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

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

            while(1)

            {

                if(DMA_GetFlagStatus(DMA1_FLAG_TC4)!=RESET)//等待通道4传输完成

                {

                    DMA_ClearFlag(DMA1_FLAG_TC4);//清除通道4传输完成标志

                    break; 

                }

                pro=DMA_GetCurrDataCounter(DMA1_Channel4);//得到当前还剩余多少个数据

                pro=1-pro/((TEXT_LENTH+2)*100);//得到百分比    

                pro*=100;      //扩大100倍

                LCD_ShowNum(60,170,pro,3,16);     

            }               

            LCD_ShowNum(60,170,100,3,16);//显示100%     

            LCD_ShowString(60,150,200,16,16,"Transimit Finished!");//提示传送完成

        }

        i++;

        delay_ms(10);

        if(i==20)

        {

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

            i=0;

        }          

    }

}


参考:


1.正点原子库函数教程


关键字:STM32  DMA传输 引用地址:STM32学习笔记一一DMA传输

上一篇:STM32学习笔记一一内存管理
下一篇:STM32学习笔记一一输入捕获

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

STM32_TIM延时讲解
今天讲解STM32F103定时器延时功能,TIM的功能很强大,后续慢慢讲解。今天从简单开始讲解定时器,以下面软件工程实例来讲述。 软件工程下载地址(360云盘): https://yunpan.cn/cP5utM7kxIReM 访问密码 6f12 工程现象:间隔(定时器延时)500ms LED变化一次, 并且串口打印 STM32F103ZE有8个定时器(TIM1 – TIM8), 这里工程使用TIM4为例。 STM32F10x的资料可以在我360云盘下载: https://yunpan.cn/crBUdUGdYKam2 访问密码 ca90 关于TIM延时,我把重要的几点在下面分别讲述,工程中没有在这里
[单片机]
STM32_TIM延时讲解
STM32平台上实现基于汽车音频总线的解决方案
A2B或“汽车音频总线”是一项主要为信息娱乐系统开发的新技术,旨在减轻音频线束的重量和成本。A2B 总线是单主多从系统,其中主控制器上的 A2B 收发芯片为主。A2B 收发器芯片通过单根非屏蔽双绞线支持多通道数字音频。此外,A2B 总线将直流电源传输到远程总线供电节点。A2B 收发器连接多个 IC 间声音 (I2S) 同步、脉冲编码调制 (PCM) 数据,节点之间最长可达 15 米,所有节点的总长度最长可达 40 米。 A2B 主节点生成时钟、同步和成帧信号以及从节点的直流电源。A2B 芯片可通过 I2C 进行编程,用于配置和从从设备读取数据。它提供对从收发器的寄存器和状态信息以及 I2C 到 I2C 通信的直接访问。 A
[单片机]
在<font color='red'>STM32</font>平台上实现基于汽车音频总线的解决方案
STM32最小系统组成详解
经常使用STM32开发的工程师对于它的开发环境的最小系统是必须要有所了解的,特别是硬件工程师在设计硬件的时候对这个最小系统就要更加的深入了解了,如果最小系统的搭建都有问题,那以后的使用很难避免不出现问题。 话不多说,进入正题说说STM32的最小系统的基本组成! 1、STM32最小系统硬件组成包括哪些? STM32的最小系统的硬件组成主要有:电源电路、复位电路、时钟电路、调试接口电路、启动电路。 电源 : 一般是3.3V输入作为STM32芯片的工作电压,实际中很多采用LDO将5V转换为3.3V进行供电,另外电路上还要加多个0.01uf去耦电容对输入电压进行滤波,稳定输入电压。 复位:STM32中有三种复位方式,分别
[单片机]
<font color='red'>STM32</font>最小系统组成详解
MPU6050工作原理及STM32控制MPU6050
一·简介: 1.要想知道MPU6050工作原理,得先了解下面俩个传感器: ①陀螺仪传感器: 陀螺仪的原理就是,一个旋转物体的旋转轴所指的方向在不受外力影响时,是不会改变的。人们根据这个道理,用它来保持方向。然后用多种方法读取轴所指示的方向,并自动将数据信号传给控制系统。我们骑自行车其实也是利用了这个原理。轮子转得越快越不容易倒,因为车轴有一股保持水平的力量。现代陀螺仪可以精确地确定运动物体的方位的仪器,它在现代航空,航海,航天和国防工业中广泛使用的一种惯性导航仪器。传统的惯性陀螺仪主要部分有机械式的陀螺仪,而机械式的陀螺仪对工艺结构的要求很高。70年代提出了现代光纤陀螺仪的基本设想,到八十年代以后,光纤陀螺仪就得到了
[单片机]
MPU6050工作原理及<font color='red'>STM32</font>控制MPU6050
STM32单片机-加密烧录Hex
一、专用烧录器加密烧录Hex 1、使用ST官方的下载器 ----------------------------------------------------------- 2、其他公司烧录器 ----------------------------------------------------------------------------------------------------------------- 二、J-Link加密烧录Hex 1、简述 本处以烧写STM32F103VET6说明,软件:Flasher_Windows_V722a(百度网盘搜索文件名或移步download.csdn.net
[单片机]
<font color='red'>STM32</font>单片机-加密烧录Hex
STM32的SPI1、SPI2、SPI3初始化及RF1101的应用(标准库与HAL库)
——基于STM32F103RCT6 ---- 标准库: 说明:相关文件共有两个:bsp_spi_cc1101.h;bsp_spi_cc1101.c;以SPI2为例。 1、“bsp_spi_cc1101.h”中的参数定义: #ifndef _BSP_SPI_1101_H_ #define _BSP_SPI_1101_H_ #include stdio.h #include stm32f10x.h #include misc.h #include os_cfg_app.h #include os.h #include core_cm3.h #include bsp.h #include stm32f10x_s
[单片机]
STM32-如何使用引脚复用功能输出PWM
#define LED0_PWM_VAL TIM3- CCR2 //比较寄存器(TIM3通道2),可以调节PWM占空比, int main(void) { u16 led0pwmval=0; u8 dir=1; Stm32_Clock_Init(9); //系统时钟设置 delay_init(72); //延时初始化 PWM_Init(900,0);//设置PWM频率,不分频 while(1) { delay_ms(10); if(dir)led0pwmval++; else led0pwmval--; if(led0pwmval 300)dir=0; if(led0pwmval==0)dir=1; LED0
[单片机]
基于单片机系统采用DMA传输方式实现高速数据采集
   摘 要: 介绍一种基于单片机系统设计的DMA硬件电路,以字块传输方式与高速A/D接口。结合在数字式磁通表设计中的应用给出其硬件软件设计方案实例。     关键词: 单片机系统 直接存储器存取(DMA)方式 高速A/D     PC机中外设与内存储器之间数据直接传输的DMA功能以其高效、高速、CPU资源占用少等特点已被广泛应用,这一功能通过安装在主板上的专用DMA控制器芯片或集成在外围控制芯片来实现。单片机的应用领域也常常需要有高速数据传输或数据采集,虽然近些年单片机速度有所提高,仍然无法应付类似单脉冲信号捕获、周期信号频谱分析等需要采用高速A/D的场合。对于速率在100ksps以上的数据采集或传输一般的中断查
[应用]
小广播
添点儿料...
无论热点新闻、行业分析、技术干货……
设计资源 培训 开发板 精华推荐

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

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

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