第21章 DMA—直接存儲區訪問—零死角玩轉STM32-F429系列


第21章     DMA—直接存儲區訪問

全套200集視頻教程和1000PDF教程請到秉火論壇下載:www.firebbs.cn

野火視頻教程優酷觀看網址:http://i.youku.com/firege

 

 

本章參考資料:《STM32F4xx中文參考手冊》DMA控制器章節。

學習本章時,配合《STM32F4xx中文參考手冊》DMA控制器章節一起閱讀,效果會更佳,特別是涉及到寄存器說明的部分。本章內容專業名稱較多,內容豐富也較難理解,但非常有必要細讀研究。

特別說明,本章內容是以STM32F42xxx系列資源講解。

21.1 DMA簡介

DMA(Direct Memory Access,直接存儲區訪問)為實現數據高速在外設寄存器與存儲器之間或者存儲器與存儲器之間傳輸提供了高效的方法。之所以稱之為高效,是因為DMA傳輸實現高速數據移動過程無需任何CPU操作控制。從硬件層次上來說,DMA控制器是獨立於Cortex-M4內核的,有點類似GPIOUSART外設一般,只是DMA的功能是可以快速移動內存數據。

STM32F4xx系列的DMA功能齊全,工作模式眾多,適合不同編程環境要求。STM32F4xx系列的DMA支持外設到存儲器傳輸、存儲器到外設傳輸和存儲器到存儲器傳輸三種傳輸模式。這里的外設一般指外設的數據寄存器,比如ADCSPII2CDCMI等等外設的數據寄存器,存儲器一般是指片內SRAM、外部存儲器、片內Flash等等。

外設到存儲器傳輸就是把外設數據寄存器內容轉移到指定的內存空間。比如進行ADC采集時我們可以利用DMA傳輸把AD轉換數據轉移到我們定義的存儲區中,這樣對於多通道采集、采樣頻率高、連續輸出數據的AD采集是非常高效的處理方法。

存儲區到外設傳輸就是把特定存儲區內容轉移至外設的數據寄存器中,這種多用於外設的發送通信。

存儲器到存儲器傳輸就是把一個指定的存儲區內容拷貝到另一個存儲區空間。功能類似於C語言內存拷貝函數memcpy,利用DMA傳輸可以達到更高的傳輸效率,特別是DMA傳輸是不占用CPU的,可以節省很多CPU資源。

21.2 DMA功能框圖

STM32F4xx系列的DMA可以實現外設寄存器與存儲器之間或者存儲器與存儲器之間傳輸三種模式,這要得益於DMA控制器是采樣AHB主總線的,可以控制AHB總線矩陣來啟動AHB事務。圖 211DMA控制器的框圖。

211 DMA框圖

1.    ①外設通道選擇

STM32F4xx系列資源豐富,具有兩個DMA控制器,同時外設繁多,為實現正常傳輸,DMA需要通道選擇控制。每個DMA控制器具有8個數據流,每個數據流對應8個外設請求。在實現DMA傳輸之前,DMA控制器會通過DMA數據流x配置寄存器DMA_SxCR(x0~7,對應8DMA數據流)CHSEL[2:0]位選擇對應的通道作為該數據流的目標外設。

外設通道選擇要解決的主要問題是決定哪一個外設作為該數據流的源地址或者目標地址。

DMA請求映射情況參考表 211和表 212

211 DMA1請求映射

外設請求

數據流0

數據流1

數據流2

數據流3

數據流4

數據流5

數據流6

數據流7

通道0

SPI3_RX

  

SPI3_RX

SPI2_RX

SPI2_TX

SPI3_TX

  

SPI3_TX

通道1

I2C1_RX

  

TIM7_UP

  

TIM7_UP

I2C1_RX

I2C1_TX

I2C1_TX

通道2

TIM4_CH1

  

I2S3_EXT_RX

TIM4_CH2

I2S2_EXT_TX

I2S3_EXT_TX

TIM4_UP

TIM4_CH3

通道3

I2S3_EXT_RX

TIM2_UP
TIM2_CH3

I2C3_RX

I2S2_EXT_RX

I2C3_TX

TIM2_CH1

TIM2_CH2
TIM2_CH4

TIM2_UP
TIM2_CH4

通道4

UART5_RX

USART3_RX

UART4_RX

USART3_TX

UART4_TX

USART2_RX

USART2_TX

UART5_TX

通道5

UART8_TX

UART7_TX

TIM3_CH4
TIM3_UP

UART7_RX

TIM3_CH1
TIM3_TRIG

TIM3_CH2

UART8_RX

TIM3_CH3

通道6

TIM5_CH3
TIM5_UP

TIM5_CH4
TIM5_TRIG

TIM5_CH1

TIM5_CH4
TIM5_TRIG

TIM5_CH2

  

TIM5_UP

  

通道7

  

TIM6_UP

I2C2_RX

I2C2_RX

USART3_TX

DAC1

DAC2

I2C2_TX

212 DMA2請求映射

外設請求

數據流0

數據流1

數據流2

數據流3

數據流4

數據流5

數據流6

數據流7

通道0

ADC1

  

TIM8_CH1
TIM8_CH2
TIM8_CH3

  

ADC1

  

TIM1_CH1
TIM1_CH2
TIM1_CH3

  

通道1

 

DCMI

ADC2

ADC2

  

SPI6_TX

SPI6_RX

DCMI

通道2

ADC3

ADC3

 

SPI5_TX

SPI5_TX

CRYP_OUT

CRYP_IN

HASH_IN

通道3

SPI1_RX

 

SPI1_RX

SPI1_TX

  

SPI1_TX

  

  

通道4

SPI4_RX

SPI4_TX

USART1_RX

SDIO

  

USART1_RX

SDIO

USART1_TX

通道5

 

USART6_RX

USART6_RX

SPI14_RX

SPI4_TX

  

USART6_TX

USART6_TX

通道6

TIM1_TRIG

TIM1_CH1

TIM1_CH2

TIM1_CH1

TIM1_CH4
TIM1_COM
TIM1_TRIG

TIM1_UP

TIM1_CH3

  

通道7

  

TIM8_UP

TIM8_CH1

