正点原子HAL库 STM32F4 DMA(学习自用附源码)

本文详细介绍了STM32F4中的DMA(直接存储器访问)技术,包括DMA的原理、如何配置和使用DMA控制器(如DMA2和DMA1)进行数据传输,以及如何通过HAL库实现DMA与外设如USART的连接和中断处理。
摘要由CSDN通过智能技术生成

一、什么是DMA?

DMA,全称为: Direct Memory Access,即直接存储器访问。 DMA 传输方式无需 CPU 直接
控制传输,也没有中断处理方式那样保留现场和恢复现场的过程,通过硬件为 RAM 与 I/O 设备
开辟一条直接传送数据的通路, 能使 CPU 的效率大为提高。
STM32F4 最多有 2 个 DMA 控制器(DMA1 和 DMA2), 共 16 个数据流(每个控制器 8个), 每一个 DMA 控制器都用于管理一个或多个外设的存储器访问请求。每个数据流总共可以有多达 8个通道(或称请求)。每个数据流通道都有一个仲裁器,用于处理 DMA 请求间的优先级。
即:DMA是一个协助CPU搬运数据的助手,可以大大减小CPU在搬运数据工作的工作量

DMA 控制器执行直接存储器传输:因为采用 AHB 主总线,它可以控制 AHB 总线矩阵来
启动 AHB 事务。它可以执行下列事务:
1, 外设到存储器的传输
3, 存储器到外设的传输
3, 存储器到存储器的传输
这里特别注意一下,存储器到存储器需要外设接口可以访问存储器,而仅 DMA2 的外设接口可以访问存储器,所以仅 DMA2 控制器支持存储器到存储器的传输, DMA1 不支持。

二、DMA如何使用?

1)使能 DMA2 时钟,并等待数据流可配置。

DMA 的时钟使能是通过 AHB1ENR 寄存器来控制的,这里我们要先使能时钟,才可以配置 DMA
相关寄存器。 HAL 库方法为:

__HAL_RCC_DMA2_CLK_ENABLE();//DMA2 时钟使能
__HAL_RCC_DMA1_CLK_ENABLE();//DMA1 时钟使能

2) 初始化 DMA2 数据流7 ,包括配置通道,外设地址,存储器地址,传输数据量等。

DMA 的某个数据流各种配置参数初始化是通过 HAL_DMA_Init 函数实现的,该函数声明为:

HAL_StatusTypeDef HAL_DMA_Init(DMA_HandleTypeDef *hdma);

该函数只有一个 DMA_HandleTypeDef 结构体指针类型入口参数,结构体定义为:

typedef struct __DMA_HandleTypeDef
{
  DMA_Stream_TypeDef         *Instance;                                                        /*!< Register base address                  */

  DMA_InitTypeDef            Init;                                                             /*!< DMA communication parameters           */ 

  HAL_LockTypeDef            Lock;                                                             /*!< DMA locking object                     */  

  __IO HAL_DMA_StateTypeDef  State;                                                            /*!< DMA transfer state                     */

  void                       *Parent;                                                          /*!< Parent object state                    */ 

  void                       (* XferCpltCallback)( struct __DMA_HandleTypeDef * hdma);         /*!< DMA transfer complete callback         */

  void                       (* XferHalfCpltCallback)( struct __DMA_HandleTypeDef * hdma);     /*!< DMA Half transfer complete callback    */

  void                       (* XferM1CpltCallback)( struct __DMA_HandleTypeDef * hdma);       /*!< DMA transfer complete Memory1 callback */
  
  void                       (* XferM1HalfCpltCallback)( struct __DMA_HandleTypeDef * hdma);   /*!< DMA transfer Half complete Memory1 callback */
  
  void                       (* XferErrorCallback)( struct __DMA_HandleTypeDef * hdma);        /*!< DMA transfer error callback            */
  
  void                       (* XferAbortCallback)( struct __DMA_HandleTypeDef * hdma);        /*!< DMA transfer Abort callback            */  

  __IO uint32_t              ErrorCode;                                                        /*!< DMA Error code                          */
  
  uint32_t                   StreamBaseAddress;                                                /*!< DMA Stream Base Address                */

  uint32_t                   StreamIndex;                                                      /*!< DMA Stream Index                       */
 
}DMA_HandleTypeDef;

        成员变量 Instance 是用来设置寄存器基地址,例如要设置为 DMA2 的数据流 7,那么取值
