STM32DMA学习记录

一、DMA相关知识介绍

1、什么是DMA?

DMA 全称Direct Memory Access,即直接存储器访问,作用是将数据从一个地址空间复制到另一个地址空间。但是这个传输的动作不需要CPU参与,而是通过DMA控制器进行控制(硬件控制,通过硬件为RAM和IO设备开辟一条直接传输数据的通道)。在CPU初始化完成这个动作以后,在数据需要进行传输时会直接进行这个动作,使得CPU的效率大大提高。

除此之外,每一个DMA请求和外设都是一一对应(DMA请求映射),在满足一定要求下可以触发各个事件的中断(DMA半传输、DMA传输完成、DMA传输错误、DMA FIFO错误、直接模式错误)。

2、STM32的DMA情况?

STM32有多个DMA控制器(不同的芯片不一样,F4最多2个),每个DMA控制器都用于管理一个或者多个外设的存储器访问请求。每个DMA控制器最多有8个数据流(DMA_StreamX),每个数据流最多可以有多达8个通道(或请求),每个数据流有单独的四级 32 位先进先出存储器缓冲区(FIFO),可用于 FIFO 模式或直接模式。每个通道都有一个仲裁器,用于处理DMA请求间的优先级。

DMA数据流请求之间的优先级可用软件编程(4个级别:非常高、高、中、低),在软件优先级相同的情况下可以通过硬件决定优先级(例如,请求О的优先级高于请求1)

FIFO模式:可通过软件将阈值级别选取为FIFO大小的1/4、1/2或3/4。

直接模式:每个DMA请求会立即启动对存储器的传输。当在直接模式(禁止FIFO)下将DMA请求配置为以存储器到外设模式传输数据时,DMA仅会将一个数据从存储器预加载到内部FIFO,从而确保一旦外设触发DMA请求时则立即传输数据。

框图如下:

 3、DMA的传输方向和数据长度是什么?

通过硬件可以将每个数据流配置为:外设到存储器、存储器到外设、存储器到存储器传输的常规通道。也支持在存储器方双缓冲的双缓冲区通道。

要传输的数据项的数目可以由DMA控制器或外设管理:

DMA流控制器:要传输的数据项的数目是1到65535,可用软件编程。

外设流控制器:要传输的数据项的数目未知并由源或目标外设控制,这些外设通过硬件发出传输结束的信号

独立的源和目标传输宽度(字节、半字、字):源和目标的数据宽度不相等时,DMA自动封装/解封必要的传输数据来优化带宽。这个特性仅在FIFO模式下可用

对源和目标的增量或非增量寻址

支持4个、8个和16个节拍的增量突发传输。突发增量的大小可由软件配置,通常等于外设FIFO大小的一半

每个数据流都支持循环缓冲区管理

4、DMA数据流

8个DMA控制器数据流都能够提供源和目标之间的单向传输链路,传输方向有3种。每个数据流配置后都可以执行:

常规类型事务:存储器到外设、外设到存储器或存储器到存储器的传输。

双缓冲区类型事务:使用存储器的两个存储器指针的双缓冲区传输(当DMA正在进行自/至缓冲区的读/写操作时,应用程序可以进行至/自其它缓冲区的写/读操作)。

要传输的数据量(多达65535)可以编程,并与连接到外设AHB端口的外设(请求DMA传输)的源宽度相关。每个事务完成后,包含要传输的数据项总量的寄存器都会递减。

5、DMA事务

DMA事务由给定数目的数据传输序列组成。要传输的数据项的数目及其宽度(8位、16位或32位)可用软件编程。每个DMA传输包含三项操作:

通过DMA_SxPAR或DMA_SxMOAR寄存器寻址,从外设数据寄存器或存储器单元中加载数据。

通过 DMA_SxPAR或 DMA_SxMOAR寄存器寻址,将加载的数据存储到外设数据寄存器或存储器单元。

DMA_SxNDTR计数器在数据存储结束后递减,该计数器中包含仍需执行的事务数。

 6、仲裁器

仲裁器为两个AHB主端口(存储器和外设端口)提供基于请求优先级的8个DMA数据流请求管理,并启动外设/存储器访问序列。

优先级管理分为两个阶段:

软件:每个数据流优先级都可以在 DMA_SxCR寄存器中配置。分为四个级别:非常高优先级—>-高优先级—>中优先级—>低优先级。

硬件:如果两个请求具有相同的软件优先级,则编号低的数据流优先于编号高的数据流。例如,数据流2的优先级高于数据流4。

 7、指针递增

