DMA是直接存儲器訪問(DirectMemory Access)的縮寫。在MCU芯片中,DMA是除CPU之外,最常見的總線主設備。作為總線主設備,DMA控制器可以輸出地址和控制信號到總線上,主動地發起和控制數據傳輸過程,它能夠按照程序的配置,在兩個從設備之間傳輸數據。例如在存儲器和I2C模塊之間傳輸數據,實現I2C數據的發送或接收,或從ADC讀出數據再傳送到USART進行發送。
下圖是LPC82x的部分框圖,圖中醒目標出了總線主設備,和DMA控制器。
? ? ? ? ? ? ? ? ? ?? ??圖1.LPC82x結構框圖(部分)在LPC82x的所有片內外設中,只有DMA控制器是總線主設備,其它都是總線從設備,只有主設備才能主動發起數據的傳輸操作。DMA的優勢是,可以在CPU最少的干預下,高效地執行數據塊的傳輸,節省CPU的時間,同時可以在CPU執行內部操作而不訪問總線時,更高效地利用總線的時間。
1.1DMA控制器的一些基本操作在介紹LPC800的DMA控制器之前,先通過這個傳輸示意圖,回顧一下通用DMA控制器必須具備的基本操作。
圖2.DMA傳輸示意圖
■產生數據傳輸的源地址和目標地址:DMA控制器在接到傳輸請求后,在內部總線上產生源數據地址SA,讀出要傳輸的數據,然后再產生存放數據的目標地址DA,將數據寫入指定的地方。
■控制每次傳輸后地址的變化:可以控制每次DMA傳輸是涉及到一個連續的地址區域還是單個獨立的地址。每次讀寫的源地址和目標地址分別改變或不改變,也可以同步地改變。
■控制傳輸的數據長度:軟件需要指定每次DMA傳輸的數據數量n。
■指定傳輸的數據寬度:軟件需要指定每次DMA讀寫的數據寬度,一般是以內部數據總線的寬度為限。對于32位MCU,可以是1個字節、2個字節(半字)或4個字節(字)。
■控制傳輸數據的節奏,即傳輸數據的時機:每次DMA讀寫都需要在有傳輸請求時才會執行。傳輸請求可以來自于數據源設備,例如ADC轉換結束;傳輸請求也可以來自于數據目標設備,例如SPI的發送就緒。因此每兩次傳輸請求的間隔可以不一致。DMA的傳輸請求也可以由DMA控制器內部產生,用于內存中數據塊的傳送。
■狀態查詢和中斷控制:DMA控制器的狀態和中斷可以是多種多樣,通常有傳輸開始、傳輸結束、傳輸錯誤等。
LPC800的DMA控制器實現了上述所有的基本控制功能,而且還有不少自己的特色,下面一一介紹。
1.2DMA傳輸與CPU指令的執行不管是CPU還是DMA控制器,都要通過同一條總線訪問存儲器和各種片內外設,進行數據傳輸。CPU的基本操作就是取指、譯碼、運算、執行的過程,取指操作需要占用總線,執行階段的讀數據或寫數據操作也需要占用總線。DMA控制器可以充分地利用CPU不占用總線的時間,在總線上傳輸數據。
如果在同一個時間,CPU和DMA控制器都需要占用總線,這種情況下需要有仲裁機制,協調兩個總線主設備的動作。
如果在總線已經被某個主設備(例如CPU)占用的時候,另一個主設備(例如DMA控制器)就會稍作等待,待總線空閑時,再開始數據傳輸。
從以上描述可以看出,DMA可以在不需CPU干預的情況下,利用CPU不占用總線的空閑時間進行數據傳輸。這樣不但提高了總線的利用率,還減輕了CPU搬運數據的負擔,提高了系統的并行性,能夠實現更復雜的控制要求,或降低整體的功耗。
1.3LPC800的DMA控制器LPC800的DMA控制器具有如下特性:▲多個通道,每個通道唯一地連接到一個片內外設的輸入或輸出請求,例如USART、SPI和I2C等通信外設。▲DMA傳輸可以由片內或片外事件觸發,每個DMA通道都可以有多個觸發輸入源,每次傳輸只能選擇一個觸發源。▲可以指定每個DMA通道的優先級,當通道之間的傳輸需求發生沖突時,高優先級通道先進行傳輸。▲傳輸描述符機制,通過多個傳輸描述符互聯,可以實現鏈式的DMA傳輸控制。▲每次(每個傳輸描述符)最多可以傳輸1024個字(1024x4=4096字節)。▲地址增量的多種選項,允許靈活的數據包處理。LPC800各系列的DMA配置如下:
|
系列
|
通道數目
|
觸發輸入源數目
|
|
LPC80x、LPC81x
|
0
|
0
|
|
LPC82x
|
18
|
9
|
|
LPC83x
|
18
|
8
|
|
LPC84x
|
25
|
13
|
DMA控制器有15個寄存器,涉及到所有通道,可以分為四組。
|
寄存器組
|
寄存器名稱
|
功能
|
說明
|
|
通用寄存器組
|
CTL
|
DMA控制器寄存器
|
只有一個控制位,使能DMA控制器
|
|
INTSTAT
|
中斷狀態寄存器
|
標志是否有掛起的中斷
|
|
|
SRAMBASE
|
傳輸描述符地址寄存器
|
所有通道第一個傳輸描述符的存放地址(必須512字節對齊)
|
|
|
通道控制寄存器組
|
ENABLESET0
|
通道使能寄存器
|
每個通道占用一位。表示是否使能對應通道
|
|
ENABLECLR0
|
通道失能寄存器
|
每個通道占用一位。表示是否失能對應通道
|
|
|
ACTIVE0
|
通道激活狀態寄存器
|
每個通道占用一位。表示對應通道是否加載了傳輸描述符
|
|
|
BUSY0
|
通道忙狀態寄存器
|
每個通道占用一位。表示對應通道是否正在搬運數據
|
|
|
通道中斷寄存器組
|
ERRINT0
|
錯誤中斷狀態寄存器
|
每個通道占用一位。表示是否有錯誤中斷
|
|
INTENSET0
|
中斷使能寄存器
|
每個通道占用一位。表示是否使能對應通道的中斷
|
|
|
INTENCLR0
|
中斷失能寄存器
|
每個通道占用一位。表示是否失能對應通道的中斷
|
|
|
INTA0
|
中斷A狀態寄存器
|
每個通道占用一位。表示是否有中斷A
|
|
|
INTB0
|
中斷B狀態寄存器
|
每個通道占用一位。表示是否有中斷B
|
|
|
傳輸控制寄存器
|
SETVALID0
|
設置“有效”控制位寄存器
|
每個通道占用一位。用于設置描述符的“有效”控制位
|
|
SETTRIG0
|
設置“觸發”控制位寄存器
|
每個通道占用一位。用于觸發對應的通道傳輸
|
|
|
ABORT0
|
通道中止傳輸寄存器
|
每個通道占用一位。用于中止對應的通道傳輸
|
|
寄存器名稱
|
功能
|
說明
|
|
CFG
|
通道配置寄存器
|
用于配置通道的使能、觸發、成組傳輸(Burst)和優先級的選項
|
|
CTLSTAT
|
通道控制狀態寄存器
|
用于標示通道的有效和觸發狀態
|
|
XFERCFG
|
通道傳輸配置寄存器
|
用于配置通道的各個配置選項
|
除了上述寄存器外,LPC800的DMA控制器通過位于內存中的傳輸描述符,控制每次的DMA傳輸。每個傳輸描述符有4個字(32位/字),內容如下:
多個傳輸描述符可以構成一個連續的鏈條或循環鏈,鏈條中的每一個描述符對應一次DMA傳輸。傳輸的數據數量、數據寬度以及地址變化的方式等,由XFERCFG寄存器的內容指定。
每次DMA傳輸開始前,DMA控制器都要把一個完整的描述符讀入,傳輸描述符中偏移地址為0x0的字會被傳送到XFERCFG寄存器中,用于控制各項傳輸參數。一個鏈條的第一次傳輸參數,需要由軟件直接寫入到XFERCFG寄存器,因此鏈條中的第一個描述符的第一個字為保留位。在傳輸開始時,DMA控制器會把傳輸區的地址讀入內部寄存器中。
使用描述符的鏈接特性,可以方便地實現多種數據傳輸控制。
例如需要使用SPI驅動一個LCD屏幕產生動畫效果時,可以配置為下圖所示的乒乓結構的DMA描述符鏈條,CPU只需要不斷地生成顯示圖片,由DMA控制器平行地進行圖片數據至LCD屏幕的傳送。
? ? ? ? ? ? ? ? ? ??圖3.乒乓結構的DMA描述符鏈這里使用了三個傳輸描述符。第一個描述符(鏈頭)指示將緩沖區A的數據傳輸到SPI的發送寄存器,后面兩個描述符分別指示緩沖區B和緩沖區A的數據,輪流傳輸到SPI的發送寄存器。CPU只需要在對應的緩沖區準備好數據,再設置對應的描述符為“有效”,接下來DMA控制器就會直接把數據傳送到SPI模塊進行發送。
1.3.3 DMA傳輸通道每個通信外設的發送傳輸可以產生DMA請求,接收傳輸也可以產生DMA請求。
LPC800的DMA控制器非常簡單,每個片內外設產生的DMA傳輸請求信號,唯一地連接到一個固定的DMA通道。即如果把片內外設作為DMA傳輸的源或目標,并且希望由該外設來控制傳輸的節奏(通過DMA傳輸請求信號),則必須使用對應的通道。如果能夠使用其它的方法(例如時鐘觸發等),保證外設不會發生數據溢出的情況,則可以使用任意通道,但這種用法不能最優地利用帶寬時間,除非需要特殊的時序控制,一般不建議使用。
每個通道對應的DMA請求源如下表所示:


