????在前面我們提到過(guò) DMA,這一章我們就來(lái)學(xué)習(xí) STM32F1 的DMA 使用。要實(shí)現(xiàn)的功能是:通過(guò) K_UP 按鍵控制 DMA 串口 1 數(shù)據(jù)的傳送,在傳送過(guò)程中讓 D2 指示燈不斷閃爍,直到數(shù)據(jù)傳送完成。D1 指示燈閃爍提示系統(tǒng)正常運(yùn)行。學(xué)習(xí)時(shí)可以參考《STM32F10x 中文參考手冊(cè)》-10 DMA 控制器(DMA)章節(jié)。
DMA 簡(jiǎn)介
??? DMA,全稱(chēng)是 Direct Memory Access,中文意思為直接存儲(chǔ)器訪(fǎng)問(wèn)。DMA 可用于實(shí)現(xiàn)外設(shè)與存儲(chǔ)器之間或者存儲(chǔ)器與存儲(chǔ)器之間數(shù)據(jù)傳輸?shù)母咝浴V苑Q(chēng)為高效, 是因?yàn)?DMA 傳輸數(shù)據(jù)移動(dòng)過(guò)程無(wú)需 CPU 直接操作, 這樣節(jié)省的 CPU 資源就可供其它操作使用。從硬件層面來(lái)理解,DMA 就好像是 RAM 與 I/O 設(shè)備間數(shù)據(jù)傳輸?shù)耐罚?外設(shè)與存儲(chǔ)器之間或者存儲(chǔ)器與存儲(chǔ)器之間可以直接在這條通路上進(jìn)行數(shù)據(jù)傳輸。這里說(shuō)的外設(shè)一般指外設(shè)的數(shù)據(jù)寄存器, 比如 ADC、SPI、I2C、DCMI等外設(shè)的數(shù)據(jù)寄存器, 存儲(chǔ)器一般是指片內(nèi) SRAM、 外部存儲(chǔ)器、 片內(nèi) Flash等。
??? STM32F1 最多有 2 個(gè) DMA 控制器 ( DMA2 僅存在大容量產(chǎn)品中) ,DMA1 有7 個(gè)通道。DMA2 有 5 個(gè)通道。每個(gè)通道專(zhuān)門(mén)用來(lái)管理來(lái)自于一個(gè)或多個(gè)外設(shè)對(duì)存儲(chǔ)器訪(fǎng)問(wèn)的請(qǐng)求。還有一個(gè)仲裁器來(lái)協(xié)調(diào)各個(gè) DMA 請(qǐng)求的優(yōu)先權(quán)。
STM32F1 的 DMA 有以下主要特性:
● 12 個(gè)獨(dú)立的可配置的通道(請(qǐng)求):DMA1 有 7 個(gè)通道, DMA2 有 5 個(gè)通道
● 每個(gè)通道都直接連接專(zhuān)用的硬件 DMA 請(qǐng)求,每個(gè)通道都同樣支持軟件觸發(fā)。這些功能通過(guò)軟件來(lái)配置。
● 在同一個(gè) DMA 模塊上, 多個(gè)請(qǐng)求間的優(yōu)先權(quán)可以通過(guò)軟件編程設(shè)置(共有四級(jí):很高、高、中等和低),優(yōu)先權(quán)設(shè)置相等時(shí)由硬件決定(請(qǐng)求 0 優(yōu)先于請(qǐng)求1,依此類(lèi)推) 。
● 獨(dú)立數(shù)據(jù)源和目標(biāo)數(shù)據(jù)區(qū)的傳輸寬度(字節(jié)、半字、全字),模擬打包和拆包的過(guò)程。源和目標(biāo)地址必須按數(shù)據(jù)傳輸寬度對(duì)齊。
● 支持循環(huán)的緩沖器管理
● 每個(gè)通道都有 3 個(gè)事件標(biāo)志(DMA 半傳輸、 DMA 傳輸完成和 DMA 傳輸出錯(cuò)),這3 個(gè)事件標(biāo)志邏輯或成為一個(gè)單獨(dú)的中斷請(qǐng)求。
● 存儲(chǔ)器和存儲(chǔ)器間的傳輸
● 外設(shè)和存儲(chǔ)器、存儲(chǔ)器和外設(shè)之間的傳輸
● 閃存、 SRAM、外設(shè)的 SRAM、 APB1、 APB2 和 AHB 外設(shè)均可作為訪(fǎng)問(wèn)的源和目標(biāo)。
● 可編程的數(shù)據(jù)傳輸數(shù)目:最大為 65535
DMA 結(jié)構(gòu)框圖
??? DMA 控制器獨(dú)立于內(nèi)核,屬于一個(gè)單獨(dú)的外設(shè),結(jié)構(gòu)比較簡(jiǎn)單,從編程的角度來(lái)看,我們只需掌握結(jié)構(gòu)框圖中的三部分內(nèi)容即可。如圖所示(大家也可以查看《STM32F10x中文參考手冊(cè)》-10 DMA 控制器(DMA)章節(jié)內(nèi)容)。
????我們把 DMA 結(jié)構(gòu)框圖分成3個(gè)子模塊,按照順序依次進(jìn)行簡(jiǎn)單介紹。
(1)標(biāo)號(hào) 1:DMA 請(qǐng)求
????如果外設(shè)要想通過(guò) DMA 來(lái)傳輸數(shù)據(jù), 必須先給 DMA 控制器發(fā)送 DMA 請(qǐng)求,DMA 收到請(qǐng)求信號(hào)之后, 控制器會(huì)給外設(shè)一個(gè)應(yīng)答信號(hào), 當(dāng)外設(shè)應(yīng)答后且 DMA 控制器收到應(yīng)答信號(hào)之后,就會(huì)啟動(dòng) DMA 的傳輸,直到傳輸完畢。
????根據(jù)前面介紹我們知道,DMA 含有 DMA1 和 DMA2 兩個(gè)控制器,其中 DMA1 含有 7個(gè)通道,DMA2 含有 5 個(gè)通道,不同的 DMA 控制器的通道對(duì)應(yīng)著不同的外設(shè)請(qǐng)求。
????從DMA 請(qǐng)求映射圖中可以知道各通道所對(duì)應(yīng)的外設(shè)請(qǐng)求,如圖分別是DMA1和DMA2請(qǐng)求映射圖:
??? DMA2 請(qǐng)求通道中 ADC3、 SDIO 和 TIM8 的 DMA 請(qǐng)求只在大容量產(chǎn)品中存在,這個(gè)在具體項(xiàng)目時(shí)要注意。
(2)標(biāo)號(hào) 2:DAM 通道
??? DMA 具有 12 個(gè)獨(dú)立可編程的通道,其中 DMA1 有 7 個(gè)通道, DMA2 有 5個(gè)通道,每個(gè)通道對(duì)應(yīng)不同的外設(shè)的 DMA 請(qǐng)求。雖然每個(gè)通道可以接收多個(gè)外設(shè)的請(qǐng)求,但是同一時(shí)間只能接收一個(gè),不能同時(shí)接收多個(gè)。
(3)標(biāo)號(hào) 3:仲裁器
????當(dāng)發(fā)生多個(gè) DMA 通道請(qǐng)求時(shí),就意味著有先后響應(yīng)處理的順序問(wèn)題,這個(gè)就由仲裁器也管理。仲裁器管理 DMA 通道請(qǐng)求分為兩個(gè)階段。第一階段屬于軟件階段,可以在 DMA_CCRx 寄存器中設(shè)置,有 4 個(gè)等級(jí):非常高、高、中和低四個(gè)優(yōu)先級(jí)。第二階段屬于硬件階段,如果兩個(gè)或以上的 DMA 通道請(qǐng)求設(shè)置的優(yōu)先級(jí)一樣,則他們優(yōu)先級(jí)取決于通道編號(hào),編號(hào)越低優(yōu)先權(quán)越高,比如通道 0高于通道 1。在大容量產(chǎn)品和互聯(lián)型產(chǎn)品中,DMA1 控制器擁有高于 DMA2 控制器的優(yōu)先級(jí)。
DMA 數(shù)據(jù)配置
????使用 DMA, 最核心就是配置要傳輸?shù)臄?shù)據(jù), 包括數(shù)據(jù)從哪里來(lái), 要到哪里去,傳輸?shù)臄?shù)據(jù)的單位是什么,要傳多少數(shù)據(jù),是一次傳輸還是循環(huán)傳輸?shù)取?/p>
(1)從哪里來(lái)到哪里去
????我們知道 DMA 傳輸數(shù)據(jù)的方向有三個(gè):從外設(shè)到存儲(chǔ)器,從存儲(chǔ)器到外設(shè),從存儲(chǔ)器到存儲(chǔ)器。具體的方向 DMA_CCR 位 4 DIR 配置:0 表示從外設(shè)到存儲(chǔ)器, 1 表示從存儲(chǔ)器到外設(shè)。這里面涉及到的外設(shè)地址由 DMA_CPAR 配置,存儲(chǔ)器地址由 DMA_CMAR配置。
外設(shè)到存儲(chǔ)器
????當(dāng)我們使用從外設(shè)到存儲(chǔ)器傳輸時(shí),以 ADC 采集為例。DMA 外設(shè)寄存器的地址對(duì)應(yīng)的就是 ADC 數(shù)據(jù)寄存器的地址,DMA 存儲(chǔ)器的地址就是我們自定義的變量(用來(lái)接收存儲(chǔ) AD 采集的數(shù)據(jù))的地址。方向我們?cè)O(shè)置外設(shè)為源地址。
存儲(chǔ)器到外設(shè)
????當(dāng)我們使用從存儲(chǔ)器到外設(shè)傳輸時(shí), 以串口向電腦端發(fā)送數(shù)據(jù)為例。DMA 外設(shè)寄存器的地址對(duì)應(yīng)的就是串口數(shù)據(jù)寄存器的地址, DMA 存儲(chǔ)器的地址就是我們自定義的變量(相當(dāng)于一個(gè)緩沖區(qū),用來(lái)存儲(chǔ)通過(guò)串口發(fā)送到電腦的數(shù)據(jù))的地址。方向我們?cè)O(shè)置外設(shè)為目標(biāo)地址。
存儲(chǔ)器到存儲(chǔ)器
????當(dāng)我們使用從存儲(chǔ)器到存儲(chǔ)器傳輸時(shí),以?xún)?nèi)部 FLASH 向內(nèi)部 SRAM 復(fù)制數(shù)據(jù)為例。DMA 外設(shè)寄存器的地址對(duì)應(yīng)的就是內(nèi)部 FLASH(我們這里把內(nèi)部 FALSH當(dāng)作一個(gè)外設(shè)來(lái)看)的地址, DMA 存儲(chǔ)器的地址就是我們自定義的變量(相當(dāng)于一個(gè)緩沖區(qū),用來(lái)存儲(chǔ)來(lái)自?xún)?nèi)部 FLASH 的數(shù)據(jù))的地址。方向我們?cè)O(shè)置外設(shè)(即內(nèi)部 FLASH)為源地址。跟上面兩個(gè)不一樣的是,這里需要把 DMA_CCR 位14:MEM2MEM:存儲(chǔ)器到存儲(chǔ)器模式配置為 1,啟動(dòng) M2M 模式。
(2)要傳多少,單位是什么
????當(dāng)我們配置好數(shù)據(jù)要從哪里來(lái)到哪里去之后, 我們還需要知道我們要傳輸?shù)臄?shù)據(jù)是多少,數(shù)據(jù)的單位是什么。
????以串口向電腦發(fā)送數(shù)據(jù)為例,我們可以一次性給電腦發(fā)送很多數(shù)據(jù),具體多少由 DMA_CNDTR 配置,這是一個(gè) 32 位的寄存器,一次最多只能傳輸 65535 個(gè)數(shù)據(jù)。
????要想數(shù)據(jù)傳輸正確,源和目標(biāo)地址存儲(chǔ)的數(shù)據(jù)寬度還必須一致,串口數(shù)據(jù)寄存器是 8 位的,所以我們定義的要發(fā)送的數(shù)據(jù)也必須是 8 位。外設(shè)的數(shù)據(jù)寬度由 DMA_CCR 的 PSIZE[1:0]配置,可以是 8/16/32 位,存儲(chǔ)器的數(shù)據(jù)寬度由DMA_CCR 的 MSIZE[1:0]配置,可以是 8/16/32 位。
????在 DMA 控制器的控制下,數(shù)據(jù)要想有條不紊的從一個(gè)地方搬到另外一個(gè)地方,還必須正確設(shè)置兩邊數(shù)據(jù)指針的增量模式。外設(shè)的地址指針由 DMA_CCRx 的PINC 配置,存儲(chǔ)器的地址指針由 MINC 配置。以串口向電腦發(fā)送數(shù)據(jù)為例,要發(fā)送的數(shù)據(jù)很多,每發(fā)送完一個(gè),那么存儲(chǔ)器的地址指針就應(yīng)該加 1,而串口數(shù)據(jù)寄存器只有一個(gè),那么外設(shè)的地址指針就固定不變。具體的數(shù)據(jù)指針的增量模式由實(shí)際情況決定。
(3)什么時(shí)候傳輸完成
????數(shù)據(jù)什么時(shí)候傳輸完成,我們可以通過(guò)查詢(xún)標(biāo)志位或者通過(guò)中斷的方式來(lái)判斷。每個(gè) DMA 通道在 DMA 傳輸過(guò)半、傳輸完成和傳輸錯(cuò)誤時(shí)都會(huì)有相應(yīng)的標(biāo)志位,如果使能了該類(lèi)型的中斷后,則會(huì)產(chǎn)生中斷。有關(guān)各個(gè)標(biāo)志位的詳細(xì)描述請(qǐng)參考 DMA 中斷狀態(tài)寄存器DMA_ISR 的詳細(xì)描述。
????傳輸完成還分兩種模式,是一次傳輸還是循環(huán)傳輸,一次傳輸很好理解,即傳輸一次之后就停止,要想再傳輸?shù)脑?huà),必須關(guān)斷 DMA 使能后再重新配置后才能繼續(xù)傳輸。循環(huán)傳輸則是一次傳輸完成之后又恢復(fù)第一次傳輸時(shí)的配置循環(huán)傳輸,不斷的重復(fù)。具體的由 DMA_CCR 寄存器的 CIRC 循環(huán)模式位控制。
DMA 配置步驟
????接下來(lái)我們介紹下如何使用庫(kù)函數(shù)對(duì) DMA 進(jìn)行配置。這個(gè)也是在編寫(xiě)程序中必須要了解的。具體步驟如下:(DMA 相關(guān)庫(kù)函數(shù)在 stm32f10x_dma.c 和stm32f10x_dma.h 文件中)
(1)使能 DMA 控制器(DMA1 或 DMA2)時(shí)鐘
????要使能 DMA 時(shí)鐘,需通過(guò)AHB1ENR 寄存器來(lái)控制,使能 DMA時(shí)鐘庫(kù)函數(shù)為:
?
void RCC_AHBPeriphClockCmd(uint32_t RCC_AHBPeriph, FunctionalState NewState);
?
例如使能 DMA1 時(shí)鐘,函數(shù)如下:
?
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
?
(2)初始化 DMA 通道,包括配置通道、外設(shè)和內(nèi)存地址、傳輸數(shù)據(jù)量等要使用 DMA,必須對(duì)其相關(guān)參數(shù)進(jìn)行設(shè)置,包括通道選擇、外設(shè)和內(nèi)存地址、
通道優(yōu)先級(jí)、傳輸數(shù)據(jù)量的配置等。該部分設(shè)置通過(guò) DMA 初始化函數(shù) DMA_Init完成的:
?
void DMA_Init(DMA_Channel_TypeDef* DMAy_Channelx,DMA_InitTypeDef* DMA_InitStruct);
?
????函數(shù)中第一個(gè)參數(shù)是用來(lái)確定 DMA 通道,參數(shù)范圍為:
?
DMA1_Channel_0~DMA1_Channel_7(DMA2?是DMA2_Channel_0-DMA2_Channel_5)
?
????第二個(gè)參數(shù)是一個(gè)結(jié)構(gòu)體指針變量,結(jié)構(gòu)體類(lèi)型是 DMA_InitTypeDef,其內(nèi)包含了 DMA 相關(guān)參數(shù)的設(shè)置。下面我們簡(jiǎn)單介紹下它的成員:
?
typedef struct { uint32_t DMA_PeripheralBaseAddr; // 外設(shè)地址 uint32_t DMA_MemoryBaseAddr; // 存儲(chǔ)器地址 uint32_t DMA_DIR; // 傳輸方向 uint32_t DMA_BufferSize; // 傳輸數(shù)目 uint32_t DMA_PeripheralInc; // 外設(shè)地址增量模式 uint32_t DMA_MemoryInc; // 存儲(chǔ)器地址增量模式 uint32_t DMA_PeripheralDataSize; // 外設(shè)數(shù)據(jù)寬度 uint32_t DMA_MemoryDataSize; // 存儲(chǔ)器數(shù)據(jù)寬度 uint32_t DMA_Mode; // 模式選擇 uint32_t DMA_Priority; // 通道優(yōu)先級(jí) uint32_t DMA_M2M; // 存儲(chǔ)器到存儲(chǔ)器模式 } DMA_InitTypeDef;
?
??? DMA_PeripheralBaseAddr:外設(shè)地址,通過(guò) DMA_CPAR 寄存器設(shè)置,一般設(shè)置為外設(shè)的數(shù)據(jù)寄存器地址,比如要進(jìn)行串口 DMA 傳輸,那么外設(shè)基地址為串口接受發(fā)送數(shù)據(jù)存儲(chǔ)器 USART1->DR 的地址,表示方法為&USART1->DR。如果是存儲(chǔ)器到存儲(chǔ)器模式則設(shè)置為其中一個(gè)存儲(chǔ)區(qū)地址。
DMA_Memory0BaseAddr:存儲(chǔ)器地址,通過(guò) DMA_CMAR 寄存器設(shè)置,一般設(shè)置為我們自定義存儲(chǔ)區(qū)的首地址,即我們存放 DMA 傳輸數(shù)據(jù)的內(nèi)存地址。比如我們定義一個(gè) u32 類(lèi)型數(shù)組,將數(shù)組首地址(直接使用數(shù)組名即可)賦值給DMA_Memory0BaseAddr,在DMA傳輸?shù)臅r(shí)候就可以把數(shù)組內(nèi)的數(shù)據(jù)發(fā)送或接收。
DMA_DIR:數(shù)據(jù)傳輸方向選擇,可選擇外設(shè)到存儲(chǔ)器、存儲(chǔ)器到外設(shè)以及存
儲(chǔ)器到存儲(chǔ)器。通過(guò)設(shè)定 DMA_CCR 寄存器的 DIR[1:0]位的值決定。比如本章實(shí)驗(yàn)是從內(nèi)存讀取數(shù)據(jù)發(fā)送到串口,所以數(shù)據(jù)傳輸方向?yàn)榇鎯?chǔ)器到外設(shè),配置為DMA_DIR_MemoryToPeripheral。
DMA_BufferSize:用來(lái)設(shè)置一次傳輸數(shù)據(jù)的大小,通過(guò) DMA_CNDTR 寄存器設(shè)置。
DMA_PeripheralInc:用來(lái)設(shè)置外設(shè)地址是遞增還是不變,通過(guò) DMA_CCR寄存器的 PINC 位設(shè)置,如果設(shè)置為遞增,那么下一次傳輸?shù)臅r(shí)候地址加 1。通常外 設(shè) 只 有 一 個(gè) 數(shù) 據(jù) 寄 存 器 , 所 以 一 般 不 會(huì) 使 能 該 位 , 即 配 置 為DMA_PeripheralInc_Disable。
DMA_MemoryInc:用來(lái)設(shè)置內(nèi)存地址是否遞增,通過(guò) DMA_CCR 寄存器的MINC位設(shè)置。我們自定義的存儲(chǔ)區(qū)一般都是存放多個(gè)數(shù)據(jù)的,所以需要使能存儲(chǔ)器地址自動(dòng)遞增功能,即配置為 DMA_MemoryInc_Enable。
DMA_PeripheralDataSize:外設(shè)數(shù)據(jù)寬度選擇,可以為字節(jié)(8 位)、半字
(16位)、字(32 位),通過(guò) DMA_CCR 寄存器的 PSIZE[1:0]位設(shè)置。例如本實(shí)驗(yàn)數(shù)據(jù)是按照 8 位字節(jié)傳輸,所以配置為DMA_PeripheralDataSize_Byte。
DMA_MemoryDataSize:存儲(chǔ)器數(shù)據(jù)寬度選擇,可以為字節(jié)(8 位)、半字(16位)、字(32 位),通過(guò) DMA_CCR 寄存器的 MSIZE[1:0]位設(shè)置。本章實(shí)驗(yàn)同樣設(shè)置為 8 位字節(jié)傳輸,這個(gè)要和我們定義的數(shù)組對(duì)應(yīng),所以配置為DMA_MemoryDataSize_Byte。
DMA_Mode:DMA 傳輸模式選擇, 可選擇一次傳輸或者循環(huán)傳輸, 通過(guò)DMA_CCR寄存器的 CIRC 位來(lái)設(shè)定。比如我們要從內(nèi)存 (存儲(chǔ)器) 中傳輸 64 個(gè)字節(jié)到串口,如果設(shè)置為循環(huán)傳輸,那么它會(huì)在 64 個(gè)字節(jié)傳輸完成之后繼續(xù)從內(nèi)存的第一個(gè)地址傳輸,如此循環(huán)。這里我們?cè)O(shè)置為一次傳輸完成之后不循環(huán)。所以設(shè)置值為DMA_Mode_Normal。
DMA_Priority:用來(lái)設(shè)置 DMA 通道的優(yōu)先級(jí),有低,中,高,超高四種級(jí)別,可通過(guò) DMA_CCR 寄存器的PL[1:0]位來(lái)設(shè)定。DMA 優(yōu)先級(jí)只有在多個(gè) DMA 通道同時(shí)使用時(shí)才有意義,本章實(shí)驗(yàn)我們只使用了一個(gè) DMA 通道,所以可以任意設(shè)置DMA 優(yōu)先級(jí),這里我們就設(shè)置為中等優(yōu)先級(jí),配置參數(shù)為DMA_Priority_Medium。
DMA_Priority:用來(lái)設(shè)置 DMA 通道的優(yōu)先級(jí),有低,中,高,超高四種級(jí)別,可通過(guò) DMA_CCR 寄存器的PL[1:0]位來(lái)設(shè)定。DMA 優(yōu)先級(jí)只有在多個(gè) DMA 通道同時(shí)使用時(shí)才有意義,本章實(shí)驗(yàn)我們只使用了一個(gè) DMA 通道,所以可以任意設(shè)置DMA 優(yōu)先級(jí),這里我們就設(shè)置為中等優(yōu)先級(jí),配置參數(shù)為DMA_Priority_Medium。
DMA_M2M:用來(lái)設(shè)置存儲(chǔ)器到存儲(chǔ)器模式,使用存儲(chǔ)器到存儲(chǔ)器時(shí)用到,設(shè)定 DMA_CCR 的位 14 MEN2MEN 即可啟動(dòng)存儲(chǔ)器到存儲(chǔ)器模式。
????了解結(jié)構(gòu)體成員功能后,就可以進(jìn)行配置,本實(shí)驗(yàn)配置代碼如下:
?
DMA_InitTypeDef DMA_InitStructure; DMA_InitStructure.DMA_PeripheralBaseAddr = par;//DMA外設(shè)地址 DMA_InitStructure.DMA_MemoryBaseAddr = mar;//DMA 存儲(chǔ)器0地址 DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;//存儲(chǔ)器到外 設(shè)模式 DMA_InitStructure.DMA_BufferSize = ndtr;//數(shù)據(jù)傳輸量 DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//外設(shè)非增量模式 DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;//存儲(chǔ)器 增量模式 DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;//外設(shè)數(shù)據(jù)長(zhǎng)度:8 位 DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;//存儲(chǔ)器數(shù)據(jù)長(zhǎng)度:8 位 DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;// 使用普通模式 DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;//中等優(yōu)先 級(jí) DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; //DMA 通道 x 沒(méi)有
?
設(shè)置為內(nèi)存到內(nèi)存?zhèn)鬏?/p>
?
DMA_Init(DMAy_Channelx, &DMA_InitStructure);//初始化DMA
?
(3)使能外設(shè) DMA功能(DMA 請(qǐng)求映射圖對(duì)應(yīng)的外設(shè))
????配置好 DMA 后,我們就需要使能外設(shè) DMA 功能,例如我們要使能串口的DMA發(fā)送功能,調(diào)用的庫(kù)函數(shù)為:
?
USART_DMACmd(USART1,USART_DMAReq_Tx,ENABLE);?//使能串口?1?的?DMA發(fā)送
?
????如果是要使能串口DMA接受, 那么第二個(gè)參數(shù)修改為USART_DMAReq_Rx即可。
????如果是其他的外設(shè)需開(kāi)啟DMA功能, 只需要在對(duì)應(yīng)的標(biāo)準(zhǔn)外設(shè)庫(kù)函數(shù)中查找到對(duì)應(yīng)的外設(shè) DMA 使能函數(shù)。
(4)開(kāi)啟 DMA 的通道傳輸
????初始化 DMA 后,要使用DMA還必須開(kāi)啟它,開(kāi)啟 DMA 通道傳輸?shù)膸?kù)函數(shù)為:
?
void?DMA_Cmd(DMA_Channel_TypeDef*?DMAy_Channelx,?FunctionalStateNewState);
?
????第一個(gè)參數(shù)為外設(shè)所對(duì)應(yīng)的 DMA 通道,例如本章使用的是 USART1_TXDMA請(qǐng)求,因此它對(duì)應(yīng)的是 DMA1_Channel4,可通過(guò)前面DMA 請(qǐng)求映射圖選擇。第二個(gè)參數(shù)相信不說(shuō)也知道,就是使能或失能。
????本實(shí)驗(yàn)使能 DMA1_Channel4函數(shù)為:
?
DMA_Cmd(DMA1_Channel4,ENABLE);
?
(5)查詢(xún) DMA 傳輸狀態(tài)
????通過(guò)以上 4 步設(shè)置,我們就可以啟動(dòng)一次 DMA 傳輸了。但是在 DMA 傳輸過(guò)程中,我們還需要查詢(xún) DMA傳輸通道的狀態(tài),使用的庫(kù)函數(shù)是:
?
FlagStatus DMA_GetFlagStatus(uint32_t DMAy_FLAG);
?
????例如我們要查詢(xún) DMA1通道4 傳輸是否完成,方法是:
?
DMA_GetFlagStatus(DMA1_FLAG_TC4);
?
????標(biāo)準(zhǔn)庫(kù)中,還提供了獲取當(dāng)前剩余數(shù)據(jù)量大小的函數(shù):
?
uint16_t?DMA_GetCurrDataCounter(DMA_Channel_TypeDef*DMAy_Channelx);
?
????例如我們要獲取 DMA1通道4 還有多少個(gè)數(shù)據(jù)沒(méi)有傳輸,方法是:
?
DMA_GetCurrDataCounter(DMA1_Channel4);
?
????同樣,標(biāo)準(zhǔn)庫(kù)中還提供了設(shè)置傳輸數(shù)據(jù)量大小的函數(shù):
?
void?DMA_SetCurrDataCounter(DMA_Channel_TypeDef*?DMAy_Channelx,uint16_t?DataNumber);
?
????將以上幾步全部配置好后,我們就可以使用 DMA 來(lái)傳輸對(duì)應(yīng)外設(shè)的數(shù)據(jù)了。
硬件設(shè)計(jì)
????本實(shí)驗(yàn)使用到硬件資源如下:
(1)D1 和 D2 指示燈
(2)K_UP 按鍵
(3)串口 1
(4)DMA
??? D1和 D2 指示燈、K_UP 按鍵、串口 1 電路在前面章節(jié)都介紹過(guò),這里就不多說(shuō),至于 DMA 它屬于 STM32F1 芯片內(nèi)部的資源,只要通過(guò)軟件配置好 DMA即可使用。D1指示燈用來(lái)提示系統(tǒng)運(yùn)行狀態(tài),K_UP 按鍵用來(lái)控制 DMA發(fā)送,每按一次K_UP鍵,DMA 就將內(nèi)存(自定義的一個(gè)數(shù)組)內(nèi)數(shù)據(jù)發(fā)送 USART1,并通過(guò)串口 1將發(fā)送的內(nèi)容打印出來(lái),在 DMA 數(shù)據(jù)傳輸?shù)倪^(guò)程中讓 D2 指示燈不斷閃爍,直到數(shù)據(jù)傳輸完成。D2 指示燈閃爍表示 CPU在執(zhí)行其他的任務(wù),說(shuō)明 DMA 傳輸是不需要占用 CPU 的。
????所要實(shí)現(xiàn)的功能是:通過(guò) K_UP 按鍵控制 DMA 串口 1 數(shù)據(jù)的傳送,在傳送過(guò)程中讓 D2 指示燈不斷閃爍,直到數(shù)據(jù)傳送完成。D1 指示燈閃爍提示系統(tǒng)正常運(yùn)行。程序框架如下:
(1)初始化 USART1_TX對(duì)應(yīng)的 DMA 通道相關(guān)參數(shù)
(2)編寫(xiě)主函數(shù)
前面介紹 DMA 配置步驟時(shí), 就已經(jīng)講解如何初始化 DMA。下面我們打開(kāi) “DMA實(shí)驗(yàn)” 工程, 在 APP 工程組中可以看到添加了dma.c文件(里面包含了 DMA 驅(qū)動(dòng)程序),在 StdPeriph_Driver 工程組中添加了stm32f10x_dma.c 庫(kù)文件。DMA 操作的庫(kù)函數(shù)都放在 stm32f10x_dma.c 和stm32f10x_dma.h 文件中,所以使用到 DMA 就必須加入 stm32f10x_dma.c文件,同時(shí)還要包含對(duì)應(yīng)的頭文件路徑。
????這里我們分析幾個(gè)重要函數(shù),其他部分程序大家可以打開(kāi)工程查看。
DMA 初始化函數(shù)
????要使用 DMA,我們必須先對(duì)它進(jìn)行配置。初始化代碼如下:
?
/**************************************************************** * 函 數(shù) 名 : DMAx_Init * 函數(shù)功能 : DMA 初始化函數(shù) * 輸 入 : DMAy_Channelx:DMA 通 道 選 擇 ,@ref DMA_channel DMA_Channel_0~DMA_Channel_7 par:外設(shè)地址 mar:存儲(chǔ)器地址 ndtr:數(shù)據(jù)傳輸量 * 輸 出 : 無(wú) *****************************************************************/ void DMAx_Init(DMA_Channel_TypeDef* DMAy_Channelx,u32 par,u32 mar,u16 ndtr) { DMA_InitTypeDef DMA_InitStructure; RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,?ENABLE);//DMA1時(shí)鐘使能 //DMA_DeInit(DMAy_Channelx); /* 配置 DMA */ DMA_InitStructure.DMA_PeripheralBaseAddr = par;//DMA外設(shè)地址 DMA_InitStructure.DMA_MemoryBaseAddr = mar;//DMA 存儲(chǔ)器0地址 DMA_InitStructure.DMA_DIR?=?DMA_DIR_PeripheralDST;//存儲(chǔ)器到外設(shè)模式 DMA_InitStructure.DMA_BufferSize = ndtr;//數(shù)據(jù)傳輸量 DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//外設(shè)非增量模式 DMA_InitStructure.DMA_MemoryInc?=?DMA_MemoryInc_Enable;//存儲(chǔ)器增量模式 DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;//外設(shè)數(shù)據(jù)長(zhǎng)度:8 位 DMA_InitStructure.DMA_MemoryDataSize?=DMA_MemoryDataSize_Byte;//存儲(chǔ)器數(shù)據(jù)長(zhǎng)度:8?位 DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;// 使用普通模式 DMA_InitStructure.DMA_Priority?=?DMA_Priority_Medium;//中等優(yōu)先級(jí) DMA_InitStructure.DMA_M2M?=?DMA_M2M_Disable;?//DMA?通道?x?沒(méi)有設(shè)置為內(nèi)存到內(nèi)存?zhèn)鬏?DMA_Init(DMAy_Channelx, &DMA_InitStructure);//初始化DMA }
?
????在 DMAx_Init()函數(shù)中,首先使能 DMA1 時(shí)鐘,然后初始化 DMA 各參數(shù),即配置 DMA_InitStructure結(jié)構(gòu)體,初始化 DMA 通道,這一過(guò)程在前面步驟介紹中已經(jīng)提了。
????通道的選擇有函數(shù)參數(shù)DMAy_Channelx傳遞進(jìn)來(lái), 函數(shù)中還有另外3個(gè)形參,par 傳遞的是外設(shè)地址,mar傳遞的是存儲(chǔ)器(內(nèi)存)地址,ndtr傳遞的是DMA數(shù)據(jù)傳輸量。這樣做的好處是方便大家修改。
開(kāi)啟 DMA 傳輸函數(shù)
????配置好 DMA 后,我們需要開(kāi)啟它,代碼如下:
?
/**************************************************************** * 函 數(shù) 名 : DMAx_Enable * 函數(shù)功能 : 開(kāi)啟一次 DMA 傳輸 * 輸 入 : DMAy_Channelx:DMA 通道選擇,@ref DMA_channel DMA_Channel_0~DMA_Channel_7 ndtr:數(shù)據(jù)傳輸量 * 輸 出 : 無(wú) *****************************************************************/ void DMAx_Enable(DMA_Channel_TypeDef *DMAy_Channelx,u16 ndtr) { DMA_Cmd(DMAy_Channelx, DISABLE); //關(guān)閉 DMA 傳輸 DMA_SetCurrDataCounter(DMAy_Channelx,ndtr);?//數(shù)據(jù)傳輸量 DMA_Cmd(DMAy_Channelx,?ENABLE);?//開(kāi)啟DMA傳輸 }
?
????此函數(shù)功能很簡(jiǎn)單,首先失能 DMA 傳輸通道,然后設(shè)置傳輸?shù)臄?shù)據(jù)量大小,最后使能 DMA 傳輸通道。函數(shù)帶有形參 DMAy_Channelx 和 ndtr,用于方便選擇對(duì)應(yīng)外設(shè)的通道和數(shù)據(jù)量。
主函數(shù)
????編寫(xiě)好 DMA 的初始化和使能函數(shù)后, 接下來(lái)就可以編寫(xiě)主函數(shù)了, 代碼如下:
?
/**************************************************************** * 函 數(shù) 名 : main * 函數(shù)功能 : 主函數(shù) * 輸 入 : 無(wú) * 輸 出 : 無(wú) *****************************************************************/ int main() { u8 i=0; u8 key; SysTick_Init(72); NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);?//中斷優(yōu)先級(jí)分組?分2?組 LED_Init(); USART1_Init(9600); KEY_Init(); DMAx_Init(DMA1_Channel4,(u32)&USART1->DR,(u32)send_buf,send_buf_len); Send_Data(send_buf); while(1) { key=KEY_Scan(0); if(key==KEY_UP) { USART_DMACmd(USART1,USART_DMAReq_Tx,ENABLE);?//使能串口?1的DMA?發(fā)送 DMAx_Enable(DMA1_Channel4,send_buf_len);?//開(kāi)始一次DMA 傳輸! //等待 DMA傳輸完成,此時(shí)我們來(lái)做另外一些事 //實(shí)際應(yīng)用中,傳輸數(shù)據(jù)期間,可以執(zhí)行另外的任務(wù) while(1) { if(DMA_GetFlagStatus(DMA1_FLAG_TC4)!=0)//判斷通道?4傳輸完成 { DMA_ClearFlag(DMA1_FLAG_TC4); break; } led2=!led2; delay_ms(300); } } i++; if(i%20==0) { led1=!led1; } delay_ms(10); } }
?
????主函數(shù)實(shí)現(xiàn)的功能很簡(jiǎn)單,首先調(diào)用之前編寫(xiě)好的硬件初始化函數(shù),包括SysTick系統(tǒng)時(shí)鐘, 中斷分組, LED初始化等。然后調(diào)用我們前面編寫(xiě)的DMAx_Init函數(shù),由于 USART1_TX 是在 DMA1 的通道 4 中,所以通道選擇為 DMA1_Channel4。
????外設(shè)地址傳遞的是&USART1->DR,因?yàn)?USART1 用來(lái)接收數(shù)據(jù)的寄存器是USART1->DR,加上一個(gè)&就轉(zhuǎn)換成地址了。存儲(chǔ)器地址為send_buf(數(shù)組名就是數(shù)組的地址) , 這個(gè)是我們自定義的一個(gè)u8類(lèi)型數(shù)組, 數(shù)組大小為send_buf_len,這個(gè)也是我們定義的一個(gè)宏,值為 5000。然后調(diào)用Send_Data 函數(shù),此函數(shù)功能是將數(shù)組 send_buf 內(nèi)所有成員全部賦值為字符 5,代碼如下:
?
#define send_buf_len 5000 u8 send_buf[send_buf_len]; void Send_Data(u8 *p) { u16 i; for(i=0;i?
????最后進(jìn)入 while 循環(huán),調(diào)用KEY_Scan 函數(shù),不斷檢測(cè) K_UP按鍵是否按下,如果 K_UP 按鍵按下,啟動(dòng)一次 USART_TX 的 DMA 傳輸,在傳輸?shù)倪^(guò)程中讓 CPU控制 D2指示燈閃爍,直到 DMA 數(shù)據(jù)傳輸完成。D1 指示燈間隔200ms 閃爍,提示系統(tǒng)正常運(yùn)行。
????將工程程序編譯后下載到開(kāi)發(fā)板內(nèi),可以看到 D1 指示燈不斷閃爍,表示程序正常運(yùn)行。當(dāng) K_UP 按鍵按下,DMA 開(kāi)始將內(nèi)存數(shù)組內(nèi)的數(shù)據(jù)傳輸?shù)酱? 上,同時(shí)傳輸過(guò)程中,D2 指示燈閃爍,直到傳輸完成。如果想在串口調(diào)試助手上看到傳輸信息,可以打開(kāi)“串口調(diào)試助手”,首先勾選下標(biāo)號(hào) 1 DTR 框,然后再取消勾選。這是因?yàn)榇舜谥謫?dòng)時(shí)會(huì)把系統(tǒng)復(fù)位住,通過(guò) DTR 狀態(tài)切換下即可。然后設(shè)置好波特率等參數(shù)后,串口助手上即會(huì)收到串口發(fā)送過(guò)來(lái)的信息。(串口助手上先勾選下標(biāo)號(hào)1 DTR 框,然后再取
消勾選)如圖所示:
審核編輯:湯梓紅
評(píng)論
查看更多