根据DMA_SxCR寄存器中 PINC和 MINC位的状态,外设和存储器指针在每次传输后可以自动向后递增或保持常量。

通过单个寄存器访问外设源或目标数据时,禁止递增模式十分有用。

如果使能了递增模式,则根据在DMA_SxCR寄存器PSIZE或MSIZE位中编程的数据宽度,下一次传输的地址将是前一次传输的地址递增1(对于字节)、2(对于半字)或4(对于字)。

为了优化封装操作,可以不管AHB外设端口上传输的数据的大小,将外设地址的增量偏移大小固定下来。DMA_SxCR寄存器中的PINCOS位用于将增量偏移大小与外设AHB端口或32位地址(此时地址递增4)上的数据大小对齐。PINCOS位仅对AHB外设端口有影响。如果将PINCOS位置1,则不论PSIZE值是多少,下一次传输的地址总是前一次传输的地址递增4(自动与32位地址对齐))。但是,AHB存储器端口不受此操作影响。

如果AHB外设端口或AHB存储器端口分别请求突发事务,为了满足AMBA 协议(在固定地址模式下不允许突发事务),则需要将 PINC 或 MINC位置1。

通俗讲就是在一定场景下发送或接收数据时如果使能了指针递增在每接收一个数据后指针自动递增使数据达到设定长度,随后一次性处理。

8、循环模式

这个模式类似于定时器的自动重装载。在接收到数据后会依次存放,直到全部存满后再重头开始存放。

循环模式可用于处理循环缓冲区和连续数据流(例如ADC扫描模式)。可以使用DMA_SxCR寄存器中的CIRC位使能此特性。

当激活循环模式时,要传输的数据项的数目在数据流配置阶段自动用设置的初始值进行加载,并继续响应DMA请求。

 9、双缓冲区模式

此模式可用于所有DMA1和 DMA2数据流。

通过将DMA_SxCR寄存器中的DBM位置1,即可使能双缓冲区模式。

除了有两个存储器指针之外,双缓冲区数据流的工作方式与常规(单缓冲区)数据流的一样。使能双缓冲区模式时,将自动使能循环模式(DMA_SxCR中的CIRC位的状态是“无关”),并在每次事务结束时交换存储器指针。

在此模式下,每次事务结束时,DMA控制器都从一个存储器目标交换为另一个存储器目标。这样,软件在处理一个存储器区域的同时,DMA传输还可以填充/使用第二个存储器区域。

简单说就是在第一个缓冲区进行数据传输时可以同时对第二个缓冲区进行数据写入,在第一个缓冲区发送完成以后第二缓冲区可以立马进行数据传输从而减少了等待数据写入的过程。

10、DMA配置过程

外设请求—>通道选择—>同步控制—>输出到 DMA 控制器的请求信号

  1. 如果使能了数据流,通过重置DMA_SxCR寄存器中的EN位将其禁止,然后读取此位以确认没有正在进行的数据流操作。将此位写为О不会立即生效,因为实际上只有所有当前传输都已完成时才会将其写为0。当所读取EN位的值为О时,才表示可以配置数据流。因此在开始任何数据流配置之前,需要等待EN位置0。应将先前的数据块 DMA传输中在状态寄存器(DMA_LISR和 DMA_HISR)中置1的所有数据流专用的位置О,然后才可重新使能数据流。
  2. 在 DMA_SxPAR寄存器中设置外设端口寄存器地址。外设事件发生后,数据会从此地址移动到外设端口或从外设端口移动到此地址。
  3. 在DMA_SxMAOR寄存器(在双缓冲区模式的情况下还有DMA_SxMA1R寄存器)中设置存储器地址。外设事件发生后,将从此存储器读取数据或将数据写入此存储器。在 DMA_SxNDTR寄存器中配置要传输的数据项的总数。每出现一次外设事件或每出现一个节拍的突发传输,该值都会递减。
  4. 使用 DMA_SxCR寄存器中的CHSEL[2:0]选择DMA通道(请求)。
  5. 如果外设用作流控制器而且支持此功能,请将DMA_SxCR寄存器中的PFCTRL位置1。
  6. 使用DMA_SxCR寄存器中的PL[1:0]位配置数据流优先级。
  7. 配置FIFO的使用情况(使能或禁止,发送和接收阈值)。
  8. 配置数据传输方向、外设和存储器增量Ⅰ固定模式、单独或突发事务、外设和存储器数
  9. 据宽度、循环模式、双缓冲区模式和传输完成一半和/或全部完成,和/或DMA_SxCR寄存器中错误的中断。
  10. 通过将DMA_SxCR寄存器中的EN位置1激活数据流。