TIM8_CH2

TIM8_CH3

SPI5_RX

SPI5_TX

TIM8_CH4
TIM8_TRIG
TIM8_COM

每個外設請求都占用一個數據流通道,相同外設請求可以占用不同數據流通道。比如SPI3_RX請求,即SPI3數據發送請求,占用DMA1的數據流0的通道0,因此當我們使用該請求時,我們需要在把DMA_S0CR寄存器的CHSEL[2:0]設置為"000",此時相同數據流的其他通道不被選擇,處於不可用狀態,比如此時不能使用數據流0的通道1I2C1_RX請求等等。

查閱表 211可以發現SPI3_RX請求不僅僅在數據流0的通道0,同時數據流2的通道0也是SPI3_RX請求,實際上其他外設基本上都有兩個對應數據流通道,這兩個數據流通道都是可選的,這樣設計是盡可能提供多個數據流同時使用情況選擇。

2.    ②仲裁器

一個DMA控制器對應8個數據流,數據流包含要傳輸數據的源地址、目標地址、數據等等信息。如果我們需要同時使用同一個DMA控制器(DMA1DMA2)多個外設請求時,那必然需要同時使用多個數據流,那究竟哪一個數據流具有優先傳輸的權利呢?這就需要仲裁器來管理判斷了。

仲裁器管理數據流方法分為兩個階段。第一階段屬於軟件階段,我們在配置數據流時可以通過寄存器設定它的優先級別,具體配置DMA_SxCR寄存器PL[1:0]位,可以設置為非常高、高、中和低四個級別。第二階段屬於硬件階段,如果兩個或以上數據流軟件設置優先級一樣,則他們優先級取決於數據流編號,編號越低越具有優先權,比如數據流2優先級高於數據流3

3.    ③FIFO

每個數據流都獨立擁有四級32FIFO(先進先出存儲器緩沖區)DMA傳輸具有FIFO模式和直接模式。

直接模式在每個外設請求都立即啟動對存儲器傳輸。在直接模式下,如果DMA配置為存儲器到外設傳輸那DMA會見一個數據存放在FIFO內,如果外設啟動DMA傳輸請求就可以馬上將數據傳輸過去。

FIFO用於在源數據傳輸到目標地址之前臨時存放這些數據。可以通過DMA數據流xFIFO控制寄存器DMA_SxFCRFTH[1:0]位來控制FIFO的閾值,分別為1/41/23/4和滿。如果數據存儲量達到閾值級別時,FIFO內容將傳輸到目標中。

FIFO對於要求源地址和目標地址數據寬度不同時非常有用,比如源數據是源源不斷的字節數據,而目標地址要求輸出字寬度的數據,即在實現數據傳輸時同時把原來48位字節的數據拼湊成一個32位字數據。此時使用FIFO功能先把數據緩存起來,分別根據需要輸出數據。

FIFO另外一個作用使用於突發(burst)傳輸。

4.    ④存儲器端口、⑤外設端口

DMA控制器實現雙AHB主接口,更好利用總線矩陣和並行傳輸。DMA控制器通過存儲器端口和外設端口與存儲器和外設進行數據傳輸,關系見圖 212DMA控制器的功能是快速轉移內存數據,需要一個連接至源數據地址的端口和一個連接至目標地址的端口。

DMA2(DMA控制器2)的存儲器端口和外設端口都是連接到AHB總線矩陣,可以使用AHB總線矩陣功能。DMA2存儲器和外設端口可以訪問相關的內存地址,包括有內部Flash、內部SRAMAHB1外設、AHB2外設、APB2外設和外部存儲器空間。

DMA1的存儲區端口相比DMA2的要減少AHB2外設的訪問權,同時DMA1外設端口是沒有連接至總線矩陣的,只有連接到APB1外設,所以DMA1不能實現存儲器到存儲器傳輸。

212 兩個DMA控制器系統實現

5.    ⑥編程端口

AHB從器件編程端口是連接至AHB2外設的。AHB2外設在使用DMA傳輸時需要相關控制信號。

21.3 DMA數據配置

DMA工作模式多樣,具有多種可能工作模式,具體可能配置見表 213。

213 DMA配置可能情況

DMA傳輸模式

目標

流控制器

循環模式

傳輸類型

直接模式

雙緩沖模式

外設

到存儲器

AHB

外設端口

AHB

存儲器端口

DMA

允許

單次

允許

允許

突發

禁止

外設

禁止

單次

允許

禁止

突發

禁止

存儲器

到外設

AHB

存儲器端口

AHB

外設端口

DMA

允許

單次

允許

允許

突發

禁止

外設

禁止

單次

允許

禁止

突發

禁止

存儲器

到存儲器

AHB

存儲器端口

AHB

存儲器端口

DMA

禁止

單次

禁止

禁止

突發

1.    DMA傳輸模式

DMA2支持全部三種傳輸模式,而DMA1只有外設到存儲器和存儲器到外設兩種模式。模式選擇可以通過DMA_SxCR寄存器的DIR[1:0]位控制,進而將DMA_SxCR寄存器的EN位置1就可以使能DMA傳輸。

DMA_SxCR寄存器的PSIZE[1:0]MSIZE[1:0]位分別指定外設和存儲器數據寬度大小,可以指定為字節(8)、半字(16)和字(32),我們可以根據實際情況設置。直接模式要求外設和存儲器數據寬度大小一樣,實際上在這種模式下DMA數據流直接使用PSIZEMSIZE不被使用。

2.    源地址和目標地址

DMA數據流x外設地址DMA_SxPAR(x0~7)寄存器用來指定外設地址,它是一個32位數據有效寄存器。DMA數據流x存儲器0地址DMA_SxM0AR(x0~7) 寄存器和DMA數據流x存儲器1地址DMA_SxM1AR(x0~7) 寄存器用來存放存儲器地址,其中DMA_SxM1AR只用於雙緩沖模式,DMA_SxM0ARDMA_SxM1AR都是32位數據有效的。