为 DMA2_Stream7。
        成员变量 Parent 是 HAL 库处理中间变量,用来指向 DMA 通道外设句柄。
        成员变量 XferCpltCallback(传输完成回调函数) , XferHalfCpltCallback(半传输完成
回调函数) , XferM1CpltCallback(Memory1 传输完成回调函数)和 XferErrorCallback(传输
错误回调函数)是四个函数指针,用来指向回调函数入口地址。
        成员变量 StreamBaseAddress 和 StreamIndex 是数据流基地址和索引号,这个是 HAL 库处
理的时候会自动计算,用户无需设置。
        其他成员变量 HAL 库处理过程状态标识变量,这里就不做过多讲解。接下来我们着重看看
成员变量 Init,它是 DMA_InitTypeDef 结构体类型,该结构体定义为:

typedef struct
{
uint32_t Channel; //通道,例如: DMA_CHANNEL_4
uint32_t Direction;//传输方向,例如存储器到外设 DMA_MEMORY_TO_PERIPH
uint32_t PeriphInc;//外设(非)增量模式,非增量模式 DMA_PINC_DISABLE
uint32_t MemInc;//存储器(非)增量模式,增量模式 DMA_MINC_ENABLE
uint32_t PeriphDataAlignment; //外设数据大小: 8/16/32 位。
uint32_t MemDataAlignment; //存储器数据大小: 8/16/32 位。
uint32_t Mode;//模式:外设流控模式/循环模式/普通模式
uint32_t Priority; //DMA 优先级:低/中/高/非常高
uint32_t FIFOMode;//FIFO 模式开启或者禁止
uint32_t FIFOThreshold; //FIFO 阈值选择:
uint32_t MemBurst; //存储器突发模式:单次/4 个节拍/8 个节拍/16 个节拍
uint32_t PeriphBurst; //外设突发模式:单次/4 个节拍/8 个节拍/16 个节拍
}DMA_InitTypeDef;

该结构体成员变量非常多,但是每个成员变量配置的基本都是 DMA_SxCR 寄存器和DMA_SxFCR 寄存器的相应位。我们把结构体各个成员变量的含义都通过注释的方式列出来了。
例如本实验我们要用到 DMA2_Stream7 的 DMA_CHANNEL_4,把内存中数组的值发送到串口外设发送寄存器 DR,所以方向为存储器到外设 DMA_MEMORY_TO_PERIPH,一个一个字节发送,需要数字索引自动增加,所以是存储器增量模式 DMA_MINC_ENABLE,存储器和外设的字宽都是字节 8 位。
具体配置如下:

DMA_HandleTypeDef UART1TxDMA_Handler; //DMA 句柄
UART1TxDMA_Handler.Instance= DMA2_Stream7; //数据流选择
UART1TxDMA_Handler.Init.Channel=DMA_CHANNEL_4; //通道选择
UART1TxDMA_Handler.Init.Direction=DMA_MEMORY_TO_PERIPH; //存储器到外设
UART1TxDMA_Handler.Init.PeriphInc=DMA_PINC_DISABLE; //外设非增量模式
UART1TxDMA_Handler.Init.MemInc=DMA_MINC_ENABLE; //存储器增量模式
UART1TxDMA_Handler.Init.PeriphDataAlignment=DMA_PDATAALIGN_BYTE;//外设: 8 位
UART1TxDMA_Handler.Init.MemDataAlignment=DMA_MDATAALIGN_BYTE; //存储器: 8 位
UART1TxDMA_Handler.Init.Mode=DMA_NORMAL; //普通模式
UART1TxDMA_Handler.Init.Priority=DMA_PRIORITY_MEDIUM; //中等优先级
UART1TxDMA_Handler.Init.FIFOMode=DMA_FIFOMODE_DISABLE;
UART1TxDMA_Handler.Init.FIFOThreshold=DMA_FIFO_THRESHOLD_FULL;
UART1TxDMA_Handler.Init.MemBurst=DMA_MBURST_SINGLE; //存储器突发单次传输
UART1TxDMA_Handler.Init.PeriphBurst=DMA_PBURST_SINGLE; //外设突发单次传输

这里注意, HAL 库为了处理各类外设的 DMA 请求,在调用相关函数之前,需要调用
一个宏定义标识符,来连接 DMA 和外设句柄。例如要使用串口 DMA 发送,所以方式为:

