完整教程下载地址:http://www.armbbs.cn/forum.php?mod=viewthread&tid=86980
本章节为大家讲解DMA1(Direct memory access controller,直接存储器访问控制器)和DMA2,相比前面章节的BDMA,功能要强些,属于通用型DMA。
目录
第42章 STM32H7的DMA基础知识和HAL库API
42.1 初学者重要提示
42.2 DMA基础知识
42.2.1 DMA硬件框图
42.2.2 DMA传输
42.2.3 DMA的循环模式和正常模式
42.2.4 DMA数据封装和解封
42.2.5 DMA双缓冲
42.2.6 DMA的FIFO和突发支持
42.2.7 DMA支持的各种配置
42.3 DMA的HAL库用法
42.3.1 DMA寄存器结构体
42.3.2 DMA句柄结构体DMA_HandleTypeDef
42.3.3 DMA的状态标志清除问题
42.3.4 DMA初始化流程总结
42.4 源文件stm32h7xx_hal_dma.c
42.4.1 函数HAL_DMA_Init
42.4.2 函数HAL_DMA_Start
42.4.3 函数HAL_DMA_Start_IT
42.4.4 函数HAL_DMAEx_MultiBufferStart
42.4.5 函数HAL_DMAEx_MultiBufferStart_IT
42.5 总结
DMA的几个关键知识点放在开头说:
认识一个外设,最好的方式就是看他的框图,方便我们快速的了解DMA的基本功能,然后再看手册了解细节。框图如下所示(DMA1和DMA2是一样的):
通过这个框图,我们可以得到如下信息:
这里是8路来自DMAMUX1的DMA请求信号。
通道0 – 通道7的中断触发。
通道0 – 通道7的传输完成标志,可以用于触发MDMA 。
用于仲裁当期要处理的DMA请求。通过这里我们可以看出虽然是8路,但这8路不是并行工作的,而是由DMA的仲裁器决定当前处理哪一路。
DMA1和DMA2有两个接口,可以分别用于源地址和目的地址的传输。
DMA支持如下几种传输模式:
关于这几种传输方式要注意以下几个问题:
MDK中全局变量的数据对齐问题说明:
http://www.armbbs.cn/forum.php?mod=viewthread&tid=13511 。
BDMA主要有两种模式,一个是Normal正常模式,传输一次后就停止传输;另一种是Circular循环模式,会一直循环的传输下去,即使有DMA中断,传输也是一直在进行的。
这两种模式各有用途。
适合用于单次传输,比如存储器到存储器的数据复制粘贴,又比如串口的数据单次发送,下次还需要发送的时候,使能下即可。
适合用于需要连续传输的场合,比如定时器触发BDMA实现任意IO的PWM输出。
另外特别注意,循环模式不可用于存储器到存储器模式。
DMA1和DMA2可以实现源和目标数据宽度不等时的传输,实现的关键是务必开启FIFO。无需像F1系列那样强行要求数据缓冲的4字节对齐。下面是各种源地址和目的地址数据宽度传输的效果,可以帮助大家更好的理解。
这里为大家说下这个图应该如何查看:
注:PINGCOS=1表示外设地址偏移固定为4,PINCOS=0表示外设地址的偏移量由用户配置的外设数据宽度,即PSIZE决定。
存储器的传输数目 = NDT * 外设端口宽度 / 存储器端口宽度。
有了这两条,大家看上面的表格就方便很多了。最后就是注意PINCOS配置所代表的含义。
DMA1和DMA2支持双缓冲模式的,双缓冲的含义是源地址或者目的地址可以设置两个缓冲区,这种方式的好处是一个缓冲区在接收或者发送数据的时候,另一个缓冲区可以动态更新数据或者处理已经接收到的数据。
当用户开启了DMA传输完成中断后,通过寄存器CCRx的CT位判断当前使用的是哪个缓冲区:
另外注意,存储器到存储器的DMA传输不支持双缓冲模式,仅可以用于存储器到外设或者外设到存储器。
使用DMA的FIFO主要有两个作用,一个是降低总线带宽的需求,另一个是前面说的源地址数据宽度和目的地址数据宽度不同时的数据传输。
而突发传输的含义是每个DMA请求后可以连续传输的数据项目数,支持4次,8次和16次。
了解到以上两点就够用了,现在重点讲解下使用中的注意事项,使用FIFO要注意的事项较多。
最后要特别注意一点,一般应用中最好关闭FIFO,实际测试发现容易有一些比较奇怪的问题,大家测试的时候要注意。
不像BDMA,配置DMA1和DMA2时要注意的事项较多,通过下面的图可以帮助大家方便的验证配置选项是否合理:
DMA的HAL库用法其实就是几个结构体变量成员的配置和使用,然后配置时钟,并根据需要配置NVIC、中断和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)
<-----展开下面DMA1_Stream0的宏定义,(DMA_Stream_TypeDef *) 0x40020010
#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。
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;
这个参数是用户接触最多的,用于配置DMA的基本参数,像波特率,奇偶校验,停止位等。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;
用于设置支持的DMA请求,对于DMA来说,主要来自DMAMUX1。
/* D2 Domain : DMAMUX1 requests */
#define DMA_REQUEST_MEM2MEM 0U /*!< memory to memory transfer */
#define DMA_REQUEST_GENERATOR0 1U /*!< DMAMUX1 request generator 0 */
#define DMA_REQUEST_GENERATOR1 2U /*!< DMAMUX1 request generator 1 */
#define DMA_REQUEST_GENERATOR2 3U /*!< DMAMUX1 request generator 2 */
#define DMA_REQUEST_GENERATOR3 4U /*!< DMAMUX1 request generator 3 */
#define DMA_REQUEST_GENERATOR4 5U /*!< DMAMUX1 request generator 4 */
#define DMA_REQUEST_GENERATOR5 6U /*!< DMAMUX1 request generator 5 */
#define DMA_REQUEST_GENERATOR6 7U /*!< DMAMUX1 request generator 6 */
#define DMA_REQUEST_GENERATOR7 8U /*!< DMAMUX1 request generator 7 */
#define DMA_REQUEST_ADC1 9U /*!< DMAMUX1 ADC1 request */
#define DMA_REQUEST_ADC2 10U /*!< DMAMUX1 ADC2 request */
#define DMA_REQUEST_TIM1_CH1 11U /*!< DMAMUX1 TIM1 CH1 request */
#define DMA_REQUEST_TIM1_CH2 12U /*!< DMAMUX1 TIM1 CH2 request */
#define DMA_REQUEST_TIM1_CH3 13U /*!< DMAMUX1 TIM1 CH3 request */
#define DMA_REQUEST_TIM1_CH4 14U /*!< DMAMUX1 TIM1 CH4 request */
#define DMA_REQUEST_TIM1_UP 15U /*!< DMAMUX1 TIM1 UP request */
#define DMA_REQUEST_TIM1_TRIG 16U /*!< DMAMUX1 TIM1 TRIG request */
#define DMA_REQUEST_TIM1_COM 17U /*!< DMAMUX1 TIM1 COM request */
中间部分省略未写
#define DMA_REQUEST_TIM16_CH1 109U /*!< DMAMUX1 TIM16 CH1 request */
#define DMA_REQUEST_TIM16_UP 110U /*!< DMAMUX1 TIM16 UP request */
#define DMA_REQUEST_TIM17_CH1 111U /*!< DMAMUX1 TIM17 CH1 request */
#define DMA_REQUEST_TIM17_UP 112U /*!< DMAMUX1 TIM17 UP request */
#define DMA_REQUEST_SAI3_A 113U /*!< DMAMUX1 SAI3 A request */
#define DMA_REQUEST_SAI3_B 114U /*!< DMAMUX1 SAI3 B request */
#define DMA_REQUEST_ADC3 115U /*!< DMAMUX1 ADC3 request */
用于设置传输方向,外设到存储器,存储器到外设或者存储器到存储器,具体支持的参数如下:
#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 DMA_MEMORY_TO_MEMORY ((uint32_t)DMA_SxCR_DIR_1) /*!< Memory to memory direction */
用于设置外设地址是否使能递增,即每完成一次传输,外设地址自增,增加的大小由参数PeriphDataAlignment决定。具体支持的参数如下:
#define DMA_PINC_ENABLE ((uint32_t)DMA_SxCR_PINC) /*!< Peripheral increment mode enable */
#define DMA_PINC_DISABLE ((uint32_t)0x00000000U) /*!< Peripheral increment mode disable */
用于设置存储器地址是否使能递增,即每完成一次传输,存储器地址自增,增加的大小由参数MemDataAlignment决定。具体支持的参数如下:
#define DMA_MINC_ENABLE ((uint32_t)DMA_SxCR_MINC) /*!< Memory increment mode enable */
#define DMA_MINC_DISABLE ((uint32_t)0x00000000U) /*!< Memory increment mode disable */
用于设置外设支持的数据宽度,可以选择字节,半字和字进行传输。
#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 DMA_PDATAALIGN_WORD ((uint32_t)DMA_SxCR_PSIZE_1) /*!< Peripheral data alignment: Word */
用于设置存储器支持的数据宽度,可以选择字节,半字和字进行传输。
#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 DMA_MDATAALIGN_WORD ((uint32_t)DMA_SxCR_MSIZE_1) /*!< Memory data alignment: Word */
用于设置正常模式、循环模式和流控制,对于BDMA而言,仅支持正常模式和循环模式。
#define DMA_NORMAL ((uint32_t)0x00000000U) /*!< Normal mode */
#define DMA_CIRCULAR ((uint32_t)DMA_SxCR_CIRC) /*!< Circular mode */
#define DMA_PFCTRL ((uint32_t)DMA_SxCR_PFCTRL) /*!< Peripheral flow control mode */
用于DMA通道进行传输时的优先级设置,控制多通道同时请求时优先响应谁。支持四种优先级设置:
#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 DMA_PRIORITY_VERY_HIGH ((uint32_t)DMA_SxCR_PL) /*!< Priority level: Very High */
用于配置是否使能FIFO,BDMA不支持FIFO,仅DMA1和DMA2支持。
#define DMA_FIFOMODE_DISABLE ((uint32_t)0x00000000U) /*!< FIFO mode disable */
#define DMA_FIFOMODE_ENABLE ((uint32_t)DMA_SxFCR_DMDIS) /*!< FIFO mode enable */
使能了FIFO后,用于FIFO阀值设置,BDMA不支持此参数,仅DMA1和DMA2支持。可以设置FIFO空间的四分之一,四分之二,四分之三和使用所有空间作为阀值(FIFO总大小是16字节)。
/*!< FIFO threshold 1 quart full configuration */
#define DMA_FIFO_THRESHOLD_1QUARTERFULL ((uint32_t)0x00000000U)
/*!< FIFO threshold half full configuration */
#define DMA_FIFO_THRESHOLD_HALFFULL ((uint32_t)DMA_SxFCR_FTH_0)
/*!< FIFO threshold 3 quarts full configuration */
#define DMA_FIFO_THRESHOLD_3QUARTERSFULL ((uint32_t)DMA_SxFCR_FTH_1)
/*!< FIFO threshold full configuration */
#define DMA_FIFO_THRESHOLD_FULL ((uint32_t)DMA_SxFCR_FTH)
存储器突发配置,必须使能了FIFO才有效,否则设置此参数没有意义,BDMA不支持此参数,仅DMA1,DMA2支持。SINGLE表示每个DMA请求进行1次,INC4表示每个DMA请求进行4次DMA传输。
#define DMA_MBURST_SINGLE ((uint32_t)0x00000000U)
#define DMA_MBURST_INC4 ((uint32_t)DMA_SxCR_MBURST_0)
#define DMA_MBURST_INC8 ((uint32_t)DMA_SxCR_MBURST_1)
#define DMA_MBURST_INC16 ((uint32_t)DMA_SxCR_MBURST)
外设突发配置,必须使能了FIFO才有效,否则设置此参数没有意义,BDMA不支持此参数,仅DMA1,DMA2支持。SINGLE表示每个DMA请求进行1次,INC4表示每个DMA请求进行4次DMA传输。
#define DMA_PBURST_SINGLE ((uint32_t)0x00000000U)
#define DMA_PBURST_INC4 ((uint32_t)DMA_SxCR_PBURST_0)
#define DMA_PBURST_INC8 ((uint32_t)DMA_SxCR_PBURST_1)
#define DMA_PBURST_INC16 ((uint32_t)DMA_SxCR_PBURST)
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半传输完成回调,传输错误回调和传输终止回调。
下面我们介绍__HAL_DMA_GET_FLAG函数。这个函数用来检查定时器标志位是否被设置。
/**
* @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,主要是前三个中断标志。
传输完成标志。
半传输完成标志。
传输错误标志。
直接模式错误标志。
FIFO错误标志。
DMA支持的标志参数如下:
/** @defgroup DMA_flag_definitions DMA flag definitions
* @brief DMA flag definitions
* @{
*/
#define DMA_FLAG_FEIF0_4 ((uint32_t)0x00800001U)
#define DMA_FLAG_DMEIF0_4 ((uint32_t)0x00800004U)
#define DMA_FLAG_TEIF0_4 ((uint32_t)0x00000008U)
#define DMA_FLAG_HTIF0_4 ((uint32_t)0x00000010U)
#define DMA_FLAG_TCIF0_4 ((uint32_t)0x00000020U)
#define DMA_FLAG_FEIF1_5 ((uint32_t)0x00000040U)
#define DMA_FLAG_DMEIF1_5 ((uint32_t)0x00000100U)
#define DMA_FLAG_TEIF1_5 ((uint32_t)0x00000200U)
#define DMA_FLAG_HTIF1_5 ((uint32_t)0x00000400U)
#define DMA_FLAG_TCIF1_5 ((uint32_t)0x00000800U)
#define DMA_FLAG_FEIF2_6 ((uint32_t)0x00010000U)
#define DMA_FLAG_DMEIF2_6 ((uint32_t)0x00040000U)
#define DMA_FLAG_TEIF2_6 ((uint32_t)0x00080000U)
#define DMA_FLAG_HTIF2_6 ((uint32_t)0x00100000U)
#define DMA_FLAG_TCIF2_6 ((uint32_t)0x00200000U)
#define DMA_FLAG_FEIF3_7 ((uint32_t)0x00400000U)
#define DMA_FLAG_DMEIF3_7 ((uint32_t)0x01000000U)
#define DMA_FLAG_TEIF3_7 ((uint32_t)0x02000000U)
#define DMA_FLAG_HTIF3_7 ((uint32_t)0x04000000U)
#define DMA_FLAG_TCIF3_7 ((uint32_t)0x08000000U)
这里要注意以下几点:
Stream4 到 Stream7的标志是通过HISR寄存器查看。
#define DMA_FLAG_DMEIF0_4 ((uint32_t)0x00800004U)
置红的数字8是什么意义?看了参考手册,原来这个bit没有定义,实际上这bit在DMA的库代码中也没有被用到过。
与标志获取函数__HAL_TIM_GET_FLAG对应的清除函数是__HAL_LPTIM_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__)))
清除标志函数所支持的参数跟获取函数是一 一对应的。除了这两个函数,还有DMA的中断开启和中断关闭函数,有时候也要用到。
/**
* @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,比如要操作DMA1的寄存器LISR,直接调用DMA1->LISR操作即可。
使用方法由HAL库提供:
第1步:通过函数HAL_DMA_Init配置各项参数。
第2步:DMA查询方式。
第3步:DMA中断方式。
第4步:使用函数 HAL_DMA_GetState()可以获得DMA状态,函数HAL_DMA_GetError()获取获取错误类型。
第5步:使用函数HAL_DMA_Abort()可以终止DMA传输。
第6步:下面是几个常用的DMA宏定义。
此文件涉及到的函数比较多,这里把我们几个常用的函数做个说明:
函数原型:
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) /*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。
函数参数:
注意事项:
使用举例:
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);
函数原型:
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配置了基础功能后,就可以调用此函数启动DMA了。DMA1,DMA2和BDMA都是用的这个函数。
函数参数:
注意事项:
使用举例:
/*
*********************************************************************************************************
* 函 数 名: 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);
}
函数原型:
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配置了基础功能后,就可以调用此函数启动DMA了。DMA1,DMA2和BDMA都是用的这个函数。
函数参数:
注意事项:
使用举例:
/*
*********************************************************************************************************
* 函 数 名: 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);
}
函数原型:
HAL_StatusTypeDef HAL_DMAEx_MultiBufferStart(DMA_HandleTypeDef *hdma, uint32_t SrcAddress, uint32_t DstAddress, uint32_t SecondMemAddress, uint32_t DataLength)
{
HAL_StatusTypeDef status = HAL_OK;
__IO uint32_t *ifcRegister_Base = NULL;
/* 检测参数 */
assert_param(IS_DMA_BUFFER_SIZE(DataLength));
/* 如果是存储器到存储器的传输,不支持双缓冲 */
if ( (IS_D2_DMA_INSTANCE(hdma) == 0U) || (hdma->Init.Direction == DMA_MEMORY_TO_MEMORY))
{
hdma->ErrorCode = HAL_DMA_ERROR_NOT_SUPPORTED;
status = HAL_ERROR;
}
else
{
/* 上锁 */
__HAL_LOCK(hdma);
if(HAL_DMA_STATE_READY == hdma->State)
{
/* 设置DMA忙 */
hdma->State = HAL_DMA_STATE_BUSY;
/* 设置无错误 */
hdma->ErrorCode = HAL_DMA_ERROR_NONE;
/* 使能DMA双缓冲 */
((DMA_Stream_TypeDef *)hdma->Instance)->CR |= (uint32_t)DMA_SxCR_DBM;
/* 配置DMA Stream的另一个缓冲地址 */
((DMA_Stream_TypeDef *)hdma->Instance)->M1AR = SecondMemAddress;
/* 配置源地址,目的地址和数据长度 */
DMA_MultiBufferSetConfig(hdma, SrcAddress, DstAddress, DataLength);
/* 计算寄存器IFCR地址,因为DMA1和DMA2的这寄存器地址不同,所以要获取下 */
ifcRegister_Base = (uint32_t *)((uint32_t)(hdma->StreamBaseAddress + 8U));
/* 清除所有标志 */
*ifcRegister_Base = 0x3FU << hdma->StreamIndex;
/* 清除DMAMUX同步溢出标志 */
hdma->DMAmuxChannelStatus->CFR = hdma->DMAmuxChannelStatusMask;
if(hdma->DMAmuxRequestGen != 0U)
{
/* 如果使用了DMAMUX的请求发生器,清除请求发生器溢出标志 */
hdma->DMAmuxRequestGenStatus->RGCFR = hdma->DMAmuxRequestGenStatusMask;
}
/* 使能DMA */
__HAL_DMA_ENABLE(hdma);
}
else
{
/* 设置DMA错误标志,DMA忙 */
hdma->ErrorCode = HAL_DMA_ERROR_BUSY;
/* 返回错误状态 */
status = HAL_ERROR;
}
}
return status;
}
函数描述:
调用函数HAL_DMA_Init配置了基础功能后,就可以调用此函数启动DMA双缓冲了。
函数参数:
注意事项:
使用举例:
/*
*********************************************************************************************************
* 函 数 名: bsp_InitTimDMA
* 功能说明: 配置DMAMUX的定时器触+DMA双缓冲控制任意IO做PWM和脉冲数控制
* 形 参: 无
* 返 回 值: 无
*********************************************************************************************************
*/
void bsp_InitTimDMA(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- 使能DMA1时钟并配置 ##################################################*/
__HAL_RCC_DMA1_CLK_ENABLE();
DMA_Handle.Instance = DMA1_Stream1; /* 使用的DMA1 Stream1 */
DMA_Handle.Init.Request = DMA_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; /* 禁止FIFO*/
DMA_Handle.Init.FIFOThreshold = DMA_FIFO_THRESHOLD_FULL; /* 禁止FIFO此位不起作用,用于设置阀值 */
DMA_Handle.Init.MemBurst = DMA_MBURST_SINGLE; /* 禁止FIFO此位不起作用,用于存储器突发 */
DMA_Handle.Init.PeriphBurst = DMA_PBURST_SINGLE; /* 禁止FIFO此位不起作用,用于外设突发 */
/* 初始化DMA */
if(HAL_DMA_Init(&DMA_Handle) != HAL_OK)
{
Error_Handler(__FILE__, __LINE__);
}
/* 开启DMA1 Stream1的中断 */
HAL_NVIC_SetPriority(DMA1_Stream1_IRQn, 2, 0);
HAL_NVIC_EnableIRQ(DMA1_Stream1_IRQn);
/*##-4- 配置DMAMUX ###########################################################*/
dmamux_ReqGenParams.SignalID = HAL_DMAMUX1_REQ_GEN_TIM12_TRGO; /* 请求触发器选择LPTIM2_OUT */
dmamux_ReqGenParams.Polarity = HAL_DMAMUX_REQ_GEN_RISING; /* 上升沿触发 */
dmamux_ReqGenParams.RequestNumber = 1; /* 触发后,传输进行1次DMA传输 */
HAL_DMAEx_ConfigMuxRequestGenerator(&DMA_Handle, &dmamux_ReqGenParams); /* 配置DMAMUX */
HAL_DMAEx_EnableMuxRequestGenerator (&DMA_Handle); /* 使能DMAMUX请求发生器 */
/*##-4- 启动DMA双缓冲传输 ################################################*/
/*
1、此函数会开启DMA的TC,TE和DME中断
2、如果用户配置了回调函数DMA_Handle.XferHalfCpltCallback,那么函数HAL_DMA_Init会开启半传输完成中断。
3、如果用户使用了DMAMUX的同步模式,此函数会开启同步溢出中断。
4、如果用户使用了DMAMUX的请求发生器,此函数会开始请求发生器溢出中断。
*/
HAL_DMAEx_MultiBufferStart(&DMA_Handle, (uint32_t)IO_Toggle, (uint32_t)&GPIOB->BSRRL,(uint32_t)IO_Toggle1, 8);
}
函数原型:
HAL_StatusTypeDef HAL_DMAEx_MultiBufferStart_IT(DMA_HandleTypeDef *hdma, uint32_t SrcAddress, uint32_t DstAddress, uint32_t SecondMemAddress, uint32_t DataLength)
{
HAL_StatusTypeDef status = HAL_OK;
__IO uint32_t *ifcRegister_Base = NULL;
/* 检测参数 */
assert_param(IS_DMA_BUFFER_SIZE(DataLength));
/* 存储器到存储器的传输不支持双缓冲 */
if( (IS_D2_DMA_INSTANCE(hdma) == 0U) || (hdma->Init.Direction == DMA_MEMORY_TO_MEMORY))
{
hdma->ErrorCode = HAL_DMA_ERROR_NOT_SUPPORTED;
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_Stream_TypeDef *)hdma->Instance)->CR |= (uint32_t)DMA_SxCR_DBM;
/* 设置DMA Stream另一个缓冲区地址 */
((DMA_Stream_TypeDef *)hdma->Instance)->M1AR = SecondMemAddress;
* 配置源地址,目的地址和数据长度 */
DMA_MultiBufferSetConfig(hdma, SrcAddress, DstAddress, DataLength);
/* 计算寄存器IFCR地址,因为DMA1和DMA2的这寄存器地址不同,所以要获取下 */
ifcRegister_Base = (uint32_t *)((uint32_t)(hdma->StreamBaseAddress + 8U));
/* 清除所有标志 */
*ifcRegister_Base = 0x3FU << hdma->StreamIndex;
/* 清除DMAMUX同步溢出标志 */
hdma->DMAmuxChannelStatus->CFR = hdma->DMAmuxChannelStatusMask;
if(hdma->DMAmuxRequestGen != 0U)
{
/* 如果使用了DMAMUX的请求发生器,清除请求发生器溢出标志 */
hdma->DMAmuxRequestGenStatus->RGCFR = hdma->DMAmuxRequestGenStatusMask;
}
/* 使能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;
}
/* 检测DMAMUX同步是否已经使能 */
if((hdma->DMAmuxChannel->CCR & DMAMUX_CxCR_SE) != 0)
{
/* 使能DMAMUX同步溢出中断 */
hdma->DMAmuxChannel->CCR |= DMAMUX_CxCR_SOIE;
}
if(hdma->DMAmuxRequestGen != 0U)
{
/* 如果使用DMAMUX请求发生器,使能DMAMUX请求发生器溢出中断 */
hdma->DMAmuxRequestGen->RGCR |= DMAMUX_RGxCR_OIE;
}
/* 使能DMA */
__HAL_DMA_ENABLE(hdma);
}
else
{
/* 设置错误代码,DMA忙 */
hdma->ErrorCode = HAL_DMA_ERROR_BUSY;
/* 返回HAL_ERROR */
status = HAL_ERROR;
}
return status;
}
函数描述:
调用函数HAL_DMA_Init配置了基础功能后,就可以调用此函数启动DMA双缓冲了,中断方式。
函数参数:
注意事项:
使用举例:
/*
*********************************************************************************************************
* 函 数 名: bsp_InitTimDMA
* 功能说明: 配置DMAMUX的定时器触+DMA双缓冲控制任意IO做PWM和脉冲数控制
* 形 参: 无
* 返 回 值: 无
*********************************************************************************************************
*/
void bsp_InitTimDMA(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- 使能DMA1时钟并配置 ##################################################*/
__HAL_RCC_DMA1_CLK_ENABLE();
DMA_Handle.Instance = DMA1_Stream1; /* 使用的DMA1 Stream1 */
DMA_Handle.Init.Request = DMA_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; /* 禁止FIFO*/
DMA_Handle.Init.FIFOThreshold = DMA_FIFO_THRESHOLD_FULL; /* 禁止FIFO此位不起作用,用于设置阀值 */
DMA_Handle.Init.MemBurst = DMA_MBURST_SINGLE; /* 禁止FIFO此位不起作用,用于存储器突发 */
DMA_Handle.Init.PeriphBurst = DMA_PBURST_SINGLE; /* 禁止FIFO此位不起作用,用于外设突发 */
/* 初始化DMA */
if(HAL_DMA_Init(&DMA_Handle) != HAL_OK)
{
Error_Handler(__FILE__, __LINE__);
}
/* 开启DMA1 Stream1的中断 */
HAL_NVIC_SetPriority(DMA1_Stream1_IRQn, 2, 0);
HAL_NVIC_EnableIRQ(DMA1_Stream1_IRQn);
/*##-4- 配置DMAMUX ###########################################################*/
dmamux_ReqGenParams.SignalID = HAL_DMAMUX1_REQ_GEN_TIM12_TRGO; /* 请求触发器选择LPTIM2_OUT */
dmamux_ReqGenParams.Polarity = HAL_DMAMUX_REQ_GEN_RISING; /* 上升沿触发 */
dmamux_ReqGenParams.RequestNumber = 1; /* 触发后,传输进行1次DMA传输 */
HAL_DMAEx_ConfigMuxRequestGenerator(&DMA_Handle, &dmamux_ReqGenParams); /* 配置DMAMUX */
HAL_DMAEx_EnableMuxRequestGenerator (&DMA_Handle); /* 使能DMAMUX请求发生器 */
/*##-4- 启动DMA双缓冲传输 ################################################*/
/*
1、此函数会开启DMA的TC,TE和DME中断
2、如果用户配置了回调函数DMA_Handle.XferHalfCpltCallback,那么函数HAL_DMA_Init会开启半传输完成中断。
3、如果用户使用了DMAMUX的同步模式,此函数会开启同步溢出中断。
4、如果用户使用了DMAMUX的请求发生器,此函数会开始请求发生器溢出中断。
*/
HAL_DMAEx_MultiBufferStart_IT(&DMA_Handle, (uint32_t)IO_Toggle, (uint32_t)&GPIOB->BSRRL,(uint32_t)IO_Toggle1, 8);
}
本章节就为大家讲解这么多,BDMA作为一个重要的外设,务必要熟练掌握。另外注意DMA1,DMA2和BDMA的区别。