「正點原子NANO STM32開發板資料連載」第二十二章 DMA 實驗

1)實驗平臺:alientek NANO STM32F411 V1開發板

2)摘自《正點原子STM32F4 開發指南(HAL 庫版》關注官方微信號公眾號,獲取更多資料:正點原子

「正點原子NANO STM32開發板資料連載」第二十二章 DMA 實驗

第二十二章 DMA 實驗

本章我們將向大家介紹 STM32F4 的 DMA。在本章中,我們將利用 STM32F4 的 DMA來實現串口數據傳送,並在串口助手打印顯示。本章分為如下幾個部分:

22.1 STM32F4 DMA 簡介

22.2 硬件設計

22.3 軟件設計

22.4 下載驗證

22.1 STM32F4 DMA 簡介

DMA,全稱為:Direct Memory Access,即直接存儲器訪問。DMA 傳輸方式無需 CPU 直接控制傳輸,也沒有中斷處理方式那樣保留現場和恢復現場的過程,通過硬件為 RAM 與 I/O 設備開闢一條直接傳送數據的通路,能使 CPU 的效率大為提高。

STM32F4 最多有 2 個 DMA 控制器(DMA1 和 DMA2),共 16 個數據流(每個控制器 8 個),

每一個 DMA 通道都用於管理來自於一個或多個外設對存儲器訪問的請求。每個數據流總共可以

有多達 8 個通道(或稱請求)。每個數據流通道都有一個仲裁器,用於處理 DMA 請求間的優先

級。

STM32F4 的 DMA 有以下一些特性:

●雙 AHB 主總線架構,一個用於存儲器訪問,另一個用於外設訪問。

● 僅支持 32 位訪問的 AHB 從編程接口

● 每個 DMA 控制器有 8 個數據流,每個數據流有多達 8 個通道(或稱請求)

● 每個數據流有單獨的四級 32 位先進先出存儲器緩衝區(FIFO),可用於 FIFO 模式或直

接模式。

● 通過硬件可以將每個數據流配置為:

1,支持外設到存儲器、存儲器到外設和存儲器到存儲器傳輸的常規通道

2,支持在存儲器方雙緩衝的雙緩衝區通道

● 8 個數據流中的每一個都連接到專用硬件 DMA 通道(請求)

● DMA 數據流請求之間的優先級可用軟件編程(4 個級別:非常高、高、中、低),在

軟件優先級相同的情況下可以通過硬件決定優先級(例如,請求 0 的優先級高於請求 1)

● 每個數據流也支持通過軟件觸發存儲器到存儲器的傳輸(僅限 DMA2 控制器)

● 可供每個數據流選擇的通道請求多達 8 個。此選擇可由軟件配置,允許幾個外設啟動

DMA 請求

● 要傳輸的數據項的數目可以由 DMA 控制器或外設管理:

1,DMA 流控制器:要傳輸的數據項的數目是 1 到 65535,可用軟件編程

2,外設流控制器:要傳輸的數據項的數目未知並由源或目標外設控制,這些外設通過硬

件發出傳輸結束的信號

● 獨立的源和目標傳輸寬度(字節、半字、字):源和目標的數據寬度不相等時,DMA

自動封裝/解封必要的傳輸數據來優化帶寬。這個特性僅在 FIFO 模式下可用。

● 對源和目標的增量或非增量尋址

● 支持 4 個、8 個和 16 個節拍的增量突發傳輸。突發增量的大小可由軟件配置,通常等

於外設 FIFO 大小的一半

● 每個數據流都支持循環緩衝區管理

● 5 個事件標誌(DMA 半傳輸、DMA 傳輸完成、DMA 傳輸錯誤、DMA FIFO 錯誤、

直接模式錯誤),進行邏輯或運算,從而產生每個數據流的單箇中斷請求

STM32F4 有兩個 DMA 控制器,DMA1 和 DMA2,本章,我們僅針對 DMA2 進行介紹。

STM32F4 的 DMA 控制器框圖如圖 22.1.1 所示:


「正點原子NANO STM32開發板資料連載」第二十二章 DMA 實驗

圖 22.1.1 DMA1 個通道一覽表

DMA 控制器執行直接存儲器傳輸:因為採用 AHB 主總線,它可以控制 AHB 總線矩陣來

啟動 AHB 事務。它可以執行下列事務:

1,外設到存儲器的傳輸

2,存儲器到外設的傳輸

3,存儲器到存儲器的傳輸

這裡特別注意一下,存儲器到存儲器需要外設接口可以訪問存儲器,而僅 DMA2 的外設接口

可以訪問存儲器,所以僅 DMA2 控制器支持存儲器到存儲器的傳輸,DMA1 不支持。

圖 22.1.1 中數據流的多通道選擇,是通過 DMA_SxCR 寄存器控制的,如圖 22.1.2 所示:


「正點原子NANO STM32開發板資料連載」第二十二章 DMA 實驗

圖 22.1.2 DMA 數據流通道選擇

從上圖可以看出,DMA_SxCR 控制數據流到底使用哪一個通道,每個數據流有 8 個通道可

供選擇,每次只能選擇其中一個通道進行 DMA 傳輸。接下來,我們看看 DMA2 的各數據流通

道映射表,如表 22.1.1 所示

「正點原子NANO STM32開發板資料連載」第二十二章 DMA 實驗

表 22.1.1 DMA2 各數據流通道映射表

上表就列出了 DMA2 所有可能的選擇情況,來總共 64 種組合,比如本章我們要實現串口 1

的 DMA 發送,即 USART1_TX,就必須選擇 DMA2 的數據流 7,通道 4,來進行 DMA 傳輸。這裡注

意一下,有的外設(比如 USART1_RX)可能有多個通道可以選擇,大家隨意選擇一個就可以了。

接下來,我們介紹一下 DMA 設置相關的幾個寄存器。

第一個是 DMA 中斷狀態寄存器,該寄存器總共有 2 個:DMA_LISR 和 DMA_HISR,每個寄存器

管理 4 數據流(總共 8 個),DMA_LISR 寄存器用於管理數據流 0~3,而 DMA_HISR 用於管理數據

流 4~7。這兩個寄存器各位描述都完全一模一樣,只是管理的數據流不一樣。

這裡,我們僅以 DMA_LISR 寄存器為例進行介紹,DMA_LISR 各位描述如圖 22.1.3 所示:


「正點原子NANO STM32開發板資料連載」第二十二章 DMA 實驗

圖 22.1.3 DMA_LISR 寄存器各位描述

如果開啟了 DMA_LISR 中這些位對應的中斷,則在達到條件後就會跳到中斷服務函數里面

去,即使沒開啟,我們也可以通過查詢這些位來獲得當前 DMA 傳輸的狀態。這裡我們常用的是

TCIFx 位,即數據流 x 的 DMA 傳輸完成與否標誌。注意此寄存器為只讀寄存器,所以在這些位

被置位之後,只能通過其他的操作來清除。DMA_HISR 寄存器各位描述通 DMA_LISR 寄存器各位

描述完全一樣,只是對應數據流 4~7,這裡我們就不列出來了。

第二個是 DMA 中斷標誌清除寄存器, 該寄存器同樣有 2 個:DMA_LIFCR 和 DMA_HIFCR,同樣

是每個寄存器控制 4 個數據流,DMA_LIFCR 寄存器用於管理數據流 0~3,而 DMA_ HIFCR 用於管

理數據流 4~7。這兩個寄存器各位描述都完全一模一樣,只是管理的數據流不一樣。

這裡,我們僅以 DMA_LIFCR 寄存器為例進行介紹,DMA_LIFCR 各位描述如圖 22.1.4 所示:


「正點原子NANO STM32開發板資料連載」第二十二章 DMA 實驗

圖 22.1.4 DMA_LIFCR 寄存器各位描述

DMA_LIFCR 的各位就是用來清除 DMA_LISR 的對應位的,通過寫 1 清除。在 DMA_LISR 被置

位後,我們必須通過向該位寄存器對應的位寫入 1 來清除。DMA_HIFCR 的使用同 DMA_LIFCR 類

似,這裡就不做介紹了。

第三個是 DMA 數據流 x 配置寄存器(DMA_SxCR)(x=0~7,下同)。該寄存器的我們在這裡

就不貼出來了,見《STM32F411xC/E 參考手冊》第 190 頁 9.5.5 一節。該寄存器控制著 DMA 的

很多相關信息,包括數據寬度、外設及存儲器的寬度、優先級、增量模式、傳輸方向、中斷允

許、使能等都是通過該寄存器來設置的。所以 DMA_ SxCR 是 DMA 傳輸的核心控制寄存器。

第四個是 DMA 數據流 x 數據項數寄存器(DMA_SxNDTR)。這個寄存器控制 DMA 數據流 x 的

每次傳輸所要傳輸的數據量。其設置範圍為 0~65535。並且該寄存器的值會隨著傳輸的進行而

減少,當該寄存器的值為 0 的時候就代表此次數據傳輸已經全部發送完成了。所以可以通過這

個寄存器的值來知道當前 DMA 傳輸的進度。特別注意,這裡是數據項數目,而不是指的字節數。

比如設置數據位寬為 16 位,那麼傳輸一次(一個項)就是 2 個字節。

第五個是 DMA 數據流 x 的外設地址寄存器(DMA_SxPAR)。該寄存器用來存儲 STM32F4 外設

的地址,比如我們使用串口 1,那麼該寄存器必須寫入 0x40011004(其實就是&USART1_DR)。

如果使用其他外設,就修改成相應外設的地址就行了。

最後一個是 DMA 數據流 x 的存儲器地址寄存器,由於 STM32F4 的 DMA 支持雙緩存,所以存

儲器地址寄存器有兩個:DMA_SxM0AR 和 DMA_SxM1AR,其中 DMA_SxM1AR 僅在雙緩衝模式下,才

有效。本章我們沒用到雙緩衝模式,所以存儲器地址寄存器就是:DMA_SxM0AR,該寄存器和

DMA_CPARx 差不多,但是是用來放存儲器的地址的。比如我們使用 SendBuf[8200]數組來做存儲

器,那麼我們在 DMA_SxM0AR 中寫入&SendBuff 就可以了。

DMA 相關寄存器就為大家介紹到這裡,關於這些寄存器的詳細描述,請參考《STM32F411xC/E

參考手冊》第 9.5 節。本章我們要用到串口 1 的發送,屬於 DMA2 的數據流 7,通道 4,接下來

我們就介紹 HAL 庫配置步驟和方法。首先這裡我們需要指出的是,DMA 相關的庫函數文件在文

件 stm32f4xx_hal_dma.c/stm32f4xx_hal_dma_ex.c 以及對應的頭文件中,同時因為我們是用串

口的 DMA 功能,所以還要加入串口相關的文件 stm32f4xx_hal_uart.c。具體步驟如下:

1)使能 DMA2 時鐘

