学堂 学堂 学堂公众号手机端

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

lewis 1年前 (2024-04-07) 阅读数 7 #技术

​​​​

第40章 STM32H7的BDMA基础知识和HAL库API

本章节为大家讲解BDMA(Basic direct memory access controller,基本直接存储器访问控制器),相比通用的DMA1和DMA2,BDMA功能稍弱,支持一些基本的DMA功能。

40.1 初学者重要提示


40.2 BDMA基础知识

40.3 BDMA的HAL库用法

40.4 源文件stm32h7xx_hal_dma.c

40.5 总结

40.1 初学者重要提示BDMA只能操作D3域的存储器和外设,这点比较重要,操作的时候容易被遗忘。详情看本章2.6小节。BDMA支持8路通道。虽然是8路,但这8路不是并行工作的,而是由BDMA的仲裁器决定当前处理哪一路。BDMA不支持硬件FIFO,但是支持双缓冲。BDMA不支持突发模式。BDMA最大传输次数65535次,每次传输单位可以是字节、半字和字。BDMA的循环模式不可用于存储器到存储器模式。HAL库没有配套BDMA的双缓,当前的HAL库V1.3.0版本没有对双缓冲进行支持,详情看此贴:​​http://www.armbbs.cn/forum.php?mod=viewthread&tid=91149​​ 。40.2 BDMA基础知识

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

由于总线矩阵的存在,各个主控的道路四通八达,从而可以让DMA和CPU同时开工,但是注意一点,如果他们同时访问的同一个外设,会有一点性能影响的。BDMA支持存储器到外设,外设到存储器,存储器到存储器和外设到外设的传输,其中外设到外设的传输,DMA1和DMA2是不支持的,这个模式在低功耗模式下比较有用。BDMA只有一个AHB总线主控,而DMA1和DMA2是有两个的,可以分别用于源地址和目的地址的传输。源地址和目的地址的数据宽度可以不同,但是数据地址必须要跟其数据类型对齐。比如源地址是uint32类型的,那么此数组的地址必须4字节对齐。BDMA主要有两种模式,一个是Normal正常模式,传输一次后就停止传输;另一种是Circular循环模式,会一直循环的传输下去,即使有DMA中断,传输也是一直在进行的。BDMA的通道请求(Channel0 – Channel7)的优先级可编程,分为四级Very high priority,High priority,Medium priority和Low priority。通道的优先级配置相同的情况下,如果同时产生请求,会优先响应编号低的,即Channel0优先响应。40.2.1 BDMA硬件框图

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


(注:ST做的DMA框图没有其它外设做的好,不够详细)

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

bdma_tcif[0:7]接口

通道0 – 通道7的传输完成标志。

bdma_it[0:7]接口

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

bdma_req[0:7]接口

通道0 –通道7的请求信号接口。

Arbiter仲裁器

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

AHB总线接口

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

DMA1和DMA2:


BDMA:


40.2.2 BDMA传输

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

存储器到外设。外设到存储器。存储器到存储器。外设到外设的传输。

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

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

源地址和目的地址的数据宽度可以不同,但是数据地址必须要跟其数据类型对齐。比如源地址是uint32类型的,那么此数组的地址必须4字节对齐。BDMA不可以操作TCM区,其它的SRAM区均可操作,在第25章专门讲解过这个问题。

拓展知识

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

​​http://www.armbbs.cn/forum.php?mod=viewthread&tid=13511​​ 。

40.2.3 BDMA的循环模式和正常模式

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

这两种模式各有用途。

Normal正常模式

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

Circular循环模式

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

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

40.2.4 BDMA数据封装和解封

独立的源和目标传输宽度(字节、半字、字):源和目标的数据宽度不相等时, DMA 自动封装/解封必要的传输数据来优化带宽。无需像F1系列那样强行要求数据缓冲的4字节对齐。下面是各种源地址和目的地址数据宽度传输4次的效果,可以帮助大家更好的理解。


40.2.5 BDMA双缓冲

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

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

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

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

40.2.6 BDMA可以操作的区域

根据第3章的总线互联方式,BDMA仅可以操作:AHB4,APB4的外设以及SRAM4,Backup RAM。

实际应用的时候要特别注意,防止操作错误。


40.3 BDMA的HAL库用法

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

40.3.1 BDMA寄存器结构体

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

注:当前的HAL库版本V1.3.0对CMAR寄存器的定义不完善,详情看此贴:

​​http://www.armbbs.cn/forum.php?mod=viewthread&tid=91207​​ 。

typedef struct
{
__IO uint32_t ISR; /*!< DMA interrupt status register, Address offset: 0x00 */
__IO uint32_t IFCR; /*!< DMA interrupt flag clear register, Address offset: 0x04 */
} BDMA_TypeDef;

typedef struct
{
__IO uint32_t CCR; /*!< DMA channel x configuration register Addr offset: 0x08 + 0x14 * x,x = 0 to 7 */
__IO uint32_t CNDTR;/*!< DMA channel x number of data register Addr offset: 0x0C + 0x14 * x,x = 0 to 7 */
__IO uint32_t CPAR; /*!< DMA channel x peripheral address register Addr offset: 0x10 + 0x14 * x, x = 0 to 7*/
__IO uint32_t CMAR; /*!< DMA channel x memory address register, Addr offset: 0x14 + 0x14 * x, x = 0 to 7 */
} BDMA_Channel_TypeDef;

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

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

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

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