__HAL_LINKDMA(&UART1_Handler,hdmatx,UART1TxDMA_Handler);

UART1_Handler 是串口初始化句柄,我们在 usart.c 中定义过了(具体见上次串口通信文章)。UART1TxDMA_Handler是 DMA 初始化句柄。 hdmatx 是外设句柄结构体的成员变量, 在这里实际就是 UART1_Handler 的成员变量在 HAL 库中,任何一个可以使用 DMA 的外设,它的初始化结构体句柄都会有一个DMA_HandleTypeDef 指针类型的成员变量,是 HAL 库用来做相关指向的。 Hdmatx 就是DMA_HandleTypeDef 结构体指针类型。
这句话的含义就是把UART1_Handler 句柄的成员变 量 hdmatx和DMA句柄
UART1TxDMA_Handler 连接起来,是纯软件处理,没有任何硬件操作。

3)使能串口 1 的 DMA 发送

串口 1 的 DMA 发送实际是串口控制寄存器 CR3 的位 7 来控制的,在 HAL 库中,多次操作该
寄存器来使能串口 DMA 发送,但是它并没有提供一个独立的使能函数,所以这里我们可以通过
直接操作寄存器方式来实现:

USART1->CR3 |= USART_CR3_DMAT;//使能串口 1 的 DMA 发送

HAL 库还提供了对串口的 DMA 发送的停止,暂停,继续等操作函数:

HAL_StatusTypeDef HAL_UART_DMAStop(UART_HandleTypeDef *huart); //停止
HAL_StatusTypeDef HAL_UART_DMAPause(UART_HandleTypeDef *huart); //暂停
HAL_StatusTypeDef HAL_UART_DMAResume(UART_HandleTypeDef *huart);//恢复

4)使能 DMA2 数据流 7,启动传输

使能 DMA 数据流的函数为:

HAL_StatusTypeDef HAL_DMA_Start(DMA_HandleTypeDef *hdma, uint32_t SrcAddress,uint32_t DstAddress, uint32_t DataLength);

这个函数比较好理解,第一个参数是 DMA 句柄,第二个是传输源地址,第三个是传输目标
地址,第四个是传输的数据长度。
通过以上 4 步设置,我们就可以启动一次 USART1 的 DMA 传输了。

5)查询 DMA 传输状态

在 DMA 传输过程中,我们要查询 DMA 传输通道的状态,使用的方法是:

__HAL_DMA_GET_FLAG(&UART1TxDMA_Handler,DMA_FLAG_TCIF3_7);

获取当前传输剩余数据量:

__HAL_DMA_GET_COUNTER(&UART1TxDMA_Handler);

同样,我们也可以设置对应的 DMA 数据流传输的数据量大小,函数为

__HAL_DMA_SET_COUNTER(&UART1TxDMA_Handler,1000);

6) DMA 中断使用方法

DMA 中断对于每个流都有一个中断服务函数,比如 DMA2_Stream7 的中断服务函数为
DMA2_Stream7_IRQHandler。同样, HAL 库也提供了一个通用的 DMA 中断处理函数
HAL_DMA_IRQHandler,在该函数内部,会对 DMA 传输状态进行分析,然后调用相应的中断
处理回调函数:

void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart);//发送完成回调函数
void HAL_UART_TxHalfCpltCallback(UART_HandleTypeDef *huart);/发送一半回调函数
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart);//接收完成回调函数
void HAL_UART_RxHalfCpltCallback(UART_HandleTypeDef *huart);//接收一半回调函数
void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart);//传输出错回调函数

对于串口 DMA 开启,使能数据流,启动传输,这些步骤,如果使用了中断,可以直接调
用 HAL 库函数 HAL_USART_Transmit_DMA,该函数声明如下:

HAL_StatusTypeDef HAL_USART_Transmit_DMA(USART_HandleTypeDef *husart,uint8_t *pTxData, uint16_t Size);

三、代码验证

需要的硬件资源(集成在开发板上):

1) 指示灯 DS0
2) KEY0 按键
3) 串口
4) TFTLCD 模块
5) DMA
我们将利用外部按键 KEY0 来控制 DMA 的传送,每按一次 KEY0, DMA 就传送一次数据到
USART1,然后在 TFTLCD 模块上显示进度等信息。 DS0 用来做为程序运行的指示灯。
需要注意 P6 口的 RXD 和 TXD 是否和 PA9 和 PA10 连接上,如果没有,请先连接。