表1.DMA通道與DMA請求源的對應表
1.3.4 DMA觸發源的選擇
在DMA控制器之外,有一個DMA觸發輸入 (DMA TRIGMUX)模塊,每個DMA通道都在這個模塊中有一個對應的多選一選擇器,由DMA_ITRIG_INMUXn寄存器控制(n對應表1的通道號),用戶可以在多種信號中選擇一個作為DMA的觸發信號。下表列出了所有可能的選項:
表2.DMA觸發選項(1):在LPC82x/83x中,n取值0~17;在LPC84x中,n取值0~24。
注*:每一個DMA通道都輸出一個觸發信號,所有通道輸出的觸發信號都連接到兩個多選一的選擇器:DMA_INMUX_INMUX0和DMA_INMUX_INMUX1,這兩個多選一選擇器的輸出可以作為另一個DMA通道的觸發源選項之一。這個配置允許多個DMA通道的協同操作。
? ? ? ? ? ? ? ? ? ? ? ?圖4.DMA觸發輸入框圖
每次DMA傳輸(即圖2示意中的每一次讀寫)的時機,則由DMA請求信號決定。
成組傳輸(Burst)是指在一次觸發之后,按照指定的次數進行一組數據的傳輸,這組數據傳輸完成后,需要另一次的觸發條件才能進行下一組的數據傳輸。
成組傳輸控制和DMA傳輸請求控制組合為四種操作模式,他們與觸發信號的關系如下表所示。
表中的DMA傳輸請求信號以USART的TXRDY為例:
|
組合模式
|
使能成組傳輸(TRIGBURST,見1.5.1節)
|
|||
|
使能外設請求(PERIPHREQEN,見1.5.1節)
|
||||
|
操作模式說明
|
||||
|
0
|
0
|
0
|
觸發信號用于啟動完整的DMA傳輸過程,只需一個觸發信號即可完成所有數據傳輸。
|
DMA將以最快的速度,連續不斷地傳送數據,直到完成所有數據。
|
|
1
|
0
|
1
|
每個DMA傳輸請求(TXRDY),只能傳輸一個數據。
|
|
|
2
|
1
|
0
|
每個觸發信號啟動一組DMA傳輸,每組傳送BURSTPOWER(見1.5.1節)個數據。因此總共需要(XFERCOUNT/BURSTPOWER)組的傳輸(即需要相同數目的觸發信號),才能完成所有數據。
XFERCOUNT位于XFERCFG寄存器(見1.5.2節)。
|
DMA將以最快的速度,連續不斷地傳送數據,直到完成所有數據。
|
|
3
|
1
|
1
|
每個DMA傳輸請求(TXRDY),只能傳輸一個數據。
|
|
表3.成組傳輸和外設請求與觸發信號的關系表
表中的組合模式0適合于從存儲器至存儲器的數據塊拷貝;組合模式1適合于常用的通信模塊的數據發送和接收。
組合模式2、3則視具體的應用情況,由用戶自由發揮。例如,要在USART上發送若干個固定長度的數據包,而發送每個數據包的時間需要由定時器來決定,則可以使用上述的組合模式3,設置BURSTPOWER為數據包的長度,設置SCT定時器產生觸發信號(見表2的SCT_DMA0/1)。
拿自動步槍做一個形象的比喻,成組的概念相當于子彈夾,外設請求相當于扳機,觸發相當于擊發保險,一次DMA傳輸中需要打出一箱子彈。那么每種組合模式有如下對應:■組合模式0:打開擊發保險后,不需其它動作,整箱子彈即全部射出。■組合模式1:打開擊發保險后,每扣動一次扳機,射出一發子彈,直到打完整箱子彈。■組合模式2:打開擊發保險后,不需其它動作,一個彈夾中的子彈即全部射出。然后再次打開擊發保險,即射出另一個彈夾中的全部子彈。重復上述操作直到整箱子彈打光。■組合模式3:打開擊發保險后,每扣動一次扳機,射出一個彈夾中的一顆子彈,重復直到這個彈夾中的子彈打光,擊發保險自動關閉。然后須再次打開擊發保險,再一次次地扣動扳機,逐個射出另一個彈夾中的所有子彈,擊發保險再次自動關閉。重復上述過程直到打完整箱子彈。
這里有一個要求,即XFERCOUNT必須能被BURSTPOWER整除,即一箱子彈的數目,必須是一個彈夾能容納子彈個數的倍數。
1.5 DMA通道參數寄存器
在1.3.1節中列出的寄存器,用于控制整個DMA控制器,以及控制每個通道的使能、中斷和觸發等狀態。每個通道的具體工作模式,由下述三個寄存器來描述。
1.5.1DMA通道配置寄存器(CFG)