#define PERIPH_BASE         ((uint32_t)0x40000000)
#define D3_AHB1PERIPH_BASE (PERIPH_BASE + 0x18020000)
#define BDMA_BASE (D3_AHB1PERIPH_BASE + 0x5400)
#define BDMA ((BDMA_TypeDef *) BDMA_BASE)

#define BDMA_Channel0_BASE (BDMA_BASE + 0x0008)
#define BDMA_Channel1_BASE (BDMA_BASE + 0x001C)
#define BDMA_Channel2_BASE (BDMA_BASE + 0x0030)
#define BDMA_Channel3_BASE (BDMA_BASE + 0x0044)
#define BDMA_Channel4_BASE (BDMA_BASE + 0x0058)
#define BDMA_Channel5_BASE (BDMA_BASE + 0x006C)
#define BDMA_Channel6_BASE (BDMA_BASE + 0x0080)
#define BDMA_Channel7_BASE (BDMA_BASE + 0x0094)

<-----展开下面的宏定义,(BDMA_Channel_TypeDef *) 0x58025408
#define BDMA_Channel0 ((BDMA_Channel_TypeDef *) BDMA_Channel0_BASE)
#define BDMA_Channel1 ((BDMA_Channel_TypeDef *) BDMA_Channel1_BASE)
#define BDMA_Channel2 ((BDMA_Channel_TypeDef *) BDMA_Channel2_BASE)
#define BDMA_Channel3 ((BDMA_Channel_TypeDef *) BDMA_Channel3_BASE)
#define BDMA_Channel4 ((BDMA_Channel_TypeDef *) BDMA_Channel4_BASE)
#define BDMA_Channel5 ((BDMA_Channel_TypeDef *) BDMA_Channel5_BASE)
#define BDMA_Channel6 ((BDMA_Channel_TypeDef *) BDMA_Channel6_BASE)
#define

我们访问BDMA的ISR寄存器可以采用这种形式:BDMA->ISR = 0,而访问通道0的CCR就可以采用这种形式BDMA_Channel0->CCR = 0。

40.3.2 BDMA句柄结构体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);
void (* XferErrorCallback)( struct __DMA_HandleTypeDef * hdma);
void (* XferAbortCallback)( struct __DMA_HandleTypeDef * hdma);
__IO uint32_t ErrorCode;
uint32_t StreamBaseAddress;
uint32_t StreamIndex;
DMAMUX_Channel_TypeDef *DMAmuxChannel;
DMAMUX_ChannelStatus_TypeDef *DMAmuxChannelStatus;
uint32_t DMAmuxChannelStatusMask;
DMAMUX_RequestGen_TypeDef *DMAmuxRequestGen;
DMAMUX_RequestGenStatus_TypeDef *DMAmuxRequestGenStatus;
uint32_t DMAmuxRequestGenStatusMask;
}DMA_HandleTypeDef;

这里重点介绍前几个参数,其它参数主要是HAL库内部使用的。

void *Instance

用于BDMA,DMA1和DMA2的例化,主要是相关寄存器的操作。

因为DMA1,DMA2和BDMA都使用的这个结构体句柄,而DMA1,DMA2与BDMA的寄存器结构体封装是不同的,这里的定义比较巧妙, 定义为void *空类型后,就可以直接使用DMA1,DMA2和BDMA的结构体定义了。

比如操作DMA1 Stream1的寄存器CR:

DMA_HandleTypeDef DMA_Handle;

DMA_Handle.Instance = DMA1_Stream1;

((DMA_Stream_TypeDef *) DMA_Handle ->Instance)->CR =0;

又比如操作BDMA Channel1的寄存器CCR:

DMA_HandleTypeDef BDMA_Handle;

BDMA_Handle.Instance = BDMA_Channel1;

((BDMA_Channel_TypeDef *) DMA_Handle ->Instance)->CCR =0;

DMA_InitTypeDef Init;

这个参数是用户接触最多的,用于配置BDMA的基本参数。

DMA_InitTypeDef结构体的定义如下:

typedef struct
{
uint32_t Request;
uint32_t Direction;
uint32_t PeriphInc;
uint32_t MemInc;
uint32_t PeriphDataAlignment;
uint32_t MemDataAlignment;
uint32_t Mode;
uint32_t Priority;
uint32_t FIFOMode;
uint32_t FIFOThreshold;
uint32_t MemBurst;
uint32_t PeriphBurst;
}DMA_InitTypeDef;

成员Request

用于设置支持的DMA请求,对于BDAM来说,主要来自DMAMUX2。

/* D3 Domain : DMAMUX2 requests */
#define BDMA_REQUEST_MEM2MEM 0U /*!< memory to memory transfer */
#define BDMA_REQUEST_GENERATOR0 1U /*!< DMAMUX2 request generator 0 */
#define BDMA_REQUEST_GENERATOR1 2U /*!< DMAMUX2 request generator 1 */
#define BDMA_REQUEST_GENERATOR2 3U /*!< DMAMUX2 request generator 2 */
#define BDMA_REQUEST_GENERATOR3 4U /*!< DMAMUX2 request generator 3 */
#define BDMA_REQUEST_GENERATOR4 5U /*!< DMAMUX2 request generator 4 */
#define BDMA_REQUEST_GENERATOR5 6U /*!< DMAMUX2 request generator 5 */
#define BDMA_REQUEST_GENERATOR6 7U /*!< DMAMUX2 request generator 6 */
#define BDMA_REQUEST_GENERATOR7 8U /*!< DMAMUX2 request generator 7 */
#define BDMA_REQUEST_LPUART1_RX 9U /*!< DMAMUX2 LP_UART1_RX request */
#define BDMA_REQUEST_LPUART1_TX 10U /*!< DMAMUX2 LP_UART1_TX request */
#define BDMA_REQUEST_SPI6_RX 11U /*!< DMAMUX2 SPI6 RX request */
#define BDMA_REQUEST_SPI6_TX 12U /*!< DMAMUX2 SPI6 TX request */
#define BDMA_REQUEST_I2C4_RX 13U /*!< DMAMUX2 I2C4 RX request */
#define BDMA_REQUEST_I2C4_TX 14U /*!< DMAMUX2 I2C4 TX request */
#define BDMA_REQUEST_SAI4_A 15U /*!< DMAMUX2 SAI4 A request */
#define BDMA_REQUEST_SAI4_B 16U /*!< DMAMUX2 SAI4 B request */
#define