代码:

dma.h
 

DMA_HandleTypeDef  UART1TxDMA_Handler;      //DMA句柄

//DMAx的各通道配置
//这里的传输形式是固定的,这点要根据不同的情况来修改
//从存储器->外设模式/8位数据宽度/存储器增量模式
//DMA_Streamx:DMA数据流,DMA1_Stream0~7/DMA2_Stream0~7
//chx:DMA通道选择,@ref DMA_channel DMA_CHANNEL_0~DMA_CHANNEL_7
void MYDMA_Config(DMA_Stream_TypeDef *DMA_Streamx,u32 chx)
{ 
	if((u32)DMA_Streamx>(u32)DMA2)//得到当前stream是属于DMA2还是DMA1
	{
        __HAL_RCC_DMA2_CLK_ENABLE();//DMA2时钟使能	
	}else 
	{
        __HAL_RCC_DMA1_CLK_ENABLE();//DMA1时钟使能 
	}
    
    __HAL_LINKDMA(&UART1_Handler,hdmatx,UART1TxDMA_Handler);    //将DMA与USART1联系起来(发送DMA)
    
    //Tx DMA配置
    UART1TxDMA_Handler.Instance=DMA_Streamx;                            //数据流选择
    UART1TxDMA_Handler.Init.Channel=chx;                                //通道选择
    UART1TxDMA_Handler.Init.Direction=DMA_MEMORY_TO_PERIPH;             //存储器到外设
    UART1TxDMA_Handler.Init.PeriphInc=DMA_PINC_DISABLE;                 //外设非增量模式
    UART1TxDMA_Handler.Init.MemInc=DMA_MINC_ENABLE;                     //存储器增量模式
    UART1TxDMA_Handler.Init.PeriphDataAlignment=DMA_PDATAALIGN_BYTE;    //外设数据长度:8位
    UART1TxDMA_Handler.Init.MemDataAlignment=DMA_MDATAALIGN_BYTE;       //存储器数据长度:8位
    UART1TxDMA_Handler.Init.Mode=DMA_NORMAL;                            //外设普通模式
    UART1TxDMA_Handler.Init.Priority=DMA_PRIORITY_MEDIUM;               //中等优先级
    UART1TxDMA_Handler.Init.FIFOMode=DMA_FIFOMODE_DISABLE;              
    UART1TxDMA_Handler.Init.FIFOThreshold=DMA_FIFO_THRESHOLD_FULL;      
    UART1TxDMA_Handler.Init.MemBurst=DMA_MBURST_SINGLE;                 //存储器突发单次传输
    UART1TxDMA_Handler.Init.PeriphBurst=DMA_PBURST_SINGLE;              //外设突发单次传输
    
    HAL_DMA_DeInit(&UART1TxDMA_Handler);   
    HAL_DMA_Init(&UART1TxDMA_Handler);
} 


//开启一次DMA传输
//huart:串口句柄
//pData:传输的数据指针
//Size:传输的数据量
void MYDMA_USART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
{
    HAL_DMA_Start(huart->hdmatx, (u32)pData, (uint32_t)&huart->Instance->DR, Size);//开启DMA传输
    
    huart->Instance->CR3 |= USART_CR3_DMAT;//使能串口DMA发送
}	  

 

该部分代码仅仅 2 个函数, MYDMA_Config 函数,基本上就是按照我们上面介绍的步骤来初
始化 DMA 的, 该函数是一个通用的 DMA 配置函数, DMA1/DMA2 的所有通道,都可以利用该函数配置,不过有些固定参数可能要适当修改(比如位宽,传输方向等)。 该函数在外部只能修改DMA 及数据流编号、 通道号、 外设地址、 存储器地址(SxM0AR)传输数据量等几个参数, 更多的其他设置只能在该函数内部修改。

dma.h

extern DMA_HandleTypeDef  UART1TxDMA_Handler;      //DMA句柄

void MYDMA_Config(DMA_Stream_TypeDef *DMA_Streamx,u32 chx);
void MYDMA_USART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size);

main.h

#define SEND_BUF_SIZE 8200	//发送数据长度,最好等于sizeof(TEXT_TO_SEND)+2的整数倍.