各個控制域的說明如下:▲PERIPHREQEN:使能外設請求。0 – 使能外設請求;1 – 不使能外設請求。
每個通道的外設請求來源是固定的,見表1。例如對應SPI0的發送就緒信號(TXRDY)的DMA通道,在LPC82x/83x中是通道7,在LPC84x中是通道11。▲HWTRIGEN:使能硬件觸發。硬件觸發信號源由DMA_ITRIG_INMUXn寄存器選擇,見1.3.4節。0 – 使能硬件觸發;1 – 不使能硬件觸發。▲TRIGPOL:觸發極性。▲TRIGTYPE:觸發類型。TRIGPOL和TRIGTYPE共同決定如何使用觸發信號,組合關系如下表。
|
TRIGTYPE
|
TRIGPOL
|
說明
|
|
0
|
0
|
下降沿觸發
|
|
0
|
1
|
上升沿觸發
|
|
1
|
0
|
低電平觸發
|
|
1
|
1
|
高電平觸發
|
0 – 觸發之后不按組傳輸(或可理解為所有數據為一組);1 - 觸發之后執行成組傳輸。▲BURSTPOWER:成組傳輸中每組的長度。這個域的內容為2的冪次數值,取值為0~10,表示每組長度為1(20)、2(21)、4(22) 、8(23)、……、1024(210)。不支持0~10之外的數值。
注意:XFERCOUNT必須是BURSTPOWER的倍數。
▲SRCBURSTWRAP:成組傳輸中每組傳輸結束后,是否需要恢復傳輸的源地址。0 – 不恢復源地址;1 – 恢復源地址。這個控制項適合于重復地讀出相同的數據塊,或相同的一組寄存器。▲DSTBURSTWRAP:成組傳輸中每組傳輸結束后,是否需要恢復傳輸的目標地址。0 – 不恢復目標地址;1 – 恢復目標地址。
這個控制項適合于重復地寫入相同的存儲區,例如重復地讀出一組傳感器的數值,軟件只關心即時的數值,而不關心數值變化的過程。▲CHPRIORITY:設置本通道的優先級。在多個通道同時請求獲得總線進行傳輸時,優先級高的通道先得到總線的使用權限。
0 – 最高優先級;7 – 最低優先級。
1.5.2 DMA通道傳輸配置寄存器(XFERCFG)

