【STM32H7教程】第42章 STM32H7的DMA基础知识和HAL库API

2023-04-13  

42.1 初学者重要提示

  1. DMA1和DMA2均支持8路通道。虽然是8路,但这8路不是并行工作的,而是由DMA的仲裁器决定当前处理那一路。

  2. DMA最大传输次数65535次,每次传输单位可以是字节、半字和字。

  3. DMA的循环模式不可用于存储器到存储器模式。

  4. DMA1和DMA2带的FIFO是4个32bit的空间,即16字节。

  5. 使用DMA的FIFO和突发需要注意的问题较多,详情可看本章2.7小节。

  6. STM32H7的参数手册DMA章节对存储器到存储器,外设到存储器,外设到存储器模式的传输过程进行了讲解,推荐大家看完本章节后读一下。

42.2 DMA基础知识

DMA的几个关键知识点放在开头说:

  • 由于总线矩阵的存在,各个主控的道路四通八达,从而可以让DMA和CPU同时开工,但是注意一点,如果他们同时访问的同一个外设,会有一点性能影响的。

  • DMA支持存储器到外设,外设到存储器和存储器到存储器的传输,不支持外设到外设的传输,而BDMA是支持的,这个模式在低功耗模式下比较有用。

  • DMA1和DMA2是有两个AHB总线主控,可以分别用于源地址和目的地址的传输。

  • 源地址和目的地址的数据宽度可以不同,但是数据地址必须要跟其数据类型对齐。比如源地址是uint32类型的,那么此数组的地址必须4字节对齐。

  • DMA主要有两种模式,一个是Normal正常模式,传输一次后就停止传输;另一种是Circular循环模式,会一直循环的传输下去,即使有DMA中断,传输也是一直在进行的。

  • DMA的数据流请求(Stream0 – Stream7)的优先级可编程,分为四级Very high priority,High priority,Medium priority和Low priority。通道的优先级配置相同的情况下,如果同时产生请求,会优先响应编号低的,即Stream0优先响应。

42.2.1 DMA硬件框图

认识一个外设,最好的方式就是看他的框图,方便我们快速的了解DMA的基本功能,然后再看手册了解细节。框图如下所示(DMA1和DMA2是一样的):

通过这个框图,我们可以得到如下信息:

  • dma_str0 – dma_str7

这里是8路来自DMAMUX1的DMA请求信号。

  • dma_it[0:7]接口

通道0 – 通道7的中断触发。

  • dma_tcif[0:7]接口

通道0 – 通道7的传输完成标志,可以用于触发MDMA 。

  • Arbiter仲裁器

用于仲裁当期要处理的DMA请求。通过这里我们可以看出虽然是8路,但这8路不是并行工作的,而是由DMA的仲裁器决定当前处理哪一路。

  • AHB总线接口

DMA1和DMA2有两个接口,可以分别用于源地址和目的地址的传输。

42.2.2 DMA传输

DMA支持如下几种传输模式:

  • 存储器到外设。

  • 外设到存储器。

  • 存储器到存储器。

关于这几种传输方式要注意以下几个问题:

  • 源地址和目的地址的数据宽度可以不同,但是数据地址必须要跟其数据类型对齐。比如源地址是uint32类型的,那么此数组的地址必须4字节对齐。

  • DMA1和DMA2是不支持外设到外设的传输,BDMA是支持的,这个模式在低功耗模式下比较有用。

  • 使用存储器到存储器模式不支持循环传输模式,同时必须开启FIFO,即不支持直接模式(关闭了FIFO就是直接模式Direct mode)。

  • 拓展知识

MDK中全局变量的数据对齐问题说明:

armbbs.cn/forum.php? 。

42.2.3 DMA的循环模式和正常模式

BDMA主要有两种模式,一个是Normal正常模式,传输一次后就停止传输;另一种是Circular循环模式,会一直循环的传输下去,即使有DMA中断,传输也是一直在进行的。

这两种模式各有用途。

  • Normal正常模式

适合用于单次传输,比如存储器到存储器的数据复制粘贴,又比如串口的数据单次发送,下次还需要发送的时候,使能下即可。

  • Circular循环模式

适合用于需要连续传输的场合,比如定时器触发BDMA实现任意IO的PWM输出。

另外特别注意,循环模式不可用于存储器到存储器模式。

42.2.4 DMA数据封装和解封

DMA1和DMA2可以实现源和目标数据宽度不等时的传输,实现的关键是务必开启FIFO。无需像F1系列那样强行要求数据缓冲的4字节对齐。下面是各种源地址和目的地址数据宽度传输的效果,可以帮助大家更好的理解。

这里为大家说下这个图应该如何查看:

  • 首先看NDT(要传输的数据项目列),这一列跟外设传输的项目数是一样的。对应到外设端口地址列,PINCOS=1和PINCOS=0时的传输次数也是一致的。