u8 SendBuff[SEND_BUF_SIZE]; //发送数据缓冲区
const u8 TEXT_TO_SEND[]={"ALIENTEK Explorer STM32F4 DMA 串口实验"}; 

int main(void)
{
	u16 i;
	u8 t=0;
	u8 j,mask=0;
	float pro=0; 
	
    HAL_Init();                   	//初始化HAL库    
    Stm32_Clock_Init(336,8,2,7);  	//设置时钟,168Mhz
	delay_init(168);               	//初始化延时函数
	uart_init(115200);             	//初始化USART
	usmart_dev.init(84); 		    //初始化USMART
	LED_Init();						//初始化LED	
	KEY_Init();						//初始化KEY
 	LCD_Init();           			//初始化LCD

    MYDMA_Config(DMA2_Stream7,DMA_CHANNEL_4);//初始化DMA
    POINT_COLOR=RED; 
	LCD_ShowString(30,50,200,16,16,"Explorer STM32F4");	
	LCD_ShowString(30,70,200,16,16,"DMA TEST");	
	LCD_ShowString(30,90,200,16,16,"ATOM@ALIENTEK");
	LCD_ShowString(30,110,200,16,16,"2017/4/13");	 
	LCD_ShowString(30,130,200,16,16,"KEY0:Start"); 
	POINT_COLOR=BLUE;//设置字体为蓝色 
    //显示提示信息	
	j=sizeof(TEXT_TO_SEND);	   
	for(i=0;i<SEND_BUF_SIZE;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(0);
		if(t==KEY0_PRES)  //KEY0按下
		{
			printf("\r\nDMA DATA:\r\n"); 	    
			LCD_ShowString(30,150,200,16,16,"Start Transimit....");
			LCD_ShowString(30,170,200,16,16,"   %") ;     //显示百分号      
		    
			HAL_UART_Transmit_DMA(&UART1_Handler,SendBuff,SEND_BUF_SIZE);//启动传输
			//使能串口1的DMA发送 //等待DMA传输完成,此时我们来做另外一些事,点灯
		    //实际应用中,传输数据期间,可以执行另外的任务
		    while(1)
		    {
                if(__HAL_DMA_GET_FLAG(&UART1TxDMA_Handler,DMA_FLAG_TCIF3_7))//等待DMA2_Steam7传输完成
                {
                    __HAL_DMA_CLEAR_FLAG(&UART1TxDMA_Handler,DMA_FLAG_TCIF3_7);//清除DMA2_Steam7传输完成标志
                    HAL_UART_DMAStop(&UART1_Handler);      //传输完成以后关闭串口DMA
					break; 
                }
				pro=__HAL_DMA_GET_COUNTER(&UART1TxDMA_Handler);//得到当前还剩余多少个数据
				pro=1-pro/SEND_BUF_SIZE;    //得到百分比	  
				pro*=100;      			    //扩大100倍
				LCD_ShowNum(30,170,pro,3,16);	    
		    }
			LCD_ShowNum(30,170,100,3,16);//显示100%	  
		    LCD_ShowString(30,150,200,16,16,"Transimit Finished!");//提示传送完成
		}
		i++;
		delay_ms(10);
		if(i==20)
		{
			LED0=!LED0;//提示系统正在运行	
			i=0;
		}		   
	}
}

main 函数的流程大致是:先初始化内存 SendBuff 的值,然后通过 KEY0 开启串口 DMA 发送,
在发送过程中,通过__HAL_DMA_GET_COUNTER()函数获取当前还剩余的数据量来计算传输百分比,
最后在传输结束之后清除相应标志位,提示已经传输完成。这里还有一点要注意,因为是使用
的串口 1 DMA 发送,所以代码中使用 HAL_UART_Transmit_DMA 函数开启串口的 DMA 发送:
至此, DMA 串口传输的软件设计就完成了。

四、结果


 

针对于stm32f4xx芯片开发使用的hal版本库 /** ****************************************************************************** * @file stm32f4xx_hal.h * @author MCD Application Team * @version V1.4.2 * @date 10-November-2015 * @brief This file contains all the functions prototypes for the HAL * module driver. ****************************************************************************** * @attention * * © COPYRIGHT(c) 2015 STMicroelectronics * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * 3. Neither the name of STMicroelectronics nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * ****************************************************************************** */
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值