__HAL_RCC_DMA2_CLK_ENABLE(); //DMA1 時鐘使能

2)初始化 DMA 通道 7,包括配置通道,外設地址,存儲器地址,傳輸數據量等參數

DMA 的某個數據流各種配置參數初始化是通過 HAL_DMA_Init 函數實現的,該函數聲明為:

HAL_StatusTypeDef HAL_DMA_Init(DMA_HandleTypeDef *hdma);

該函數只有一個 DMA_HandleTypeDef 結構體指針類型入口參數,結構體定義為:

typedef struct __DMA_HandleTypeDef

{

DMA_Channel_TypeDef

*Instance;

DMA_InitTypeDef

Init;

HAL_LockTypeDef

Lock;

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;

DMA_TypeDef

*DmaBaseAddress;

uint32_t

ChannelIndex;

}DMA_HandleTypeDef;

成員變量 Instance 是用來設置寄存器基地址,例如要設置為 DMA1 的通道 4,那麼取值為

DMA1_Channel4。

成員變量 Parent 是 HAL 庫處理中間變量,用來指向 DMA 通道外設句柄。

成員變量 XferCpltCallback(傳輸完成回調函數),XferHalfCpltCallback(半傳輸完成

回調函數),XferErrorCallback(傳輸錯誤回調函數),XferAbortCallback(傳輸中止回調

函數)是四個函數指針,用來指向回調函數入口地址。