成员Direction

用于设置传输方向,外设到存储器、存储器到外设或者存储器到存储器,具体支持的参数如下:

#define DMA_PERIPH_TO_MEMORY         ((uint32_t)0x00000000U)     /*!< Peripheral to memory direction */
#define DMA_MEMORY_TO_PERIPH ((uint32_t)DMA_SxCR_DIR_0) /*!< Memory to peripheral direction */
#define

成员PeriphInc

用于设置外设地址是否使能递增,即每完成一次传输,外设地址自增,增加的大小由参数PeriphDataAlignment决定。具体支持的参数如下:

#define DMA_PINC_ENABLE        ((uint32_t)DMA_SxCR_PINC)  /*!< Peripheral increment mode enable  */
#define

成员MemInc

用于设置存储器地址是否使能递增,即每完成一次传输,存储器地址自增,增加的大小由参数MemDataAlignment决定。具体支持的参数如下:

#define DMA_MINC_ENABLE         ((uint32_t)DMA_SxCR_MINC)  /*!< Memory increment mode enable  */
#define

成员PeriphDataAlignment

用于设置外设支持的数据宽度,可以选择字节、半字和字进行传输。

#define DMA_PDATAALIGN_BYTE     ((uint32_t)0x00000000U)      /*!< Peripheral data alignment: Byte     */
#define DMA_PDATAALIGN_HALFWORD (uint32_t)DMA_SxCR_PSIZE_0) /*!< Peripheral data alignment: HalfWord */
#define

成员MemDataAlignment

用于设置存储器支持的数据宽度,可以选择字节、半字和字进行传输。

#define DMA_MDATAALIGN_BYTE          ((uint32_t)0x00000000U)       /*!< Memory data alignment: Byte     */
#define DMA_MDATAALIGN_HALFWORD ((uint32_t)DMA_SxCR_MSIZE_0) /*!< Memory data alignment: HalfWord */
#define

成员Mode

用于设置正常模式、循环模式和流控制,对于BDMA而言,仅支持正常模式和循环模式。

#define DMA_NORMAL         ((uint32_t)0x00000000U)       /*!< Normal mode                  */
#define DMA_CIRCULAR ((uint32_t)DMA_SxCR_CIRC) /*!< Circular mode */
#define

成员Priority

用于BDMA通道进行传输时的优先级设置,控制多通道同时请求时优先响应谁。支持四种优先级设置。

#define DMA_PRIORITY_LOW             ((uint32_t)0x00000000U)    /*!< Priority level: Low       */
#define DMA_PRIORITY_MEDIUM ((uint32_t)DMA_SxCR_PL_0) /*!< Priority level: Medium */
#define DMA_PRIORITY_HIGH ((uint32_t)DMA_SxCR_PL_1) /*!< Priority level: High */
#define

成员FIFOMode

BDMA不支持FIFO。

成员FIFOThreshold

FIFO阀值设置,BDMA不支持此参数。

成员MemBurst

存储器突发配置,BDMA不支持此参数。

成员PeriphBurst

外设突发配置,BDMA不支持此参数。

HAL_LockTypeDef Lock

__IO HAL_DMA_StateTypeDef State

这两个变量主要供函数内部使用。Lock用于设置锁状态,而State用于设置DMA状态。

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

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

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

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

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

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

这里是定义了六个回调函数指针,分别用于配置传输完成回调,半传输完成回调,Memory1传输完成回调,Memory1半传输完成回调,传输错误回调和传输终止回调。

40.3.3 BDMA的状态标志清除问题

下面我们介绍__HAL_DMA_GET_FLAG函数。这个函数用来检查BDMA标志位是否被设置。

/**
* @brief Get the DMA Stream pending flags.
* @param __HANDLE__: DMA handle
* @param __FLAG__: Get the specified flag.
* This parameter can be any combination of the following values:
* @arg DMA_FLAG_TCIFx: Transfer complete flag.
* @arg DMA_FLAG_HTIFx: Half transfer complete flag.
* @arg DMA_FLAG_TEIFx: Transfer error flag.
* @arg DMA_FLAG_DMEIFx: Direct mode error flag.
* @arg DMA_FLAG_FEIFx: FIFO error flag.
* Where x can be 0_4, 1_5, 2_6 or 3_7 to select the DMA Stream flag.
* @retval The state of FLAG (SET or RESET).
*/
#define __HAL_DMA_GET_FLAG(__HANDLE__, __FLAG__)\
(((uint32_t)((__HANDLE__)->Instance) > (uint32_t)DMA2_Stream7)? (BDMA->ISR & (__FLAG__)) :\
((uint32_t)((__HANDLE__)->Instance) > (uint32_t)DMA2_Stream3)? (DMA2->HISR & (__FLAG__)) :\
((uint32_t)((__HANDLE__)->Instance) > (uint32_t)DMA1_Stream7)? (DMA2->LISR & (__FLAG__)) :\
((uint32_t)((__HANDLE__)->Instance) > (uint32_t)DMA1_Stream3)? (DMA1->HISR & (__FLAG__)) : (DMA1->LISR &
(__FLAG__)))