當選擇外設到存儲器模式時,即設置DMA_SxCR寄存器的DIR[1:0] 位為"00",DMA_SxPAR寄存器為外設地址,也是傳輸的源地址,DMA_SxM0AR寄存器為存儲器地址,也是傳輸的目標地址。對於存儲器到存儲器傳輸模式,即設置DIR[1:0] 位為"10"時,采用與外設到存儲器模式相同配置。而對於存儲器到外設,即設置DIR[1:0]位為"01"時,DMA_SxM0AR寄存器作為為源地址,DMA_SxPAR寄存器作為目標地址。

3.    流控制器

流控制器主要涉及到一個控制DMA傳輸停止問題。DMA傳輸在DMA_SxCR寄存器的EN位被置1后就進入准備傳輸狀態,如果有外設請求DMA傳輸就可以進行數據傳輸。很多情況下,我們明確知道傳輸數據的數目,比如要傳1000個或者2000個數據,這樣我們就可以在傳輸之前設置DMA_SxNDTR寄存器為要傳輸數目值,DMA控制器在傳輸完這么多數目數據后就可以控制DMA停止傳輸。

DMA數據流x數據項數DMA_SxNDTR(x0~7)寄存器用來記錄當前仍需要傳輸數目,它是一個16位數據有效寄存器,即最大值為65535,這個值在程序設計是非常有用也是需要注意的地方。我們在編程時一般都會明確指定一個傳輸數量,在完成一次數目傳輸后DMA_SxNDTR計數值就會自減,當達到零時就說明傳輸完成。

如果某些情況下在傳輸之前我們無法確定數據的數目,那DMA就無法自動控制傳輸停止了,此時需要外設通過硬件通信向DMA控制器發送停止傳輸信號。這里有一個大前提就是外設必須是可以發出這個停止傳輸信號,只有SDIO才有這個功能,其他外設不具備此功能。

4.    循環模式

循環模式相對應於一次模式。一次模式就是傳輸一次就停止傳輸,下一次傳輸需要手動控制,而循環模式在傳輸一次后會自動按照相同配置重新傳輸,周而復始直至被控制停止或傳輸發生錯誤。

通過DMA_SxCR寄存器的CIRC位可以使能循環模式。

5.    傳輸類型

DMA傳輸類型有單次(Single)傳輸和突發(Burst)傳輸。突發傳輸就是用非常短時間結合非常高數據信號率傳輸數據,相對正常傳輸速度,突發傳輸就是在傳輸階段把速度瞬間提高,實現高速傳輸,在數據傳輸完成后恢復正常速度,有點類似達到數據塊"秒傳"效果。為達到這個效果突發傳輸過程要占用AHB總線,保證要求每個數據項在傳輸過程不被分割,這樣一次性把數據全部傳輸完才釋放AHB總線;而單次傳輸時必須通過AHB的總線仲裁多次控制才傳輸完成。

單次和突發傳輸數據使用具體情況參考表 214。其中PBURST[1:0]MBURST[1:0]位是位於DMA_SxCR寄存器中的,用於分別設置外設和存儲器不同節拍數的突發傳輸,對應為單次傳輸、4個節拍增量傳輸、8個節拍增量傳輸和16個節拍增量傳輸。PINC位和MINC位是寄存器DMA_SxCR寄存器的第9和第10位,如果位被置1則在每次數據傳輸后數據地址指針自動遞增,其增量由PSIZEMSIZE值決定,比如,設置PSIZE為半字大小,那么下一次傳輸地址將是前一次地址遞增2

214 DMA傳輸類型

AHB主端口

項目

單次傳輸

突發傳輸

外設

寄存器

PBURST[1:0]=00PINC無要求

PBURST[1:0]不為0PINC必須為1

描述

每次DMA請求就傳輸一次字節/半字/(取決於PSIZE)數據

每次DMA請求就傳輸4/8/16(取決於PBURST[1:0])字節/半字/(取決於PSIZE)數據

存儲器

寄存器

MBURST[1:0]=00MINC無要求

MBURST[1:0]不為0MINC必須為1

描述

每次DMA請求就傳輸一次字節/半字/(取決於MSIZE)數據

每次DMA請求就傳輸4/8/16(取決於MBURST[1:0])字節/半字/(取決於MSIZE)數據

突發傳輸與FIFO密切相關,突發傳輸需要結合FIFO使用,具體要求FIFO閾值一定要是內存突發傳輸數據量的整數倍。FIFO閾值選擇和存儲器突發大小必須配合使用,具體參考表 215

215 FIFO閾值配置

MSIZE

FIFO級別

MBURST=INCR4

MBURST=INCR8

MBURST=INCR16

字節

1/4

4個節拍的1次突發

禁止

禁止

1/2

4個節拍的2次突發

8個節拍的1次突發

3/4

4個節拍的3次突發

禁止

滿

4個節拍的4次突發

8個節拍的2次突發

16個節拍的1次突發

半字

1/4

禁止

禁止

禁止

1/2

4個節拍的1次突發

3/4

禁止

滿

4個節拍的2次突發

8個節拍的1次突發

1/4

禁止

禁止

1/2

3/4

滿

4個節拍的1次突發

6.    直接模式

默認情況下,DMA工作在直接模式,不使能FIFO閾值級別。

直接模式在每個外設請求都立即啟動對存儲器傳輸的單次傳輸。直接模式要求源地址和目標地址的數據寬度必須一致,所以只有PSIZE控制,而MSIZE值被忽略。突發傳輸是基於FIFO的所以直接模式不被支持。另外直接模式不能用於存儲器到存儲器傳輸。

在直接模式下,如果DMA配置為存儲器到外設傳輸那DMA會見一個數據存放在FIFO內,如果外設啟動DMA傳輸請求就可以馬上將數據傳輸過去。

7.    雙緩沖模式

設置DMA_SxCR寄存器的DBM位為1可啟動雙緩沖傳輸模式,並自動激活循環模式。雙緩沖不應用與存儲器到存儲器的傳輸。雙緩沖模式下,兩個存儲器地址指針都有效,即DMA_SxM1AR寄存器將被激活使用。開始傳輸使用DMA_SxM0AR寄存器的地址指針所對應的存儲區,當這個存儲區數據傳輸完DMA控制器會自動切換至DMA_SxM1AR寄存器的地址指針所對應的另一塊存儲區,如果這一塊也傳輸完成就再切換至DMA_SxM0AR寄存器的地址指針所對應的存儲區,這樣循環調用。