成員變量 DmaBaseAddress 和 ChannelIndex 是通道基地址和索引好,這個是 HAL 庫處理的

時候會自動計算,用戶無需設置。

其他成員變量 HAL 庫處理過程狀態標識變量,這裡就不做過多講解。接下來我們著重看看

成員變量 Init,它是 DMA_InitTypeDef 結構體類型,該結構體定義為:

typedef struct

{

uint32_t Channel; //通道,例如:DMA_CHANNEL_4

uint32_t Direction;//傳輸方向,例如存儲器到外設 DMA_MEMORY_TO_PERIPH

uint32_t PeriphInc;//外設(非)增量模式,非增量模式 DMA_PINC_DISABLE

uint32_t MemInc;//存儲器(非)增量模式,增量模式 DMA_MINC_ENABLE

uint32_t PeriphDataAlignment; //外設數據大小:8/16/32 位。

uint32_t MemDataAlignment;

//存儲器數據大小:8/16/32 位。

uint32_t Mode;//模式:外設流控模式/循環模式/普通模式

uint32_t Priority;

//DMA 優先級:低/中/高/非常高

uint32_t FIFOMode;//FIFO 模式開啟或者禁止

uint32_t FIFOThreshold; //FIFO 閾值選擇:

uint32_t MemBurst; //存儲器突發模式:單次/4 個節拍/8 個節拍/16 個節拍

uint32_t PeriphBurst; //外設突發模式:單次/4 個節拍/8 個節拍/16 個節拍

} DMA_InitTypeDef;

該結構體成員變量非常多,但是每個成員變量配置的基本都是 DMA_SxCR 寄存器和

DMA_SxFCR 寄存器的相應位。我們把結構體各個成員變量的含義都通過註釋的方式列出來了。

例如本實驗我們要用到 DMA2_Stream7 的 DMA_CHANNEL_4,把內存中數組的值發送到串口外設發

送寄存器 DR,所以方向為存儲器到外設 DMA_MEMORY_TO_PERIPH,一個一個字節發送,需要數字

索引自動增加,所以是存儲器增量模式 DMA_MINC_ENABLE,存儲器和外設的字寬都是字節 8 位。

具體配置如下:

DMA_HandleTypeDef

UART1TxDMA_Handler;

//DMA 句柄

UART1TxDMA_Handler.Instance= DMA2_Stream7;

//數據流選擇

UART1TxDMA_Handler.Init.Channel=DMA_CHANNEL_4;

//通道選擇

UART1TxDMA_Handler.Init.Direction=DMA_MEMORY_TO_PERIPH; //存儲器到外設

UART1TxDMA_Handler.Init.PeriphInc=DMA_PINC_DISABLE;

//外設非增量模式

UART1TxDMA_Handler.Init.MemInc=DMA_MINC_ENABLE;

//存儲器增量模式

UART1TxDMA_Handler.Init.PeriphDataAlignment=DMA_PDATAALIGN_BYTE;//外設:8 位

UART1TxDMA_Handler.Init.MemDataAlignment=DMA_MDATAALIGN_BYTE; //存儲器:8 位

UART1TxDMA_Handler.Init.Mode=DMA_NORMAL;

//普通模式

UART1TxDMA_Handler.Init.Priority=DMA_PRIORITY_MEDIUM;

//中等優先級

UART1TxDMA_Handler.Init.FIFOMode=DMA_FIFOMODE_DISABLE;

UART1TxDMA_Handler.Init.FIFOThreshold=DMA_FIFO_THRESHOLD_FULL;

UART1TxDMA_Handler.Init.MemBurst=DMA_MBURST_SINGLE;

//存儲器突發單次傳輸

UART1TxDMA_Handler.Init.PeriphBurst=DMA_PBURST_SINGLE; //外設突發單次傳輸

這裡大家要注意,HAL 庫為了處理各類外設的 DMA 請求,在調用相關函數之前,需要調用

一個宏定義標識符,來連接 DMA 和外設句柄。例如要使用串口 DMA 發送,所以方式為:

__HAL_LINKDMA(&UART1_Handler,hdmatx,UART1TxDMA_Handler);

其中 UART1_Handler 是串口初始化句柄,我們在 usart.c 中定義過了。

UART1TxDMA_Handler

是 DMA 初始化句柄。hdmatx 是外設句柄結構體的成員變量,在這裡實際就是 UART1_Handler 的

成員變量。在 HAL 庫中,任何一個可以使用 DMA 的外設,它的初始化結構體句柄都會有一個

DMA_HandleTypeDef 指 針類 型的 成員 變量 ,是 HAL 庫 用來 做相 關指 向的 。 Hdmatx 就 是

DMA_HandleTypeDef 結構體指針類型。

這 句 話 的 含 義 就 是 把 UART1_Handler 句 柄 的 成 員 變 量 hdmatx 和 DMA 句 柄

UART1TxDMA_Handler 連接起來,是純軟件處理,沒有任何硬件操作。

這裡我們就點到為止,如果大家要詳細瞭解 HAL 庫指向關係,請查看本實驗宏定義標識符

__HAL_LINKDMA 的定義和調用方法,就會很清楚了。

3)使能串口 1DMA 發送

串口 1 的 DMA 發送實際是串口控制寄存器 CR3 的位 7 來控制的,在 HAL 庫中,多次操作該

寄存器來使能串口 DMA 發送,但是它並沒有提供一個獨立的使能函數,所以這裡我們可以通過

直接操作寄存器方式來實現:

USART1->CR3 | =USART_CR3_DMAT;//使能串口 1 的 DMA 發送

HAL 庫還提供了對串口的 DMA 發送的停止,暫停,繼續等操作函數:

HAL_StatusTypeDef HAL_USART_DMAStop(USART_HandleTypeDef *husart);//停止

HAL_StatusTypeDef HAL_USART_DMAPause(USART_HandleTypeDef *husart);//暫停

HAL_StatusTypeDef HAL_USART_DMAResume(USART_HandleTypeDef *husart);//恢復

這些函數使用方法這裡我們就不累贅了。

4) 使能 DMA2 通道 7,啟動傳輸。

使能串口 DMA 發送之後,我們接著就要使能 DMA 傳輸通道:

HAL_StatusTypeDef HAL_DMA_Start(DMA_HandleTypeDef *hdma, uint32_t SrcAddress,

uint32_t DstAddress, uint32_t DataLength);

這個函數比較好理解,第一個參數是 DMA 句柄,第二個是傳輸源地址,第三個是傳輸目

標地址,第四個是傳輸的數據長度。

通過以上 4 步設置,我們就可以啟動一次 USART1 的 DMA 傳輸了。

5)查詢 DMA 傳輸狀態

在 DMA 傳輸過程中,我們要查詢 DMA 傳輸通道的狀態,使用的函數是:

__HAL_DMA_GET_FLAG(&UART1TxDMA_Handler,DMA_FLAG_TCIF3_7);

獲取當前傳輸剩餘數據量:

__HAL_DMA_GET_COUNTER(&UART1TxDMA_Handler);

6)DMA 中斷使用方法

DMA 中斷對於每個通道都有一箇中斷服務函數,比如 DMA2_Channel7 的中斷服務函

數為 DMA2_Channel7_IRQHandler。同樣,HAL 庫也提供了一個通用的 DMA 中斷處理函

數 HAL_DMA_IRQHandler,在該函數內部,會對 DMA 傳輸狀態進行分析,然後調用響應

的中斷處理回調函數:

void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart);//發送完成回調函數

void HAL_UART_TxHalfCpltCallback(UART_HandleTypeDef *huart);//發送一半回調函數

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart);//接收完成回調函數

void HAL_UART_RxHalfCpltCallback(UART_HandleTypeDef *huart);//接收一半回調函數

void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart);//傳輸出錯回調函數

對於串口 DMA 開啟,使能數據流,啟動傳輸,這些步驟,如果使用了中斷,可以直接調

用 HAL 庫函數 HAL_USART_Transmit_DMA,該函數聲明如下:

HAL_StatusTypeDef HAL_UART_Transmit_DMA(UART_HandleTypeDef *huart,

uint8_t *pData, uint16_t Size);

22.2 硬件設計

所以本章用到的硬件資源有:

1) 指示燈 DS0、DS2

2) KEY0 按鍵

3) 串口

4) DMA

本章我們將利用外部按鍵 KEY0 來控制 DMA 的傳送,每按一次 KEY0,DMA 就傳送一次數據到

USART1,同時 DS2 燈作為傳輸進度燈。DS0 還是用來做為程序運行的指示燈。

本章實驗需要注意 P5 口的 RXD 和 TXD 是否和 PA9 和 PA10 連接上,如果沒有,請先連接。

22.3 軟件設計

打開我們的 DMA 傳輸實驗,可以發現,我們的實驗中多了 dma.c 文件和其頭文件 dma.h,

同時我們要引入 dma 相關的庫函數文件 stm32f4xx_hal_dma.c 和 stm32f4xx_hal_dma.h。

打開 dma.c 文件,代碼如下:

DMA_HandleTypeDef UART1TxDMA_Handler;

//DMA 句柄

//DMAx 的各通道配置

//這裡的傳輸形式是固定的,這點要根據不同的情況來修改

//從存儲器->外設模式/8 位數據寬度/存儲器增量模式

//DMA_Streamx:DMA 數據流,DMA1_Stream0~7/DMA2_Stream0~7

//chx:DMA 通道選擇,@ref DMA_channel DMA_CHANNEL_0~DMA_CHANNEL_7

void MYDMA_Config(DMA_Stream_TypeDef *DMA_Streamx,u32 chx)

{

if((u32)DMA_Streamx>(u32)DMA2)//得到當前 stream 是屬於 DMA2 還是 DMA1

{

__HAL_RCC_DMA2_CLK_ENABLE();//DMA2 時鐘使能

}else

{

__HAL_RCC_DMA1_CLK_ENABLE();//DMA1 時鐘使能

}

__HAL_LINKDMA(&UART1_Handler,hdmatx,UART1TxDMA_Handler);

//將 DMA 與 USART1 聯繫起來(發送 DMA)

//Tx DMA 配置

UART1TxDMA_Handler.Instance=DMA_Streamx;

//數據流選擇

UART1TxDMA_Handler.Init.Channel=chx;

//通道選擇

UART1TxDMA_Handler.Init.Direction=DMA_MEMORY_TO_PERIPH; //存儲器到外設

UART1TxDMA_Handler.Init.PeriphInc=DMA_PINC_DISABLE; //外設非增量模式

UART1TxDMA_Handler.Init.MemInc=DMA_MINC_ENABLE; //存儲器增量模式

UART1TxDMA_Handler.Init.PeriphDataAlignment=DMA_PDATAALIGN_BYTE;

//外設數據長度:8 位

UART1TxDMA_Handler.Init.MemDataAlignment=DMA_MDATAALIGN_BYTE;

//存儲器數據長度:8 位

UART1TxDMA_Handler.Init.Mode=DMA_NORMAL; //外設普通模式

UART1TxDMA_Handler.Init.Priority=DMA_PRIORITY_MEDIUM; //中等優先級

UART1TxDMA_Handler.Init.FIFOMode=DMA_FIFOMODE_DISABLE;

UART1TxDMA_Handler.Init.FIFOThreshold=DMA_FIFO_THRESHOLD_FULL;

UART1TxDMA_Handler.Init.MemBurst=DMA_MBURST_SINGLE;

//存儲器突發單次傳輸

UART1TxDMA_Handler.Init.PeriphBurst=DMA_PBURST_SINGLE;

//外設突發單次傳輸

HAL_DMA_DeInit(&UART1TxDMA_Handler);

HAL_DMA_Init(&UART1TxDMA_Handler);

}

//開啟一次 DMA 傳輸

//huart:串口句柄

//pData:傳輸的數據指針

//Size:傳輸的數據量

void MYDMA_USART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData,

uint16_t Size)

{

HAL_DMA_Start(huart->hdmatx, (u32)pData, (uint32_t)&huart->Instance->DR, Size);

//開啟 DMA 傳輸

huart->Instance->CR3 |= USART_CR3_DMAT;//使能串口 DMA 發送

}

該部分代碼僅僅 2 個函數,MYDMA_Config 函數,基本上就是按照我們上面 28.1 小節介紹

的步驟 1 和步驟 2 來使能 DMA 時鐘和初始化 DMA 的,該函數是一個通用的 DMA 配置函數,

DMA1/DMA2 的所有通道,都可以利用該函數配置,不過有些固定參數可能要適當修改(比如位

寬,傳輸方向等)。該函數在外部只能修改 DMA 數據流編號和通道號,更多的其他設置只能在

該函數內部修改。MYDMA_USART_Transmit 函數就是按照 28.1 小節講解的步驟 3 和步驟 4 來啟

動串口 DMA 傳輸的。對照前面的配置步驟的詳細講解來分析這部分代碼即可。

dma.h 頭文件內容比較簡單,主要是函數聲明,這裡我們不細說。

接下來我們看看 main 函數如下:

const u8 TEXT_TO_SEND[]={"ALIENTEK NANO STM32 DMA 串口實驗"};

#define TEXT_LENTH sizeof(TEXT_TO_SEND)-1

//TEXT_TO_SEND 字符串長度(不包含結束符)

u8 SendBuff[(TEXT_LENTH+2)*100];

int main(void)

{

u16 i;

u8 t=0;

HAL_Init();

//初始化 HAL 庫

Stm32_Clock_Init(96,4,2,4);

//設置時鐘,96Mhz

delay_init(96);

//初始化延時函數

uart_init(115200);

//初始化串口 115200

LED_Init();

//初始化 LED

KEY_Init();

//按鍵初始化

MYDMA_Config(DMA2_Stream7,DMA_CHANNEL_4);//初始化 DMA

printf("NANO STM32\\r\\n");

printf("DMA TEST\\r\\n");

printf("KEY0:Start\\r\\n");

//顯示提示信息

for(i=0;i

{

if(t>=TEXT_LENTH)//加入換行符

{

SendBuff[i++]=0x0d;

SendBuff[i]=0x0a;

t=0;

}else SendBuff[i]=TEXT_TO_SEND[t++];//複製 TEXT_TO_SEND 語句

}

i=0;

while(1)

{

t=KEY_Scan(0);

if(t==KEY0_PRES)//KEY0 按下

{

printf("\\r\\nDMA DATA:\\r\\n");

HAL_UART_Transmit_DMA(&UART1_Handler,SendBuff,

(TEXT_LENTH+2)*100);//啟動傳輸

//等待 DMA 傳輸完成,此時我們來做另外一些事,點燈

//實際應用中,傳輸數據期間,可以執行另外的任務

while(1)

{

if(__HAL_DMA_GET_FLAG(&UART1TxDMA_Handler,

DMA_FLAG_TCIF3_7))//等待 DMA2_Steam7 傳輸完成

{

__HAL_DMA_CLEAR_FLAG(&UART1TxDMA_Handler,

DMA_FLAG_TCIF3_7);//清除 DMA2_Steam7 傳輸完成標誌

HAL_UART_DMAStop(&UART1_Handler);

//傳輸完成以後關閉串口 DMA

break;

}

LED2=!LED2;

delay_ms(50);

}

LED2=1;

printf("Transimit Finished!\\r\\n");//提示傳送完成

}

i++;

delay_ms(10);

if(i==20)

{

LED0=!LED0;//提示系統正在運行

i=0;

}

}

}

main 函數的流程大致是:先初始化內存 SendBuff 的值,然後通過 KEY0 開啟串口 DMA 發

送,在發送過程中,通過__HAL_DMA_GET_FLAG(&UART1TxDMA_Handler),獲取當前是否傳

輸結束,並且 DS2 閃爍。最後在傳輸結束之後清除相應標誌位,提示已經傳輸完成。

至此,DMA 串口傳輸的軟件設計就完成了。

22.4 下載驗證

在代碼編譯成功之後,我們下載代碼到 ALIENTEK NANO STM32F4 上,我們打開串口調

試助手,可以看到串口顯示如圖 22.4.1 所示:

「正點原子NANO STM32開發板資料連載」第二十二章 DMA 實驗

圖 22.4.1 DMA 串口實驗實物測試圖

伴隨 DS0 的不停閃爍,提示程序在運行。然後按 KEY0,DMA 數據開始傳輸,DS2 快閃

以表示數據正在傳輸,正常可以看到串口顯示如圖 22.4.2 所示的內容:


「正點原子NANO STM32開發板資料連載」第二十二章 DMA 實驗

圖 22.4.2 串口收到的數據內容

可以看到串口收到了 NANO STM32F4 發送過來的數據。

至此,我們整個 DMA 實驗就結束了,希望大家通過本章的學習,掌握 STM32F4 的 DMA

使用。DMA 是個非常好的功能,它不但能減輕 CPU 負擔,還能提高數據傳輸速度,合理的應

用 DMA,往往能讓你的程序設計變得簡單。


分享到:


相關文章: