HDMI系列之一:基於Nios II的HDMI顯示圖片
一休哥將在本文中介紹一個基於Nios II的HDMI顯示圖片的工程。我將主要分三個部分來介紹這一工程,從而實現工程效果。
1、 Nios II的常規使用套路
2、 自定義HDMI IP核的製作
3、 Nios II顯示圖片
本文涉及到的全部資料連結:
連結:http://pan.baidu.com/s/1eRNXagy 密碼:uv9w
1 Nios II的常規使用套路
雖然使用Nios II可以為我們簡化編寫Verilog程式碼的難度。但往往需要額外更多的GUI介面操作。本文為了照顧到Nios II的初學者,將帶領大家重新建立一個基於Nios II的FPGA工程。
上圖是基於Nios II的FPGA工程的簡要結構圖,其中屬於Nios II的部分有兩個,一個是Qsys系統,它是Nios II的硬體;一個是軟核工程,它是Nios II的軟體。
首先我們新建一個空的FPGA工程,然後點選軟體介面的Qsys按鈕,進入Qsys的建立介面。
然後,在介面中,我們雙擊Clock Source IP核,修改裡面的引數,設定時鐘頻率為100MHz,並給這個IP核重新命名為clk。
接著,我們新增一個Nios II Processor IP核,在左上角的Library的搜尋視窗中搜nios即可。然後重新命名為nios_qsys。
然後,新增SDRAM Controller IP核,同樣搜尋之後,我們需要根據自己板子上SDRAM的型號來配置IP核。我使用的是鋯石科技的A4開發板,使用的SDRAM晶片為MT48LC16M16A2,所以僅供大家參考。最後同樣的重新命名為sdram。
然後接著新增JTAG UART和System ID Peripheral IP核,無需進行任何配置,僅需要重新命名為jtag_uart和sysid_qsys即可。
接著,我們新增自定義的HDMI IP核。大家在使用該IP時,需要將我提供的IP核檔案放在下圖這個路徑上。(嘿嘿,由於這個IP核是我仿照鋯石科技的VGA IP核而製作的,為了防止被鋯石大大給和諧掉,因此也放在同樣的IP核路徑中。)
最後,我們可以在zircon_ip的IP組裡找到zircon_hdmi的IP了,直接呼叫就行了。
至此,Qsys系統所需的IP核就呼叫完了,接下來的操作就是連線,連線主要有clk埠,reset埠,Nios的資料與指令埠和Avalon—MM埠。首先我們把除Clock Source IP核以外的IP核的時鐘埠連上。由於我們這個Qsys系統只有一個Clock Source IP核,所以當然是用這個IP提供的時鐘埠來連線其他IP核。然後將Clock Source IP核的復位埠連線上其他IP核。接著將Nios II Processor IP核的jtag_debug_module_reset復位埠連線上其他IP核。
接著,我們來連線Nios的資料與指令埠,連線這個的時候有一個規律,就是有儲存功能的IP核需要同時連線資料與指令埠,如SDRAM,RAM,ROM,EPCS等,而其他外設只需要連線資料埠。
最後,由於我製作的HDMI IP核是基於Avalon-MM協議的,通過這個協議可以實現HDMI IP核與Nios II Processor IP核和SDRAM Controller IP核的資料通訊,所以需要將HDMI IP核的Avalon—MM的主埠與SDRAM Controller IP核來讀取儲存在SDRAM中的圖片資料,HDMI IP核的Avalon—MM的從埠與Nios II Processor IP核的資料埠相連來接收Nios II軟體傳送的圖片地址資料。關於HDMI IP核具體的介紹,將會在後兩個部分中詳細介紹。
完成所有的連線之後,需要雙擊Nios II Processor IP核,設定其復位向量和異常向量為sdram.s1。接著,設定jtag_uart IP核的中斷號,引出sdram IP和HDMI IP的引腳。點選軟體介面右上角的System下拉選單中的Assign Base Address自動分配各個IP的地址。點選軟體介面右上角的File下拉選單中的Save as儲存Qsys檔案。最後點選軟體介面右上角的Generate下拉選單中的Generate,生成Qsys系統。這樣,一個Qsys系統就生成完了。
然後,回到Quartus主介面,右鍵點選Files,新增新生成的Qsys.qip檔案。
最後,我們的頂層檔案是這樣的,這樣我們的硬體工程就算建立完畢了。
module Qsys_Hdmi_Ip
(
//時鐘復位埠
CLK_50M,RST_N,
//SDRAM埠
SDRAM_ADDR,SDRAM_BA,SDRAM_CAS_N,SDRAM_CLK,SDRAM_CKE,
SDRAM_CS_N,SDRAM_DQ,SDRAM_DQM,SDRAM_RAS_N,SDRAM_WE_N,
//HDMI埠
HDMI_CLK,HDMI_RST_N,HDMI_HS,HDMI_VS,HDMI_DE,
HDMI_RGB_R,HDMI_RGB_G,HDMI_RGB_B,HDMI_SCLK,HDMI_SDAT
);
//時鐘復位
input CLK_50M;
input RST_N;
// SDRAM Interface
output [12:0] SDRAM_ADDR;
output [ 1:0] SDRAM_BA;
output SDRAM_CAS_N;
output SDRAM_CLK;
output SDRAM_CKE;
output SDRAM_CS_N;
inout [15:0] SDRAM_DQ;
output [ 1:0] SDRAM_DQM;
output SDRAM_RAS_N;
output SDRAM_WE_N;
//VGA
output HDMI_CLK;
output HDMI_RST_N;
output HDMI_HS;
output HDMI_VS;
output HDMI_DE;
output [ 7:0] HDMI_RGB_R;
output [ 7:0] HDMI_RGB_G;
output [ 7:0] HDMI_RGB_B;
output HDMI_SCLK; //iic的時鐘訊號
inout HDMI_SDAT; //iic的資料訊號
assign HDMI_RST_N = 1'b1;
wire clk_100m;
wire clk_30m;
wire HDMI_CLK;
PLL PLL_Init
(
.inclk0 (CLK_50M ),
.c0 (clk_100m ),
.c1 (SDRAM_CLK ),
.c2 (clk_30m )
);
PLL_Hdmi PLL_Hdmi_Init
(
.inclk0 (clk_30m ),
.c0 (HDMI_CLK )
);
Qsys_system Qsys_system_Init
(
.clk_clk (clk_100m ), //clk.clk
.reset_reset_n (RST_N ), //reset.reset_n
.sdram_addr (SDRAM_ADDR ), //sdram_conduit.addr
.sdram_ba (SDRAM_BA ), // .ba
.sdram_cas_n (SDRAM_CAS_N ), // .cas_n
.sdram_cke (SDRAM_CKE ), // .cke
.sdram_cs_n (SDRAM_CS_N ), // .cs_n
.sdram_dq (SDRAM_DQ ), // .dq
.sdram_dqm (SDRAM_DQM ), // .dqm
.sdram_ras_n (SDRAM_RAS_N ), // .ras_n
.sdram_we_n (SDRAM_WE_N ), // .we_n
.zircon_avalon_hdmi_clk (HDMI_CLK ), // zircon_hdmi.clk
.zircon_avalon_hdmi_hsync (HDMI_HS ), // .hsync
.zircon_avalon_hdmi_vsync (HDMI_VS ), // .vsync
.zircon_avalon_hdmi_de (HDMI_DE ), // .de
.zircon_avalon_hdmi_rgb (HDMI_RGB ), // .rgb
.zircon_avalon_hdmi_sclk (HDMI_SCLK ), // .sclk
.zircon_avalon_hdmi_sdat (HDMI_SDAT ) // .sdat
);
wire [31:0] HDMI_RGB;
assign HDMI_RGB_R = {HDMI_RGB[23:18],2'b11};
assign HDMI_RGB_G = {HDMI_RGB[15:10],2'b11};
assign HDMI_RGB_B = {HDMI_RGB[ 7: 2],2'b11};
endmodule
2 自定義HDMI IP核的製作
在第一個部分,一休哥詳細的介紹瞭如何新建Nios II的硬體工程。大家可以發現,這個工程十分簡潔,兩個PLL模組用於產生100M時鐘,SDRAM的時鐘和HDMI的時鐘,一個Qsys系統的頂層模組。這個Qsys系統的頂層模組是我們在Qsys設計介面中生成的。所以,在這個工程中,不需要我們手動編寫Verilog邏輯程式碼嗎?嘿嘿,好像真的是這樣哦,這就是Nios的簡便之處吧,不需要編寫複雜程式碼就能夠控制SDRAM。
接下來,一休哥將著重介紹HDMI IP核的製作。首先我們來看下HDMI的硬體原理圖。HDMI晶片與FPGA相連的介面可以分為兩類,一類是IIC介面,一類是類似於VGA的視訊時序介面。驅動HDMI晶片需要兩個步驟,先使用IIC介面對HDMI晶片進行配置,然後使用視訊時序介面輸出視訊時序訊號。這些操作都是通過HDMI IP核來實現的。
接下來,開啟HDMI IP核的資料夾。可以看到有5個v檔案,這就是HDMI IP核的硬體。字尾為hw的tcl檔案是硬體配置檔案,這一檔案是通過Qsys介面生成的,通過這個檔案可以讓Qsys系統生成時自動呼叫這5個v檔案融於系統中。
其中zircon_avalon_hdmi 是HDMI IP核的硬體的頂層檔案,i2c_timing_ctrl是用來完成IIC配置的,zircon_avalon_hdmi_logic是用來模擬視訊時序的,並控制讀取zircon_avalon_hdmi_fifo的FIFO模組中的資料。zircon_avalon_hdmi_register是HDMI IP核內部的暫存器操作,通過Avalon-MM協議可以實現HDMI IP核與Nios II Processor IP核和SDRAM Controller IP核的資料通訊。關於每個v檔案具體的作用,大家可以參考鋯石科技推出的《軟核演練篇》系列教程中VGA IP核的相關內容,在這裡就不一一介紹了。
接下來,我教大家來製作字尾為hw的硬體配置tcl檔案。首先我們在路徑下將資料夾中的hw檔案刪除。這時開啟Qsys介面,大家可以發現,我們已經找不到HDMI IP核了。
然後,雙擊New Component,然後配置IP核的資訊。嘿嘿,由於我這個IP核有抄襲鋯石科技的嫌疑,在這裡允許我把建立人寫為zircon吧。(鋯石大大,別怪我)
然後進入下一個介面,點選新增按鈕,將路徑中的那5個v檔案選中新增,並且將zircon_avalon_hdmi.v檔案設定為頂層檔案,點選Analysis Synthesis Files檔案。
接著,來到最後一個Interfaces介面,針對avalon_slave介面、avalon_master介面和conduit_end介面做如下修改:改名和新增復位訊號。最後點選Finish完成IP核的硬體建立。
最後在FPGA工程的資料夾目錄中可以找到zircon_avalon_hdmi_hw.tcl,然後開啟這個檔案,對檔案中的部分程式碼進行修改。然後將這個檔案剪下到源HDMI IP核路徑中即可。然後重新開啟Qsys介面,就可以重新搜尋到HDMI IP核了。
3 Nios II顯示圖片
完成了上述兩個部分後,接下來我們來介紹Nios 軟體工程。首先在FPGA工程的根目錄下新建一個software資料夾。
然後點選Quartus軟體介面中的Nios II Software Build Tools for Eclipse,在彈出來的軟體工程路徑中選擇C:\Users\Administrator\Desktop\Qsys_Hdmi_Ip\software。開始建立軟體工程。
接下來我們進入Nios II-Eclipse軟體介面,點選Nios II Application and BSP from Template,開始建立工程。
建立完工程後,首先我們點選更新BSP
此時在BSP目錄下就可以找到HDMI IP核的c和h檔案。問個問題,這些檔案是如何自動綜合到BSP中的呢?答案就是之前提到的HDMI IP核中的zircon_avalon_hdmi_sw.tcl檔案,裡面包含了所有c和h檔案的詳細路徑。
其中,kai、qiong、qiu1、qiu2、shui和ying的h檔案是我使用軟體Img2Lcd將圖片轉換成陣列的(該軟體的使用方法在上篇博文中具體介紹過。)該軟體的具體配置如下,然後點選儲存為h檔案即可。可以看到,一休哥將640*480的24bit圖片轉換成了相應的陣列。由於一休哥採取的是24位深度轉換,所以得到的是一個長度為921600的無符號字元型陣列,每相連的3個8bit資料組成一個24bit的畫素值。
HDMI IP核中,除了包含圖片資料外,還包含了IP核所必備的暫存器標頭檔案,功能函式庫的c和h檔案。
首先,我們從main函式開始看起。首先需要呼叫HDMI IP核初始化函式zircon_avalon_hdmi_init。這個函式非常簡單。第一步,對HDMI IP核的控制暫存器寫0,即預設HDMI IP核停止工作。第二步,對HDMI IP核的資料暫存器進行配置,將陣列hdmi_buffer的首地址值寫進去。第三步,對HDMI IP核的控制暫存器寫1,即讓DMI IP核開始工作。其中,hdmi_buffer是一個大小為640*480的深度為32bit的陣列,這一陣列是一個全域性變數,即它預先就在SDRAM中佔據了一塊固定的記憶體。通過將陣列hdmi_buffer的首地址值寫入HDMI IP核的資料暫存器。則HDMI IP核就會連續對SDRAM中這塊記憶體進行迴圈讀取操作(起始地址為陣列hdmi_buffer的首地址值,範圍大小為640*480,深度為32bit)
進行完初始化之後,直接進入一個while的死迴圈中,在這裡就執行著顯示圖片的操作zircon_avalon_hdmi_DisplayPic1,這裡有六個顯示圖片函式,並且中間會有一段時間的延時。所以本工程的效果就是六張圖片來回切換。我們最後來介紹下zircon_avalon_hdmi_DisplayPic1函式,這個函式就是一個連續畫點函式,先將原圖片陣列的3個8bit資料拼成一個24bit資料,然後依次寫入陣列hdmi_buffer。
最後我們執行程式,來看一下效果。
總結一下,學習Nios II,我們需要關心Nios II與IP核、IP核與SDRAM之間的資料交換,也就是Avalon-MM協議,這才是我們學習的重點所在。一般我們會用一個FIFO來快取資料(而不會用ram),解決跨時鐘域的資料交換問題。大家可以發現,在本工程的效果圖中,圖片切換的過程中存在明顯的馬賽克現象,這是因為SDRAM不支援同時讀寫的緣故,馬賽克的持續時間是由於Nios的讀寫速度造成的。因此,可以選擇將SDRAM換成可支援同時讀寫的大容量SRAM晶片,不過這個成本很高。最值得嘗試的解決辦法就是,可以嘗試使用DDR晶片,加快讀寫速度,縮短馬賽克現象的存在時間。