當其中一個存儲區傳輸完成時都會把傳輸完成中斷標志TCIF位置1,如果我們使能了DMA_SxCR寄存器的傳輸完成中斷,則可以產生中斷信號,這個對我們編程非常有用。另外一個非常有用的信息是DMA_SxCR寄存器的CT位,當DMA控制器是在訪問使用DMA_SxM0ARCT=0,此時CPU不能訪問DMA_SxM0AR,但可以向DMA_SxM1AR填充或者讀取數據;當DMA控制器是在訪問使用DMA_SxM1ARCT=1,此時CPU不能訪問DMA_SxM1AR,但可以向DMA_SxM0AR填充或者讀取數據。另外在未使能DMA數據流傳輸時,可以直接寫CT位,改變開始傳輸的目標存儲區。

雙緩沖模式應用在需要解碼程序的地方是非常有效的。比如MP3格式音頻解碼播放,MP3是被壓縮的文件格式,我們需要特定的解碼庫程序來解碼文件才能得到可以播放的PCM信號,解碼需要一定的實際,按照常規方法是讀取一段原始數據到緩沖區,然后對緩沖區內容進行解碼,解碼后才輸出到音頻播放電路,這種流程對CPU運算速度要求高,很容易出現播放不流暢現象。如果我們使用DMA雙緩沖模式傳輸數據就可以非常好的解決這個問題,達到解碼和輸出音頻數據到音頻電路同步進行的效果。

8.    DMA中斷

每個DMA數據流可以在發送以下事件時產生中斷:

1)    達到半傳輸:DMA數據傳輸達到一半時HTIF標志位被置1,如果使能HTIE中斷控制位將產生達到半傳輸中斷;

2)    傳輸完成:DMA數據傳輸完成時TCIF標志位被置1,如果使能TCIE中斷控制位將產生傳輸完成中斷;

3)    傳輸錯誤:DMA訪問總線發生錯誤或者在雙緩沖模式下試圖訪問"受限"存儲器地址寄存器時TEIF標志位被置1,如果使能TEIE中斷控制位將產生傳輸錯誤中斷;

4)    FIFO錯誤:發生FIFO下溢或者上溢時FEIF標志位被置1,如果使能FEIE中斷控制位將產生FIFO錯誤中斷;

5)    直接模式錯誤:在外設到存儲器的直接模式下,因為存儲器總線沒得到授權,使得先前數據沒有完成被傳輸到存儲器空間上,此時DMEIF標志位被置1,如果使能DMEIE中斷控制位將產生直接模式錯誤中斷。

21.4 DMA初始化結構體詳解

標准庫函數對每個外設都建立了一個初始化結構體xxx_InitTypeDef(xxx為外設名稱),結構體成員用於設置外設工作參數,並由標准庫函數xxx_Init()調用這些設定參數進入設置外設相應的寄存器,達到配置外設工作環境的目的。

結構體xxx_InitTypeDef和庫函數xxx_Init配合使用是標准庫精髓所在,理解了結構體xxx_InitTypeDef每個成員意義基本上就可以對該外設運用自如了。結構體xxx_InitTypeDef定義在stm32f4xx_xxx.h(后面xxx為外設名稱)文件中,庫函數xxx_Init定義在stm32f4xx_xxx.c文件中,編程時我們可以結合這兩個文件內注釋使用。

DMA_ InitTypeDef初始化結構體

1 typedef struct {

2 uint32_t DMA_Channel; //通道選擇

3 uint32_t DMA_PeripheralBaseAddr; //外設地址

4 uint32_t DMA_Memory0BaseAddr; //存儲器0地址

5 uint32_t DMA_DIR; //傳輸方向

6 uint32_t DMA_BufferSize; //數據數目

7 uint32_t DMA_PeripheralInc; //外設遞增

8 uint32_t DMA_MemoryInc; //存儲器遞增

9 uint32_t DMA_PeripheralDataSize; //外設數據寬度

10 uint32_t DMA_MemoryDataSize; //存儲器數據寬度

11 uint32_t DMA_Mode; //模式選擇

12 uint32_t DMA_Priority; //優先級

13 uint32_t DMA_FIFOMode; //FIFO模式

14 uint32_t DMA_FIFOThreshold; //FIFO閾值

15 uint32_t DMA_MemoryBurst; //存儲器突發傳輸

16 uint32_t DMA_PeripheralBurst; //外設突發傳輸

17 } DMA_InitTypeDef;

5)    DMA_Channel:DMA請求通道選擇,可選通道0至通道7,每個外設對應固定的通道,具體設置值需要查表 211和表 212;它設定DMA_SxCR寄存器的CHSEL[2:0]位的值。例如,我們使用模擬數字轉換器ADC3規則采集4個輸入通道的電壓數據,查表 212可知使用通道2。

6)    DMA_PeripheralBaseAddr:外設地址,設定DMA_SxPAR寄存器的值;一般設置為外設的數據寄存器地址,如果是存儲器到存儲器模式則設置為其中一個存儲區地址。ADC3的數據寄存器ADC_DR地址為((uint32_t)ADC3+0x4C)。

7)    DMA_Memory0BaseAddr:存儲器0地址,設定DMA_SxM0AR寄存器值;一般設置為我們自定義存儲區的首地址。我們程序先自定義一個16位無符號整形數組ADC_ConvertedValue[4]用來存放每個通道的ADC值,所以把數組首地址(直接使用數組名即可)賦值給DMA_Memory0BaseAddr。

8)    DMA_DIR:傳輸方向選擇,可選外設到存儲器、存儲器到外設以及存儲器到存儲器。它設定DMA_SxCR寄存器的DIR[1:0]位的值。ADC采集顯然使用外設到存儲器模式。

9)    DMA_BufferSize:設定待傳輸數據數目,初始化設定DMA_SxNDTR寄存器的值。這里ADC是采集4個通道數據,所以待傳輸數目也就是4。