对于BDMA,主要是前三个中断标志。

DMA_FLAG_TCIFx

传输完成标志。

DMA_FLAG_HTIFx

半传输完成标志。

DMA_FLAG_TEIFx

传输错误标志。

DMA_FLAG_DMEIFx

直接模式错误标志。

DMA_FLAG_FEIFx

FIFO错误标志。

BDMA支持的标志参数如下:

/** @defgroup BDMA_flag_definitions BDMA flag definitions
* @brief BDMA flag definitions
* @{
*/
#define BDMA_FLAG_GL0 ((uint32_t)0x00000001)
#define BDMA_FLAG_TC0 ((uint32_t)0x00000002)
#define BDMA_FLAG_HT0 ((uint32_t)0x00000004)
#define BDMA_FLAG_TE0 ((uint32_t)0x00000008)
#define BDMA_FLAG_GL1 ((uint32_t)0x00000010)
#define BDMA_FLAG_TC1 ((uint32_t)0x00000020)
#define BDMA_FLAG_HT1 ((uint32_t)0x00000040)
#define BDMA_FLAG_TE1 ((uint32_t)0x00000080)
#define BDMA_FLAG_GL2 ((uint32_t)0x00000100)
#define BDMA_FLAG_TC2 ((uint32_t)0x00000200)
#define BDMA_FLAG_HT2 ((uint32_t)0x00000400)
#define BDMA_FLAG_TE2 ((uint32_t)0x00000800)
#define BDMA_FLAG_GL3 ((uint32_t)0x00001000)
#define BDMA_FLAG_TC3 ((uint32_t)0x00002000)
#define BDMA_FLAG_HT3 ((uint32_t)0x00004000)
#define BDMA_FLAG_TE3 ((uint32_t)0x00008000)
#define BDMA_FLAG_GL4 ((uint32_t)0x00010000)
#define BDMA_FLAG_TC4 ((uint32_t)0x00020000)
#define BDMA_FLAG_HT4 ((uint32_t)0x00040000)
#define BDMA_FLAG_TE4 ((uint32_t)0x00080000)
#define BDMA_FLAG_GL5 ((uint32_t)0x00100000)
#define BDMA_FLAG_TC5 ((uint32_t)0x00200000)
#define BDMA_FLAG_HT5 ((uint32_t)0x00400000)
#define BDMA_FLAG_TE5 ((uint32_t)0x00800000)
#define BDMA_FLAG_GL6 ((uint32_t)0x01000000)
#define BDMA_FLAG_TC6 ((uint32_t)0x02000000)
#define BDMA_FLAG_HT6 ((uint32_t)0x04000000)
#define BDMA_FLAG_TE6 ((uint32_t)0x08000000)
#define BDMA_FLAG_GL7 ((uint32_t)0x10000000)
#define BDMA_FLAG_TC7 ((uint32_t)0x20000000)
#define BDMA_FLAG_HT7 ((uint32_t)0x40000000)
#define

与标志获取函数__HAL_DMA_GET_FLAG对应的清除函数是__HAL_DMA_CLEAR_FLAG:

/**
* @brief Clear the DMA Stream pending flags.
* @param __HANDLE__: DMA handle
* @param __FLAG__: specifies the flag to clear.
* This parameter can be any combination of the following values:
* @arg DMA_FLAG_TCIFx: Transfer complete flag.
* @arg DMA_FLAG_HTIFx: Half transfer complete flag.
* @arg DMA_FLAG_TEIFx: Transfer error flag.
* @arg DMA_FLAG_DMEIFx: Direct mode error flag.
* @arg DMA_FLAG_FEIFx: FIFO error flag.
* Where x can be 0_4, 1_5, 2_6 or 3_7 to select the DMA Stream flag.
* @retval None
*/
#define __HAL_DMA_CLEAR_FLAG(__HANDLE__, __FLAG__) \
(((uint32_t)((__HANDLE__)->Instance) > (uint32_t)DMA2_Stream7)? (BDMA->IFCR = (__FLAG__)) :\
((uint32_t)((__HANDLE__)->Instance) > (uint32_t)DMA2_Stream3)? (DMA2->HIFCR = (__FLAG__)) :\
((uint32_t)((__HANDLE__)->Instance) > (uint32_t)DMA1_Stream7)? (DMA2->LIFCR = (__FLAG__)) :\
((uint32_t)((__HANDLE__)->Instance) > (uint32_t)DMA1_Stream3)? (DMA1->HIFCR = (__FLAG__)) : (DMA1->LIFCR = (__FLAG__)))

清除标志函数所支持的参数跟获取函数是一 一对应的。除了这两个函数,还有BDMA的中断开启和中断关闭函数,有时候也要用到。

/**
* @brief Enable the specified DMA Stream interrupts.
* @param __HANDLE__: DMA handle
* @param __INTERRUPT__: specifies the DMA interrupt sources to be enabled or disabled.
* This parameter can be one of the following values:
* @arg DMA_IT_TC: Transfer complete interrupt mask.
* @arg DMA_IT_HT: Half transfer complete interrupt mask.
* @arg DMA_IT_TE: Transfer error interrupt mask.
* @arg DMA_IT_FE: FIFO error interrupt mask.
* @arg DMA_IT_DME: Direct mode error interrupt.
* @retval None
*/
#define __HAL_DMA_ENABLE_IT(__HANDLE__, __INTERRUPT__) ((IS_D2_DMA_INSTANCE(__HANDLE__))?\
(__HAL_DMA_D2_ENABLE_IT((__HANDLE__), (__INTERRUPT__))) :\
(__HAL_DMA_D3_ENABLE_IT((__HANDLE__), (__INTERRUPT__))))