一旦使能了流,即可响应连接到数据流的外设发出的任何DMA请求。

一旦在AHB目标端口上传输了一半数据,传输一半标志(HTIF)便会置1,如果传输一半中断使能位(HTIE)置1,还会生成中断。传输结束时,传输完成标志(TCIF)便会置1,如果传输完成中断使能位(TCIE)置1,还会生成中断。

二、DMA相关函数介绍

1、HAL库配置函数

初始化函数:入口参数为DMA初始化结构体句柄。

DMA初始化函数:HAL_DMA_Init(DMA_HandleTypeDef *hdma);

DMA初始化失能函数:HAL_DMA_DeInit (DMA_HandleTypeDef *hdma);

DMA_HandleTypeDef 结构体参数意义详解,在实现不同功能时选择需要的参数进行赋值并初始化。

 启动DMA函数:入口参数分别为DMA初始化句柄、源地址、目标地址、数据长度。

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

      HAL_DMA_Start_IT(DMA_HandleTypeDef *hdma, uint32_t SrcAddress, uint32_t DstAddress, uint32_t DataLength)

中止DMA传输函数:入口参数为DMA初始化句柄

        HAL_StatusTypeDef HAL_DMA_Abort(DMA_HandleTypeDef *hdma);

        HAL_StatusTypeDef HAL_DMA_Abort_IT(DMA_HandleTypeDef *hdma);

传输轮询完成:入口参数分别为DMA初始化句柄、DMA传输级别、超时时间。

  HAL_StatusTypeDef HAL_DMA_PollForTransfer(DMA_HandleTypeDef *hdma, uint32_t CompleteLevel, uint32_t Timeout);

DMA中断服务函数:入口参数为DMA初始化句柄

        void HAL_DMA_IRQHandler(DMA_HandleTypeDef *hdma);

DMA注册回调函数:

  HAL_StatusTypeDef HAL_DMA_RegisterCallback( DMA_HandleTypeDef *hdma,        HAL_DMA_CallbackIDTypeDef CallbackID,  void (* pCallback)( DMA_HandleTypeDef * _hdma));

DMA注销服务函数:

 HAL_StatusTypeDef HAL_DMA_UnRegisterCallback(DMA_HandleTypeDef *hdma,         HAL_DMA_CallbackIDTypeDef CallbackID);

DMA传输状态查询函数:入口参数为DMA初始化句柄

        HAL_DMA_StateTypeDef HAL_DMA_GetState(DMA_HandleTypeDef *hdma);

DMA错误代码获取函数:入口参数为DMA初始化句柄

        uint32_t HAL_DMA_GetError(DMA_HandleTypeDef *hdma);

获取当前传输剩余数据量: 入口参数为DMA初始化句柄

__HAL_DMA_GET_COUNTER(&UART1TxDMA_Handler);在HAL_对该函数进行了宏定义:((__HANDLE__)->Instance->CNDTR)。在实际使用中我们可以自己编写一个函数,入口参数为DMA初始化结构体句柄,再定义一个变量,将((__HANDLE__) -> Instance -> CNDTR)的值赋给变量,最后再return变量的值。如:

int DMA_Length(DMA_HandleTypeDef *huart)

{

        uint16_t length = 0;

                 length = (huart->Instance->CNDTR);

        return length;

}

2、串口通信与DMA相关的函数

DMA发送函数:入口参数为串口初始化句柄(取地址),发送数据地址、数据长度。

        HAL_StatusTypeDef HAL_UART_Transmit_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size);

DMA接收函数:入口参数为串口初始化句柄(取地址),数据存放地址、数据长度。

        HAL_StatusTypeDef HAL_UART_Receive_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size);

DMA发送暂停函数:

        HAL_StatusTypeDef HAL_UART_DMAPause(UART_HandleTypeDef *huart);

DMA发送恢复函数:入口参数为串口初始化句柄(取地址)

        HAL_StatusTypeDef HAL_UART_DMAResume(UART_HandleTypeDef *huart);

DMA发送停止函数:入口参数为串口初始化句柄(取地址)

        HAL_StatusTypeDef HAL_UART_DMAStop(UART_HandleTypeDef *huart);

三、F103_USART通过DMA实现发送和接收实例讲解

接下来讲解一个DMA实现串口通信的cubemx配置过程及程序修改。我测试的时候使用的是正点原子F103mini开发板,板载芯片为STM32F103RCT6,硬件设计如下: LED1(PA8)   LED0(PD2)  KEY0(PC5,按下低电平)  USART(PA9、PA10),外部晶振8M.