10)    DMA_PeripheralInc:如果配置為DMA_PeripheralInc_Enable,使能外設地址自動遞增功能,它設定DMA_SxCR寄存器的PINC位的值;一般外設都是只有一個數據寄存器,所以一般不會使能該位。ADC3的數據寄存器地址是固定並且只有一個所以不使能外設地址遞增。

11)    DMA_MemoryInc:如果配置為DMA_MemoryInc_Enable,使能存儲器地址自動遞增功能,它設定DMA_SxCR寄存器的MINC位的值;我們自定義的存儲區一般都是存放多個數據的,所以使能存儲器地址自動遞增功能。我們之前已經定義了一個包含4個元素的數字用來存放數據,使能存儲區地址遞增功能,自動把每個通道數據存放到對應數組元素內。

12)    DMA_PeripheralDataSize:外設數據寬度,可選字節(8位)、半字(16位)和字(32位),它設定DMA_SxCR寄存器的PSIZE[1:0]位的值。ADC數據寄存器只有低16位數據有效,使用半字數據寬度。

13)    DMA_MemoryDataSize:存儲器數據寬度,可選字節(8位)、半字(16位)和字(32位),它設定DMA_SxCR寄存器的MSIZE[1:0]位的值。保存ADC轉換數據也要使用半字數據寬度,這跟我們定義的數組是相對應的。

14)    DMA_Mode:DMA傳輸模式選擇,可選一次傳輸或者循環傳輸,它設定DMA_SxCR寄存器的CIRC位的值。我們希望ADC采集是持續循環進行的,所以使用循環傳輸模式。

15)    DMA_Priority:軟件設置數據流的優先級,有4個可選優先級分別為非常高、高、中和低,它設定DMA_SxCR寄存器的PL[1:0]位的值。DMA優先級只有在多個DMA數據流同時使用時才有意義,這里我們設置為非常高優先級就可以了。

16)    DMA_FIFOMode:FIFO模式使能,如果設置為DMA_FIFOMode_Enable表示使能FIFO模式功能;它設定DMA_SxFCR寄存器的DMDIS位。ADC采集傳輸使用直接傳輸模式即可,不需要使用FIFO模式。

17)    DMA_FIFOThreshold:FIFO閾值選擇,可選4種狀態分別為FIFO容量的1/4、1/2、3/4和滿;它設定DMA_SxFCR寄存器的FTH[1:0]位; DMA_FIFOMode設置為DMA_FIFOMode_Disable,那DMA_FIFOThreshold值無效。ADC采集傳輸不使用FIFO模式,設置改值無效。

18)    DMA_MemoryBurst:存儲器突發模式選擇,可選單次模式、4節拍的增量突發模式、8節拍的增量突發模式或16節拍的增量突發模式,它設定DMA_SxCR寄存器的MBURST[1:0]位的值。ADC采集傳輸是直接模式,要求使用單次模式。

19)    DMA_PeripheralBurst:外設突發模式選擇,可選單次模式、4節拍的增量突發模式、8節拍的增量突發模式或16節拍的增量突發模式,它設定DMA_SxCR寄存器的PBURST[1:0]位的值。ADC采集傳輸是直接模式,要求使用單次模式。

21.5 DMA存儲器到存儲器模式實驗

DMA工作模式多樣,具體如何使用需要配合實際傳輸條件具體分析。接下來我們通過兩個實驗詳細講解DMA不同模式下的使用配置,加深我們對DMA功能的理解。

DMA運行高效,使用方便,在很多測試實驗都會用到,這里先詳解存儲器到存儲器和存儲器到外設這兩種模式,其他功能模式在其他章節會有很多使用到的情況,也會有相關的分析。

存儲器到存儲器模式可以實現數據在兩個內存的快速拷貝。我們先定義一個靜態的源數據,然后使用DMA傳輸把源數據拷貝到目標地址上,最后對比源數據和目標地址的數據,看看是否傳輸准確。

21.5.1 硬件設計

DMA存儲器到存儲器實驗不需要其他硬件要求,只用到RGB彩色燈用於指示程序狀態,關於RGB彩色燈電路可以參考GPIO章節。

21.5.2 軟件設計

這里只講解核心的部分代碼,有些變量的設置,頭文件的包含等並沒有涉及到,完整的代碼請參考本章配套的工程。這個實驗代碼比較簡單,主要程序代碼都在main.c文件中。

1.    編程要點

1)    使能DMA數據流時鍾並復位初始化DMA數據流;

2)    配置DMA數據流參數;

3)    使能DMA數據流,進行傳輸;

4)    等待傳輸完成,並對源數據和目標地址數據進行比較。

2.    代碼分析
DMA宏定義及相關變量定義

代碼清單 211 DMA數據流和相關變量定義

1 /* 相關宏定義,使用存儲器到存儲器傳輸必須使用DMA2 */

2 #define DMA_STREAM DMA2_Stream0

3 #define DMA_CHANNEL DMA_Channel_0

4 #define DMA_STREAM_CLOCK RCC_AHB1Periph_DMA2

5 #define DMA_FLAG_TCIF DMA_FLAG_TCIF0

6

7 #define BUFFER_SIZE 32

8 #define TIMEOUT_MAX 10000 /* Maximum timeout value */

9

10 /* 定義aSRC_Const_Buffer數組作為DMA傳輸數據源

11 const關鍵字將aSRC_Const_Buffer數組變量定義為常量類型 */

12 const uint32_t aSRC_Const_Buffer[BUFFER_SIZE]= {

13 0x01020304,0x05060708,0x090A0B0C,0x0D0E0F10,

14 0x11121314,0x15161718,0x191A1B1C,0x1D1E1F20,

15 0x21222324,0x25262728,0x292A2B2C,0x2D2E2F30,

16 0x31323334,0x35363738,0x393A3B3C,0x3D3E3F40,

17 0x41424344,0x45464748,0x494A4B4C,0x4D4E4F50,

18 0x51525354,0x55565758,0x595A5B5C,0x5D5E5F60,

19 0x61626364,0x65666768,0x696A6B6C,0x6D6E6F70,

20 0x71727374,0x75767778,0x797A7B7C,0x7D7E7F80

21 };

22 /* 定義DMA傳輸目標存儲器 */

23 uint32_t aDST_Buffer[BUFFER_SIZE];