/**
* @brief Disable the specified DMA Stream interrupts.
* @param __HANDLE__: DMA handle
* @param __INTERRUPT__: specifies the DMA interrupt sources to be enabled or disabled.
* This parameter can be one of the following values:
* @arg DMA_IT_TC: Transfer complete interrupt mask.
* @arg DMA_IT_HT: Half transfer complete interrupt mask.
* @arg DMA_IT_TE: Transfer error interrupt mask.
* @arg DMA_IT_FE: FIFO error interrupt mask.
* @arg DMA_IT_DME: Direct mode error interrupt.
* @retval None
*/
#define __HAL_DMA_DISABLE_IT(__HANDLE__, __INTERRUPT__) ((IS_D2_DMA_INSTANCE(__HANDLE__))?\
(__HAL_DMA_D2_DISABLE_IT((__HANDLE__), (__INTERRUPT__))) :\
(__HAL_DMA_D3_DISABLE_IT((__HANDLE__), (__INTERRUPT__))))

注意:操作DMA的寄存器不限制必须要用HAL库提供的API,比如要操作寄存器ISR,直接调用BDMA->ISR操作即可。

40.3.4 BDMA初始化流程总结

使用方法由HAL库提供:

第1步:通过函数HAL_DMA_Init配置各项参数。

第2步:BDMA查询方式。

配置了源地址、目的地址和数据长度后,调用函数HAL_DMA_Start()启动传输。使用函数HAL_DMA_PollForTransfer()查询当前传输是否结束,用户还可以给此函数配置超时等待时间。

第3步:BDMA中断方式。

使用函数HAL_NVIC_SetPriority配置BDMA优先级。使用函数HAL_NVIC_EnableIRQ使能BDMA中断。配置了源地址、目的地址和数据长度后,调用函数HAL_DMA_Start_IT()可以启动传输(注,此函数会使能BDMA中断)。将函数HAL_DMA_IRQHandler()填到中断服务程序BDMA_Channelx_IRQHandler里面。传输结束后会调用函数HAL_DMA_IRQHandler(),此函数里面会执行回调函数,即用户需要为XferCpltCallback,XferErrorCallback等函数配置实体(如果用到的话)。

第4步:使用函数 HAL_DMA_GetState()可以获得DMA状态,函数HAL_DMA_GetError()获取错误类型。


第5步:使用函数HAL_DMA_Abort()可以终止DMA传输。

存储器到存储器方式,不支持循环模式。DMA FIFO的作用是降低对总线的需求和源地址,目的地址不同数据宽度的传输。当FIFO禁止后,不允许配置源数据和目的数据宽度不同,此时将统一使用外设数据宽度。

第6步:下面是几个常用的DMA宏定义。

__HAL_DMA_ENABLE: 使能指定的DMA Stream__HAL_DMA_DISABLE: 禁止指定的DMA Stream__HAL_DMA_GET_FS: 返回当前DMA Stream FIFO填充情况__HAL_DMA_ENABLE_IT: 使能指定的DMA Stream中断__HAL_DMA_DISABLE_IT: 禁止指定的DMA Stream中断__HAL_DMA_GET_IT_SOURCE: 检查指定的DMA Stream中断是否使能

40.4 源文件stm32h7xx_hal_dma.c

此文件涉及到的函数比较多,这里把我们几个常用的函数做个说明:

HAL_DMA_InitHAL_DMA_StartHAL_DMA_Start_IT40.4.1 函数HAL_DMA_Init

函数原型:

HAL_StatusTypeDef HAL_DMA_Init(DMA_HandleTypeDef *hdma)
{
uint32_t registerValue = 0U;
uint32_t tickstart = HAL_GetTick();
DMA_Base_Registers *regs = NULL;

/* 省略 */

/* DMA1或者DMA2的初始化 */
if(IS_D2_DMA_INSTANCE(hdma) != RESET)
{
/* 省略 */
}

/* BDMA的初始 */
else if(IS_D3_DMA_INSTANCE(hdma) != RESET) /*<BDMA channel , D3 domain*/
{
/* 省略 */
}
else
{
hdma->ErrorCode = HAL_DMA_ERROR_PARAM;
hdma->State = HAL_DMA_STATE_ERROR;

return HAL_ERROR;
}

/* 初始化DMAMUX */
DMA_CalcDMAMUXChannelBaseAndMask(hdma);

if(hdma->Init.Direction == DMA_MEMORY_TO_MEMORY)
{
/* 如果是内存到内存模式,强制使用请求DMA_REQUEST_MEM2MEM */
hdma->Init.Request = DMA_REQUEST_MEM2MEM;
}


/* 设置外设请求 */
hdma->DMAmuxChannel->CCR = (hdma->Init.Request & DMAMUX_CxCR_DMAREQ_ID);

/* 清除DMAMUX同步溢出标志 */
hdma->DMAmuxChannelStatus->CFR = hdma->DMAmuxChannelStatusMask;

/* 如果请求类型是DMA_REQUEST_GENERATOR0 到 DMA_REQUEST_GENERATOR7,那么设置请求发生器 */
if((hdma->Init.Request >= DMA_REQUEST_GENERATOR0) && (hdma->Init.Request <= DMA_REQUEST_GENERATOR7))
{
/* 省略 */
}
else
{
hdma->DMAmuxRequestGen = 0U;
hdma->DMAmuxRequestGenStatus = 0U;
hdma->DMAmuxRequestGenStatusMask = 0U;
}

/* 无错误 */
hdma->ErrorCode = HAL_DMA_ERROR_NONE;

/* 设置DMA就绪 */
hdma->State = HAL_DMA_STATE_READY;

return HAL_OK;
}

