完整教程下載地址:http://www.armbbs.cn/forum.php?mod=viewthread&tid=86980
第42章 STM32H7的DMA基礎知識和HAL庫API
本章節爲大家講解DMA1(Direct memory access controller,直接存儲器訪問控制器)和DMA2,相比前面章節的BDMA,功能要強些,屬於通用型DMA。
42.1 初學者重要提示
42.2 DMA基礎知識
42.3 DMA的HAL庫用法
42.4 源文件stm32h7xx_hal_dma.c
42.5 總結
42.1 初學者重要提示
- DMA1和DMA2均支持8路通道。雖然是8路,但這8路不是並行工作的,而是由DMA的仲裁器決定當前處理那一路。
- DMA最大傳輸次數65535次,每次傳輸單位可以是字節、半字和字。
- DMA的循環模式不可用於存儲器到存儲器模式。
- DMA1和DMA2帶的FIFO是4個32bit的空間,即16字節。
- 使用DMA的FIFO和突發需要注意的問題較多,詳情可看本章2.7小節。
- 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中全局變量的數據對齊問題說明:
http://www.armbbs.cn/forum.php?mod=viewthread&tid=13511 。
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) <-----展開下面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。
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); 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;
- 成員Request
用於設置支持的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 */
- 成員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 DMA_MEMORY_TO_MEMORY ((uint32_t)DMA_SxCR_DIR_1) /*!< Memory to memory direction */
- 成員PeriphInc
用於設置外設地址是否使能遞增,即每完成一次傳輸,外設地址自增,增加的大小由參數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 */
- 成員MemInc
用於設置存儲器地址是否使能遞增,即每完成一次傳輸,存儲器地址自增,增加的大小由參數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 */
- 成員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 DMA_PDATAALIGN_WORD ((uint32_t)DMA_SxCR_PSIZE_1) /*!< Peripheral data alignment: Word */
- 成員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 DMA_MDATAALIGN_WORD ((uint32_t)DMA_SxCR_MSIZE_1) /*!< Memory data alignment: Word */
- 成員Mode
用於設置正常模式、循環模式和流控制,對於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 */
- 成員Priority
用於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 */
- 成員FIFOMode
用於配置是否使能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 */
- 成員FIFOThreshold
使能了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)
- 成員MemBurst
存儲器突發配置,必須使能了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)
- 成員PeriphBurst
外設突發配置,必須使能了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半傳輸完成回調,傳輸錯誤回調和傳輸終止回調。
42.3.3 DMA的狀態標誌清除問題
下面我們介紹__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,主要是前三個中斷標誌。
- DMA_FLAG_TCIFx
傳輸完成標誌。
- DMA_FLAG_HTIFx
半傳輸完成標誌。
- DMA_FLAG_TEIFx
傳輸錯誤標誌。
- DMA_FLAG_DMEIFx
直接模式錯誤標誌。
- DMA_FLAG_FEIFx
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)
這裏要注意以下幾點:
- 比如DMA_FLAG_FEIF0_4,表示DMA1或者DMA2的Stream0和Stream4的標誌,而不是Stream0到Stream4,同理1和5就是Srteam1和Stream5。
- DMA1和DMA2的Stream0 到 Stream3標誌是通過LISR寄存器查看(DMA1和DMA2的寄存器是相同的,只是位於不同的地址)。
Stream4 到 Stream7的標誌是通過HISR寄存器查看。
- #define DMA_FLAG_FEIF0_4 ((uint32_t)0x00800001U)
#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操作即可。
42.3.4 DMA初始化流程總結
使用方法由HAL庫提供:
第1步:通過函數HAL_DMA_Init配置各項參數。
第2步:DMA查詢方式。
- 配置了源地址、目的地址和數據長度後,調用函數HAL_DMA_Start()啓動傳輸。
- 使用函數HAL_DMA_PollForTransfer()查詢當前傳輸是否結束,用戶還可以給此函數配置超時等待時間。
第3步:DMA中斷方式。
- 使用函數HAL_NVIC_SetPriority配置DMA優先級。
- 使用函數HAL_NVIC_EnableIRQ使能DMA中斷。
- 配置了源地址、目的地址和數據長度後,調用函數HAL_DMA_Start_IT()可以啓動傳輸(注,此函數會使能BDMA中斷)。
- 將函數HAL_DMA_IRQHandler()填到中斷服務程序DMAx_Streamx_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中斷是否使能
42.4 源文件stm32h7xx_hal_dma.c
此文件涉及到的函數比較多,這裏把我們幾個常用的函數做個說明:
- HAL_DMA_Init
- HAL_DMA_Start
- HAL_DMA_Start_IT
- HAL_DMAEx_MultiBufferStart
- HAL_DMAEx_MultiBufferStart_IT
42.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);
42.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配置了基礎功能後,就可以調用此函數啓動DMA了。DMA1,DMA2和BDMA都是用的這個函數。
函數參數:
- 第1個參數是DMA_HandleTypeDef類型結構體指針變量。
- 第2個參數是DMA傳輸的源地址。
- 第3個參數是DMA傳輸的目的地址。
- 第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); }
42.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配置了基礎功能後,就可以調用此函數啓動DMA了。DMA1,DMA2和BDMA都是用的這個函數。
函數參數:
- 第1個參數是DMA_HandleTypeDef類型結構體指針變量。
- 第2個參數是DMA傳輸的源地址。
- 第3個參數是DMA傳輸的目的地址。
- 第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); }
42.4.4 函數HAL_DMAEx_MultiBufferStart
函數原型:
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雙緩衝了。
函數參數:
- 第1個參數是DMA_HandleTypeDef類型結構體指針變量。
- 第2個參數是DMA傳輸的源地址。
- 第3個參數是DMA傳輸的目的地址。
- 第4個參數是雙緩衝模式時,另一個緩衝的地址。
- 第5個參數是傳輸的數據長度。
- 返回值,返回HAL_ERROR表示配置失敗,HAL_OK表示配置成功,HAL_BUSY表示忙(操作中),HAL_TIMEOUT表示時間溢出。
注意事項:
- 第1個參數的結構體成員介紹在本章的3.2小節進行了詳細說明。
- 存儲器到存儲器的傳輸方式不支持雙緩衝,僅存儲器到外設或者外設到存儲器支持雙緩衝。
- 當前的HAL庫V1.3.0版本未支持BDMA的雙緩衝,僅對DMA1和DMA2做了支持。
使用舉例:
/* ********************************************************************************************************* * 函 數 名: 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); }
42.4.5 函數HAL_DMAEx_MultiBufferStart_IT
函數原型:
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雙緩衝了,中斷方式。
函數參數:
- 第1個參數是DMA_HandleTypeDef類型結構體指針變量。
- 第2個參數是DMA傳輸的源地址。
- 第3個參數是DMA傳輸的目的地址。
- 第4個參數是雙緩衝模式時,另一個緩衝的地址。
- 第5個參數是傳輸的數據長度。
- 返回值,返回HAL_ERROR表示配置失敗,HAL_OK表示配置成功,HAL_BUSY表示忙(操作中),HAL_TIMEOUT表示時間溢出。
注意事項:
- 第1個參數的結構體成員介紹在本章的3.2小節進行了詳細說明。
- 對於DMA1和DMA2,這個函數會開啓TC,TE和MDE中斷,如果註冊了半傳輸完成回調函數,還會開啓半傳輸中斷。
- 如果使用了DMAMUX同步模式,還會開啓同步溢出中斷。
- 如果使用了DMAMUX請求發生器,會開啓請求發生器溢出中斷。
- 存儲器到存儲器的傳輸方式不支持雙緩衝,僅存儲器到外設或者外設到存儲器支持雙緩衝。
- 當前的HAL庫V1.3.0版本未支持BDMA的雙緩衝,僅對DMA1和DMA2做了支持。
使用舉例:
/* ********************************************************************************************************* * 函 數 名: 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); }
42.5 總結
本章節就爲大家講解這麼多,BDMA作爲一個重要的外設,務必要熟練掌握。另外注意DMA1,DMA2和BDMA的區別。