使用宏定義設置外設配置方便程序修改和升級。

存儲器到存儲器傳輸必須使用DMA2,但對數據流編號以及通道選擇就沒有硬性要求,可以自由選擇。

aSRC_Const_Buffer[BUFFER_SIZE]是定義用來存放源數據的,並且使用了const關鍵字修飾,即常量類型,使得變量是存儲在內部flash空間上。

DMA數據流配置

代碼清單 212 DMA傳輸參數配置

1 static void DMA_Config(void)

2 {

3 DMA_InitTypeDef DMA_InitStructure;

4 __IO uint32_t Timeout = TIMEOUT_MAX;

5

6 /* 使能DMA時鍾 */

7 RCC_AHB1PeriphClockCmd(DMA_STREAM_CLOCK, ENABLE);

8

9 /* 復位初始化DMA數據流 */

10 DMA_DeInit(DMA_STREAM);

11

12 /* 確保DMA數據流復位完成 */

13 while (DMA_GetCmdStatus(DMA_STREAM) != DISABLE) {

14 }

15

16 /* DMA數據流通道選擇 */

17 DMA_InitStructure.DMA_Channel = DMA_CHANNEL;

18 /* 源數據地址 */

19 DMA_InitStructure.DMA_PeripheralBaseAddr=(uint32_t)aSRC_Const_Buffer;

20 /* 目標地址 */

21 DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)aDST_Buffer;

22 /* 存儲器到存儲器模式 */

23 DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToMemory;

24 /* 數據數目 */

25 DMA_InitStructure.DMA_BufferSize = (uint32_t)BUFFER_SIZE;

26 /* 使能自動遞增功能 */

27 DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Enable;

28 /* 使能自動遞增功能 */

29 DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;

30 /* 源數據是字大小(32) */

31 DMA_InitStructure.DMA_PeripheralDataSize=DMA_PeripheralDataSize_Word;

32 /* 目標數據也是字大小(32) */

33 DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Word;

34 /* 一次傳輸模式,存儲器到存儲器模式不能使用循環傳輸 */

35 DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;

36 /* DMA數據流優先級為高 */

37 DMA_InitStructure.DMA_Priority = DMA_Priority_High;

38 /* 禁用FIFO模式 */

39 DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;

40 DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full;

41 /* 單次模式 */

42 DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;

43 /* 單次模式 */

44 DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;

45 /* 完成DMA數據流參數配置 */

46 DMA_Init(DMA_STREAM, &DMA_InitStructure);

47

48 /* 清除DMA數據流傳輸完成標志位 */

49 DMA_ClearFlag(DMA_STREAM,DMA_FLAG_TCIF);

50

51 /* 使能DMA數據流,開始DMA數據傳輸 */

52 DMA_Cmd(DMA_STREAM, ENABLE);

53

54 /* 檢測DMA數據流是否有效並帶有超時檢測功能 */

55 Timeout = TIMEOUT_MAX;

56 while ((DMA_GetCmdStatus(DMA_STREAM) != ENABLE) && (Timeout-- > 0)) {

57 }

58

59 /* 判斷是否超時 */

60 if (Timeout == 0) {

61 /* 超時就讓程序運行下面循環:RGB彩色燈閃爍 */

62 while (1) {

63 LED_RED;

64 Delay(0xFFFFFF);

65 LED_RGBOFF;

66 Delay(0xFFFFFF);

67 }

68 }

69 }

使用DMA_InitTypeDef結構體定義一個DMA數據流初始化變量,這個結構體內容我們之前已經有詳細講解。定義一個無符號32位整數變量Timeout用來計數超時。

調用RCC_AHB1PeriphClockCmd函數開啟DMA數據流時鍾,使用DMA控制器之前必須開啟對應的時鍾。

DMA_DeInit函數見數據流復位到缺省配置狀態。

使用DMA_GetCmdStatus函數獲取當前DMA數據流狀態,該函數接收一個DMA數據流的參數,返回當前數據流狀態,復位DMA數據流之前需要調用該函數來確保DMA數據流復位完成。

存儲器到存儲器模式通道選擇沒有具體規定,源地址和目標地址使用之前定義的數組首地址,只能使用一次傳輸模式不能循環傳輸,最后我調用DMA_Init函數完成DMA數據流的初始化配置。

DMA_ClearFlag函數用於清除DMA數據流標志位,代碼用到傳輸完成標志位,使用之前清除標志位以免產生不必要干擾。DMA_ClearFlag函數需要兩個形參,一個是DMA數據流,一個是事件標志位,可選有數據流傳輸完成標志位、半傳輸標志位、FIFO錯誤標志位、傳輸錯誤標志位以及直接模式錯誤標志位。

DMA_Cmd函數用於啟動或者停止DMA數據流傳輸,它接收連個參數,第一個是DMA數據流,另外一個是開啟ENABLE或者停止DISABLE

開啟DMA傳輸后需要使用DMA_GetCmdStatus函數獲取DMA數據流狀態,確保DMA數據流配置有效,為防止程序卡死,添加了超時檢測功能。

如果DMA配置超時錯誤閃爍RGB彩燈提示。

存儲器數據對比

代碼清單 213 源數據與目標地址數據對比

1 uint8_t Buffercmp(const uint32_t* pBuffer,

2 uint32_t* pBuffer1, uint16_t BufferLength)

3 {

4 /* 數據長度遞減 */

5 while (BufferLength--) {

6 /* 判斷兩個數據源是否對應相等 */

7 if (*pBuffer != *pBuffer1) {

8 /* 對應數據源不相等馬上退出函數,並返回0 */

9 return 0;

10 }

11 /* 遞增兩個數據源的地址指針 */

12 pBuffer++;

13 pBuffer1++;

14 }

15 /* 完成判斷並且對應數據相對 */

16 return 1;

17 }

判斷指定長度的兩個數據源是否完全相等,如果完全相等返回1;只要其中一對數據不相等返回0。它需要三個形參,前兩個是兩個數據源的地址,第三個是要比較數據長度。

主函數

代碼清單 214 存儲器到存儲器模式主函數

1 int main(void)