函数描述:

此函数用于初始化DMA1,DMA2和BDMA。

函数参数:

第1个参数是DMA_HandleTypeDef类型结构体指针变量,用于配置要初始化的参数。结构体变量成员的详细介绍看本章3.2小节。返回值,返回HAL_ERROR表示配置失败,HAL_OK表示配置成功,HAL_BUSY表示忙(操作中),HAL_TIMEOUT表示时间溢出。

注意事项:

第1个参数的结构体成员介绍在本章的3.2小节进行了详细说明。

使用举例:

DMA_HandleTypeDef DMA_Handle = {0};

DMA_Handle.Instance = BDMA_Channel0; /* 使用的BDMA通道0 */
DMA_Handle.Init.Request = BDMA_REQUEST_GENERATOR0; /* 请求类型采用的DMAMUX请求发生器通道0 */
DMA_Handle.Init.Direction = DMA_MEMORY_TO_PERIPH; /* 传输方向是从存储器到外设 */
DMA_Handle.Init.PeriphInc = DMA_PINC_DISABLE; /* 外设地址自增禁止 */
DMA_Handle.Init.MemInc = DMA_MINC_ENABLE; /* 存储器地址自增使能 */
DMA_Handle.Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD; /* 外设数据传输位宽选择字,即32bit */
DMA_Handle.Init.MemDataAlignment = DMA_MDATAALIGN_WORD; /* 存储器数据传输位宽选择字,即32bit */
DMA_Handle.Init.Mode = DMA_CIRCULAR; /* 循环模式 */
DMA_Handle.Init.Priority = DMA_PRIORITY_LOW; /* 优先级低 */
DMA_Handle.Init.FIFOMode = DMA_FIFOMODE_DISABLE; /* BDMA不支持FIFO */
DMA_Handle.Init.FIFOThreshold = DMA_FIFO_THRESHOLD_FULL; /* BDMA不支持FIFO阀值设置 */
DMA_Handle.Init.MemBurst = DMA_MBURST_SINGLE; /* BDMA不支持存储器突发 */
DMA_Handle.Init.PeriphBurst = DMA_PBURST_SINGLE; /* BDMA不支持外设突发 */

HAL_DMA_Init(&DMA_Handle);

40.4.2 函数HAL_DMA_Start

函数原型:

HAL_StatusTypeDef HAL_DMA_Start(DMA_HandleTypeDef *hdma, uint32_t SrcAddress, uint32_t DstAddress, uint32_t DataLength)
{
HAL_StatusTypeDef status = HAL_OK;

/* 检测参数 */
assert_param(IS_DMA_BUFFER_SIZE(DataLength));

/* 检测句柄 */
if(hdma == NULL)
{
return HAL_ERROR;
}

/* 上锁 */
__HAL_LOCK(hdma);

if(HAL_DMA_STATE_READY == hdma->State)
{
/* 设置DMA状态 */
hdma->State = HAL_DMA_STATE_BUSY;

/* 无错误 */
hdma->ErrorCode = HAL_DMA_ERROR_NONE;

/* 禁止DMA */
__HAL_DMA_DISABLE(hdma);

/* 配置源地址,目的地址和数据长度 */
DMA_SetConfig(hdma, SrcAddress, DstAddress, DataLength);

/* 使能DMA */
__HAL_DMA_ENABLE(hdma);
}
else
{
/* 解锁 */
__HAL_UNLOCK(hdma);

/* 设置忙 */
hdma->ErrorCode = HAL_DMA_ERROR_BUSY;

/* 返回HAL_ERROR */
status = HAL_ERROR;
}
return status;
}

函数描述:

调用函数HAL_DMA_Init配置了基础功能后,就可以调用此函数启动BDMA了。DMA1,DMA2和BDMA都是用的这个函数。

函数参数:

第1个参数是DMA_HandleTypeDef类型结构体指针变量。第2个参数是BDMA传输的源地址。第3个参数是BDMA传输的目的地址。第4个参数是传输的数据长度。返回值,返回HAL_ERROR表示配置失败,HAL_OK表示配置成功,HAL_BUSY表示忙(操作中),HAL_TIMEOUT表示时间溢出。

注意事项:

第1个参数的结构体成员介绍在本章的3.2小节进行了详细说明。

使用举例:

/*
*********************************************************************************************************
* 函 数 名: bsp_InitTimBDMA
* 功能说明: 配置DMAMUX的定时器触+DMA控制任意IO做PWM和脉冲数控制
* 形 参: 无
* 返 回 值: 无
*********************************************************************************************************
*/
void bsp_InitTimBDMA(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
DMA_HandleTypeDef DMA_Handle = {0};
HAL_DMA_MuxRequestGeneratorConfigTypeDef dmamux_ReqGenParams ={0};


/*##-1- 配置PB1用于PWM输出######################################*/
__HAL_RCC_GPIOB_CLK_ENABLE();

GPIO_InitStruct.Pin = GPIO_PIN_1;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);


/*##-2- 配置DMA ##################################################*/
__HAL_RCC_BDMA_CLK_ENABLE();