XFERCFG各個控制域的說明如下:▲CFGVALID:表示所對應的描述符是否有效。0 – 描述符無效;1 – 描述符有效。▲RELOAD:當前描述符所指定的傳輸完成后,是否需要讀入下一個描述符。0 – 不讀入下一個描述符;1 – 讀入下一個描述符,允許描述符的鏈接操作。▲SWTRIG:軟件觸發。0 – 需要由HWTRIGEN、TRIGPOL和TRIGTYPE指定通道的觸發條件。
1 – 設置此位表示該通道的觸發條件立即滿足。
注意:使用軟件觸發時,在TRIGBURST=0時,不得使用電平觸發。▲CLRTRIG:當前描述符的傳輸結束后,是否清除觸發條件。0 – 不清除。如果RELOAD=1,則下一個描述符的觸發條件滿足。
1 – 清除。當前描述符指示的傳輸結束后,清除觸發條件。
注意:只有軟件觸發條件和邊沿觸發條件可以被清除,而電平觸發條件不能被清除。▲SETINTA:當前描述符的傳輸結束后,是否產生中斷標志INTA。0 – 不產生中斷標志;1 – 產生中斷標志。▲SETINTB:當前描述符的傳輸結束后,是否產生中斷標志INTB。0 – 不產生中斷標志;1 – 產生中斷標志。
INTA和INTB在硬件上沒有差別,用戶可以用這兩個中斷(標志)區別是哪個描述符的傳輸完成了,尤其是在乒乓結構的傳輸中。▲WIDTH:表示每次DMA傳輸的數據寬度。源地址的讀和目標地址的寫,使用相同的數據寬度。0 – 8位數據傳輸;1 – 16位數據傳輸;2 – 32位數據傳輸;3 – 保留組合,不得使用。
注意:如果要求的數據寬度是16位或32位,則傳輸的地址也必須分別是2字節或4字節對齊的。▲SRCINC:表示每次傳輸一個數據后,源地址的增量變化。0 – 地址無變化。
1 – 地址按數據寬度+1,指向數據區中的下一個數據。
2 – 地址按數據寬度+2。
3 – 地址按數據寬度+4。▲DSTINC:表示每次傳輸一個數據后,目標地址的增量變化。該域的取值含義與SRCINC一樣。
SRCINC和DSTINC取值為0,最常見的應用場景是面對外設寄存器的讀寫,例如傳送一個數據塊至SPI0的TXDAT,或從USART的RXDAT讀出一組數據至存儲區。
SRCINC和DSTINC取值為1,最常見的應用場景對一個連續的存儲區的讀寫。
SRCINC和DSTINC取值為2或3時,一個應用案例是,當WIDTH=0(8位數據)時,希望傳輸一組數據字或半字中的某個字節,而不管其它字節。▲XFERCOUNT:傳輸的數據總數,寄存器中填入(總數-1)。該域有10位,即最大傳輸數據數目為1024。
傳輸的字節總數為:(XFERCOUNT + 1) *WIDTH。
注意1:在DMA傳輸過程中,DMA控制器會遞減該數值,因此不能在傳輸過程中或傳輸結束后,讀出該域而得知預設的傳輸數目。
注意2:如果設置了TRIGBURST =1,則XFERCOUNT必須是BURSTPOWER的倍數。
1.5.3DMA通道控制和狀態寄存器(CTLSTAT)
這個寄存器只有兩個標志位,用戶可以檢查這些標志位,獲知當前DMA控制器的部分運行狀態。▲VALIDPENDING:延遲的有效位。見0的說明。▲TRIG:觸發標志。該位表示是否有觸發條件。設置觸發條件有多種途徑:■通道傳輸配置寄存器(XFERCFG)的SWTRIG控制位。■設置觸發控制寄存器(SETTRIG0) ,該寄存器可以同時設置一個或多個通道的觸發條件。■DMA觸發輸入模塊選定的硬件觸發信號,滿足TRIGPOL和TRIGTYPE時。清除觸發條件也有多種途徑:■當CLRTRIG=1時,描述符指定的傳輸結束時,清除觸發條件。■當失能DMA控制器時,見CTRL寄存器。
1.6描述符有效位的延遲設置機制通道傳輸配置寄存器(XFERCFG)的CFGVALID位,指定該描述符是否有效。當一個有效的描述符被讀入DMA控制器后,當CTLSTAT寄存器的TRIG標志被設置后,DMA傳輸就會立即開始,這是最理想的情況。通常的情況是,當準備好一個描述符A,尤其是使用描述符鏈時,描述符A所對應的存儲區的數據可能還沒有準備好,循環的乒乓結構就是一個很好的例子。這種情況下,就需要先設置描述符A的CFGVALID=0,待數據區準備好后再設置它為有效。這樣延遲設置描述符有效,是通過SETVALID0寄存器來完成。
SETVALID0寄存器的每一位對應一個DMA通道,第n位寫’1’表示延遲設置通道n的描述符有效。
使用SETVALID0寄存器實現延遲設置描述符有效,是為了避免設置錯誤。設想一下,當DMA控制器已經在運行鏈上的某個描述符B時,軟件無法知道另一個描述符A是否已經被讀入DMA控制器。如果它還未被讀入DMA控制器,則可以直接操作描述符A所在的存儲區;如果它已經被讀入DMA控制器,則應該操作XFERCFG寄存器。
SETVALID0寄存器就是為了正確地設置描述符的有效位。
經過以上介紹可以看到,如果當前加載到DMA控制器的描述符是有效的,設置SETVALID0寄存器表示延遲設置鏈中下一個描述符為有效,此時CTLSTAT寄存器的VALIDPENDING為‘1’,標示這種狀態;當下一個描述符被讀入DMA控制器時,經延遲的設置描述符有效的操作才最終完成,此時VALIDPENDING位被清除。如果當前加載到DMA控制器的描述符是無效的,設置SETVALID0寄存器表示改變當前這個描述符為有效,不需經過延遲,操作立即生效。
使用這種延遲機制,軟件可以從容地先準備好描述符鏈,然后再按部就班地準備好數據區,逐步推進數據傳輸進程,而不必費周折查詢等待DMA控制器的狀態。
1.7若干DMA傳輸例程
本節的幾個例程,分別展示幾種DMA的常見用法。所有例程都會用到這樣幾個結構體。
結構體DMA_CHDESC_T是所有通道的描述符鏈中的第一個描述符。
|
typedef struct {
|
|
|
uint32_t notused; // 第一個描述符的這個位置是保留位
|
|
|
uint32_t source; // DMA傳輸源數據區的末地址
|
|
|
uint32_t dest; // DMA傳輸目標數據區的末地址
|
|
|
uint32_t next; // 鏈接到下一個描述符
|
|
|
} DMA_CHDESC_T;
|
|
ALIGN(512) DMA_CHDESC_T Chan_Desc_Table[];
|
每個通道的第一個描述符,必須放在這個數組中與通道編號對應的單元中。例如USART1_RX_DMA通道的第一個描述符需要放在數組的第2個單元中(見表1)。
結構體DMA_RELOADDESC_T適用于其它描述符。所有描述符必須位于16字節對齊的內存地址。
|
typedef struct {
|
|
|
uint32_t xfercfg; // 描述符的傳輸配置寄存器
|
|
|
uint32_t source; // DMA傳輸源數據區的末地址
|
|
|
uint32_t dest; // DMA傳輸目標數據區的末地址
|
|
|
uint32_t next; // 鏈接到下一個描述符
|
|
|
} DMA_RELOADDESC_T;
|
使用DMA的最簡單應用就是在內存中拷貝一個數據塊,這是一個非常有效率的搬移數據塊的方法,尤其是數據量比較大時,CPU可以同時執行更多的操作。DMA拷貝數據塊不涉及到任何外設請求,使用軟件觸發,所以也不涉及到任何硬件的觸發。
本例程先用隨機數初始化數組Buffer1[ ],然后用DMA從Buffer1[ ]傳送數據至Buffer2[ ]。數組定義如下:
下面是初始化DMA控制器,并啟動DMA的函數。代碼片段1.使用DMA在內存中拷貝一個數據塊
01 void DMA_M2M_Init(uint32_t *buf1, uint32_t *buf2, uint32_t length)
02 { uint32_t ch_cfg_val, xfercount, xfercfg;
03
04 LPC_SYSCON->SYSAHBCLKCTRL |= DMA;
05 LPC_DMA->CTRL = 0;
06
07 LPC_DMA->SRAMBASE = (uint32_t)(&Chan_Desc_Table);
08
09 xfercount = length - 1;
10 ch_cfg_val = 0;
11 xfercfg = 0 << DMA_XFERCFG_CFGVALID | // 暫時設置為無效
12 0 << DMA_XFERCFG_RELOAD | // 沒有下一個描述符
13 1 << DMA_XFERCFG_SWTRIG | // 軟件觸發
14 1 << DMA_XFERCFG_CLRTRIG | // 傳輸結束時清除觸發標志
15 1 << DMA_XFERCFG_SETINTA | // 傳輸結束時設置INTA中斷
16 0 << DMA_XFERCFG_SETINTB |
17 2 << DMA_XFERCFG_WIDTH | // 數據寬度為32位
18 1 << DMA_XFERCFG_SRCINC | // 每次傳輸后源地址遞增
19 1 << DMA_XFERCFG_DSTINC | // 每次傳輸后目標地址遞增
20 xfercount << DMA_XFERCFG_XFERCOUNT; // 傳輸長度
21 LPC_DMA->CHANNEL[CH_USART0_RX].CFG = ch_cfg_val;
22 LPC_DMA->CHANNEL[CH_USART0_RX].XFERCFG = xfercfg;
23
24 Chan_Desc_Table[CH_USART0_RX].source = (uint32_t)(&buf1[xfercount]);
25 Chan_Desc_Table[CH_USART0_RX].dest = (uint32_t)(&buf2[xfercount]);
26 Chan_Desc_Table[CH_USART0_RX].next = (uint32_t)0L;
27
28 LPC_DMA->INTENSET0 = 1 << CH_USART0_RX;
29 LPC_DMA->ENABLESET0 = 1 << CH_USART0_RX;
30
31 LPC_DMA->CTRL = 1;
32
33 LPC_DMA->SETVALID0 = 1 << CH_USART0_RX;
34 // LPC_DMA->SETTRIG0 = 1 << CH_USART0_RX;
35 }
在這個例程中,用到了宏定義CH_USART0_RX,這是對應USART0接收方向的DMA通道號(見表1),在頭文件中定義了所有的通道號:|
#define CH_USART0_RX 0 // USART0接收就緒
|
|
|
#define CH_USART0_TX 1 // USART0發送就緒
|
|
|
#define CH_USART1_RX 2 // USART1接收就緒
|
|
|
#define CH_USART1_TX 3 // USART1發送就緒
|
|
|
......
|
|
|
......
|
|
ALIGN(512) DMA_CHDESC_T Chan_Desc_Table[1];
|
在本例程中,由于使用的是通道0,而沒有使用其它通道,所以在數組中只配置了一個單元。
原則上,這個數組中對應不使用的通道的描述符單元,可以挪做其它用途。
在這個例程里,配置好所有的寄存器和描述符,并且使能了整個DMA控制器后,在第33行配置了對應的傳輸符為有效,該語句執行后DMA傳輸立即開始了。
如果第13行沒有配置XFERCFG的“軟件觸發”位,執行第33行后DMA通道還需要等待觸發信號才能開始傳輸,第34行就是由軟件發出DMA觸發信號的另一種途徑。
由上面的介紹可以看出,可以有多種方式,靈活地安排啟動DMA傳輸的時機和方法,讓用戶可以更加有效地安排自己的應用流程。
下面是這個例程的主函數和中斷處理程序。
代碼片段2.使用DMA拷貝一個數據塊的中斷函數和主函數
01 uint8_t DMA_IntA_Flag;
02 void DMA_IRQHandler(void) // DMA中斷處理程序
03 {
04 if (LPC_DMA->INTA0 & (1 << CH_USART0_RX)) {
05 LPC_DMA->INTA0 = 1 << CH_USART0_RX;
06 DMA_IntA_Flag = 1;
07 }
08 if (LPC_DMA->ERRINT0 & (1 << CH_USART0_RX))
09 LPC_DMA->ERRINT0 = 1 << CH_USART0_RX;
10 }
11
12 void main()
13 { uint32_t pp;
14 for (pp = 0; pp < BUF_SIZE; pp++)
15 Buffer1[pp] = rand();
16
17 DMA_IntA_Flag = 0;
18 DMA_M2M_Init(Buffer1, Buffer2, BUF_SIZE);
19
20 NVIC_EnableIRQ(DMA_IRQn);
21 do {
22 __WFI();
23 } while (DMA_IntA_Flag == 0);
24
25 while (1);
26 }
DMA傳輸結束后產生中斷,在中斷函數中將軟件標志置’1’,主函數可以知道DMA傳輸是否已經完成。在實際的項目中,用戶程序可以替換上述21~23行的代碼,執行其它的一些操作。
1.7.2DMA執行USART0的連續發送(硬件觸發)下面這個例程是使用DMA通過USART0發送一個字符串,需要配置使用USART0的發送請求,并采用開發板上的USER_KEY產生硬件觸發,即按下按鍵后才送出所有數據,用戶可以在PC端的虛擬串口上看到送出的字符串。
首先還是DMA的初始化函數,這個函數與前面的數據塊搬運初始化基本一致,指示CFG和XFERCFG寄存器的內容有所變化。
代碼片段3. DMA通過USART0發送字符串01 void DMA_UART_Send(uint8_t *buf, uint32_t length)
02 { uint32_t ch_cfg_val, xfercount, xfercfg;
03
04 LPC_SYSCON->SYSAHBCLKCTRL |= DMA;
05 LPC_DMA->CTRL = 0;
06
07 LPC_DMA->SRAMBASE = (uint32_t)(&Chan_Desc_Table);
08
09 xfercount = length - 1;
10 ch_cfg_val = 1 << DMA_CFG_PERIPHREQEN | // 外設請求
11 1 << DMA_CFG_HWTRIGEN | // 硬件觸發
12 0 << DMA_CFG_TRIGTYPE | // 邊沿觸發
13 0 << DMA_CFG_TRIGPOL; // 下降沿觸發
14
15 xfercfg = 0 << DMA_XFERCFG_CFGVALID | // 暫時設置為無效
16 0 << DMA_XFERCFG_RELOAD | // 沒有下一個描述符
17 0 << DMA_XFERCFG_SWTRIG | // 沒有軟件觸發
18 1 << DMA_XFERCFG_CLRTRIG | // 傳輸結束時清除觸發標志
19 1 << DMA_XFERCFG_SETINTA | // 傳輸結束時設置INTA中斷
20 0 << DMA_XFERCFG_SETINTB |
21 0 << DMA_XFERCFG_WIDTH | // 數據寬度為8位
22 1 << DMA_XFERCFG_SRCINC | // 每次傳輸后源地址遞增
23 0 << DMA_XFERCFG_DSTINC | // 每次傳輸后目標地址不遞增
24 xfercount << DMA_XFERCFG_XFERCOUNT; // 傳輸長度
25 LPC_DMA->CHANNEL[CH_USART0_TX].CFG = ch_cfg_val;
26 LPC_DMA->CHANNEL[CH_USART0_TX].XFERCFG = xfercfg;
27
28 Chan_Desc_Table[CH_USART0_TX].source = (uint32_t)(&buf[xfercount]);
29 Chan_Desc_Table[CH_USART0_TX].dest = (uint32_t)(&LPC_USART0->TXDAT);
30 Chan_Desc_Table[CH_USART0_TX].next = (uint32_t)0L;
31
32 LPC_DMA->INTENSET0 = 1 << CH_USART0_TX;
33 LPC_DMA->ENABLESET0 = 1 << CH_USART0_TX;
34
35 LPC_DMA->CTRL = 1;
36
37 LPC_DMA->SETVALID0 = 1 << CH_USART0_TX;
38 }
上述代碼里用橙色標注出與代碼片段1不同的地方。還有一個明顯的不同是,此處所有涉及到DMA通道號時,都換成了CH_USART0_TX。
USART0的初始化部分與USART章節的代碼完全一致,現抄錄如下。
代碼片段4.基本UART收發例程的USART0初始化
00 void USART0_init() {
01 LPC_SYSCON->SYSAHBCLKCTRL |= (UART0 | SWM);
02
03 LPC_SYSCON->PRESETCTRL &= (UART0_RST_N);
04 LPC_SYSCON->PRESETCTRL |= ~(UART0_RST_N);
05
06 ConfigSWM(U0_TXD, P0_4);
07 ConfigSWM(U0_RXD, P0_0);
08
09 LPC_SYSCON->UARTCLKDIV = LPC_SYSCON->SYSAHBCLKDIV; // 設置USART時鐘的分頻系數
10 LPC_SYSCON->UARTFRGMULT = 4;
11 LPC_SYSCON->UARTFRGDIV = 255;
12 LPC_USART0->BRG = 16 - 1;
13
14 // 8個數據位,無校驗位,1個停止位,沒有硬件流控,異步模式
15 LPC_USART0->CFG = DATA_LENG_8 | PARITY_NONE | STOP_BIT_1;
16
17 LPC_USART0->CTL = 0;
18
19 LPC_USART0->STAT = 0xFFFF;
20
21 LPC_USART0->INTENSET = RXRDY;
22 NVIC_EnableIRQ(UART0_IRQn);
23 }
接下來是本節的重點。代碼片段3的第11~13行,配置DMA通道為硬件觸發信號的下降沿觸發,下面是初始化PINTINT,使用USER_KEY產生觸發信號,和對應的中斷程序。代碼片段5.初始化按鍵產生DMA硬件觸發信號
01 #define PINTSEL0 0 // 定義引腳中斷0的編號
02 #define KEY_USER P0_1 // 定義按鍵USER_KEY的引腳
03
04 void PININT0_IRQHandler(void)
05 {
06 if (LPC_PIN_INT->RISE & (1<
07 LPC_PIN_INT->RISE = 1<// 清除上升沿中斷標志
08 if (LPC_PIN_INT->FALL & (1<
09 LPC_PIN_INT->FALL = 1<// 清除下降沿中斷標志
10 }
11
12 void PINT_Init_Key_User()
13 {
14 LPC_GPIO_PORT->DIRCLR0 = 1 << KEY_USER; // 配置USER_KEY對應的引腳為輸入
15 LPC_SYSCON->PINTSEL[PINTSEL0] = KEY_USER; // USER_KEY對應對應到引腳中斷0(PINTSEL0)
16 LPC_PIN_INT->ISEL = 0 << PINTSEL0; // 配置引腳中斷0(PINTSEL0)為邊沿觸發
17 LPC_PIN_INT->IENR = 1 << PINTSEL0; // 配置引腳中斷0(PINTSEL0)是上升沿觸發
18 LPC_PIN_INT->IENF = 0 << PINTSEL0; // 配置引腳中斷0(PINTSEL0)不是下降沿觸發
19 LPC_PIN_INT->IST = 0xFF; // 清除所有可能的引腳中斷標志
20 NVIC_EnableIRQ(PININT0_IRQn); // 使能引腳中斷
21 }
上述代碼是對PINTINT的初始化,它配置USER_KEY對應的引腳產生一個中斷信號,確切地說是按鍵按下再抬起時的上升沿將產生中斷。第04行的中斷處理程序中,只是簡單地清除可能的上升沿或下降沿中斷標志。
這里要澄清兩個概念,一個是引腳中斷的觸發信號,另一個是DMA的觸發信號。前者是引腳上的信號,用于產生中斷;后者是芯片內部的中斷標志對應的信號,用于觸發DMA傳輸。兩個信號分別有上升沿和下降沿的選項,但兩者是不等價的,本例程中引腳中斷選擇的是上升沿,而DMA觸發信號選擇的是下降沿。
下圖顯示出了這兩個信號之間的關系:
? ? ? ? ? ? ? ? ? ? ??圖5.引腳中斷與DMA觸發信號的關系圖圖中的時間點④是觸發DMA傳輸的時間,這個時間點與代碼片段5第07行的執行相對應。如果清除上升沿中斷的動作被推遲,則DMA傳輸的時間也會被推遲,所以用戶要盡快地響應PINTINT中斷并清除中斷標志,以實現快速高效傳輸。
下面是主函數代碼,主函數中調用了前面介紹過的DMA、USART和PINTINT函數。
代碼片段6.硬件觸發DMA傳輸UART發送數據例程
01 const unsigned char Hello[] = "Hello DMA World!
"; // 待發送的數據串
02 void main()
03 {
04 USART0_init(); // 初始化USART0
05
06 DMA_IntA_Flag = 0; // 清除DMA中斷標記
07
08 DMA_UART_Send((uint8_t *)Hello, sizeof(Hello)-1); // 初始化DMA控制器
09
10 LPC_DMATRIGMUX->DMA_ITRIG_INMUX1 = 0x05;
11 PINT_Init_Key_User();
12
13 NVIC_EnableIRQ(DMA_IRQn);
14 do {
15 __WFI();
16 }
17 while (DMA_IntA_Flag == 0);
18
19 while (1);
20 }
這個主函數與前面那個例程(見代碼片段2)的主要區別,就是第10、11行配置DMA觸發源和對觸發源(引腳中斷0)的初始化。
這里需要注意的是第10行配置DMA觸發源,一定要在使能DMA時鐘之后執行。本例程中,DMA的時鐘是在DMA_UART_Send()中設置的,見代碼片段3。
1.7.3DMA執行USART0的成組發送
這個例程是在上一個例程的基礎上,增加了使用乒乓鏈接的描述符,同時設置成組傳輸。
下圖所示為本例程中乒乓鏈接的描述符鏈。鏈頭描述符指向一個開始字符串Hello,并鏈接到描述符B。描述符A指向一個字符串A,描述符B指向另一個字符串B。傳輸執行的效果是,先發送Hello,然后循環發送SpingBàSpingAàSpingB......。
圖6.DMA執行USART0的成組發送例程的描述符鏈另外,例程中安排了以成組(Burst)方式發送每個字符串,即每次觸發只發送BURSTPOWER指定長度的數據,見1.5.1的說明。
和上節一樣,例程中的觸發源也是與USER_KEY相連的引腳中斷0。
本例程用到下述變量。
ALIGN(512) DMA_CHDESC_T Chan_Desc_Table[2]; // 所有通道描述符鏈頭數組
ALIGN(16) DMA_RELOADDESC_T Descriptor_A; // 描述符A
ALIGN(16) DMA_RELOADDESC_T Descriptor_B; // 描述符B
uint8_t DMA_IntA_Flag; // 中斷A軟件標志
uint8_t DMA_IntB_Flag; // 中斷B軟件標志
// 1234----1234----1234----1234----
const uint8_t Hello[] = ">> Hello my DMA world!
"; // 鏈頭對應的字符串
const uint8_t StringA[] = ">> Hello dear StringA.
"; // 描述符A的字符串
const uint8_t StringB[] = "<< Hello I am saying String B.
"; // 描述符B的字符串
下面是DMA初始化的代碼代碼片段7.執行USART0的成組發送例程的DMA初始化代碼
01 void DMA_UART_PingPong_Send(uint8_t *buf, uint32_t length)
02 { uint32_t ch_cfg_val, xfercount, xfercfg;
03
04 LPC_SYSCON->SYSAHBCLKCTRL |= DMA;
05 LPC_DMA->CTRL = 0;
06
07 LPC_DMA->SRAMBASE = (uint32_t)(&Chan_Desc_Table);
08
09 xfercount = length - 1;
10 ch_cfg_val = 1 << DMA_CFG_PERIPHREQEN | // 外設請求
11 1 << DMA_CFG_HWTRIGEN | // 硬件觸發
12 0 << DMA_CFG_TRIGTYPE | // 邊沿觸發
13 0 << DMA_CFG_TRIGPOL | // 下降沿觸發
14 1 << DMA_CFG_TRIGBURST | // 成組傳輸模式
15 3 << DMA_CFG_BURSTPOWER; // 每組為8(23)個數據
16
17 xfercfg = 0 << DMA_XFERCFG_CFGVALID | // 暫時設置為無效
18 1 << DMA_XFERCFG_RELOAD | // 有下一個描述符
19 0 << DMA_XFERCFG_SWTRIG | // 沒有軟件觸發
20 1 << DMA_XFERCFG_CLRTRIG | // 傳輸結束時清除觸發標志
21 1 << DMA_XFERCFG_SETINTA | // 傳輸結束時設置INTA中斷
22 0 << DMA_XFERCFG_SETINTB |
23 0 << DMA_XFERCFG_WIDTH | // 數據寬度為8位
24 1 << DMA_XFERCFG_SRCINC | // 每次傳輸后源地址遞增
25 0 << DMA_XFERCFG_DSTINC | // 每次傳輸后目標地址不遞增
26 xfercount << DMA_XFERCFG_XFERCOUNT; // 傳輸長度
27 LPC_DMA->CHANNEL[CH_USART0_TX].CFG = ch_cfg_val;
28 LPC_DMA->CHANNEL[CH_USART0_TX].XFERCFG = xfercfg;
29
30 Chan_Desc_Table[CH_USART0_TX].source = (uint32_t)(&buf[xfercount]);
31 Chan_Desc_Table[CH_USART0_TX].dest = (uint32_t)(&LPC_USART0->TXDAT);
32 Chan_Desc_Table[CH_USART0_TX].next = (uint32_t)& Descriptor_B;
33
34 xfercount = sizeof(StringB)- 2; // 去掉字符串結尾的字符’’
35 Descriptor_B.xfercfg = 1 << DMA_XFERCFG_CFGVALID |
36 1 << DMA_XFERCFG_RELOAD |
37 1 << DMA_XFERCFG_CLRTRIG |
38 1 << DMA_XFERCFG_SETINTB |
39 1 << DMA_XFERCFG_SRCINC |
40 xfercount << DMA_XFERCFG_XFERCOUNT;
41 Descriptor_B.source = (uint32_t)(&StringB[xfercount]);
42 Descriptor_B.dest = (uint32_t)(&LPC_USART0->TXDAT);
43 Descriptor_B.next = (uint32_t)&Descriptor_A;
44
45 xfercount = sizeof(StringA)- 2; // 去掉字符串結尾的字符’’
46 Descriptor_A.xfercfg = 1 << DMA_XFERCFG_CFGVALID |
47 1 << DMA_XFERCFG_RELOAD |
48 1 << DMA_XFERCFG_CLRTRIG |
49 1 << DMA_XFERCFG_SETINTA |
50 1 << DMA_XFERCFG_SRCINC |
51 xfercount << DMA_XFERCFG_XFERCOUNT;
52 Descriptor_A.source = (uint32_t)(&StringA[xfercount]);
53 Descriptor_A.dest = (uint32_t)(&LPC_USART0->TXDAT);
54 Descriptor_A.next = (uint32_t)&Descriptor_B;
55
56 LPC_DMA->INTENSET0 = 1 << CH_USART0_TX;
57 LPC_DMA->ENABLESET0 = 1 << CH_USART0_TX;
58
59 LPC_DMA->CTRL = 1;
60
61 LPC_DMA->SETVALID0 = 1 << CH_USART0_TX;
62 }
DMA控制器中設置的兩個中斷INTA和INTB的內部機制完全一致,分為兩個中斷源只是為了讓用戶區分對應的描述符,用戶可以自由安排。本例中設置描述符A執行結束后會產生中斷A,描述符B執行結束后會產生中斷B,因此相比前一個例程中斷處理程序也多了出來INTB的代碼。
代碼片段8.執行USART0的成組發送例程中斷處理程序
01 void DMA_IRQHandler(void)
02 {
03 if (LPC_DMA->INTA0 & (1 << CH_USART0_TX)) {
04 LPC_DMA->INTA0 = 1 << CH_USART0_TX;
05 DMA_IntA_Flag++;
06 }
07 if (LPC_DMA->INTB0 & (1 << CH_USART0_TX)) {
08 LPC_DMA->INTB0 = 1 << CH_USART0_TX;
09 DMA_IntB_Flag++;
10 }
11 if (LPC_DMA->ERRINT0 & (1 << CH_USART0_TX))
12 LPC_DMA->ERRINT0 = 1 << CH_USART0_TX;
13 }
每次執行完一個描述符后,就會相應地產生一個中斷(INTA或INTB),用戶可以自行在代碼片段8的第05和09行安插代碼在適當的時候結束整個描述符鏈的DMA傳輸,例如當循環次數滿足一定要求時。
代碼片段9.執行USART0的成組發送例程的主函數
01 void main()
02 {
03 USART0_init(); // 初始化USART0
04
05 DMA_IntA_Flag = DMA_IntB_Flag = 0; // 清除DMA中斷標記
06
07 DMA_UART_PingPong_Send((uint8_t *)Hello, sizeof(Hello)-1); // 初始化DMA控制器
08
09 LPC_DMATRIGMUX->DMA_ITRIG_INMUX1 = 0x05;
10 PINT_Init_Key_User();
11
12 NVIC_EnableIRQ(DMA_IRQn);
13 while (1)
14 __WFI();
15 }
主函數和前面的代碼片段6基本一致。執行這個例程后,每按一次按鍵DMA會發送8個字符,在串口助手上有如下顯示:

END
更多恩智浦AI-IoT市場和產品信息,邀您同時關注“NXP客棧”微信公眾號
?
?
?NXP客棧
恩智浦致力于打造安全的連接和基礎設施解決方案,為智慧生活保駕護航。
長按二維碼,關注我們
恩智浦MCU加油站
這是由恩智浦官方運營的公眾號,著重為您推薦恩智浦MCU的產品信息、開發技巧、教程文檔、培訓課程等內容。
?長按二維碼,關注我們
原文標題:LPC800前生今世-第九章 直接存儲器訪問 (DMA)
文章出處:【微信公眾號:恩智浦MCU加油站】歡迎添加關注!文章轉載請注明出處。
-
mcu
+關注
關注
147文章
18925瀏覽量
398244 -
恩智浦
+關注
關注
14文章
6095瀏覽量
147401
原文標題:LPC800前生今世-第九章 直接存儲器訪問 (DMA)
文章出處:【微信號:NXP_SMART_HARDWARE,微信公眾號:恩智浦MCU加油站】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
【「Altium Designer 25 電路設計精進實踐」閱讀體驗】+第九章實例- what?這不就是官方的SAM V71開發板嗎?
【「Altium Designer 25 電路設計精進實踐」閱讀體驗】+本書概覽與內容特點介紹
【「Altium Designer 25 電路設計精進實踐」閱讀體驗】+讀后感
CW32F030 RAM存儲器的介紹
LPC800系列MCU:低功耗與高性能的完美結合
芯源的片上存儲器介紹
第九屆集創賽全國總決賽“紫光同創杯”圓滿落幕
鎧俠第九代 BiCS FLASH? 512Gb TLC 存儲器開始送樣
第九章 W55MH32 HTTP Server示例
LPC800前生今世-第九章 直接存儲器訪問 (DMA)
評論