注:PINGCOS=1表示外设地址偏移固定为4,PINCOS=0表示外设地址的偏移量由用户配置的外设数据宽度,即PSIZE决定。

  • 有了要传输的数据个数,外设端口宽度和存储器端口宽度后

存储器的传输数目 = NDT * 外设端口宽度 / 存储器端口宽度。

有了这两条,大家看上面的表格就方便很多了。最后就是注意PINCOS配置所代表的含义。

42.2.5 DMA双缓冲

DMA1和DMA2支持双缓冲模式的,双缓冲的含义是源地址或者目的地址可以设置两个缓冲区,这种方式的好处是一个缓冲区在接收或者发送数据的时候,另一个缓冲区可以动态更新数据或者处理已经接收到的数据。

当用户开启了DMA传输完成中断后,通过寄存器CCRx的CT位判断当前使用的是哪个缓冲区:

  • 如果CT = 1表示当前正在使用缓冲区1,即寄存器DMA_SxM1AR记录的地址。

  • 如果CT = 0表示当前正在使用缓冲区0,即寄存器DMA_SxM0AR记录的地址。

另外注意,存储器到存储器的DMA传输不支持双缓冲模式,仅可以用于存储器到外设或者外设到存储器。

42.2.6 DMA的FIFO和突发支持

使用DMA的FIFO主要有两个作用,一个是降低总线带宽的需求,另一个是前面说的源地址数据宽度和目的地址数据宽度不同时的数据传输。

而突发传输的含义是每个DMA请求后可以连续传输的数据项目数,支持4次,8次和16次。

了解到以上两点就够用了,现在重点讲解下使用中的注意事项,使用FIFO要注意的事项较多。

  • 禁止FIFO的情况下,即STM32H7参考手册里面所说的直接模式Direct Mode,务必要保证外设数据宽度和内存数据宽度是一样的,而且禁止了FIFO的情况下,不支持突发,即使配置了,也是无效的。

  • 禁止了FIFO的情况下,也不可用于存储器到存储器的数据传输,仅支持外设到存储器或者存储器到外设方式。

  • 使能FIFO的情况下,可以使用突发模式,也可以不使用。

  • 独立的源和目标传输宽度(字节、半字、字):源和目标的数据宽度不相等时, DMA 自动封装/解封必要的传输数据来优化带宽。这个特性仅在 FIFO 模式下可用。这个特性非常重要,在H7使用 SDIO时要用到。无需像F1系列那样强行数据缓冲的4字节对齐要求。

最后要特别注意一点,一般应用中最好关闭FIFO,实际测试发现容易有一些比较奇怪的问题,大家测试的时候要注意。

42.2.7 DMA支持的各种配置

不像BDMA,配置DMA1和DMA2时要注意的事项较多,通过下面的图可以帮助大家方便的验证配置选项是否合理:

42.3 DMA的HAL库用法

DMA的HAL库用法其实就是几个结构体变量成员的配置和使用,然后配置时钟,并根据需要配置NVIC、中断和DMA。下面我们逐一展开为大家做个说明。

42.3.1 DMA寄存器结构体

DMA相关的寄存器是通过HAL库中的结构体DMA_TypeDef和DMA_Stream_TypeDef定义的,在stm32h743xx.h中可以找到这个类型定义:

typedef struct

{

  __IO uint32_t LISR;   /*!< DMA low interrupt status register,      Address offset: 0x00 */

  __IO uint32_t HISR;   /*!< DMA high interrupt status register,     Address offset: 0x04 */

  __IO uint32_t LIFCR;  /*!< DMA low interrupt flag clear register,  Address offset: 0x08 */

  __IO uint32_t HIFCR;  /*!< DMA high interrupt flag clear register, Address offset: 0x0C */

} DMA_TypeDef;


typedef struct

{

  __IO uint32_t CR;     /*!< DMA stream x configuration register      */

  __IO uint32_t NDTR;   /*!< DMA stream x number of data register     */

  __IO uint32_t PAR;    /*!< DMA stream x peripheral address register */

  __IO uint32_t M0AR;   /*!< DMA stream x memory 0 address register   */

  __IO uint32_t M1AR;   /*!< DMA stream x memory 1 address register   */

  __IO uint32_t FCR;    /*!< DMA stream x FIFO control register       */

} DMA_Stream_TypeDef;

__IO表示volatile, 这是标准C语言中的一个修饰字,表示这个变量是非易失性的,编译器不要将其优化掉。core_m7.h 文件定义了这个宏:


#define     __O     volatile             /*!< Defines 'write only' permissions */

#define     __IO    volatile             /*!< Defines 'read / write' permissions */

与其它外设的的定义方式不同,DMA有8组通道,每个通道都有一组DMA_Stream_TypeDef结构体所定义的寄存器。


解决这个问题的办法就是定义一套DMA1_Stream0 - DMA_Stream7来解决,定义在stm32h743xx.h文件。