DMA_Handle.Instance = BDMA_Channel0; /* 使用的BDMA通道0 */
DMA_Handle.Init.Request = BDMA_REQUEST_GENERATOR0; /* 请求类型采用的DMAMUX请求发生器通道0 */
DMA_Handle.Init.Direction = DMA_MEMORY_TO_PERIPH; /* 传输方向是从存储器到外设 */
DMA_Handle.Init.PeriphInc = DMA_PINC_DISABLE; /* 外设地址自增禁止 */
DMA_Handle.Init.MemInc = DMA_MINC_ENABLE; /* 存储器地址自增使能 */
DMA_Handle.Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD; /* 外设数据传输位宽选择字,即32bit */
DMA_Handle.Init.MemDataAlignment = DMA_MDATAALIGN_WORD; /* 存储器数据传输位宽选择字,即32bit */
DMA_Handle.Init.Mode = DMA_CIRCULAR; /* 循环模式 */
DMA_Handle.Init.Priority = DMA_PRIORITY_LOW; /* 优先级低 */
DMA_Handle.Init.FIFOMode = DMA_FIFOMODE_DISABLE; /* BDMA不支持FIFO */
DMA_Handle.Init.FIFOThreshold = DMA_FIFO_THRESHOLD_FULL; /* BDMA不支持FIFO阀值设置 */
DMA_Handle.Init.MemBurst = DMA_MBURST_SINGLE; /* BDMA不支持存储器突发 */
DMA_Handle.Init.PeriphBurst = DMA_PBURST_SINGLE; /* BDMA不支持外设突发 */

HAL_DMA_Init(&DMA_Handle);

/* 开启BDMA Channel0的中断 */
HAL_NVIC_SetPriority(BDMA_Channel0_IRQn, 2, 0);
HAL_NVIC_EnableIRQ(BDMA_Channel0_IRQn);

/*##-3- 配置DMAMUX #########################################################*/
dmamux_ReqGenParams.SignalID = HAL_DMAMUX2_REQ_GEN_LPTIM2_OUT; /* 请求触发器选择LPTIM2_OUT */
dmamux_ReqGenParams.Polarity = HAL_DMAMUX_REQ_GEN_RISING_FALLING; /* LPTIM2输出的上升沿和下降沿均可触
发 */
dmamux_ReqGenParams.RequestNumber = 1; /* 触发后,传输进行1次DMA传输 */
HAL_DMAEx_ConfigMuxRequestGenerator(&DMA_Handle, &dmamux_ReqGenParams); /* 配置DMAMUX */
HAL_DMAEx_EnableMuxRequestGenerator (&DMA_Handle); /* 使能DMAMUX请求发生器 */

/*##-4- 启动DMA传输 ################################################*/
HAL_DMA_Start(&DMA_Handle, (uint32_t)IO_Toggle, (uint32_t)&GPIOB->BSRRL, 8);
}

40.4.3 函数HAL_DMA_Start_IT

函数原型:

HAL_StatusTypeDef HAL_DMA_Start_IT(DMA_HandleTypeDef *hdma, uint32_t SrcAddress, uint32_t DstAddress, uint32_t DataLength)
{
HAL_StatusTypeDef status = HAL_OK;

/* 检测参数 */
assert_param(IS_DMA_BUFFER_SIZE(DataLength));

/* 检测句柄 */
if(hdma == NULL)
{
return HAL_ERROR;
}

/* 上锁 */
__HAL_LOCK(hdma);

if(HAL_DMA_STATE_READY == hdma->State)
{
/* 设置DMA忙 */
hdma->State = HAL_DMA_STATE_BUSY;

/* 设置无错误 */
hdma->ErrorCode = HAL_DMA_ERROR_NONE;

/* 禁止DMA */
__HAL_DMA_DISABLE(hdma);

/* 配置DMA源地址,目的地址和数据长度 */
DMA_SetConfig(hdma, SrcAddress, DstAddress, DataLength);

/* DMA1和DMA2配置 */
if(IS_D2_DMA_INSTANCE(hdma) != RESET)
{
/* 使能TC,TE和DME中断 */
MODIFY_REG(((DMA_Stream_TypeDef *)hdma->Instance)->CR, (DMA_IT_TC | DMA_IT_TE | DMA_IT_DME |
DMA_IT_HT), (DMA_IT_TC | DMA_IT_TE | DMA_IT_DME));
((DMA_Stream_TypeDef *)hdma->Instance)->FCR |= DMA_IT_FE;

if(hdma->XferHalfCpltCallback != NULL)
{
/* 如何设置了半传输回调函数,同时开启半传输中断 */
((DMA_Stream_TypeDef *)hdma->Instance)->CR |= DMA_IT_HT;
}
}
else /* BDMA配置 */
{
/* 使能TC和TC中断 */
MODIFY_REG(((BDMA_Channel_TypeDef *)hdma->Instance)->CCR, (BDMA_CCR_TCIE | BDMA_CCR_HTIE |
BDMA_CCR_TEIE), (BDMA_CCR_TCIE | BDMA_CCR_TEIE));

if(hdma->XferHalfCpltCallback != NULL)
{
/* 如何设置了半传输回调函数,同时开启半传输中断 */
((BDMA_Channel_TypeDef *)hdma->Instance)->CCR |= BDMA_CCR_HTIE;
}
}

/* 检测是否使能DMAMUX同步传输 */
if((hdma->DMAmuxChannel->CCR & DMAMUX_CxCR_SE) != 0U)
{
/* 使能了的话,开启同步溢出中断 */
hdma->DMAmuxChannel->CCR |= DMAMUX_CxCR_SOIE;
}

if(hdma->DMAmuxRequestGen != 0U)
{
/* 如果使用了DMAMUX请求发生器,使能请求发生器溢出中断 */
hdma->DMAmuxRequestGen->RGCR |= DMAMUX_RGxCR_OIE;

}

/* 使能DMA */
__HAL_DMA_ENABLE(hdma);
}
else
{
/* 解锁 */
__HAL_UNLOCK(hdma);

/* 设置DMA忙 */
hdma->ErrorCode = HAL_DMA_ERROR_BUSY;

/* 返回HAL_ERROR */
status = HAL_ERROR;
}

return status;
}