2 {

3 /* 定義存放比較結果變量 */

4 uint8_t TransferStatus;

5

6 /* LED 端口初始化 */

7 LED_GPIO_Config();

8

9 /* 設置RGB彩色燈為紫色 */

10 LED_PURPLE;

11

12 /* 簡單延時函數 */

13 Delay(0xFFFFFF);

14

15 /* DMA傳輸配置 */

16 DMA_Config();

17

18 /* 等待DMA傳輸完成 */

19 while (DMA_GetFlagStatus(DMA_STREAM,DMA_FLAG_TCIF)==DISABLE) {

20

21 }

22

23 /* 比較源數據與傳輸后數據 */

24 TransferStatus=Buffercmp(aSRC_Const_Buffer, aDST_Buffer, BUFFER_SIZE);

25

26 /* 判斷源數據與傳輸后數據比較結果*/

27 if (TransferStatus==0) {

28 /* 源數據與傳輸后數據不相等時RGB彩色燈顯示紅色 */

29 LED_RED;

30 } else {

31 /* 源數據與傳輸后數據相等時RGB彩色燈顯示藍色 */

32 LED_BLUE;

33 }

34

35 while (1) {

36 }

37 }

首先定義一個變量用來保存存儲器數據比較結果。

RGB彩色燈用來指示程序進程,使用之前需要初始化它,LED_GPIO_Config定義在bsp_led.c文件中。開始設置RGB彩色燈為紫色,LED_PURPLE是定義在bsp_led.h文件的一個宏定義。

Delay函數只是一個簡單的延時函數。

調用DMA_Config函數完成DMA數據流配置並啟動DMA數據傳輸。

DMA_GetFlagStatus函數獲取DMA數據流事件標志位的當前狀態,這里獲取DMA數據傳輸完成這個標志位,使用循環持續等待直到該標志位被置位,即DMA傳輸完成這個事件發生,然后退出循環,運行之后程序。

確定DMA傳輸完成之后就可以調用Buffercmp函數比較源數據與DMA傳輸后目標地址的數據是否一一對應。TransferStatus保存比較結果,如果為1表示兩個數據源一一對應相等說明DMA傳輸成功;相反,如果為0表示兩個數據源數據存在不等情況,說明DMA傳輸出錯。

如果DMA傳輸成功設置RGB彩色燈為藍色,如果DMA傳輸出錯設置RGB彩色燈為紅色。

21.5.3 下載驗證

確保開發板供電正常,編譯程序並下載。觀察RGB彩色燈變化情況。正常情況下RGB彩色燈先為紫色,然后變成藍色。如果DMA傳輸出錯才會為紅色。

21.6 DMA存儲器到外設模式實驗

DMA存儲器到外設傳輸模式非常方便把存儲器數據傳輸外設數據寄存器中,這在STM32芯片向其他目標主機,比如電腦、另外一塊開發板或者功能芯片,發送數據是非常有用的。RS-232串口通信是我們常用開發板與PC端通信的方法。我們可以使用DMA傳輸把指定的存儲器數據轉移到USART數據寄存器內,並發送至PC端,在串口調試助手顯示。

21.6.1 硬件設計

存儲器到外設模式使用到USART1功能,具體電路設置參考USART章節,無需其他硬件設計。

21.6.2 軟件設計

這里只講解核心的部分代碼,有些變量的設置,頭文件的包含等並沒有涉及到,完整的代碼請參考本章配套的工程。我們編寫兩個串口驅動文件bsp_usart_dma.cbsp_usart_dma.h,有關串口和DMA的宏定義以及驅動函數都在里邊。

1.    編程要點

1)    配置USART通信功能;

2)    設置DMA為存儲器到外設模式,設置數據流通道,指定USART數據寄存器為目標地址,循環發送模式;

3)    使能DMA數據流;

4)    使能USART的DMA發送請求;

5)    DMA傳輸同時CPU可以運行其他任務。

2.    代碼分析
USART和DMA宏定義

代碼清單 215 USARTDMA相關宏定義

1 //USART

2 #define DEBUG_USART USART1

3 #define DEBUG_USART_CLK RCC_APB2Periph_USART1

4 #define DEBUG_USART_RX_GPIO_PORT GPIOA

5 #define DEBUG_USART_RX_GPIO_CLK RCC_AHB1Periph_GPIOA

6 #define DEBUG_USART_RX_PIN GPIO_Pin_10

7 #define DEBUG_USART_RX_AF GPIO_AF_USART1

8 #define DEBUG_USART_RX_SOURCE GPIO_PinSource10

9

10 #define DEBUG_USART_TX_GPIO_PORT GPIOA

11 #define DEBUG_USART_TX_GPIO_CLK RCC_AHB1Periph_GPIOA

12 #define DEBUG_USART_TX_PIN GPIO_Pin_9

13 #define DEBUG_USART_TX_AF GPIO_AF_USART1

14 #define DEBUG_USART_TX_SOURCE GPIO_PinSource9

15

16 #define DEBUG_USART_BAUDRATE 115200

17

18 //DMA

19 #define DEBUG_USART_DR_BASE (USART1_BASE+0x04)

20 #define SENDBUFF_SIZE 5000 //一次發送的數據量

21 #define DEBUG_USART_DMA_CLK RCC_AHB1Periph_DMA2

22 #define DEBUG_USART_DMA_CHANNEL DMA_Channel_4

23 #define DEBUG_USART_DMA_STREAM DMA2_Stream7

使用宏定義設置外設配置方便程序修改和升級。

USART部分設置與USART章節內容相同,可以參考USART章節內容理解。

查閱表 212可知USART1對應DMA2的數據流7通道4

串口DMA傳輸配置

代碼清單 216 USART1 發送請求DMA設置

1 void USART_DMA_Config(void)

