文章目錄
- 前言
- 一、VGA概述
- 1.1 簡述
- 1.2 管腳定義
- 1.3 VGA顯示原理
- 1.4 VGA時序標準
- 1.5 VGA 顯示模式及相關參數
- 二、VGA顯示自定義的漢字字符
- 2.1 點陣漢字生成
- 2.2 生成BMP文件
- 2.3 生成txt文件
- 2.4 實現效果
- 三、VGA顯示條紋
- 3.1 實現流程
- 3.2 實現效果
- 四、VGA輸出一幅彩色圖像
- 4.1 bmp圖片轉hex文件
- 4.2 引入ROM ip核
- 4.3 代碼實現
- 4.4 實現效果
- 五、代碼
- 5.1 時鐘分頻
- 5.2 vga驅動模塊
- 5.3 顯示數據生成模塊
- 5.4 按鍵消抖模塊
- 六、整體實現效果(加入按鍵)
- 總結
- 參考
前言
本文內容:
-
深入了解VGA協議,理解不同顯示模式下的VGA控制時序參數(行頻、場頻、水平/垂直同步時鐘周期、顯示后沿/前沿等概念和計算方式);
-
通過Verilog編程,在至少2種顯示模式下(640480@60Hz,1024768@75Hz)分別實現以下VGA顯示,并對照VGA協議信號做時序分析:1)屏幕上顯示彩色條紋;2)顯示自定義的漢字字符(姓名-學號);3)輸出一幅彩色圖像。
-
在Verilog代碼中,將行、場同步信號中,故意分別加入一定 ms延時(用delay命令),觀察會出現什么現象。
一、VGA概述
1.1 簡述
圖像顯示設備在日常生活中隨處可見,例如家庭電視機、計算機顯示屏幕等,這些設備之所以能夠顯示我們需要的數據圖像信息,歸功于視頻傳輸接口。常見的視頻傳輸接口有三種:VGA 接口、 DVI 接口和 HDMI 接口,目前的顯示設備都配有這三種視頻傳輸接口。
三類視頻接口的發展歷程為 VGA → DVI → HDMI 。其中 VGA 接口出現最早,只能傳輸模擬圖像信號; 隨后出現的 DVI 接口又分為三類: DVI-A 、 DVI-D 、 DVI-I ,分別可傳輸純模擬圖像信號、純數字圖像信號和兼容模擬、數字圖像信號;最后的 HDMI 在傳輸數字圖像信號的基礎上又可以傳輸音頻信號。
VGA,英文全稱“ Video Graphics Array ”,譯為視頻圖形陣列,是一種使用模擬信號進行視頻傳輸的標準協議,由 IBM 公司于 1987 年推出,因其分辨率高、顯示速度快、顏色豐富等優點,廣泛應用于彩色顯示器領域。由于 VGA 接口體積較大,與追求小巧便攜的筆記本電腦背道而馳,在筆記本電腦領域,VGA 接口已被逐漸淘汰,但對于體積較大的臺式機,這種情況并未發生,雖然 VGA 標準在當前個人電腦市場中已經過時,但因其在顯示標準中的重要性和良好的兼容性,VGA 仍然是最多制造商所共同支持的一個標準,個人電腦在加載自己獨特驅動程序之前,都必須支持 VGA 的標準。
早期的 CRT 顯示器只能接收模擬信號,不能接收數字信號,計算機內部顯卡將數字信號轉換成模擬信號,通過 VGA 接口傳給 VGA 顯示器,雖然現如今許多種類的顯示器可以直接接收數字信號,但為了兼容顯卡的 VGA 接口,大都支持 VGA 標準。
VGA 接口中以針式引出信號線的稱為公頭,以孔式引出信號線的稱為母頭。在計算機和 VGA 顯示器上一般引出母頭接口,使用兩頭均為公頭的 VGA 連接線將計算機與 VGA顯示器連接起來,兩者圖像傳輸時,使用的是 VGA 圖像傳輸標準,該標準的具體內容在后面博文會詳細說明。VGA 公頭、母頭接口和 VGA 連接線。
1.2 管腳定義
下面,我們結合 VGA 接口引腳圖和各引腳定義表格,對 VGA 接口各引腳做一下簡單介紹。
? ? ? 由圖4可知,VGA 接口共有 15 個引腳,分為 3 排,每排各 5 個, 按照自上而下、從左向右的順序排列。其中第一排的引腳 1 、 2 、 3 和第三排的引腳 13 、 14 最為重要。
? ? ? VGA 使用工業界通用的 RGB 色彩模式作為色彩顯示標準,這種色彩顯示標準是根據三原色中紅色、綠色、藍色所占比例多少及三原色之間的相互疊加得到各式各樣的顏色。引腳 1 紅基色 (RED) 、引腳 2 綠基色 (GREEN) 、引腳 3 藍基色 (BLUE) 就是 VGA 接口中負責傳輸三原色的傳輸通道。要注意的是,這 3 個引腳傳輸的是模擬信號。
? ? ? 引腳 13 行同步信號 (HSYNC) 、引腳 14 場同步信號 (VSYNC) ,這兩個信號,是在 VGA顯示圖像時,負責同步圖像色彩信息的同步信號。在后面博文中,我們會對這兩個信號進行詳細講解。
? ? ? 引腳 5 、 9 :這兩個引腳分別是 VGA 接口的自測試和預留接口,不過不同生產廠家對這兩個接口定義不同,在接線時,兩引腳可懸空不接。
? ? ? 引腳 4 、 11 、 12 、 15 :這四個是 VGA 接口的地址碼,可以懸空不接。
? ? ? 引腳 6 、 7 、 8 、 10 :這四個引腳接地,無需解釋。
1.3 VGA顯示原理
VGA 顯示器顯示圖像,并不是直接讓圖像在顯示器上顯示出來,而是采用掃描的方式,將構成圖像的像素點,在行同步信號和場同步信號的同步下,按照從上到下、由左到右的順序掃描到顯示屏上。VGA 顯示器掃描方式,具體見圖 1 。
結合
??結合 VGA 顯示器掃描方式示意圖,我們簡要說明一下 VGA 顯示器的掃描規律。
?? (1) 在行、場同步信號的同步作用下,掃描坐標定位到左上角第一個像素點坐標;
?? (2) 自左上角 ( 第一行 ) 第一個像素點坐標,逐個像素點向右掃描 ( 圖中第一個水平方向箭頭) ;
?? (3) 掃描到第一行最后一個數據,一行圖像掃描完成,進行圖像消隱,掃描坐標自第一行行尾轉移到第二行行首( 圖中第一條虛線 ) ;
?? (4) 重復若干次掃描至最后一行行尾,一幀圖像掃描完成,進行圖像消隱,掃描坐標跳轉回到左上角第一行行首( 圖中對角線箭頭 ) ,開始下一幀圖像的掃描。
?? 在掃描的過程中會對每一個像素點進行單獨賦值,使每個像素點顯示對應色彩信息,
??當一幀圖像掃描結束后,開始下一幀圖像的掃描,循環往復,當掃描速度足夠快,加之人眼的視覺暫留特性,我們會看到一幅完整的圖片,而不是一個個閃爍的像素點。這就是VGA 顯示的原理。
1.4 VGA時序標準
??????為了適應匹配不同廠家的 VGA 顯示器, VGA 視頻傳輸接口有自己的一套 VGA 時序標準,只有遵循 VGA 的時序標準,才能正確的進行圖像信息的顯示。在這里我們以 VESA VGA 時序標準為例,為大家講解一下 VGA 時序標準,具體見圖 2 。
??????由 VESA VGA 時序標準圖可知, VGA 時序由兩部分構成,行同步時序與場同步時序,為了方便大家理解,我們將行同步時序與場同步時序分開講解。
???? (1) 行同步時序,具體見圖 3 。
??????圖中 Video 代表傳輸的圖像信息, HSync 表示行同步信號。 HSync 自上升沿起到下一個上升沿止為一個完整周期,我們稱之為行掃描周期。
??????一個完整的行掃描周期,包含 6 部分: Sync (同步)、 Back Porch (后沿)、 Left Border(左邊框)、 Addressable Video (有效圖像)、 Right Border (右邊框)、 Front Porch(前沿),這 6 部分的基本單位是 pixel (像素),即一個像素時鐘周期。在一個完整的行掃描周期中,Video 圖像信息在 HSync 行同步信號的同步下完成一行圖像的掃描顯示,Video 圖像信息只有在 “Addressable” Video (有效圖像)階段,圖像信息有效,其他階段圖像信息無效。
??????HSync 行同步信號在 Sync (同步)階段,維持高電平,其他階段均保持低電平,在下一個行掃描周期的 Sync (同步)階段, HSync 行掃描信號會再次拉高,其他階段拉低,周而復始。
??????(2) 場同步時序,具體見圖 4 。
??????理解了行同步時序,場同步時序就更容易理解了,兩者相類似,如圖所示,圖中Video 代表傳輸的圖像信息, VSync 表示場同步信號, VSync 自上升沿起到下一個上升沿止為一個完整周期,我們稱之為場掃描周期。
??????一個完整的場掃描周期,也包含 6 部分: Sync (同步)、 Back Porch (后沿)、 Top Border(上邊框)、 “Addressable” Video (有效圖像)、 Bottom Border (底邊框)、 Front Porch(前沿),與行同步信號不同的是,這 6 部分的基本單位是 line (行),即一個完整的行掃描周期。
?????? 在一個完整的場掃描周期中,Video 圖像信息在 HSync (行同步信號)和 VSync (場同步信號)的共同作用下完成一幀圖像的顯示,Video 圖像信息只有在 “Addressable” Video(有效圖像)階段,圖像信息有效,其他階段圖像信息無效。VSync 行同步信號在 Sync (同步)階段,維持高電平,其他階段均保持低電平,完成一個場掃描周期后,進入下一幀圖像的掃描。
?????? 綜上所述,將行同步時序圖與場同步時序圖結合起來就構成了 VGA 時序圖,具體見圖 5。
圖中的紅色區域表示在一個完整的行掃描周期中,Video 圖像信息只在此區域有效,黃色區域表示在一個完整的場掃描周期中,Video 圖像信息只在此區域有效,兩者相交的橙色區域,就是 VGA 圖像的最終顯示區域。
1.5 VGA 顯示模式及相關參數
行同步時序可分為 6 個階段,對于這 6 個階段的參數是有嚴格定義的,參數配置不正確,VGA 不能正常顯示。 VGA 顯示器可支持多種分辨率,不同分辨率對應個階段的參數是不同的,常用 VGA 分辨率時序參數,具體見圖 6 。
?????? 下面我們以經典 VGA 顯示模式 640x480@60 為例,為讀者講解一下 VGA 顯示的相關參數。
?????? (1) 顯示模式: 640x480@60
?????? ?????? 640x480 是指 VGA 的分辨率, 640 是指有效顯示圖像每一行有 640 個像素點, 480 是指每一幀圖像有 480 行, 640 * 480 = 307200 ≈ 300000 ,每一幀圖片包含約 30 萬個像素點,之前某品牌手機廣告上所說的 30 萬像素指的就是這個; @60 是指 VGA 顯示圖像的刷新頻率,60 就是指 VGA 顯示器每秒刷新圖像 60 次,即每秒鐘需要顯示 60 幀圖像。
?????? (2) 時鐘 (MHz) : 25.175MHz
?????? ?????? 這是 VGA 顯示的工作時鐘,像素點掃描頻率。
?????? (3) 行同步信號時序 ( 像素 ) 、場同步信號時序 ( 行數 )
?????? ?????? 行同步信號時序分為 6 段, Sync (同步)、 Back Porch (后沿)、 Left Border (左邊框)、 “Addressable” Video (有效圖像)、 Right Border (右邊框)、 Front Porch (前沿),這 6 段構成一個行掃描周期,單位為像素時鐘周期。
?????? ?????? 同步階段,參數為 96,指在行時序的同步階段,行同步信號需要保持 96 個像素時鐘周期的高電平, 其他幾個階段與此相似。
?????? ?????? 場同步信號時序與其類似,只是單位不再是像素時鐘周期,而是一個完整的行掃描周期,在此不再贅述。
?????? ?????? 在這里,我們看回圖 6,由圖可知,即使 VGA 顯示分辨率相同,但刷新頻率不同的話,相關參數也存在差異,如 640x480@60 、 640x480@75 ,這兩個顯示模式雖然具有相同的分辨率,但是 640x480@75 的刷新頻率更快,所以像素時鐘更快,時序參數也有區別。
?????? 下面我們以顯示模式 640x480@60 、 640x480@75 為例,學習一下時鐘頻率的計算方法。
?????? 行掃描周期 * 場掃描周期 * 刷新頻率 = 時鐘頻率
?????? 640x480@60:
?????? 行掃描周期:800( 像素 ) ,場掃描周期: 525( 行掃描周期 ) 刷新頻率: 60Hz
?????? 800 * 525 * 60 = 25,200,000 ≈ 25.175MHz(誤差忽略不計)
?????? 640x480@75:
?????? 行掃描周期:840( 像素 ) 場掃描周期: 500( 行掃描周期 ) 刷新頻率: 75Hz
?????? 840 * 500 * 75 = 31,500,000 = 31.5MHz
?????? 在計算時鐘頻率時,大家要謹記一點,要使用行掃描周期和場掃描周期的參數進行計算,不能使用有效圖像的參數進行計算,雖然在有效圖像外的其他階段圖像信息均無效,但圖像無效階段的掃描也花費了掃描時間。
?????? 以上就是對 VGA 顯示標準中分辨率相關參數的講解,在編寫 VGA 驅動時,我們要根據 VGA 顯示模式的不同調整相關參數,只有這樣 VGA 圖像才能正常顯示。
二、VGA顯示自定義的漢字字符
筆主整篇文章中使用的板子都是 EP4CE115F29C7
,使用其他板子問題不大,但引腳綁定不太一樣,可自行對應板子查找更改。
2.1 點陣漢字生成
字模的提取可通過字符取模軟件來實現,在這里我們使用取模軟件“PCtoLCD2002”來獲取漢字 李菊芳-632109160602
的字模。如下圖所示:
打開之后會發現軟件中的字體、字寬和字高都是無法設置的,這個時候點擊菜單欄的“模式”,選擇“字符模式”。
切換到字符模式后,就可以設置字體、字寬和字高了。字寬和字高的值越高,顯示在LCD屏上的字符就越大,但是代碼也需要做相應的修改。這里將字體選擇默認的“宋體”,字寬和字高設置成“32”,然后在下方文本框中輸入漢字,如下圖所示:
2.2 生成BMP文件
然后點擊文件-另存為,把圖片保存為BMP圖片,再點擊文件-打開,切換到圖形模式,把保存的BMP圖片打開得到整體的字符
2.3 生成txt文件
再點擊選項按如下參數設置
在配置界面中,當鼠標懸浮在各配置選項上時,軟件會自動提示當前配置的含義。需要注意的是左下角“每行顯示數據”是以字節(Byte)為單位的,而一個字節的數據為8個bit,即可以表示一行點陣中的8個像素點。由于圖中的點陣每行為304個像素點,所以需要304/8=38個Byte的數據來表示一行,因此將“每行顯示數據—點陣”處設置為38。
配置字模選項完成后,點擊“生成字模”,即可得到漢字所對應的點陣數據。
最后點擊生成字符并保存字符為文本文件
最后得到字符如下
把得到的字符在verilog里面使用即可
2.4 實現效果
姓名+學號
居中顯示:
三、VGA顯示條紋
3.1 實現流程
時鐘分頻模塊
640x480像素的VGA協議所需時鐘頻率25MHZ,使用clk IP核進行時鐘分頻
根據當前行地址判斷需要顯示的顏色即可。
輸出顏色豎條
// 狀態輸出邏輯,根據不同的狀態輸出不同的RGB數據
always @( * ) begincase ( states_current )//彩條states_1 : beginif ( addr_h == 0 ) beginrgb_data = black;endelse if ( addr_h >0 && addr_h <81 ) beginrgb_data = red;endelse if ( addr_h >80 && addr_h <161 ) beginrgb_data = orange;// rgb_data = red;endelse if ( addr_h >160 && addr_h <241 ) beginrgb_data = yellow;endelse if ( addr_h >240 && addr_h <321 ) beginrgb_data = green;endelse if ( addr_h >320 && addr_h <401 ) beginrgb_data = blue;endelse if ( addr_h >400 && addr_h <481 ) beginrgb_data = indigo;endelse if ( addr_h >480 && addr_h <561 ) beginrgb_data = purple;endelse if ( addr_h >560 && addr_h <641 ) beginrgb_data = white;endelse beginrgb_data = black;endend
3.2 實現效果
四、VGA輸出一幅彩色圖像
在前面的學習中了解到圖像的格式有多種,例如JPEG,BMP,PNG,JPG等,圖像的位數也有單色、16色、256色、4096色、16位真彩色、24位真彩色、32位真彩色在這里插入圖片描述
這幾種。
VGA的驅動程序顯示的格式為RGB565,我們先找到一張需要顯示的彩色圖片,經過處理,將該圖片轉化為ROM可以存儲的格式,然后VGA驅動程序從ROM中讀取數據,輸出到VGA顯示屏顯示。盡量選一張小的圖片,因為ROM存儲空間有限。
4.1 bmp圖片轉hex文件
使用BMP2Mif軟件將bmp格式圖片轉換為mif文件
轉換后的.mif文件:
4.2 引入ROM ip核
新建Quartus工程,產生ROM IP核,將生成的mif文件保存在ROM中
雙擊選擇ROM:1-PORT
更改設置,words大小設置要大于圖片大小(50x49x24=58800< 65536),然后next
取消勾選q
如果是16位位圖,就加載HEX文件,然后next
這里因為我們轉換的是24位位圖,所以在ROM里要引入 .mif 文件。(16位位圖不用管)
勾選上輸出 inst,方便例化,然后finish
這里同時要調用前面的pll ip核生成一個25mHz的時鐘。
4.3 代碼實現
在data_drive.v文件里,從ikun_rom取出圖片數據。
其他 .v 文件與前文一致。
4.4 實現效果
640*480分辨率顯示下:彩色圖像顯示在正中間
五、代碼
5.1 時鐘分頻
分別使用640×480 60HZ和800×600 72HZ,對應時鐘分別為25M和50M,需要使用PLL進行分頻 時鐘頻率 = 行幀長 × 列幀長 * 刷新率
640 ×480 60HZ對應時鐘頻率= 800 ×525 × 60 = 25.2M
ip核里面找到ALTPLL
基礎時鐘選擇50M
取消勾選輸出使能
c0默認輸出50M即可, c1分頻到25M,如需其他時鐘頻率可以自己進行設置
勾選如下選項后finish
5.2 vga驅動模塊
vga.drive.v
// /*
module vga_dirve (input wire clk, //系統時鐘 input wire rst_n, //復位input wire [ 15:0 ] rgb_data, //RGB--565,即pixel_data[15:11]控制R、pixel_data[10:5]控制G、pixel_data[4:0]控制Boutput wire vga_clk, //vga時鐘 25Moutput reg h_sync, //行同步信號output reg v_sync, //場同步信號output reg [ 11:0 ] addr_h, //行地址output reg [ 11:0 ] addr_v, //列地址output wire [ 4:0 ] rgb_r, //紅基色output wire [ 5:0 ] rgb_g, //綠基色output wire [ 4:0 ] rgb_b //藍基色
);// 定義VGA信號的參數,基于640x480 60Hz的VGA模式
// 640 * 480 60HZ
localparam H_FRONT = 16; // 行同步前沿信號周期長
localparam H_SYNC = 96; // 行同步信號周期長
localparam H_BLACK = 48; // 行同步后沿信號周期長
localparam H_ACT = 640; // 行顯示周期長
localparam V_FRONT = 11; // 場同步前沿信號周期長
localparam V_SYNC = 2; // 場同步信號周期長
localparam V_BLACK = 31; // 場同步后沿信號周期長
localparam V_ACT = 480; // 場顯示周期長// 800 * 600 72HZ
// localparam H_FRONT = 40; // 行同步前沿信號周期長
// localparam H_SYNC = 120; // 行同步信號周期長
// localparam H_BLACK = 88; // 行同步后沿信號周期長
// localparam H_ACT = 800; // 行顯示周期長
// localparam V_FRONT = 37; // 場同步前沿信號周期長
// localparam V_SYNC = 6; // 場同步信號周期長
// localparam V_BLACK = 23; // 場同步后沿信號周期長
// localparam V_ACT = 600; // 場顯示周期長// 計算總的行和場周期
localparam H_TOTAL = H_FRONT + H_SYNC + H_BLACK + H_ACT; // 行周期 16+96+48+640 = 800
localparam V_TOTAL = V_FRONT + V_SYNC + V_BLACK + V_ACT; // 列周期 11+2+6+31+480 = 512
reg [ 11:0 ] cnt_h ; // 行計數器 0-799
reg [ 11:0 ] cnt_v ; // 場計數器 0-524
reg [ 15:0 ] rgb ; // 對應顯示顏色值// 對應計數器開始、結束、計數信號
wire flag_enable_cnt_h ;
wire flag_clear_cnt_h ;
wire flag_enable_cnt_v ;
wire flag_clear_cnt_v ;
wire flag_add_cnt_v ;
wire valid_area ;// 25M時鐘 行周期*場周期*刷新率 = 800 * 525 * 60
wire clk_25 ;
// 50M時鐘 1040 * 666 * 72
wire clk_50 ;
//PLL
pll pll_inst (.areset ( ~rst_n ),.inclk0 ( clk ),.c0 ( clk_50 ), //50M.c1 ( clk_25 ) //25M
);//根據不同分配率選擇不同頻率時鐘
assign vga_clk = clk_25;// 行計數
always @( posedge vga_clk or negedge rst_n ) beginif ( !rst_n ) begincnt_h <= 0;endelse if ( flag_enable_cnt_h ) beginif ( flag_clear_cnt_h ) begincnt_h <= 0;endelse begincnt_h <= cnt_h + 1;endendelse begincnt_h <= 0;end
end
assign flag_enable_cnt_h = 1;
assign flag_clear_cnt_h = cnt_h == H_TOTAL - 1;// 行同步信號
always @( posedge vga_clk or negedge rst_n ) beginif ( !rst_n ) beginh_sync <= 1;endelse if ( cnt_h == H_SYNC - 1 ) begin // 同步周期時為1h_sync <= 0;endelse if ( flag_clear_cnt_h ) begin // 其余為0h_sync <= 1;endelse beginh_sync <= h_sync;end
end// 場計數
always @( posedge vga_clk or negedge rst_n ) beginif ( !rst_n ) begincnt_v <= 0;endelse if ( flag_enable_cnt_v ) beginif ( flag_clear_cnt_v ) begincnt_v <= 0;endelse if ( flag_add_cnt_v ) begincnt_v <= cnt_v + 1;endelse begincnt_v <= cnt_v;endendelse begincnt_v <= 0;end
endassign flag_enable_cnt_v = flag_enable_cnt_h;
assign flag_clear_cnt_v = cnt_v == V_TOTAL - 1;
assign flag_add_cnt_v = flag_clear_cnt_h;// 場同步信號
always @( posedge vga_clk or negedge rst_n ) beginif ( !rst_n ) beginv_sync <= 1;endelse if ( cnt_v == V_SYNC - 1 ) beginv_sync <= 0;endelse if ( flag_clear_cnt_v ) beginv_sync <= 1;endelse beginv_sync <= v_sync;end
end// 對應有效區域行地址 1-640
always @( posedge vga_clk or negedge rst_n ) beginif ( !rst_n ) beginaddr_h <= 0;endelse if ( valid_area ) beginaddr_h <= cnt_h - H_SYNC - H_BLACK + 1;endelse beginaddr_h <= 0;end
end
// 對應有效區域列地址 1-480
always @( posedge vga_clk or negedge rst_n ) beginif ( !rst_n ) beginaddr_v <= 0;endelse if ( valid_area ) beginaddr_v <= cnt_v -V_SYNC - V_BLACK + 1;endelse beginaddr_v <= 0;end
end
// 有效顯示區域
assign valid_area = cnt_h >= H_SYNC + H_BLACK && cnt_h <= H_SYNC + H_BLACK + H_ACT && cnt_v >= V_SYNC + V_BLACK && cnt_v <= V_SYNC + V_BLACK + V_ACT;// 顯示顏色
always @( posedge vga_clk or negedge rst_n ) beginif ( !rst_n ) beginrgb <= 16'h0;endelse if ( valid_area ) beginrgb <= rgb_data;endelse beginrgb <= 16'b0;end
end
assign rgb_r = rgb[ 15:11 ];
assign rgb_g = rgb[ 10:5 ];
assign rgb_b = rgb[ 4:0 ];
endmodule // vga_dirve
// */
5.3 顯示數據生成模塊
data_drive.v
module data_drive (input wire vga_clk, // VGA時鐘輸入input wire rst_n, // 復位信號,低電平有效input wire [ 11:0 ] addr_h, // 水平地址輸入input wire [ 11:0 ] addr_v, // 垂直地址輸入input wire [ 2:0 ] key, // 三個按鍵輸入output reg [ 15:0 ] rgb_data // 輸出的RGB數據);// 定義一些顏色的16位表示
localparam red = 16'd63488;
localparam orange = 16'd64384;
localparam yellow = 16'd65472;
localparam green = 16'd1024;
localparam blue = 16'd31;
localparam indigo = 16'd18448;
localparam purple = 16'd32784;
localparam white = 16'd65503;
localparam black = 16'd0;//顯示的名字
// 存儲顯示字符的每一行數據
// reg [ 383:0 ] char_line[ 64:0 ];//李菊芳-632109160602 -16
//16行,每行152個bit
// reg [ 152:0 ] char_line[ 15:0 ];//李菊芳-632109160602 ——32
//32*3+16*13 = 304 304/8 = 38
reg [ 303:0 ] char_line[ 31:0 ];// 定義顯示狀態的參數
localparam states_1 = 1; // 彩條
localparam states_2 = 2; // 字符
localparam states_3 = 3; // 圖片// 圖片的尺寸參數
// parameter height = 78; // 圖片高度
// parameter width = 128; // 圖片寬度//ikun2
parameter height = 52; // 圖片高度
parameter width = 52; // 圖片寬度// 當前狀態和下一個狀態的寄存器
reg [ 2:0 ] states_current ; // 當前狀態
reg [ 2:0 ] states_next ; // 下個狀態// ROM的地址寄存器和數據輸出
reg [ 13:0 ] rom_address ; // ROM地址
// wire [ 15:0 ] rom_data ; // 圖片數據
wire [ 23:0 ] rom_data ; // 顯示彩色圖片數據// 狀態機的標志位
wire flag_enable_out1 ; // 文字有效區域標志
wire flag_enable_out2 ; // 圖片有效區域標志
wire flag_clear_rom_address ; // ROM地址清零標志
wire flag_begin_h ; // 圖片顯示行開始標志
wire flag_begin_v ; // 圖片顯示列開始標志// 狀態轉移邏輯
always @( posedge vga_clk or negedge rst_n ) beginif ( !rst_n ) beginstates_current <= states_1;// 復位時設置初始狀態為彩條endelse beginstates_current <= states_next;// 否則轉移到下一個狀態end
end// 狀態判斷邏輯,根據按鍵輸入更新下一個狀態
always @( posedge vga_clk or negedge rst_n ) beginif ( !rst_n ) beginstates_next <= states_1;endelse if ( key[ 0 ] ) beginstates_next <= states_1;endelse if ( key[ 1 ] ) beginstates_next <= states_2;endelse if ( key[ 2 ] ) beginstates_next <= states_3;endelse beginstates_next <= states_next;end
end// 狀態輸出邏輯,根據不同的狀態輸出不同的RGB數據
always @( * ) begincase ( states_current )//彩條states_1 : beginif ( addr_h == 0 ) beginrgb_data = black;endelse if ( addr_h >0 && addr_h <81 ) beginrgb_data = red;endelse if ( addr_h >80 && addr_h <161 ) begin// rgb_data = orange;rgb_data = red;endelse if ( addr_h >160 && addr_h <241 ) beginrgb_data = yellow;endelse if ( addr_h >240 && addr_h <321 ) beginrgb_data = green;endelse if ( addr_h >320 && addr_h <401 ) beginrgb_data = blue;endelse if ( addr_h >400 && addr_h <481 ) beginrgb_data = indigo;endelse if ( addr_h >480 && addr_h <561 ) beginrgb_data = purple;endelse if ( addr_h >560 && addr_h <641 ) beginrgb_data = white;endelse beginrgb_data = black;endend//字符states_2 : beginif ( flag_enable_out1 ) begin//480*640// rgb_data = char_line[ addr_v-208 ][ 532 - addr_h ]? white:black;rgb_data = char_line[ addr_v-224 ][ 472 - addr_h ]? white:black;endelse beginrgb_data = black;endend//圖片states_3 : beginif ( flag_enable_out2 ) beginrgb_data = rom_data;endelse beginrgb_data = black;endenddefault: begincase ( addr_h )0 : rgb_data = black;1 : rgb_data = red;81 : rgb_data = orange;161: rgb_data = yellow;241: rgb_data = green;321: rgb_data = blue;401: rgb_data = indigo;481: rgb_data = purple;561: rgb_data = white;default: rgb_data = rgb_data;endcaseendendcase
end//李駿飛的居中顯示參數
//32*3+16*13 = 304 304/8 = 38
// 根據當前狀態和地址范圍設置標志位
parameter ljf_width = 304; // 字符數據的寬度
parameter ljf_height = 32; // 字符數據的高度
assign flag_enable_out1 = states_current == states_2 && addr_h >= (640 - ljf_width) / 2 && addr_h < ((640 - ljf_width) / 2) + ljf_width && addr_v >= (480 - ljf_height) / 2 && addr_v < ((480 - ljf_height) / 2) + ljf_height;// assign flag_begin_h = addr_h > ( ( 640 - width ) / 2 ) && addr_h < ( ( 640 - width ) / 2 ) + width + 1;
// assign flag_begin_v = addr_v > ( ( 480 - height )/2 ) && addr_v <( ( 480 - height )/2 ) + height + 1;
assign flag_begin_h = addr_h >= ( ( 640 - width ) / 2 ) && addr_h < ( ( 640 - width ) / 2 ) + width ;
assign flag_begin_v = addr_v >= ( ( 480 - height )/2 ) && addr_v <( ( 480 - height )/2 ) + height ;
assign flag_enable_out2 = states_current == states_3 && flag_begin_h && flag_begin_v;//ROM地址計數器
always @( posedge vga_clk or negedge rst_n ) beginif ( !rst_n ) beginrom_address <= 0;// 復位時清零ROM地址endelse if ( flag_clear_rom_address ) begin //計數滿清零rom_address <= 0;endelse if ( flag_enable_out2 ) begin //在有效區域內+1rom_address <= rom_address + 1;endelse begin //無效區域保持rom_address <= rom_address;end
end
assign flag_clear_rom_address = rom_address == height * width - 1 || states_current != states_3;// 初始化顯示文字的邏輯
always@( posedge vga_clk or negedge rst_n ) beginif ( !rst_n ) begin//李菊芳-632109160602 ——32//32*3+16*13 = 304 304/8 = 38char_line[0] = 304'h0000000000000000000000000000000000000000000000000000000000000000000000000000;char_line[1] = 304'h0000000000000000000000000000000000000000000000000000000000000000000000000000;char_line[2] = 304'h0003800000101000002008000000000000000000000000000000000000000000000000000000;char_line[3] = 304'h0003C000001C1C0000380E000000000000000000000000000000000000000000000000000000;char_line[4] = 304'h000380100018180000300C000000000000000000000000000000000000000000000000000000;char_line[5] = 304'h000380380018181800300C300000000000000000000000000000000000000000000000000000;char_line[6] = 304'h3FFFFFFC3FFFFFFC1FFFFFF8000001E007C007E0008003C007C0008001E003C001E003C007E0;char_line[7] = 304'h180FE0000098180000300C000000061818600838018006201820018006180620061806200838;char_line[8] = 304'h001FF00000D8180000330C0000000C18303010181F800C3030101F800C180C300C180C301018;char_line[9] = 304'h003FB80000F8180000318C00000008183018200C01801818301801800818181808181818200C;char_line[10] = 304'h 007B9C00019000200030C800000018003018200C01801818600801801800181818001818200C;char_line[11] = 304'h 00F39E0001FFFFF000006000000010003018300C01801808600C01801000180810001808300C;char_line[12] = 304'h 01E38F800300003000004030000010000018300C0180300C600C01801000300C1000300C300C;char_line[13] = 304'h 03C387F0020300301FFFFFF8000030000018000C0180300C600C01803000300C3000300C000C;char_line[14] = 304'h 07838DFE0483083000060000000033E0003000180180300C600C018033E0300C33E0300C0018;char_line[15] = 304'h 1FFFFEF808431C300006000000003630006000180180300C600C01803630300C3630300C0018;char_line[16] = 304'h 38401F3010631830000C00007FFE381803C000300180300C701C01803818300C3818300C0030;char_line[17] = 304'h 60003C0020633030000C008000003808007000600180300C302C01803808300C3808300C0060;char_line[18] = 304'h 0001F00000232330000FFFC00000300C001800C00180300C186C0180300C300C300C300C00C0;char_line[19] = 304'h 0001E0000FFFFFB0000C00C00000300C000801800180300C0F8C0180300C300C300C300C0180;char_line[20] = 304'h 0001E010000F0030000C01800000300C000C03000180300C000C0180300C300C300C300C0300;char_line[21] = 304'h 0001C038000B8030001801800000300C000C02000180300C00180180300C300C300C300C0200;char_line[22] = 304'h 7FFFFFFC001B6030001801800000300C300C04040180180800180180300C1808300C18080404;char_line[23] = 304'h 3801C00000333830003001800000180C300C08040180181800100180180C1818180C18180804;char_line[24] = 304'h 0001C00000631C20003001800000180830081004018018183030018018081818180818181004;char_line[25] = 304'h 0001C00000C30C200060030000000C183018200C01800C30306001800C180C300C180C30200C;char_line[26] = 304'h 0001C0000183046000C0030000000E3018303FF803C0062030C003C00E3006200E3006203FF8;char_line[27] = 304'h 0001C000020300600180C300000003E007C03FF81FF803C00F801FF803E003C003E003C03FF8;char_line[28] = 304'h 003FC0000C030FE002003E000000000000000000000000000000000000000000000000000000;char_line[29] = 304'h 0007C000300303C00C001E000000000000000000000000000000000000000000000000000000;char_line[30] = 304'h 0003800000020080300008000000000000000000000000000000000000000000000000000000;char_line[31] = 304'h 0000000000000000000000000000000000000000000000000000000000000000000000000000;end
end// /*
//ikun
// ROM實例化,根據地址輸出數據
ikun_rom ikun_rom_inst (
.address ( rom_address ),
.clock ( vga_clk ),
.q ( rom_data )
);
// */endmodule // data_drive
5.4 按鍵消抖模塊
key_debounce.v
module key_debounce(input wire clk, // 時鐘信號輸入input wire rst_n, // 復位信號,低電平有效input wire key, // 按鍵輸入output reg flag, // 抖動標志,0表示抖動中,1表示抖動結束output reg key_value // 按鍵穩定后的值
);// 定義參數MAX_NUM,用于設置消抖計數的最大值
parameter MAX_NUM = 20'd1_000_000;// 內部信號聲明
reg [19:0] delay_cnt; // 用于消抖的計數器,足夠大以覆蓋抖動時間
reg key_reg; // 上一次按鍵的狀態// 第一個always塊:處理按鍵抖動
always @(posedge clk or negedge rst_n) beginif(!rst_n) beginkey_reg <= 1; // 復位時,將上一次按鍵狀態設為高電平delay_cnt <= 0; // 復位時,清零計數器endelse beginkey_reg <= key; // 非復位時,更新上一次按鍵狀態// 當上一次按鍵狀態與當前按鍵狀態不一致時,認為是抖動開始,重置計數器if(key_reg != key) begindelay_cnt <= MAX_NUM;endelse begin// 如果按鍵狀態一致,開始計數,直到計數器減到0if(delay_cnt > 0)delay_cnt <= delay_cnt - 1;elsedelay_cnt <= 0;endend
end// 第二個always塊:在按鍵穩定后輸出按鍵值和標志
always @(posedge clk or negedge rst_n) beginif(!rst_n) beginflag <= 0; // 復位時,抖動標志設為0key_value <= 1; // 復位時,按鍵值設為高電平endelse begin// 當計數器的值減到1時,認為按鍵已經穩定,更新抖動標志和按鍵值if(delay_cnt == 1) beginflag <= 1;key_value <= key;endelse begin// 如果計數器還沒減到1,保持抖動標志為0,按鍵值不變flag <= 0;key_value <= key_value;endend
endendmodule
六、整體實現效果(加入按鍵)
加入按鍵之后的實現效果
總結
-
VGA顯示,難點在于顯示驅動模塊,行場同步時序的代碼編寫,可以先寫計數器和大模塊,一些內部信號與標志信號可以逐步完善;其次就是數據顯示中顯示區域代碼的編寫。
-
VGA顯示,顯示彩條部分還可以改進,可以定義不同彩條的顯示區域。顯示文字主要就是顯示位置的確定與漢字點陣的生成,漢字點陣還是費了一些功夫,最開始實在沒找到一個比較合適的轉換工具和方法,后來在朋友的幫助指點下終于可以了。顯示圖片一定要注意24位位圖的話在ROM里面引入的是 .mif 文件,16位位圖的話就引入hex文件。
-
代碼或哪一部分有問題歡迎留言指正。
參考
VGA顯示接口簡介
基于FPGA的VGA顯示彩條、字符、圖片
【FPGA實驗】基于DE2-115平臺的VGA顯示
FPGA VGA顯示協議