奚圣鑫 王宜懷 李躍華
1(蘇州大學計算機科學與技術學院 江蘇 蘇州 215000) 2(南通大學信息科學與技術學院 江蘇 南通 226019)
直接存儲器訪問(DMA)是一種數據傳輸的方式[1],它可以將數據不經過CPU直接從一個地址空間復制到另一個地址空間,提供在外設和存儲器之間或者存儲器與存儲器之間的高速傳輸。在嵌入式微處理器的實際應用過程中,無時無刻都需要數據的傳輸,我們正常的方法都是查詢方式進行編程來控制I/O的輸入輸出,或者通過中斷來處理數據傳輸[2]。雖然采取中斷來控制I/O比查詢方式控制I/O更加有效和省時,但是仍然需要CPU的干預才能實現存儲器與I/O模塊之間的數據進行傳輸。任何I/O設備與存儲器間的數據傳輸必須通過CPU,因此大大降低了CPU的效率。這兩種形式的I/O存在以下兩種缺點:(1) I/O數據傳輸的速度受處理器性能和I/O設備所提供服務速度的限制;(2) 處理器負責控制I/O數據的傳輸時必須要執行一些指令,這就浪費了CPU的時間。為了彌補這兩種數據傳輸方式的缺點,可以采用嵌入式處理器中的DMA數據傳輸方式。當使用DMA傳輸數據時,這個動作本身是由DMA控制器(DMAC)來實現和完成的,無須CPU的介入和控制,也就不需要CPU先把所有數據復制到暫存器,然后把它們再寫回到目的地址去[3]。同時也沒有中斷處理I/O方式那樣需要保留現場和傳輸完成后的恢復現場的過程。DMA方式的數據傳輸與查詢方式訪問I/O和中斷驅動I/O相比,具有傳輸速度快、I/O響應時間短、CPU額外開銷小的明顯優點[4]。現在越來越多的嵌入式微處理器都具有DMA技術以提供外設和存儲器之間的高速數據傳輸,但卻很少會去使用它。本文將基于STM32L431RC芯片來介紹DMA控制器的基本原理及技術,在此基礎上提供一種DMA構建的封裝方式,并將其與UART結合提供具體的應用實例。
STM32中的DMA控制器(DMAC)包含2個DMA端口(DMA1,DMA2),每個端口都擁有7個通道,每個通道都可以執行DMA傳輸。其傳輸數據量是可編程的,最大可以達到65 535[5]。DMAC和STM32核心共享系統的數據總線,在DMA傳輸時,DMA請求會暫停CPU訪問系統總線若干個周期,此階段由DMAC直接掌管總線,因此,存在著一個總線控制權轉移過程。此過程是由總線仲裁器執行循環調度,以保證CPU至少可以得到一半的系統總線(存儲器或外設)帶寬[6]。另外每個端口還包含一個仲裁器、中斷接口和內部一些寄存器組,其結構如圖1所示[7]。

圖1 DMAC結構
DMAC可以將數據從源地址搬移到目的地址,在STM32中DMA操作存在三種操作模式。即存儲器到外設、外設到存儲器、存儲器到存儲器。一般的DMAC至少應該具備以下的基本功能:
(1) 能夠接收外部設備發出的DMA請求,并向CPU提出總線占用請求。
(2) 在CPU對DMAC配置結束后,DMAC可以代替CPU對總線進行控制。
(3) DMAC可以獨立地確定傳輸數據的起始地址和目的地址以及傳輸長度。
(4) DMA傳輸過程中,在發生傳輸錯誤時需要進行報錯處理。需要獨立判斷是否傳輸成功,并且在傳輸成功后能夠發出中斷信號,釋放對總線的控制權。
當然對于功能復雜的DMAC,除了完成以上功能之外,還會有其他的功能。一般的DMAC在傳遞完成一次連續地址的數據之后需要重新進行配置才能進行下一次的數據傳輸,但是復雜的DMAC可以內部增加邏輯加減,在傳輸數據時無論有多少個非連續的數據塊,可以只配置一次即可完成。一次完整的DMA傳輸過程必須經過請求、響應、傳輸和結束四個過程,其工作流程如圖2所示。

圖2 DMA控制器工作流程
(1) CPU首先會將DMAC初始化。當存儲設備I/O向DMAC發出請求DMA方式進行數據傳輸請求。
(2) DMAC向CPU發出總線請求,然后CPU釋放總線控制權,并通過DMAC通知I/O接口開始DMA傳輸。
“雅頌”之聲被古人視為“正聲”“正體”[11]。《周頌》是人類理性精神開始自覺的產物,雖然雜糅著宗教情緒,但是非理性色彩并不濃郁。與《商頌》的“雅頌”思想擁有崇奉上帝的迷狂觀念不同,《周頌》的“雅頌”思想具有倫理化特征。
(3) DMAC獲得總線控制權后,即可進行數據傳輸。整個數據傳輸過程主要可以分為兩個階段,分別為從源地址中讀取數據和寫回數據到目的地址。讀操作階段數據從源地址中被搬運到數據總線上,然后再存放到DMAC內部FIFO中用來緩存數據。寫操作是將FIFO中的緩存數據寫到目的地址上去。DMAC在同一時刻只能進行一種操作。
(4) 當完成數據傳輸后,DMAC立即釋放總線控制權,至此,整個DMA操作結束。
DMAC中每個通道都有相應的DMA配置寄存器(DMA_CCRx[1,2,…,7])、外設地址寄存器(DMA_CPARx[1,2,…,7])、存儲器地址寄存器(DMA_CMARx[1,2,…,7])、傳輸數據數量寄存器(DMA_CNDTRx[1,2,…,7]),此外還有DMA中斷狀態寄存器(DMA_ISR)和中斷標志清理寄存器(DMA_IFCR)。每個寄存器的功能如表1所示。

