1. 原理簡述
VGA即視頻圖形陣列(Video Graphics Array),是一種使用模擬信號的視頻圖像顯示標準,最初是爲CRT顯示器而設計。
CRT顯示器,又稱陰極射線顯像管,主要由五部分組成:電子槍、偏轉線圈、蔭罩、高壓石墨電極、熒光粉塗層及玻璃外殼。
工作原理如下:
顯像管的熒光屏上塗滿了按一定方式緊密排列的紅、綠、藍三種顏色的熒光粉點或熒光粉條,稱爲熒光粉單元;相鄰的紅、綠、藍熒光粉單元爲一組,稱之爲像素。
工作時,燈絲加熱陰極,陰極發射電子,然後在加速極電場的作用下,經聚焦成爲很細的電子束,在陽極高壓作用下,獲得巨大的能量,以極高的速度去轟擊熒光粉層。電子槍發射的電子束不是一束,而是三束,受電腦顯卡RGB三個基色視頻信號電壓的控制。受到高速電子束的激發,熒光粉單元發出強弱不同的紅、綠、藍三種光。
蔭罩上面有很多小孔或細槽,和同一組的熒光粉單元相對應。掃描過程中,通過蔭罩來保證三支電子束準確擊中每一個像素。
偏轉線圈協助電子槍完成高速的掃描動作,使得圖像在熒光屏上連續不斷的出現。
VGA接口又稱D-sub接口,是VGA標準輸出數據的專用接口。接口共有15針,分成3排, 每排5個孔,是顯卡上輸出模擬信號的接口。
2. 接口示意
圖1 VGA接口示意圖 |
接口編號 | 功能 |
---|---|
1 | 紅色視頻信號 |
2 | 綠色視頻信號 |
3 | 藍色視頻信號 |
4 | 地址碼bit2 |
5 | 自測試(各家定義不同) |
6 | 紅色視頻信號地線 |
7 | 綠色視頻信號地線 |
8 | 藍色視頻信號地線 |
9 | 保留(各家定義不同) |
10 | 同步信號地線 |
11 | 地址碼bit0 |
12 | 地址碼bit1/SDA |
13 | 水平/複合同步信號 |
14 | 垂直同步信號 |
15 | 地址碼bit3/SCL |
3. 時序標準
3.1. 基本時序說明
CRT掃描方式有多種,有直線掃描,圓形掃描等。直線掃描又分爲逐行掃描和隔行掃描。具體的掃描方式由顯示器而定,這裏僅介紹直線掃描中的逐行掃描。
直線逐行掃描是從屏幕左上角開始,從左至右逐點掃描。每掃描完一行後,電子束回到下一行的最左端,在這期間,需要對電子束進行行消隱,每行結束時,用行同步信號進行同步;當掃描完所有的行時,形成一幀圖像,用場同步信號進行場同步,並使掃描回到屏幕左上方,同時進行場消隱,開始下一幀。完成一行掃描的時間稱爲水平掃描時間,其倒數稱爲行頻率;完成一幀掃描的時間稱爲垂直掃描時間,其倒數稱爲場頻率。標準的VGA顯示的場頻率爲60Hz。
VGA工業標準顯示模式要求:行、場同步都爲負極性,即同步脈衝要求是負脈衝。綜合上述邏輯,其行、場時序如下:
圖2 行時序 |
圖3 場時序 |
vga信號中不存在點像素的頻率,因此具體的工作頻率需要通過分辨率和場頻率計算得來。以[email protected](60Hz)爲例,每場對應806個行週期,其中有768個顯示行;每顯示行包括1344個點時鐘,其中有1024個有效顯示點。故點時鐘頻率約爲1344x806x60=64995840Hz,即約爲65MHz。下面是一些常用的VGA分辨率時序參數:
顯示模式 | a | b | c | d | e | f | g | h | i | k |
---|---|---|---|---|---|---|---|---|---|---|
640x480@60Hz | 96 | 48 | 640 | 16 | 800 | 2 | 33 | 480 | 10 | 525 |
800x600@60Hz | 128 | 88 | 800 | 40 | 1056 | 4 | 23 | 600 | 1 | 623 |
1024x768@60Hz | 136 | 160 | 1024 | 24 | 1344 | 6 | 29 | 768 | 3 | 806 |
1280x720@60Hz | 40 | 220 | 1280 | 110 | 1650 | 5 | 20 | 720 | 5 | 750 |
1280x1024@60Hz | 112 | 248 | 1280 | 48 | 1688 | 3 | 38 | 1024 | 1 | 1066 |
1920x1080@60Hz | 44 | 148 | 1920 | 88 | 2200 | 5 | 36 | 1080 | 4 | 1125 |
注意:上表參數單位爲像素,字母含義對比時序圖理解(a/f爲同步脈衝,b/g爲顯示後沿,c/h爲顯示時序段,d/i爲顯示前沿,e/k爲總時序)。
3.2. 地址碼線說明
該部分主要是爲即插即用技術服務的。
如果支持DDC功能,那麼將允許主機和顯示器之間進行通信,顯示器將把一系列數值信息傳給主機,當顯卡接收到這部分信息後,會根據顯示器的參數自動進行匹配,實現即插即用。同時,還能夠利用該功能對顯示器的故障等方面進行檢測。
關於此部分驅動的實現,我暫時還沒有用FPGA實現過,也沒有找到可供附上的例文,後續若有涉獵,會更新附上。若有小夥伴需要用到這方面,可以通過參考arm微處理器的顯示驅動來尋找靈感,亦可使用FPGA與arm的聯合開發來實現。
4. 硬件連接
紅色視頻信號、綠色視頻信號與藍色視頻信號輸入均爲模擬信號,採用RS343電平標準。
一般在使用FPGA驅動VGA顯示時,需要配有相應的驅動電路。現在許多FPGA開發板都配有VGA接口電阻網絡,能夠較好的滿足VGA模擬信號的輸出。具體阻抗匹配辦法,可以參考VGA 接口電阻網絡阻抗匹配。
5. 示例代碼及上機驗證
下示VGA信號的行爲級建模(vhdl居然不高亮):
library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_unsigned.all;
use ieee.std_logic_arith.all;
------------------------------------------------------------------------------------------------------------------------
-- 1024*768 60Hz VGA
------------------------------------------------------------------------------------------------------------------------
entity VGA is
-- 參數設定
generic(
-- 水平掃描參數設定
LinePeriod : integer := 1344; -- 行週期數
H_SyncPulse : integer := 136; -- 行同步脈衝(Sync a)
H_BackPorch : integer := 160; -- 顯示後沿(Back porch b)
H_ActivePix : integer := 1024; -- 顯示時序段(Display interval c)
H_FrontPorch : integer := 24; -- 顯示前沿(Front porch d)
Hde_start : integer := 296;
Hde_end : integer := 1320;
H_unitlength : integer := 1;
-- 垂直掃描參數設定
FramePeriod : integer := 806; -- 列週期數
V_SyncPulse : integer := 6; -- 列同步脈衝(Sync o)
V_BackPorch : integer := 29; -- 顯示後沿(Back porch p)
V_ActivePix : integer := 768; -- 顯示時序段(Display interval q)
V_FrontPorch : integer := 3; -- 顯示前沿(Front porch r)
Vde_start : integer := 35;
Vde_end : integer := 803;
V_unitlength : integer := 1
);
-- 外部信號定義
port(
-- 輸入端口
rst : in std_logic;
fpga_clk : in std_logic;
-- 輸出端口
vga_hs : out std_logic;
vga_vs : out std_logic;
vga_r : out std_logic_vector (4 downto 0);
vga_g : out std_logic_vector (5 downto 0);
vga_b : out std_logic_vector (4 downto 0)
);
end VGA;
architecture behavioral of VGA is
-- 內部信號定義
signal x_cnt : integer range 0 to 1344;
signal y_cnt : integer range 0 to 806;
signal data : integer range 0 to 65535;
signal vga_r_reg : std_logic_vector ( 4 downto 0);
signal vga_g_reg : std_logic_vector ( 5 downto 0);
signal vga_b_reg : std_logic_vector ( 4 downto 0);
signal data_reg : std_logic_vector (15 downto 0);
signal hsync_r : std_logic;
signal vsync_r : std_logic;
signal hsync_de : std_logic;
signal vsync_de : std_logic;
signal vga_clk : std_logic;
-- 器件聲明
component PLL
port(
CLK_IN1 : in std_logic;
CLK_OUT1 : out std_logic;
RESET : in std_logic;
LOCKED : out std_logic
);
end component;
begin
-- 內部時鐘信號
P0: PLL port map (CLK_IN1=>fpga_clk, CLK_OUT1=>vga_clk, RESET=>'0');
-- 水平掃描計數
process(vga_clk)
begin
if rising_edge(vga_clk) then
if rst='0' then
x_cnt <= 1;
elsif x_cnt=LinePeriod then
x_cnt <= 1;
elsif rst='1' then
x_cnt <= x_cnt+H_unitlength;
end if;
end if;
end process;
-- 垂直掃描計數
process(vga_clk)
begin
if rising_edge(vga_clk) then
if rst='0' then
y_cnt <= 1;
elsif y_cnt=FramePeriod then
y_cnt <= 1;
elsif x_cnt=LinePeriod then
y_cnt <= y_cnt+V_unitlength;
end if;
end if;
end process;
-- 水平掃描信號
process(vga_clk)
begin
if rising_edge(vga_clk) then
if rst='0' then
hsync_r <= '1';
elsif x_cnt=H_unitlength then
hsync_r <= '0';
elsif x_cnt=H_SyncPulse then
hsync_r <= '1';
end if;
end if ;
end process;
process(vga_clk)
begin
if rising_edge(vga_clk) then
if rst='0' then
hsync_de <= '0';
elsif x_cnt=Hde_start then
hsync_de <= '1';
elsif x_cnt=Hde_end then
hsync_de <= '0';
end if;
end if ;
end process;
-- 垂直掃描信號
process(vga_clk)
begin
if rising_edge(vga_clk) then
if rst='0' then
vsync_r <= '1';
elsif y_cnt=V_unitlength then
vsync_r <= '0';
elsif y_cnt=V_SyncPulse then
vsync_r <= '1';
end if;
end if ;
end process;
process(vga_clk)
begin
if rising_edge(vga_clk) then
if rst='0' then
vsync_de <= '0';
elsif y_cnt=Vde_start then
vsync_de <= '1';
elsif y_cnt=Vde_end then
vsync_de <= '0';
end if;
end if ;
end process;
-- 外部連接
with (hsync_de and vsync_de) select
vga_r <= vga_r_reg when '1',
"00000" when '0';
with (hsync_de and vsync_de) select
vga_g <= vga_g_reg when '1',
"000000" when '0';
with (hsync_de and vsync_de) select
vga_b <= vga_b_reg when '1',
"00000" when '0';
vga_hs <= hsync_r;
vga_vs <= vsync_r;
-- 彩條圖像
process(vga_clk)
begin
if falling_edge(vga_clk) then
if x_cnt=300 then
data <= 16#f800#;
elsif x_cnt=420 then
data <= 16#07e0#;
elsif x_cnt=540 then
data <= 16#001f#;
elsif x_cnt=660 then
data <= 16#f81f#;
elsif x_cnt=780 then
data <= 16#ffe0#;
elsif x_cnt=900 then
data <= 16#07ff#;
elsif x_cnt=1020 then
data <= 16#ffff#;
elsif x_cnt=1140 then
data <= 16#fc00#;
elsif x_cnt=1260 then
data <= 16#0000#;
end if;
end if;
end process;
-- 圖像顯示
data_reg <= conv_std_logic_vector(data, 16);
process(vga_clk, rst)
begin
if rst='0' then
vga_r_reg <= "00000";
vga_g_reg <= "000000";
vga_b_reg <= "00000";
elsif falling_edge(vga_clk) then
vga_r_reg <= data_reg(15 downto 11);
vga_g_reg <= data_reg(10 downto 5);
vga_b_reg <= data_reg(4 downto 0);
end if;
end process;
end behavioral;
仿真結果如下:
圖4 仿真結果 |
上面那個好像也看不出來啥,下面是上機驗證的結果:
圖5 上機結果 |
6. 注意事項
上示代碼中,pll的rst端直接被我拉低了,我使用的實驗板型號是Xilinx Spartan6的XC6SLX9,不同型號器件可能存在差異,請詳細閱讀IP核的數據手冊。
另注意,原代碼中pll的rst端被我接了其他輸入信號,在ISE中調用pll時存在被鎖住的情況,查其原因是:在給pll輸入時鐘時,rst端的輸入信號存在"U"的時序,之後pll將會被一直鎖住。消除該段時序後,pll正常工作。