S03_CH06_AXI_VDMA_OV7725攝像頭采集系統
本課程將對Xilinx提供的一款IP核——AXI VDMA(Video Direct Memory Access) 進行詳細講解,為后續的學習和開發做好准備。內容安排如下:首先分析為什么要使用VDMA、VDMA的作用;然后詳細介紹VDMA的特點、寄存器作空間; 最后闡述如何使用VDMA,包括IP核的配置方法、代碼編寫流程等。
本章主要是理論學習,學習完本章,會對VDMA有全面的認識,有利於學習后續的圖像生成、視頻采集處理系統。由於VDMA主要用於視頻流數據的存取,單獨測試的意義不大,所以在接下來的章節會提供一些樣例設計,進一步學習如何使用VDMA。
6.1 為什么要用VDMA
在講解VDMA之前,先來探討一下為什么要學習和使用VDMA,以明確學習目的。由於使用VDMA可以方便地實現雙緩沖和多緩沖機制,所以本小節引入了幀緩存和緩沖機制的概念。另外,VDMA可以很好地契合Zynq內部架構,縮短開發周期。再加上VDMA本身能夠高效地實現數據存取,所以在基於Zynq(也包括其他Xilinx FPGA)圖像、視頻處理系統中,VDMA可謂是必不可少的。
6.1.1 什么是幀緩存
幀緩沖存儲器(Frame Buffer):簡稱幀緩存或顯存,它是屏幕所顯示畫面的一個直接映象,又稱為位映射圖(Bit Map)或光柵。幀緩存的每一存儲單元對應屏幕上的一個像素,整個幀緩存對應一幀圖像。
在開發者看來,FrameBuffer 是一塊顯示緩存,往顯示緩存中寫入特定格式的數據就意味着向屏幕輸出內容。所以說FrameBuffer就是一塊畫布,系統在畫布上繪制好畫面之后,就可以通知顯示設備讀取Frame Buffer進行顯示了。
注意,筆者這里所說的Frame Buffer和Linux的Frame Buffer不是同一個概念,這里僅指顯示緩存(畫布)本身,並不是Linux下的一個設備。
6.1.2 雙緩沖機制
最早解釋多緩沖區如何工作的方式,是通過一個現實生活中的實例來解釋的。在一個陽光明媚的日子,你想將水池里的水打滿,而又找不到水管的時候,就只能用手邊的木桶來灌滿水池。水桶滿了之后,關掉水龍頭,將水提到水池旁邊,倒進去,然后走回到水龍頭。重復上述工作,如此往復直到將水池灌滿。這就類似單緩沖工作過程,當你想將木桶里的水倒出的時候,你必須關掉水龍頭。
現在假設你用兩個木桶來做上面的工作。你會注滿第一個木桶然后將第二個木桶換到水龍頭下面,這樣,在第二個水桶注滿的時間內,你就可以將第一個木桶里面的水倒進水池里面,當你回來的時候,你只需要再將第一個木桶換下第二個注滿水木桶,當第一個木桶開始注水的時候你就將第二個木桶里面的水倒進水池里面。重復這個過程直到水池被注滿。很容易看得到用這種技術注滿水池將會更快,同時也節省了很多等待木桶被注滿的時間,而這段時間里你什么也做不了,而水龍頭也就不用等待從木桶被注滿到你回來的這段時間了。
當你雇佣另外一個人來搬運一個被注滿的木桶時,這就有點類似於三個緩沖區的工作原理。如果將搬運木桶的的時間很長,你可以用更多的木桶,雇佣更多的人,這樣水龍頭就會一直開着注滿木桶了。
在計算機圖形學中,雙緩沖是一種畫圖技術,使用這種技術可以使得畫圖沒有(至少是減少)閃爍、撕裂等不良效果,並減少等待時間。
雙緩沖機制的原理大概是:所有畫圖操作將它們畫圖的結果保存在一塊系統內存區域中,這塊區域通常被稱作“后緩沖區(back buffer)”,當所有的繪圖操作結束之后,將整塊區域復制到顯示內存中,這個復制操作通常要跟顯示器的光棧束同步,以避免撕裂。雙緩沖機制必須要求有比單緩沖更多的顯示內存和CPU消耗時間,因為“后緩沖區”需要顯示內存,而復制操作和等待同步需要CPU時間。
基於雙緩沖機制可以實現頁交換,頁交換初始狀態如下圖所示:
如上圖所示,此時由於處於初始狀態,畫圖操作的結果都在后緩沖區中,而屏幕上顯示的則是前緩沖區中的內容。此時畫圖操作尚未完成,畫圖操作完成之后,頁轉換操作開始執行,示意圖如下圖所示:
如上圖所示,畫圖操作結束,下一個畫圖操作的結果保存對象指向前緩沖區,屏幕的顯示對象指向后緩沖區,此時前緩沖區變成實際意義上的后緩沖區,后緩沖區變成實際意義上的前緩沖去,即實現“頁交換”操作。
有時候也在頁交換鏈中設置多個“后緩沖區”,這是就需要多緩沖區機制的支持。
6.1.3 Zynq硬件架構
在Zynq芯片內部,PS和PL是共享DDR控制器的。PS訪問DDR十分簡單,只要操作DDR映射的虛擬地址即可。對於PL而言,要接入DDR,必須通過AXI_HP端口。
Zynq共有四個AXI_HP通道,通道數據寬度可以配置為32位或64位,這些接口通過FIFO控制器連接PL到存儲接口上,其中有兩條連接到DDR存儲控制器上,還有一條是連接到雙端口的OCM上的,下圖是AXI_HP訪問DDR和OCM的連接圖。
由上圖可以看出,AXI_HP接口也是遵循AXI協議的,因此利用VDMA可以直接連接HP端口。除了使用VDMA,當然也可以自己開發出符合AXI協議的IP,但是綜合考慮設計成本,沒太有必要自己實現。此外,自己實現的IP功能也不見得比VDMA強大。
6.1.4 VDMA的作用
VDMA數據接口可以分為讀、寫通道,用戶可以通過寫通道將AXI-Stream類型的數據流寫入DDR3,通過讀通道可以從DDR3讀取數據,並以AXI-Stream類型的格式輸出。由此可知,VDMA本質上是一個數據搬運IP,為數據進、出DDR3提供了一種便捷的方案。
將數據存入DDR之后,CPU就可以進行一些處理(縮放、裁剪等),然后再送至顯示設備,達到期望的應用目的。當然,也可能是簡單地對捕獲的視頻進行解析,將數據存入幀緩存,以供顯示。
VDMA可以控制多達32個幀存,並可以自由地進行幀存切換,所以就能夠輕松地實現雙緩沖和多緩沖操作。這也是一個很重要的特性,在后續進行系統設計的時候,通常是采用多緩沖的方式實現顯示。
由以上分析可以發現,在基於Zynq的圖像、視頻處理系統中使用VDMA是十分有必要的。
6.2 VDMA概述
AXI VDMA是Xilinx提供的軟核IP,用於將AXI Stream格式的數據流轉換為Memory Map格式或將Memory Map格式的數據轉換為AXI Stream數據流,從而實現與DDR3進行通信。
許多視頻類應用都需要幀緩存來處理幀率變化或者進行圖像的縮放、裁剪等尺寸變換操作。AXI VDMA設計的初衷就是用於高效地實現AXI4-Stream視頻流接口和AXI4接口之間的數據傳輸。
VDMA的關鍵特性&優勢有以下幾點:
Ø 使視頻流能夠高帶寬直接接入內存
Ø 高效的二維DMA操作
Ø 獨立的異步讀寫通道操作
Ø Gen-Lock幀存同步機制
Ø 最多支持32個幀存
Ø 支持視頻格式動態切換
Ø 猝發長度和行緩存深度可調節
Ø 處理器可以控制IP的初始化、狀態、中斷和管理寄存器
Ø 基礎AXI流數據位寬為8的整數倍,如8,16,24,32等,最大可達1024個位
AXI VDMA框圖如下所示。
主要有以下幾種接口類型:
Ø AXI-lite: PS通過該接口來配置VDMA
Ø AXI Memory Map write:映射到存儲器寫
Ø AXI Memory Map read:映射到存儲器讀
Ø AXI Stream Write(S2MM):AXI Stream視頻流寫入圖像
Ø AXI Stream Read(MM2S):AXI Stream視頻流讀出圖像
從框圖中可以看出,VDMA主要由控制和狀態寄存器、數據搬運模塊、行緩沖這幾部分構成。數據進出DDR要經過行緩沖進行緩存,然后由數據搬運模塊寫入或者讀出數據。數據搬運模塊具體如何工作,由相關寄存器負責控制。VDMA的工作狀態可以通過讀取狀態寄存器進行獲取。
6.3 VDMA詳細介紹
6.3.1 接口
6.3.1.1 時鍾和復位
各種總線都有自己的時鍾信號,不用特別說明,需要指出的是,這些時鍾是異步的,並不需要用同一個時鍾。但在設計過程中,如無特別需求,可以使用相同的時鍾,以降低設計難度。
同步復位信號axi_resetn,同步時鍾為s_axi_lite_aclk,低電平有效(至少要保持16個時鍾周期的低電平,才能夠生效),有效時復位整個IP核。
6.3.1.2 AXI總線相關信號
l AXI4-Lite接口(S_AXI_LITE)
l AXI4讀接口(M_AXI_MM2S)
l AXI4寫接口(M_AXI_S2MM)
l AXI4-Stream主接口(M_AXI_MM2S)
l AXI4-Stream從接口(S_AXI_S2MM)
前綴S_、M_分別表示Slave和Master;后綴MM2S、S2MM說明數據流向是從memory map到stream還是從stream到memory map。具體每個接口所包含的信號,在基礎篇第20章已有介紹,此處不再重復。
6.3.1.3 視頻同步接口信號
信號名稱 |
方向 |
詳細描述 |
mm2s_fsync |
Frame Sync |
MM2S幀同步輸入。使能該信號后,VDMA操作開始於fsync每個下降沿。該信號至少要持續一個m_axis_mm2s_aclk時鍾周期 |
s2mm_fsync |
Frame Sync |
S2MM幀同步輸入。使能該信號后,VDMA操作開始於fsync每個下降沿。該信號至少要持續一個s_axis_s2mm_aclk時鍾周期 |
6.3.1.4 GenLock相關信號
在下一節將詳細介紹這些信號的作用和應用場合。
信號名稱 |
方向 |
詳細描述 |
mm2s_frame_ptr_in(5:0) |
輸入 |
輸入的幀編號 |
mm2s_frame_ptr_out(5:0) |
輸出 |
輸出當前幀的編號 |
s2mm_frame_ptr_in(5:0) |
輸入 |
輸入的幀編號 |
s2mm_frame_ptr_out(5:0) |
輸出 |
輸出當前幀的編號 |
6.3.2 VDMA幀存格式
在講述寄存器時,需要設定和顯示(幀存)相關的參數,為了方便讀者的理解,這里先介紹VDMA數據存放框架,如下圖所示,黑色實線內的區域為實際存儲畫面的幀存。
圖中H_STRIDE代表水平方向上的跨度,H_SIZE表示水平方向數據總量,V_SIZE表示豎直方向總共有多少行。
至於幀存內部數據如何組織,就取決於軟件代碼和硬件邏輯如何匹配了,通常來講,數據存放格式為RGB+Alpha或者Alpha+RGB。
22.3.3 讀寫通道工作時序
清晰地理解VDMA讀寫通道的工作時序,對以后的設計有很大的幫助,很多設計都是根據本小節所示的樣例時序設計出來的。在下一章,讀者就能夠有所體會。
6.3.3.1 讀通道(MM2S)時序
下圖描述了讀通道的時序,5行,每行16字節,跨度為32字節。
從圖中可以看出:在收到mm2s_fsync信號后,VDMA在m_axi_mm2s_araddr的起始地址處發出m_axi_mm2s_arvalid信號。M_axi_mm2s_arvalid總共有效5次,分別獲取一幀的5行數據。從MM讀取的數據存儲在行緩存里,當收到來自axi-stream端的m_axis_mm2s_tvalid信號后,將數據發送到axi-stream端。每一行的結束,axi-stream端會使m_axis_mm2s_tlast有效。
6.3.3.2 寫通道(S2MM)時序
下圖描述了寫通道的時序,5行,每行16字節,跨度為32字節。
從圖中可以看出:在收到s2mm_fsync信號后,VDMA發出s2mm_fsync_out和s_axis_s2mm_tready表明已經准備好接收來自axi-stream端的數據。讀取到的數據存儲在行緩存里,m_axi_s2mm_awvalid有效后,緊接着有效m_axi_s2mm_wvalid信號,同時將數據放至m_axi_s2mm_wdata。
6.3.4 寄存器
VDMA的寄存器如下表所示。所有寄存器都被映射到非緩存內存空間。該內存空間必須按照AXI字(32位)進行對齊,換句話說,寄存器偏移地址至少間隔4個字節。
寄存器名稱 |
偏移地址 |
詳細描述 |
MM2S_VDMACR |
00h |
MM2S VDMA控制寄存器 |
MM2S_VDMASR |
04h |
MM2S VDMA狀態寄存器 |
保留 |
08h~10h |
N/A |
MM2S_REG_INDEX |
14h |
MM2S寄存器索引 |
保留 |
18h~24h |
N/A |
PARK_PRT_REG |
28h |
MM2S和S2MM Park指針寄存器 |
VDMA_VERSION |
2Ch |
VDMA版本寄存器 |
S2MM_VDMACR |
30h |
S2MM VDMA控制寄存器 |
S2MM_VDMASR |
34h |
S2MM VDMA狀態寄存器 |
保留 |
38h |
N/A |
S2MM_VDMA_IRQ_MASK |
3Ch |
S2MM錯誤中斷掩碼寄存器 |
保留 |
40h |
N/A |
S2MM_REG_INDEX |
44h |
S2MM寄存器索引 |
保留 |
48h~4Ch |
N/A |
MM2S_VSIZE |
50h |
MM2S垂直方向顯示大小寄存器 |
MM2S_HSIZE |
54h |
MM2S水平方向顯示大小寄存器 |
MM2S_FRMDLY_STRIDE |
58h |
MM2S幀延遲和跨度寄存器 |
MM2S_START_ADDRESS(1~16) |
5Ch~98h |
MM2S幀存起始地址(1~16) |
保留 |
9Ch |
N/A |
S2MM_VSIZE |
A0h |
S2MM垂直方向顯示大小寄存器 |
S2MM_HSIZE |
A4h |
S2MM水平方向顯示大小寄存器 |
S2MM_FRMDLY_STRIDE |
A8h |
S2MM幀延遲和跨度寄存器 |
S2MM_START_ADDRESS(1~16) |
ACh~E8h |
S2MM幀存起始地址(1~16) |
所有寄存器字節序都是小端格式,如下圖所示。
各個寄存器的名稱和大致作用從上表就可以看出,接下來,筆者會詳細介紹重要寄存器的具體bit的作用。明白了每個bit的作用之后,自然就知道寫入什么值能夠達到自己的控制目的。
從上表可以看出,寄存器可以分為兩組,分別對應MM2S通道和S2MM通道,兩組寄存器的功能是相似的,區別僅在於偏移地址和所服務的對象。因此,在學習完MM2S通道的所有寄存器之后,只要大致瀏覽一下S2MM通道對應的寄存器的關鍵位即可(個別位不相同),在使用高級功能時,再仔細查閱VDMA用戶手冊。
6.3.2.1 MM2S VDMA 控制寄存器(00h)
顧名思義,該寄存器用於控制VDMA,具體可以實現復位、使能鎖相同步、設定幀存切換模式、啟動VDMA讀寫通道等操作。每一位作用如下圖所示,低4位是最重要的,接下來會詳細介紹。
位 |
名稱 |
默認值 |
接入類型 |
描述 |
31~4 |
非常用位,請參考VDMA使用手冊自學 |
|||
3 |
GenlockEn |
0h |
可讀可寫 |
使能鎖相同步或者動態鎖相同步模式。 0:關閉Genlock或動態Genlock同步 1:開啟Genlock或動態Genlock同步 注:該位僅在通道被配置成鎖相同步從接口或者動態鎖相主、從接口時才起作用。配置成鎖相同步主接口時,該位為保留位,值恆為0。 |
2 |
Reset |
0h |
可讀可寫 |
0:正常操作;1:復位MM2S通道 |
1 |
Circular_Park |
1h |
可讀可寫 |
指定幀存為循環模式還是停留模式 0:停留模式-顯示用緩存頁將停留在PARK_PTR_REG.RdFrmPntrRef指定的幀存; 1:循環模式-循環切換顯示用緩存頁 |
0 |
RS |
0h |
可讀可寫 |
運行/停止,控制VDMA通道的運行和停止。 開始任何VDMA操作前,該位必須置1. 0:停止;1:運行。 |
6.3.2.2 MM2S VDMA 狀態寄存器(04h)
該寄存器用於獲取VDMA工作狀態。每一位作用如下圖所示,低4位是最重要的,接下來會詳細介紹。
位 |
名稱 |
默認值 |
接入類型 |
描述 |
31~1 |
非常用位,請參考VDMA使用手冊自學 |
|||
0 |
Halted |
1h |
只讀 |
指示VDMA運行是否停止。 0:運行;1:停止。 |
6.3.2.3 PARK_PTR_REG停留指針寄存器(28h)
該寄存器用於管理讀、寫通道的數據傳輸。
位 |
名稱 |
默認值 |
接入類型 |
描述 |
31~29 |
保留 |
0h |
只讀 |
|
28~24 |
WrFrmStore |
0h |
只讀 |
用於存儲寫通道正在操作的幀的編號。指示S2MM通道正在操作的幀。 |
23~21 |
保留 |
0h |
只讀 |
|
20~16 |
RdFrmStore |
0h |
只讀 |
用於存儲讀通道正在操作的幀的編號。指示MM2S通道正在操作的幀。 |
15~13 |
保留 |
0h |
只讀 |
|
12~8 |
WrFrmPtrRef |
0h |
可讀可寫 |
通過幀編號指定寫通道操作的幀。當工作在停留模式,S2MM通道操作對象停留在WrFrmPtrRef指定的幀。 |
7~5 |
保留 |
0h |
只讀 |
|
4~0 |
RdFrmPtrRef |
0h |
可讀可寫 |
通過幀編號指定讀通道操作的幀。當工作在停留模式,MM2S通道操作對象停留在RdFrmPtrRef指定的幀。 |
學習了這個寄存器之后,就可以發現:當VDMA工作在Parked模式下,通過操作該寄存器,就能夠實現幀緩存的切換,建立自己想要的緩存切換機制。
6.3.2.4 MM2S 幀存起始地址(0x5C~0x98)
有最多32個寄存器用於存放幀存起始地址,其分別存在於兩個寄存器bank上:bank0和bank1,每個bank上有16個寄存器。這兩個bank上有相同的起始偏移地址(0x5C),選擇這兩個bank可以通過MM2S_REG_INDEX的值進行選擇。假如想訪問第1個寄存器,則給MM2S_REG_INDEX賦值為0,並設定偏移地址為0x5C;如果想訪問第17個寄存器,需要將MM2S_REG_INDEX設為1,並設定初始偏移地址為0x5C。
6.3.2.5 MM2S_FRMDLY_STRIDE MM2S幀延遲和跨度(58h)
該寄存器有兩個作用,第一是bit24~bit28指定幀延遲,僅用於Genlock從模式,指定從接口比主接口至少要延遲多少個幀;第二是低16位指定水平方向的跨度,同樣以字節為單位。所謂跨度是指每兩行第一個像素之間間隔的數據個數,具體請參考22.3.2小節,VDMA幀存格式。
6.3.2.6 MM2S_HSIZE MM2S水平方向尺寸(54h)
該寄存器的低16位用於指定每一行有多少字節的數據需要傳輸。例如顯示分辨率為640*480,每個像素4個字節(RGB+Alpha),該值應該設定為640*4。
6.3.2.7 MM2S_VSIZE MM2S垂直方向尺寸(50h)
該寄存器有兩個作用,第一是用低13位指定總共有多少行;第二是啟動MM2S的傳輸。當MM2S_VDMACR.RS=1,對該寄存器的寫操作會將所有設定參數傳遞給VDMA內部寄存器模塊,用於VDMA控制。對某個通道進行配置時,必須在最后一步設置該寄存器。
6.3.2.8 S2MM VDMA 控制寄存器(30h)
顧名思義,該寄存器用於控制VDMA S2MM通道,具體可以實現復位、使能鎖相同步、設定幀存切換模式、啟動VDMA讀寫通道等操作。每一位作用如下圖所示,低4位是最重要的,接下來會詳細介紹。
位 |
名稱 |
默認值 |
接入類型 |
描述 |
31~4 |
非常用位,請參考VDMA使用手冊自學 |
|||
3 |
GenlockEn |
0h |
可讀可寫 |
使能鎖相同步或者動態鎖相同步模式。 0:關閉Genlock或動態Genlock同步 1:開啟Genlock或動態Genlock同步 注:該位僅在通道被配置成鎖相同步從接口或者動態鎖相主、從接口時才起作用。配置成鎖相同步主接口時,該位為保留位,值恆為0。 |
2 |
Reset |
0h |
可讀可寫 |
0:正常操作;1:復位S2MM通道 |
1 |
Circular_Park |
1h |
可讀可寫 |
指定幀存為循環模式還是停留模式 0:停留模式-顯示用緩存頁將停留在PARK_PTR_REG.RdFrmPntrRef指定的幀存; 1:循環模式-循環切換顯示用緩存頁 |
0 |
RS |
0h |
可讀可寫 |
運行/停止,控制VDMA通道的運行和停止。 開始任何VDMA操作前,該位必須置1. 0:停止;1:運行。 |
6.3.2.9 S2MM VDMA 狀態寄存器(34h)
該寄存器用於獲取S2MM工作狀態。每一位作用如下圖所示,低4位是最重要的,接下來會詳細介紹。
位 |
名稱 |
默認值 |
接入類型 |
描述 |
31~1 |
非常用位,請參考VDMA使用手冊自學 |
|||
0 |
Halted |
1h |
只讀 |
指示VDMA運行是否停止。 0:運行;1:停止。 |
6.3.2.4 S2MM 幀存起始地址(0xAC~0xE8)
有最多32個寄存器用於存放幀存起始地址,其分別存在於兩個寄存器bank上:bank0和bank1,每個bank上有16個寄存器。這兩個bank上有相同的起始偏移地址(0x5C),選擇這兩個bank可以通過S2MM_REG_INDEX的值進行選擇。假如想訪問第1個寄存器,則給S2MM_REG_INDEX賦值為0,並設定偏移地址為0x5C;如果想訪問第17個寄存器,需要將MM2S_REG_INDEX設為1,並設定初始偏移地址為0x5C。
6.3.2.5 S2MM_FRMDLY_STRIDE S2MM幀延遲和跨度(A8h)
該寄存器有兩個作用,第一是bit24~bit28指定幀延遲,僅用於Genlock從模式,指定從接口比主接口至少要延遲多少個幀;第二是低16位指定水平方向的跨度,同樣以字節為單位。所謂跨度是指每兩行第一個像素之間間隔的數據個數,具體請參考22.3.2小節,VDMA幀存格式。
6.3.2.6 S2MM_HSIZE S2MM水平方向尺寸(A4h)
該寄存器的低16位用於指定每一行有多少字節的數據需要傳輸。例如顯示分辨率為640*480,每個像素4個字節(RGB+Alpha),該值應該設定為640*4。
6.3.2.7 S2MM_VSIZE S2MM垂直方向尺寸(A0h)
該寄存器有兩個作用,第一是用低13位指定總共有多少行;第二是啟動S2MM的傳輸。當S2MM_VDMACR.RS=1,對該寄存器的寫操作會將所有設定參數傳遞給VDMA內部寄存器模塊,用於VDMA控制。對某個通道進行配置時,必須在最后一步設置該寄存器。
6.3.5幀同步選項
VDMA支持以下三種幀同步源:
Ø 基於AXI4-Stream的幀同步(使用tuser(0)信號)
n 讀通道使用m_axis_mm2s_tuser(0)作為幀起始信號
n 寫通道使用s_axis_s2mm_tuser(0)作為幀起始信號
Ø S2MM幀同步(s2mm_fsync)
Ø MM2S幀同步(mm2s_fsync)
6.3.6 Genlock同步機制
6.3.6.1 什么是Genlock?
Genlock,同步鎖相,可以使一套或多套系統與同一同步源實現同步。能夠使視頻的刷新和外部視頻源保持一致。當提供了一個適當的信號后,系統就會把它的顯示刷新率和這個信號進行鎖定 。
在許多視頻應用中,輸入端產生數據的速率往往不同於輸出端數據速率,為了避免由速率不一致導致的潛在錯誤,幀緩沖的使用是很有必要的。幀緩沖機制開辟多個緩沖頁,用於保存數據,輸入和輸出端分別操作不同的幀存,從而避免了沖突。
VDMA的鎖相同步特性正是用於阻止讀、寫通道同時操作同一個幀存。VDMA的每個通道都可以選擇自己的操作類型(同步鎖相主/從或者動態同步鎖相主/從),利用該特性,禁止主從接口同時訪問同一緩存,從而保持同步。
VDMA支持四種模式的鎖相同步,分別為:
Ø Genlock Master(鎖相同步主端)
Ø Genlock Slave(鎖相同步從端)
Ø Dynamic Genlock Master(動態鎖相同步主端)
Ø Dynamic Genlock Slave(動態鎖相同步從端)
6.3.6.2 Genlock Master
讀通道(MM2S):當配置為Genlock Master時,該通道不會跳過或者重復任一幀數據,並把當前幀的編號輸出在mm2s_frame_ptr_out端口。通道不會檢測mm2s_frame_ptr_in端口提供的幀編號。Genlock Slave通道應跟隨Genlock Master通道變化,但有一定的延遲。延遲大小預定義在寄存器中(*frmdly_stride[28:24])。
寫通道(S2MM):當配置為Genlock Master時,該通道不會跳過或者重復任一幀數據,並把當前幀的編號輸出到s2mm_frame_ptr_out端口。通道不會檢測s2mm_frame_ptr_in端口提供的幀編號。Genlock Slave通道應跟隨Genlock Master通道變化,但有一定的延遲。延遲大小預定義在寄存器中(*frmdly_stride[28:24])。
6.3.6.3 Genlock Slave
讀通道(MM2S):當配置為Genlock Slave時,該通道會通過跳過或者重復一些幀的方式,嘗試與Genlock Master同步。通道會對mm2s_frame_ptr_in端口進行采樣,獲取Genlock Master的幀編號。為了實現狀態反饋,通道會把當前幀的編號輸出到mm2s_frame_ptr_out端口。
指定通道工作在Genlock Slave模式,必須進行如下操作。
Ø 將GenlockEn置1(MM2S_VDMACR[3]=1),使能主、從通道之間的Genlock同步。
Ø 將GenlockSrc置1(MM2S_VDMACR[7]=1),使能內部Genlock模式。如果在Vivado IDE中同時使能讀、寫通道,該位默認置位。當GenlockSRC=1時,VDMA默認支持內部同步鎖相總線。這樣一來就沒有必要在外部對幀指針端口(*frame_ptr_out和*_frame_ptr_in)進行連接了。
Ø 根據主從通道的幀率,使用mm2s_frmdly_stride[28:24]設定合適的延遲時間。
寫通道(S2MM):當配置為Genlock Slave時,該通道會通過跳過或者重復一些幀的方式,嘗試與Genlock Master同步。通道會對s2mm_frame_ptr_in端口進行采樣,獲取Genlock Master的幀編號。為了實現狀態反饋,通道會把當前幀的編號輸出到s2mm_frame_ptr_out端口。
指定通道工作在Genlock Slave模式,必須進行如下操作。
Ø 將GenlockEn置1(S2MM_VDMACR[3]=1),使能主、從通道之間的Genlock同步。
Ø 將GenlockSrc置1(S2MM_VDMACR[7]=1),使能內部Genlock模式。如果在Vivado IDE中同時使能讀、寫通道,該位默認置位。當GenlockSRC=1時,VDMA默認支持內部同步鎖相總線。這樣一來就沒有必要在外部對幀指針端口(*frame_ptr_out和*_frame_ptr_in)進行連接了。
Ø 根據主從通道的幀率,使用mm2s_frmdly_stride[28:24]設定合適的延遲時間。
6.3.6.4 Dynamic Genlock Master
動態Genlock Master與Genlock Master的區別在於,主通道會跳過從通道正在操作的幀。舉例而言,對於三幀存而言,動態Genlock Master會按照0,1,2,0,1,2的順序循環使用幀存,一旦檢測到Master即將操作Slave正在操作的幀,就會跳過該幀繼續循環。因此,如果Slave通道一直在操作幀存1,那么Master通道就會在幀0和幀2之間來回切換。
6.3.6.5 Dynamic Genlock Slave
Dynamic Genlock Slave通道會操作Dynamic Genlock Master通道上一周期操作的幀。
下圖描述了一種簡單的Genlock操作時序。在這個示例中,S2MM通道是Genlock Master,MM2S通道是Genlock Slave,並且寫通道幀率高於讀通道幀率。
由於讀通道幀率慢於寫通道,所以讀通道僅處理幀2和幀0,跳過幀1不做處理。
6.4 使用VDMA
6.4.1 IP核配置
Xilinx集成開發環境升級到Vivado之后,VDMA的配置項比以前少了不少,一定程度上降低了使用難度。主要配置頁面如下面兩幅圖所示。
具體配置項參見下表。
基本配置 |
高級配置 |
地址線寬度 |
是否使能異步模式(自動) |
幀存數量 |
寫通道幀同步 |
是否使能讀寫通道 |
寫通道GenLock模式選擇 |
數據線寬度 |
寫通道是否允許非對齊傳輸 |
觸發長度 |
讀通道幀同步 |
AXI-Stream流數據位寬 |
讀通道GenLock模式選擇 |
Line Buffer深度 |
讀通道是否允許非對齊傳輸 |
關於地址線和數據線寬度,需要根據設計的實際情況配置。
Line buffer深度不能太小。
GenLock和幀同步前文已經講解,根據需求自行配置即可。
6.4.2 軟件控制流程
以下步驟是最簡單的VDMA控制初始化操作。
Ø 寫VDMACR寄存器,將VDMACR.RS設為1,啟動VDMA通道。
Ø 設定有效的幀緩存起始地址。
Ø 設定幀延遲(僅針對Genlock從模式)以及跨度到FRMDLY_STRIDE寄存器。
Ø 設定水平方向字節數到HSIZE寄存器。
Ø 設定豎直方向行數到VSIZE寄存器。啟動通道的數據傳輸。
在VDMA運行過程中,可以動態的進行顯示參數配置,但是需要注意的是,想要使參數生效,必須在設置的最后一步,對VSIZE寄存器進行寫操作。
最后,給出一段通過VDMA對DDR讀寫傳輸的進行初始化的示例代碼:
//VDMA configurateAXI VDMA0
/*****************從DDR讀數據設置**********************/
XAxiVdma_WriteReg(XPAR_AXIVDMA_0_BASEADDR, 0x0, 0x4); //reset
XAxiVdma_WriteReg(XPAR_AXIVDMA_0_BASEADDR, 0x0, 0x8); //gen-lock
XAxiVdma_WriteReg(XPAR_AXIVDMA_0_BASEADDR, 0x5C, 0x08000000);
// AXI4 Data Width為32位,是4個字節數
// 0x0A000000 0x0015F900
XAxiVdma_WriteReg(XPAR_AXIVDMA_0_BASEADDR, 0x5C+4, 0x0A000000);
// 0x09000000 0x002BF200
XAxiVdma_WriteReg(XPAR_AXIVDMA_0_BASEADDR, 0x5C+8, 0x09000000);
XAxiVdma_WriteReg(XPAR_AXIVDMA_0_BASEADDR, 0x54, 640);// 640
XAxiVdma_WriteReg(XPAR_AXIVDMA_0_BASEADDR, 0x58, 0x01000280);
// 第0位: 運行 - 啟動VDMA操作,在運行VDMA時,其狀態寄存器中的停止位賦值為0 第一位:循環模式 -通過連續循環幀緩沖
XAxiVdma_WriteReg(XPAR_AXIVDMA_0_BASEADDR, 0x0, 0x03);
XAxiVdma_WriteReg(XPAR_AXIVDMA_0_BASEADDR, 0x50, 480);//480
/*********** 寫入DDR設置*************************/
XAxiVdma_WriteReg(XPAR_AXIVDMA_0_BASEADDR, 0x30, 0x4); //reset
XAxiVdma_WriteReg(XPAR_AXIVDMA_0_BASEADDR, 0x30, 0x8); //genlock
XAxiVdma_WriteReg(XPAR_AXIVDMA_0_BASEADDR, 0xAC, 0x08000000);
XAxiVdma_WriteReg(XPAR_AXIVDMA_0_BASEADDR, 0xAC+4, 0x0A000000);
XAxiVdma_WriteReg(XPAR_AXIVDMA_0_BASEADDR, 0xAC+8, 0x09000000);
XAxiVdma_WriteReg(XPAR_AXIVDMA_0_BASEADDR, 0xA4, 640);
XAxiVdma_WriteReg(XPAR_AXIVDMA_0_BASEADDR, 0xA8, 0x01000280);
XAxiVdma_WriteReg(XPAR_AXIVDMA_0_BASEADDR, 0x30, 0x03);
XAxiVdma_WriteReg(XPAR_AXIVDMA_0_BASEADDR, 0xA0, 480);
6.5 搭建VDMA圖像系統
6.5.1構架方案圖
可以看到VMDA的圖像系統和前面介紹的DMA系統相比非常類似。實際上他們都是屬於DMA系統,只是VDMA在配置完成后,可以無需依賴CPU可以獨立運行,有點類似顯卡的功能了。
6.5.2構BLOCK模塊化設計方案圖
6.6 PS部分
6.6.1 main函數
本課程提供了二種方式啟動VDMA,第一種是通過庫函數版本,第二種是通過寄存器版本。寄存器版本主要是驗證我們對VDMA的寄存器掌握情況。庫函數具備更強的功能,和可維護性。
表6-6-1
#include "sys_intr.h" #include "xaxivdma.h" #include "xaxivdma_i.h" #define VTC_BASEADDR XPAR_MIZ702_VTG_VGA_0_BASEADDR #define DDR_BASEADDR 0x00000000 //#define UART_BASEADDR 0xe0001000 #define VDMA_BASEADDR XPAR_AXI_VDMA_0_BASEADDR #define H_STRIDE 640 #define H_ACTIVE 640 #define V_ACTIVE 480 #define pi 3.14159265358 #define COUNTS_PER_SECOND (XPAR_CPU_CORTEXA9_CORE_CLOCK_FREQ_HZ)/64 #define VIDEO_LENGTH (H_STRIDE*V_ACTIVE) #define VIDEO_BASEADDR0 DDR_BASEADDR + 0x2000000 #define VIDEO_BASEADDR1 DDR_BASEADDR + 0x3000000 #define VIDEO_BASEADDR2 DDR_BASEADDR + 0x4000000 u32 *BufferPtr[3]; unsigned int srcBuffer = (XPAR_PS7_DDR_0_S_AXI_BASEADDR + 0x1000000); int run_triple_frame_buffer(XAxiVdma* InstancePtr, int DeviceId, int hsize, int vsize, int buf_base_addr, int number_frame_count, int enable_frm_cnt_intr); int main(void) { u32 Status; Miz702_EMIO_init(); ov7725_init_rgb(); XAxiVdma InstancePtr; xil_printf("Starting the first VDMA \n\r"); Status = run_triple_frame_buffer(&InstancePtr, 0, 640, 480, srcBuffer, 2, 0); if (Status != XST_SUCCESS) { xil_printf("Transfer of frames failed with error = %d\r\n",Status); return XST_FAILURE; } else { xil_printf("Transfer of frames started \r\n"); } print("TEST PASS\r\n"); //VDMA configurateAXI VDMA0 /****************往DDR寫數據設置**********************/ /*Xil_Out32((VDMA_BASEADDR + 0x030), 0x00000003);// enable circular mode Xil_Out32((VDMA_BASEADDR + 0x0AC), VIDEO_BASEADDR0); // start address Xil_Out32((VDMA_BASEADDR + 0x0B0), VIDEO_BASEADDR1); // start address Xil_Out32((VDMA_BASEADDR + 0x0B4), VIDEO_BASEADDR2); // start address Xil_Out32((VDMA_BASEADDR + 0x0A8), (H_STRIDE*4)); // h offset (640 * 4) bytes Xil_Out32((VDMA_BASEADDR + 0x0A4), (H_ACTIVE*4)); // h size (640 * 4) bytes Xil_Out32((VDMA_BASEADDR + 0x0A0), V_ACTIVE);*/ // v size (480) /*****************從DDR讀數據設置**********************/ /*Xil_Out32((VDMA_BASEADDR + 0x000), 0x00000003); // enable circular mode Xil_Out32((VDMA_BASEADDR + 0x05c), VIDEO_BASEADDR0); // start address Xil_Out32((VDMA_BASEADDR + 0x060), VIDEO_BASEADDR1); // start address Xil_Out32((VDMA_BASEADDR + 0x064), VIDEO_BASEADDR2); // start address Xil_Out32((VDMA_BASEADDR + 0x058), (H_STRIDE*4)); // h offset (640 * 4) bytes Xil_Out32((VDMA_BASEADDR + 0x054), (H_ACTIVE*4)); // h size (640 * 4) bytes Xil_Out32((VDMA_BASEADDR + 0x050), V_ACTIVE); // v size (480) */ while (1) ; return XST_SUCCESS; } |
6.6.2 vdma_api.c函數
XAxiVdma_LookupConfig函數是XILINX 庫函數的標准調用方式,可以獲取到硬件的默認配置參數。默認的配置參數保存在 參數表XAxiVdma_ConfigTable 中。
表6-6-2-1 XAxiVdma_LookupConfig
/*****************************************************************************/ /** * Look up the hardware configuration for a device instance * * @param DeviceId is the unique device ID of the device to lookup for * * @return * The configuration structure for the device. If the device ID is not found, * a NULL pointer is returned. * ******************************************************************************/ XAxiVdma_Config *XAxiVdma_LookupConfig(u16 DeviceId) { extern XAxiVdma_Config XAxiVdma_ConfigTable[]; XAxiVdma_Config *CfgPtr = NULL; int i; for (i = 0; i < XPAR_XAXIVDMA_NUM_INSTANCES; i++) { if (XAxiVdma_ConfigTable[i].DeviceId == DeviceId) { CfgPtr = &XAxiVdma_ConfigTable[i]; break; } } return CfgPtr; } |
表6-6-2-2 XAxiVdma_ConfigTable參數表
XAxiVdma_Config XAxiVdma_ConfigTable[] = { { XPAR_AXI_VDMA_0_DEVICE_ID, XPAR_AXI_VDMA_0_BASEADDR, XPAR_AXI_VDMA_0_NUM_FSTORES, XPAR_AXI_VDMA_0_INCLUDE_MM2S, XPAR_AXI_VDMA_0_INCLUDE_MM2S_DRE, XPAR_AXI_VDMA_0_M_AXI_MM2S_DATA_WIDTH, XPAR_AXI_VDMA_0_INCLUDE_S2MM, XPAR_AXI_VDMA_0_INCLUDE_S2MM_DRE, XPAR_AXI_VDMA_0_M_AXI_S2MM_DATA_WIDTH, XPAR_AXI_VDMA_0_INCLUDE_SG, XPAR_AXI_VDMA_0_ENABLE_VIDPRMTR_READS, XPAR_AXI_VDMA_0_USE_FSYNC, XPAR_AXI_VDMA_0_FLUSH_ON_FSYNC, XPAR_AXI_VDMA_0_MM2S_LINEBUFFER_DEPTH, XPAR_AXI_VDMA_0_S2MM_LINEBUFFER_DEPTH, XPAR_AXI_VDMA_0_MM2S_GENLOCK_MODE, XPAR_AXI_VDMA_0_S2MM_GENLOCK_MODE, XPAR_AXI_VDMA_0_INCLUDE_INTERNAL_GENLOCK, XPAR_AXI_VDMA_0_S2MM_SOF_ENABLE, XPAR_AXI_VDMA_0_M_AXIS_MM2S_TDATA_WIDTH, XPAR_AXI_VDMA_0_S_AXIS_S2MM_TDATA_WIDTH, XPAR_AXI_VDMA_0_ENABLE_DEBUG_INFO_1, XPAR_AXI_VDMA_0_ENABLE_DEBUG_INFO_5, XPAR_AXI_VDMA_0_ENABLE_DEBUG_INFO_6, XPAR_AXI_VDMA_0_ENABLE_DEBUG_INFO_7, XPAR_AXI_VDMA_0_ENABLE_DEBUG_INFO_9, XPAR_AXI_VDMA_0_ENABLE_DEBUG_INFO_13, XPAR_AXI_VDMA_0_ENABLE_DEBUG_INFO_14, XPAR_AXI_VDMA_0_ENABLE_DEBUG_INFO_15, XPAR_AXI_VDMA_0_ENABLE_DEBUG_ALL, XPAR_AXI_VDMA_0_ADDR_WIDTH } }; |
WriteSetup VDMA寫通道設置,主要設置分辨率,延遲參數,開啟CircularBuf 模式,使能Gen-Lock。更底層的分析讀者可以順藤摸瓜下去。
表6-6-2-3 WriteSetup
/*****************************************************************************/ /** * * This function sets up the write channel * * @param dma_context is the context pointer to the VDMA engine.. * * @return XST_SUCCESS if the setup is successful, XST_FAILURE otherwise. * * @note None. * ******************************************************************************/ static int WriteSetup(vdma_handle *vdma_context) { int Index; u32 Addr; int Status; vdma_context->WriteCfg.VertSizeInput = vdma_context->vsize; vdma_context->WriteCfg.HoriSizeInput = vdma_context->hsize; vdma_context->WriteCfg.Stride = vdma_context->hsize; vdma_context->WriteCfg.FrameDelay = 0; /* This example does not test frame delay */ vdma_context->WriteCfg.EnableCircularBuf = 1; vdma_context->WriteCfg.EnableSync = 1; /* Gen-Lock */ vdma_context->WriteCfg.PointNum = 0; vdma_context->WriteCfg.EnableFrameCounter = 0; /* Endless transfers */ vdma_context->WriteCfg.FixedFrameStoreAddr = 0; /* We are not doing parking */ /* Configure the VDMA is per fixed configuration, This configuration * is being used by majority of customers. Expert users can play around * with this if they have different configurations */ Status = XAxiVdma_DmaConfig(vdma_context->InstancePtr, XAXIVDMA_WRITE, &vdma_context->WriteCfg); if (Status != XST_SUCCESS) { xil_printf( "Write channel config failed %d\r\n", Status); return Status; } /* Initialize buffer addresses * * Use physical addresses */ Addr = vdma_context->buffer_address; /* If Debug mode is enabled write frame is shifted 3 Frames * store ahead to compare read and write frames */ #if DEBUG_MODE Addr = Addr + vdma_context->InstancePtr->MaxNumFrames * \ (vdma_context->WriteCfg.Stride * vdma_context->vsize); #endif for(Index = 0; Index < vdma_context->InstancePtr->MaxNumFrames; Index++) { vdma_context->WriteCfg.FrameStoreStartAddr[Index] = Addr; #if DEBUG_MODE xil_printf("Write Buffer %d address: 0x%x \r\n",Index,Addr); #endif Addr += (vdma_context->hsize * vdma_context->vsize); } /* Set the buffer addresses for transfer in the DMA engine */ Status = XAxiVdma_DmaSetBufferAddr(vdma_context->InstancePtr, XAXIVDMA_WRITE, vdma_context->WriteCfg.FrameStoreStartAddr); if (Status != XST_SUCCESS) { xil_printf("Write channel set buffer address failed %d\r\n", Status); return XST_FAILURE; } /* Clear data buffer */ #if DEBUG_MODE memset((void *)vdma_context->buffer_address, 0, vdma_context->ReadCfg.Stride * vdma_context->ReadCfg.VertSizeInput * vdma_context->InstancePtr->MaxNumFrames); #endif return XST_SUCCESS; } |
ReadSetup VDMA讀通道設置,主要設置分辨率,這里的延遲參數1,否則圖像會有卡頓,開啟CircularBuf 模式,使能Gen-Lock。更底層的分析讀者可以順藤摸瓜下去。
表6-6-2-4 ReadSetup
/*****************************************************************************/ /** * * This function sets up the read channel * * @param vdma_context is the context pointer to the VDMA engine. * * @return XST_SUCCESS if the setup is successful, XST_FAILURE otherwise. * * @note None. * ******************************************************************************/ static int ReadSetup(vdma_handle *vdma_context) { int Index; u32 Addr; int Status; vdma_context->ReadCfg.VertSizeInput = vdma_context->vsize; vdma_context->ReadCfg.HoriSizeInput = vdma_context->hsize; vdma_context->ReadCfg.Stride = vdma_context->hsize; vdma_context->ReadCfg.FrameDelay = 0; /* This example does not test frame delay */ vdma_context->ReadCfg.EnableCircularBuf = 1; vdma_context->ReadCfg.EnableSync = 1; /* Gen-Lock */ vdma_context->ReadCfg.PointNum = 0; vdma_context->ReadCfg.EnableFrameCounter = 0; /* Endless transfers */ vdma_context->ReadCfg.FixedFrameStoreAddr = 0; /* We are not doing parking */ /* Configure the VDMA is per fixed configuration, This configuration is being used by majority * of customer. Expert users can play around with this if they have different configurations */ Status = XAxiVdma_DmaConfig(vdma_context->InstancePtr, XAXIVDMA_READ, &vdma_context->ReadCfg); if (Status != XST_SUCCESS) { xil_printf("Read channel config failed %d\r\n", Status); return XST_FAILURE; } /* Initialize buffer addresses * * These addresses are physical addresses */ Addr = vdma_context->buffer_address; for(Index = 0; Index < vdma_context->InstancePtr->MaxNumFrames; Index++) { vdma_context->ReadCfg.FrameStoreStartAddr[Index] = Addr; /* Initializing the buffer in case of Debug mode */ #if DEBUG_MODE { u32 i; u8 *src; u32 total_pixel = vdma_context->ReadCfg.Stride * vdma_context->vsize; src = (unsigned char *)Addr; xil_printf("Read Buffer %d address: 0x%x \r\n",Index,Addr); for(i=0;i<total_pixel;i++) { src[i] = i & 0xFF; } } #endif Addr += vdma_context->hsize * vdma_context->vsize; } /* Set the buffer addresses for transfer in the DMA engine * The buffer addresses are physical addresses */ Status = XAxiVdma_DmaSetBufferAddr(vdma_context->InstancePtr, XAXIVDMA_READ, vdma_context->ReadCfg.FrameStoreStartAddr); if (Status != XST_SUCCESS) { xil_printf( "Read channel set buffer address failed %d\r\n", Status); return XST_FAILURE; } return XST_SUCCESS; } |
StartTransfer 啟動VDMA讀寫通道
表6-6-2-5 StartTransfer
/*****************************************************************************/ /** * * This function starts the DMA transfers. Since the DMA engine is operating * in circular buffer mode, video frames will be transferred continuously. * * @param InstancePtr points to the DMA engine instance * * @return * - XST_SUCCESS if both read and write start successfully * - XST_FAILURE if one or both directions cannot be started * * @note None. * ******************************************************************************/ static int StartTransfer(XAxiVdma *InstancePtr) { int Status; /* Start the write channel of VDMA */ Status = XAxiVdma_DmaStart(InstancePtr, XAXIVDMA_WRITE); if (Status != XST_SUCCESS) { xil_printf("Start Write transfer failed %d\r\n", Status); return XST_FAILURE; } /* Start the Read channel of VDMA */ Status = XAxiVdma_DmaStart(InstancePtr, XAXIVDMA_READ); if (Status != XST_SUCCESS) { xil_printf("Start read transfer failed %d\r\n", Status); return XST_FAILURE; } return XST_SUCCESS; } |