函数描述:

调用函数HAL_DMA_Init配置了基础功能后,就可以调用此函数启动BDMA了,采用的中断方式。DMA1,DMA2和BDMA都是用的这个函数。

函数参数:

第1个参数是LPTIM_HandleTypeDef类型结构体指针变量。第2个参数是BDMA传输的源地址。第3个参数是BDMA传输的目的地址。第4个参数是传输的数据长度。返回值,返回HAL_ERROR表示配置失败,HAL_OK表示配置成功,HAL_BUSY表示忙(操作中),HAL_TIMEOUT表示时间溢出。

注意事项:

第1个参数的结构体成员介绍在本章的3.2小节进行了详细说明。对于DMA1和DMA2,这个函数会开启TC,TE和MDE中断,如果注册了半传输完成回调函数,还会开启半传输中断。对于BDMA,这个函数会开始TC和TE中断,如果注册了半传输完成回调函数,还会开启半传输中断。

使用举例:

/*
*********************************************************************************************************
* 函 数 名: bsp_InitTimBDMA
* 功能说明: 配置DMAMUX的定时器触+DMA控制任意IO做PWM和脉冲数控制
* 形 参: 无
* 返 回 值: 无
*********************************************************************************************************
*/
void bsp_InitTimBDMA(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
DMA_HandleTypeDef DMA_Handle = {0};
HAL_DMA_MuxRequestGeneratorConfigTypeDef dmamux_ReqGenParams ={0};


/*##-1- 配置PB1用于PWM输出######################################*/
__HAL_RCC_GPIOB_CLK_ENABLE();

GPIO_InitStruct.Pin = GPIO_PIN_1;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);


/*##-2- 配置DMA ##################################################*/
__HAL_RCC_BDMA_CLK_ENABLE();

DMA_Handle.Instance = BDMA_Channel0; /* 使用的BDMA通道0 */
DMA_Handle.Init.Request = BDMA_REQUEST_GENERATOR0; /* 请求类型采用的DMAMUX请求发生器通道0 */
DMA_Handle.Init.Direction = DMA_MEMORY_TO_PERIPH; /* 传输方向是从存储器到外设 */
DMA_Handle.Init.PeriphInc = DMA_PINC_DISABLE; /* 外设地址自增禁止 */
DMA_Handle.Init.MemInc = DMA_MINC_ENABLE; /* 存储器地址自增使能 */
DMA_Handle.Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD; /* 外设数据传输位宽选择字,即32bit */
DMA_Handle.Init.MemDataAlignment = DMA_MDATAALIGN_WORD; /* 存储器数据传输位宽选择字,即32bit */
DMA_Handle.Init.Mode = DMA_CIRCULAR; /* 循环模式 */
DMA_Handle.Init.Priority = DMA_PRIORITY_LOW; /* 优先级低 */
DMA_Handle.Init.FIFOMode = DMA_FIFOMODE_DISABLE; /* BDMA不支持FIFO */
DMA_Handle.Init.FIFOThreshold = DMA_FIFO_THRESHOLD_FULL; /* BDMA不支持FIFO阀值设置 */
DMA_Handle.Init.MemBurst = DMA_MBURST_SINGLE; /* BDMA不支持存储器突发 */
DMA_Handle.Init.PeriphBurst = DMA_PBURST_SINGLE; /* BDMA不支持外设突发 */

HAL_DMA_Init(&DMA_Handle);

/* 开启BDMA Channel0的中断 */
HAL_NVIC_SetPriority(BDMA_Channel0_IRQn, 2, 0);
HAL_NVIC_EnableIRQ(BDMA_Channel0_IRQn);

/*##-3- 配置DMAMUX #########################################################*/
dmamux_ReqGenParams.SignalID = HAL_DMAMUX2_REQ_GEN_LPTIM2_OUT; /* 请求触发器选择LPTIM2_OUT */
dmamux_ReqGenParams.Polarity = HAL_DMAMUX_REQ_GEN_RISING_FALLING; /* LPTIM2输出的上升沿和下降沿均可触
发 */
dmamux_ReqGenParams.RequestNumber = 1; /* 触发后,传输进行1次DMA传输 */
HAL_DMAEx_ConfigMuxRequestGenerator(&DMA_Handle, &dmamux_ReqGenParams); /* 配置DMAMUX */
HAL_DMAEx_EnableMuxRequestGenerator (&DMA_Handle); /* 使能DMAMUX请求发生器 */

/*##-4- 启动DMA传输 ################################################*/
HAL_DMA_Start_IT(&DMA_Handle, (uint32_t)IO_Toggle, (uint32_t)&GPIOB->BSRRL, 8);
}

40.5 总结

本章节就为大家讲解这么多,BDMA作为一个重要的外设,务必要熟练掌握。

微信公众号:armfly_com


版权声明

本文仅代表作者观点,不代表博信信息网立场。

热门