Cubemx配置过程如下:

首先选择芯片,然后配置好系统时钟

接下来配置LED、按键外设引脚。

 配置串口外设功能,使能串口中断和DMA传输(发送和接收都要使能)。

配置定时器2,配置为延时1S,同时使能TIM2中断。

最后修改各中断抢占优先级,定时器中断为2,DMA中断为3,串口中断为4.

最后配置文件工程保存地址,勾选文件分隔以及添加必要库,随后生成工程。

 在Cubemx生成了工程代码以后我们需要做如下几处修改:

编写按键扫描函数,每次按下单次有效,有效时LED1状态翻转同时DMA发送语句“This is a DMA Send Test(key)!”,该函数存放在gpio.c文件中:

编写串口中断回调函数,存放在usart.c文件中。在该函数中指明如果是串口1中断首先调用串口1中断接收函数,随后对接收的数据进行协议判断。在这里我就只判断长度为20平且以换行结尾,如果是就通过DMA打印接收的内容,否则清除:

 在main.c文件开头添加以下代码:

 在main.h文件开头添加以下代码:

在stm32f1xx_it.c文件中,在系统滴答函数中添加sys_time++; 同时在定时器中断中添加如下代码,每进一次定时器中断都将timer_flag变量置1:

最后修改main函数,修改成以下内容.在该函数中,完成了系统初始化以后使能MDA串口接收以及启动定时器,随后在while(1)中循环执行按键扫描、定时器标志位判断和滴答计时判断。在定时器标志位为1时LEO0翻转同时DMA发送语句“This is a DMA  Send Test(timer)!”  在系统滴答定时长度为2500毫秒时LED1翻转同时发送语句“This is a DMA  Send Test(SysTick)!”:

最后编译下载到开发板中可以看到以下现象:LED1每2.5秒变换一次,LED0每1秒变换一次。同时打开串口调试助手可以看到:“This is a DMA  Send Test(timer)!”每秒更新一条,“This is a DMA  Send Test(SysTick)!”每2.5秒更新一条。当按下Key0时,收到语句“This is a DMA Send Test(key)!”。以上都是串口DMA发送的相关内容,这3条语句分别对应了按键触发、定时器触发和系统滴答延时触发这3中DMA发送触发方式。

在接收方面,通过串口调试助手加回车换行向单片机发送“USART1_DMA接收测试”时可以看到串口助手接收到了新语句“USART1_DMA接收测试”。这是在串口回调函数中定义的内容。

在F103板子上测试成功后,随即在野火挑战者V2开发板上进行了测试。野火挑战者V2开发板的单片机为STM32F429IGT6,3个LED分别为:PH10、PH11、PH12(低电平有效)、按键为PA0(按下为高电平),外部晶振为25M。

Cubemx配置图如下:

 

 

 个人增加代码如下:

在stm32f1xx_it.c文件中,在系统滴答函数中添加sys_time++; 同时在定时器中断中添加如下代码,每进一次定时器中断都将timer_flag变量置1:

 以上便是个人增加部分代码,位置和F103工程代码位置一致。最后编译工程,烧录到开发板中,实验现象和F103的现象一致。

四、个人经验总结

1、在进行DMA配置时一定要注意是否编写以下语句:

__HAL_LINKDMA(__HANDLE__, __PPP_DMA_FIELD__, __DMA_HANDLE__);入口参数为:外设初始化结构体, 外设初始化参数(是外设初始化结构体的元素,HAL库自动生成),  DMA初始化结构体。

该语句的作用是将DMA通道句柄和外设句柄连接在一起,其完整语句如下,在stm32f1xx_hal_def.h文件中被定义:

 2、在进行串口DMA发送时一定要使能串口中断,但是可以不编写终端服务函数,否则只能发送一条消息,不能连续发送;

3、在进行串口DMA接收时别忘了在系统初始化完成后加上启动DMA接收的语句:HAL_UART_Receive_DMA(&huart1,DMA_Rx,max_len); 否则将不能进行DMA接收。

4、在串口中断的中断服务函数或者回调函数中一定要使能串口中断接收,否则只能接收一次:HAL_UART_Receive_IT(&huart1,DMA_Rx,max_len);

5、如果涉及到定时器定时发送时可以通过进入定时器中断后对定时器中断标志位进行置位同时在main函数的while(1)中不断判断该标志位,一旦该标志位被置位便立马清除标志位并执行DMA发送语句。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值