#define PERIPH_BASE         ((uint32_t)0x40000000)

#define D2_AHB1PERIPH_BASE  (PERIPH_BASE + 0x00020000)

#define DMA1_BASE           (D2_AHB1PERIPH_BASE + 0x0000)

#define DMA2_BASE           (D2_AHB1PERIPH_BASE + 0x0400)

#define DMA1                ((DMA_TypeDef *) DMA1_BASE)

#define DMA2                ((DMA_TypeDef *) DMA2_BASE)


#define DMA1_Stream0_BASE   (DMA1_BASE + 0x010)

#define DMA1_Stream1_BASE   (DMA1_BASE + 0x028)

#define DMA1_Stream2_BASE   (DMA1_BASE + 0x040)

#define DMA1_Stream3_BASE   (DMA1_BASE + 0x058)

#define DMA1_Stream4_BASE   (DMA1_BASE + 0x070)

#define DMA1_Stream5_BASE   (DMA1_BASE + 0x088)

#define DMA1_Stream6_BASE   (DMA1_BASE + 0x0A0)

#define DMA1_Stream7_BASE   (DMA1_BASE + 0x0B8)


#define DMA1_Stream0        ((DMA_Stream_TypeDef *) DMA1_Stream0_BASE)

#define DMA1_Stream1        ((DMA_Stream_TypeDef *) DMA1_Stream1_BASE)

#define DMA1_Stream2        ((DMA_Stream_TypeDef *) DMA1_Stream2_BASE)

#define DMA1_Stream3        ((DMA_Stream_TypeDef *) DMA1_Stream3_BASE)

#define DMA1_Stream4        ((DMA_Stream_TypeDef *) DMA1_Stream4_BASE)

#define DMA1_Stream5        ((DMA_Stream_TypeDef *) DMA1_Stream5_BASE)

#define DMA1_Stream6        ((DMA_Stream_TypeDef *) DMA1_Stream6_BASE)

#define DMA1_Stream7        ((DMA_Stream_TypeDef *) DMA1_Stream7_BASE)


#define DMA1_Stream0_BASE   (DMA1_BASE + 0x010)

#define DMA1_Stream1_BASE   (DMA1_BASE + 0x028)

#define DMA1_Stream2_BASE   (DMA1_BASE + 0x040)

#define DMA1_Stream3_BASE   (DMA1_BASE + 0x058)

#define DMA1_Stream4_BASE   (DMA1_BASE + 0x070)

#define DMA1_Stream5_BASE   (DMA1_BASE + 0x088)

#define DMA1_Stream6_BASE   (DMA1_BASE + 0x0A0)

#define DMA1_Stream7_BASE   (DMA1_BASE + 0x0B8)

#define DMA2_Stream0        ((DMA_Stream_TypeDef *) DMA2_Stream0_BASE)

#define DMA2_Stream1        ((DMA_Stream_TypeDef *) DMA2_Stream1_BASE)

#define DMA2_Stream2        ((DMA_Stream_TypeDef *) DMA2_Stream2_BASE)

#define DMA2_Stream3        ((DMA_Stream_TypeDef *) DMA2_Stream3_BASE)

#define DMA2_Stream4        ((DMA_Stream_TypeDef *) DMA2_Stream4_BASE)

#define DMA2_Stream5        ((DMA_Stream_TypeDef *) DMA2_Stream5_BASE)

#define DMA2_Stream6        ((DMA_Stream_TypeDef *) DMA2_Stream6_BASE)

#define DMA2_Stream7        ((DMA_Stream_TypeDef *) DMA2_Stream7_BASE)

我们访问DMA1的LISR寄存器可以采用这种形式:DMA1->LISR = 0,而访问DMA1 Stream0的CR就可以采用这种形式DMA1_Stream0->CR = 0。


42.3.2 DMA句柄结构体DMA_HandleTypeDef

HAL库在DMA_TypeDef的基础上封装了一个结构体DMA_HandleTypeDef,定义如下:


typedef struct __DMA_HandleTypeDef

{

  void                            *Instance;                                         

  DMA_InitTypeDef                 Init;                                                       

  HAL_LockTypeDef                 Lock;                                                        

  __IO HAL_DMA_StateTypeDef       State;                                                    

  void                            *Parent;                                                       

  void                            (* XferCpltCallback)( struct __DMA_HandleTypeDef * hdma);     

  void                            (* XferHalfCpltCallback)( struct __DMA_HandleTypeDef * hdma);   

  void                            (* XferM1CpltCallback)( struct __DMA_HandleTypeDef * hdma);     

  void                            (* XferM1HalfCpltCallback)( struct __DMA_HandleTypeDef * hdma);  

文章来源于:电子工程世界    原文链接
本站所有转载文章系出于传递更多信息之目的,且明确注明来源,不希望被转载的媒体或个人可与我们联系,我们将立即进行删除处理。