表1 DMA寄存器列表
其中最重要的就是DMA配置寄存器,在使用DMA傳輸之前必須要對其進行初始化。15到31位保留,其余各位的含義如表2所示。

表2 DMA配置寄存器各位表
外設地址存儲器指定當傳輸時數據的地址是外設時的地址;存儲器地址寄存器指定當傳輸時數據的地址是存儲器時的地址。傳輸數量寄存器指定傳輸次數,每一次“先讀后寫”傳輸后遞減,直至為0傳輸結束,但是如果在配置寄存器中設置為循環模式時會自動再加載之前的設定值。
軟件構件技術的出現,為實現軟件構件的工業化生產提供了理論與技術基石[8]。軟件構件的封裝性、可移植性和可復用性是軟件構件的基本特性,采用構件技術設計軟件,可以使軟件具有更好的開放性、通用性和適應性。在此思想基礎上,以STM32L431芯片為基礎,提出DMA構件基礎功能封裝規則。
首先對DMA進行要點分析,即分析應該設計哪幾個函數及函數入口參數。前面已經分析了DMA傳輸數據的完整步驟,所以通用的DMA構件必須封裝DMA初始化函數、傳輸數據函數、關閉DMA函數。DMA構件由dma.h和dma.c兩個文件組成,如果想要使用,只需要將這兩個文件加入到工程項目中即可。
1) DMA模塊初始化(DMA_Init)。使用DMA功能之前必須對其進行初始化,所以初始化函數必須提供。由于DMA端口有兩個,每個端口有7個通道,且DMA傳輸模式有三種,是否循環輸入。因此DMA初始化函數的參數為DMA端口、通道數、傳輸方向、是否循環輸入。這樣DMA初始化原型可以設計為:
2) DMA傳輸開始(DMA_Start)。在DMA初始化之后就可以開始DMA傳輸,此時需要傳輸數據的源地址、目的地址、數據長度,所以參數必須要有這三個,同時開始函數還要對DMA模塊進行使能,只有使能之后才能正常使用。這樣DMA傳輸開始原型可以設計為:
void DMA_Start(uint32_t SrcAddr,uint32_t DstAddr,uint32_t Length)
3) DMA模塊關閉(DMA_DeInit)。在使用DMA傳輸數據之后,要將DMA模塊關閉,反使能模塊,重啟傳輸數據寄存器以及清除所有的包含中斷在內的標志位,所以此函數不需要傳遞參數,原型可以直接設計為:
void DMA_DeInit()
這三個函數基本滿足了對DMA操作的基本需求。還有中斷使能與禁止等函數可以根據需求添加和使用。
在軟硬件的調試過程中,經常需要將數據通過串口打印出來。使用串口進行傳輸數據,這時就可以利用DMA技術進行數據傳輸。STM32L431RC芯片不僅提供了DMA基本功能,還提供了DMA請求復用器(DMAMUX)。請求復用器可以在外設和DMA控制器之間重新配置DMA請求[9],從而實現數據直接從存儲器到串口UART、SPI或者I2C等外設,也可以從一個外設到另一個外設。
本文提供了DMA和串口UART復用的應用實例,可以直接通過串口將微控制器內存內數據直接發送給上位機,從而用來模擬我們日常生活中的printf函數的功能。printf函數有個缺陷,就是花費的時間太多了,雖然在日常使用中我們絕大部分人沒有發現,但是在一些大型的項目對數據需要大量傳輸時,不能每次都讓微控制器等待來顯示串口,所以作者便考慮到能否用DMA來取代printf。但是用DMA來執行串口的數據打印實際時間點是會比使用printf的時間點會晚個幾毫秒,因為DMA傳輸數據是在CPU完成當前時間周期之后把系統總線讓給DMAC然后才開始數據傳輸,但是printf傳輸數據是發起后立即執行。但是這幾毫秒在我們實際應用中對我們來說沒有任何影響,但是對CPU來說,DMA傳輸并沒有占用CPU,從而CPU可以運行更多的計算。DMA和串口UART復用的函數流程如圖3所示。

圖3 DMA串口復用函數流程
程序實現了兩種串口傳輸以存儲器為起始地址的28個字節的數據到上位機上。其核心代碼如下:
int main(void)
{
uint8_t SourceData[30]=″this is a dma Sample program″;
//起始地址存放源數據
gpio_init(TIME_GPIO,1,1);
//初始化GPIO引腳
gpio_set(TIME_GPIO,0);
//GPIO輸出低電平
DMA_UART(&SourceData,UART_Debug,28);
//DMA串口傳輸數據
gpio_set(TIME_GPIO,1);
//GPIO輸出高電平
delay(1000);
//延時1 s
gpio_set(TIME_GPIO,0);
//GPIO輸出低電平
printf(SourceData);
//printf傳輸數據
gpio_set(TIME_GPIO,1);
//GPIO輸出高電平
}
經反復通過對TIME_GPIO引腳高低電平狀態持續時間測試得出,在使用DMA復用串口發送一個包含了三十個字符的字符串只需要大約25 μs的CPU耗時,而printf則需要大約800 μs的CPU耗時。
本文實現了在STM32L431的微控制器上的DMA數據傳輸,并且發現在結合UART、SPI或I2C等設備時,會比我們正常使用這些設備更加方便。因為我們在使用這些設備的同時CPU會花費大量的時間在數據的傳輸上,如果我們采用了DMA來進行數據傳輸,則會大量減少CPU的工作任務,從而讓CPU去執行其他工作。實踐證明,在利用基于DMA共性技術的基礎上封裝的DMA構件,可以將繁重的數據傳輸工作通過DMA控制器來完成,從而提高了CPU的數據處理能力和CPU的工作效率。