2 {

3 DMA_InitTypeDef DMA_InitStructure;

4

5 /*開啟DMA時鍾*/

6 RCC_AHB1PeriphClockCmd(DEBUG_USART_DMA_CLK, ENABLE);

7

8 /* 復位初始化DMA數據流 */

9 DMA_DeInit(DEBUG_USART_DMA_STREAM);

10

11 /* 確保DMA數據流復位完成 */

12 while (DMA_GetCmdStatus(DEBUG_USART_DMA_STREAM) != DISABLE) {

13 }

14

15 /*usart1 tx對應dma2,通道4,數據流7*/

16 DMA_InitStructure.DMA_Channel = DEBUG_USART_DMA_CHANNEL;

17 /*設置DMA源:串口數據寄存器地址*/

18 DMA_InitStructure.DMA_PeripheralBaseAddr = DEBUG_USART_DR_BASE;

19 /*內存地址(要傳輸的變量的指針)*/

20 DMA_InitStructure.DMA_Memory0BaseAddr = (u32)SendBuff;

21 /*方向:從內存到外設*/

22 DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToPeripheral;

23 /*傳輸大小DMA_BufferSize=SENDBUFF_SIZE*/

24 DMA_InitStructure.DMA_BufferSize = SENDBUFF_SIZE;

25 /*外設地址不增*/

26 DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;

27 /*內存地址自增*/

28 DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;

29 /*外設數據單位*/

30 DMA_InitStructure.DMA_PeripheralDataSize=DMA_PeripheralDataSize_Byte;

31 /*內存數據單位 8bit*/

32 DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;

33 /*DMA模式:不斷循環*/

34 DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;

35 /*優先級:中*/

36 DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;

37 /*禁用FIFO*/

38 DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;

39 DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full;

40 /*存儲器突發傳輸 16個節拍*/

41 DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;

42 /*外設突發傳輸 1個節拍*/

43 DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;

44 /*配置DMA2的數據流7*/

45 DMA_Init(DEBUG_USART_DMA_STREAM, &DMA_InitStructure);

46

47 /*使能DMA*/

48 DMA_Cmd(DEBUG_USART_DMA_STREAM, ENABLE);

49

50 /* 等待DMA數據流有效*/

51 while (DMA_GetCmdStatus(DEBUG_USART_DMA_STREAM) != ENABLE) {

52 }

53 }

使用DMA_InitTypeDef結構體定義一個DMA數據流初始化變量,這個結構體內容我們之前已經有詳細講解。

調用RCC_AHB1PeriphClockCmd函數開啟DMA數據流時鍾,使用DMA控制器之前必須開啟對應的時鍾。

DMA_DeInit函數見數據流復位到缺省配置狀態。

使用DMA_GetCmdStatus函數獲取當前DMA數據流狀態,該函數接收一個DMA數據流的參數,返回當前數據流狀態,復位DMA數據流之前需要調用該函數來確保DMA數據流復位完成。

USART有固定的DMA通道,USART數據寄存器地址也是固定的,外設地址不可以使用自動遞增,源數據使用我們自定義的數組空間,存儲器地址使用自動遞增,采用循環發送模式,最后我調用DMA_Init函數完成DMA數據流的初始化配置。

DMA_Cmd函數用於啟動或者停止DMA數據流傳輸,它接收連個參數,第一個是DMA數據流,另外一個是開啟ENABLE或者停止DISABLE

開啟DMA傳輸后需要使用DMA_GetCmdStatus函數獲取DMA數據流狀態,確保DMA數據流配置有效。

主函數

代碼清單 217 存儲器到外設模式主函數

1 int main(void)

2 {

3 uint16_t i;

4 /* 初始化USART */

5 Debug_USART_Config();

6

7 /* 配置使用DMA模式 */

8 USART_DMA_Config();

9

10 /* 配置RGB彩色燈 */

11 LED_GPIO_Config();

12

13 printf("\r\n USART1 DMA TX 測試 \r\n");

14

15 /*填充將要發送的數據*/

16 for (i=0; i<SENDBUFF_SIZE; i++) {

17 SendBuff[i] = 'A';

18

19 }

20

21 /* USART1 DMA發出TX請求 */

22 USART_DMACmd(DEBUG_USART, USART_DMAReq_Tx, ENABLE);

23

24 /* 此時CPU是空閑的,可以干其他的事情 */

25 //例如同時控制LED

26 while (1) {

27 LED1_TOGGLE

28 Delay(0xFFFFF);

29 }

30 }

Debug_USART_Config函數定義在bsp_usart_dma.c中,它完成USART初始化配置,包括GPIO初始化,USART通信參數設置等等,具體可參考USART章節講解。

USART_DMA_Config函數也是定義在bsp_usart_dma.c中,之前我們已經詳細分析了。

LED_GPIO_Config函數定義在bsp_led.c中,它完成RGB彩色燈初始化配置,具體可參考GPIO章節講解。

使用for循環填充源數據,SendBuff[SENDBUFF_SIZE]是定義在bsp_usart_dma.c中的一個全局無符號8位整數數組,是DMA傳輸的源數據,在USART_DMA_Config函數中已經被設置為存儲器地址。

USART_DMACmd函數用於控制USARTDMA傳輸啟動和關閉。它接收三個參數,第一個參數用於設置DMA數據流,第二個參數設置DMA請求,有USART發送請求USART_DMAReq_Tx和接收請求USART_DMAReq_Rx可選,第三個參數用於設置啟動請求ENABLE或者關閉請求DISABLE。運行該函數后USARTDMA發送傳輸就開始了,根據配置它會通過USART循環發送數據。

DMA傳輸過程是不占用CPU資源的,可以一邊傳輸一次運行其他任務。

21.6.3 下載驗證

保證開發板相關硬件連接正確,用USB線連接開發板"USB TO UART"接口跟電腦,在電腦端打開串口調試助手,把編譯好的程序下載到開發板。程序運行后在串口調試助手可接收到大量的數據,同時開發板上RGB彩色燈不斷閃爍。

這里要注意為演示DMA持續運行並且CPU還能處理其它事情,持續使用DMA發送數據,量非常大,長時間運行可能會導致電腦端串口調試助手會卡死,鼠標亂飛的情況,所以在測試時最好把串口調試助手的自動清除接收區數據功能勾選上或把DMA配置中的循環模式改為單次模式。

21.7 每課一問

1、同時使用ADC1和ADC3的DMA請求時,DMA數據流及通道如何選擇?

2、根據存儲器至外設模式實驗編寫一個外設到存儲器程序,實現USART的DMA接收請求功能。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM