1)實驗平臺:alientek NANO STM32F411 V1開發板
第二十二章 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 所示:
DMA 控制器執行直接存儲器傳輸:因為採用 AHB 主總線,它可以控制 AHB 總線矩陣來
啟動 AHB 事務。它可以執行下列事務:
1,外設到存儲器的傳輸
2,存儲器到外設的傳輸
3,存儲器到存儲器的傳輸
這裡特別注意一下,存儲器到存儲器需要外設接口可以訪問存儲器,而僅 DMA2 的外設接口
可以訪問存儲器,所以僅 DMA2 控制器支持存儲器到存儲器的傳輸,DMA1 不支持。
圖 22.1.1 中數據流的多通道選擇,是通過 DMA_SxCR 寄存器控制的,如圖 22.1.2 所示:
從上圖可以看出,DMA_SxCR 控制數據流到底使用哪一個通道,每個數據流有 8 個通道可
供選擇,每次只能選擇其中一個通道進行 DMA 傳輸。接下來,我們看看 DMA2 的各數據流通
道映射表,如表 22.1.1 所示
上表就列出了 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 所示:
如果開啟了 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 所示:
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 所示:
伴隨 DS0 的不停閃爍,提示程序在運行。然後按 KEY0,DMA 數據開始傳輸,DS2 快閃
以表示數據正在傳輸,正常可以看到串口顯示如圖 22.4.2 所示的內容:
可以看到串口收到了 NANO STM32F4 發送過來的數據。
至此,我們整個 DMA 實驗就結束了,希望大家通過本章的學習,掌握 STM32F4 的 DMA
使用。DMA 是個非常好的功能,它不但能減輕 CPU 負擔,還能提高數據傳輸速度,合理的應
用 DMA,往往能讓你的程序設計變得簡單。
閱讀更多 正點原子 的文章