文章目录
本次实验所需工具:
链接: https://pan.baidu.com/s/1VYLHUd4uXtw_9jGiLIKaig?pwd=u1d5
提取码:u1d5
一、VGA协议
- VGA(Video Graphics Array)视频图形阵列是 IBM 于1987年提出的一个使用模拟信号的电脑显示标准。VGA具有分辨率高、显示速率快、颜色丰富等优点。VGA 接口不但是CRT 显示设备的标准接口,同样也是 LCD 液晶显示设备的标准接口,具有广泛的应用范围。
- 使用原理:显示器扫描方式分为逐行扫描和隔行扫描:逐行扫描是扫描从屏幕左上角一点开始,从左向右逐点扫描,每扫描完一行,电子束回到屏幕的左边下一行的起始位置,在这期间,每行结束时,用行同步信号进行同步;当扫描完所有的行,形成一帧,用场同步信号进行场同步,并使扫描回到屏幕左上方,开始下一帧。隔行扫描是指电子束扫描时每隔一行扫一线,完成一屏后在返回来扫描剩下的线,隔行扫描的显示器闪烁的厉害,会让使用者的眼睛疲劳。在此我们选择逐行扫描的方式。
二、实现
1、创建项目
首先,利用Quartus Ⅱ创建相关项目,具体可参照博主以前的博客。
2、时钟分频
按照下图所示在IP核中搜索PLL,并进行时钟分频
基础时钟选择50M:
取消勾选输出
c0默认输出50M即可,c1分频到25M,如需其他时钟频率可以自己进行设置
勾选如下选项后finish
3、提取字模
在字模提取工具里面输入需要显示的字符并设置字符大小为64*64
点击左上角文件->另存为BMP文件
再点击文件-打开,把保存的BMP图片打开得到整体的字符
再点击选项按如下参数设置
最后点击生成字模并保存字模为文本文件
4、图片显示
显示图片前需要使用工具把图片转为HEX文件,同时图片分辨率不能太高,否则会超出FPGA储存上限
转换工具设置如下(该工具只可转换24位图片):
由于图片数据太多因此需要使用ROM来存储数据
操作如下:
打开Quartus,在左侧工具栏搜索ROM
点击后创建文件名
在弹出的设置界面,选择图片位数,图片内存大小为各位选择图片的分辨率大小,例:10×10的图片,则选择100
取消勾选下列选项
找到刚才生成的图片文件:
最后勾选以下选项
三、代码实现
1、VGA Driver
module vga_dirve (input wire clk, //系统时钟
input wire rst_n, //复位
input wire [ 15:0 ] rgb_data, //16位RGB对应值
output wire vga_clk, //vga时钟 25M
output 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 //蓝基色
);
// 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; // 行周期
localparam V_TOTAL = V_FRONT + V_SYNC + V_BLACK + V_ACT; // 列周期
reg [ 11:0 ] cnt_h ; // 行计数器
reg [ 11:0 ] cnt_v ; // 场计数器
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 ) begin
if ( !rst_n ) begin
cnt_h <= 0;
end
else if ( flag_enable_cnt_h ) begin
if ( flag_clear_cnt_h ) begin
cnt_h <= 0;
end
else begin
cnt_h <= cnt_h + 1;
end
end
else begin
cnt_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 ) begin
if ( !rst_n ) begin
h_sync <= 0;
end
else if ( cnt_h == H_SYNC - 1 ) begin // 同步周期时为1
h_sync <= 1;
end
else if ( flag_clear_cnt_h ) begin // 其余为0
h_sync <= 0;
end
else begin
h_sync <= h_sync;
end
end
// 场计数
always @( posedge vga_clk or negedge rst_n ) begin
if ( !rst_n ) begin
cnt_v <= 0;
end
else if ( flag_enable_cnt_v ) begin
if ( flag_clear_cnt_v ) begin
cnt_v <= 0;
end
else if ( flag_add_cnt_v ) begin
cnt_v <= cnt_v + 1;
end
else begin
cnt_v <= cnt_v;
end
end
else begin
cnt_v <= 0;
end
end
assign 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 ) begin
if ( !rst_n ) begin
v_sync <= 0;
end
else if ( cnt_v == V_SYNC - 1 ) begin
v_sync <= 1;
end
else if ( flag_clear_cnt_v ) begin
v_sync <= 0;
end
else begin
v_sync <= v_sync;
end
end
// 对应有效区域行地址 1-640
always @( posedge vga_clk or negedge rst_n ) begin
if ( !rst_n ) begin
addr_h <= 0;
end
else if ( valid_area ) begin
addr_h <= cnt_h - H_SYNC - H_BLACK + 1;
end
else begin
addr_h <= 0;
end
end
// 对应有效区域列地址 1-480
always @( posedge vga_clk or negedge rst_n ) begin
if ( !rst_n ) begin
addr_v <= 0;
end
else if ( valid_area ) begin
addr_v <= cnt_v -V_SYNC - V_BLACK + 1;
end
else begin
addr_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 ) begin
if ( !rst_n ) begin
rgb <= 16'h0;
end
else if ( valid_area ) begin
rgb <= rgb_data;
end
else begin
rgb <= 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
2、数据显示模块
module data_drive (input wire vga_clk,
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);
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 ];
localparam states_1 = 1; // 彩条
localparam states_2 = 2; // 字符
localparam states_3 = 3; // 图片
parameter height = 78; // 图片高度
parameter width = 128; // 图片宽度
reg [ 1:0 ] states_current ; // 当前状态
reg [ 1:0 ] states_next ; // 下个状态
reg [ 13:0 ] rom_address ; // ROM地址
wire [ 15:0 ] rom_data ; // 图片数据
wire flag_enable_out1 ; // 文字有效区域
wire flag_enable_out2 ; // 图片有效区域
wire flag_clear_rom_address ; // 地址清零
wire flag_begin_h ; // 图片显示行
wire flag_begin_v ; // 图片显示列
//状态转移
always @( posedge vga_clk or negedge rst_n ) begin
if ( !rst_n ) begin
states_current <= states_1;
end
else begin
states_current <= states_next;
end
end
//状态判断
always @( posedge vga_clk or negedge rst_n ) begin
if ( !rst_n ) begin
states_next <= states_1;
end
else if ( key[ 0 ] ) begin
states_next <= states_1;
end
else if ( key[ 1 ] ) begin
states_next <= states_2;
end
else if ( key[ 2 ] ) begin
states_next <= states_3;
end
else begin
states_next <= states_next;
end
end
//状态输出
always @( * ) begin
case ( states_current )
states_1 : begin
if ( addr_h == 0 ) begin
rgb_data = black;
end
else if ( addr_h >0 && addr_h <81 ) begin
rgb_data = red;
end
else if ( addr_h >80 && addr_h <161 ) begin
rgb_data = orange;
end
else if ( addr_h >160 && addr_h <241 ) begin
rgb_data = yellow;
end
else if ( addr_h >240 && addr_h <321 ) begin
rgb_data = green;
end
else if ( addr_h >320 && addr_h <401 ) begin
rgb_data = blue;
end
else if ( addr_h >400 && addr_h <481 ) begin
rgb_data = indigo;
end
else if ( addr_h >480 && addr_h <561 ) begin
rgb_data = purple;
end
else if ( addr_h >560 && addr_h <641 ) begin
rgb_data = white;
end
else begin
rgb_data = black;
end
end
states_2 : begin
if ( flag_enable_out1 ) begin
rgb_data = char_line[ addr_v-208 ][ 532 - addr_h ]? white:black;
end
else begin
rgb_data = black;
end
end
states_3 : begin
if ( flag_enable_out2 ) begin
rgb_data = rom_data;
end
else begin
rgb_data = black;
end
end
default: begin
case ( 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;
endcase
end
endcase
end
assign flag_enable_out1 = states_current == states_2 && addr_h > 148 && addr_h < 533 && addr_v > 208 && addr_v < 273 ;
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_enable_out2 = states_current == states_3 && flag_begin_h && flag_begin_v;
//ROM地址计数器
always @( posedge vga_clk or negedge rst_n ) begin
if ( !rst_n ) begin
rom_address <= 0;
end
else if ( flag_clear_rom_address ) begin //计数满清零
rom_address <= 0;
end
else if ( flag_enable_out2 ) begin //在有效区域内+1
rom_address <= rom_address + 1;
end
else begin //无效区域保持
rom_address <= rom_address;
end
end
assign flag_clear_rom_address = rom_address == height * width - 1;
//初始化显示文字
always@( posedge vga_clk or negedge rst_n ) begin
if ( !rst_n ) begin
char_line[ 0 ] = 384'h000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000;
char_line[ 1 ] = 384'h000000000200000000000000000000000000000000000000000000000380000000000000000000000000000000000000;
char_line[ 2 ] = 384'h0000000003E0000000000000000000000000000000000000000000000780000000000000000000000000700000008000;
char_line[ 3 ] = 384'h00000000070000000000200000000000000078300001C0000000000007000000018038000C001C0000007E3FFFFFF000;
char_line[ 4 ] = 384'h000000000600000001FFFC000FFFFE000000F83FFFFFE000000000040C00100001FFFC000FFFFF000000F03C0001C000;
char_line[ 5 ] = 384'h000002060800380001C078000E001E000001E03C0001C00000000707FFFFFC0001C078200E001C000001E03C0001C000;
char_line[ 6 ] = 384'h0FFFFF87FFFFFE0001C078300E001C000003C03C0001C00007FFFFC780003C0001C078380E001C000003803C0001C000;
char_line[ 7 ] = 384'h02000FC78000380001C0781C0E001C000007803C0001C00000000F078400380001C0781E0E001C000007003C0001C000;
char_line[ 8 ] = 384'h00000F078600380001C0781E0E001C00000E003C0001C00000000F078380380001C0781F0E001C00000E003FFFFFC000;
char_line[ 9 ] = 384'h00000E0781E0380001C0780F0FFFFC00001F003FFFFFC00000001E0780F0380001C0780F0FFFFC00001F803C0701E000;
char_line[ 10 ] = 384'h00001E0780F8380001C0780E0E001C00003F003C0701000001001E078078380001FFF80C0E001C00007F002007000000;
char_line[ 11 ] = 384'h01801C078078780001FFF8000E001C00006F00000700008000C03C078038780001C078000E001C0000CF0000070001C0;
char_line[ 12 ] = 384'h00403C078030780001C078000E001C0001CF0000070003E0006038078000780001C078000E001C00038F7FFFFFFFFFF0;
char_line[ 13 ] = 384'h003038078000700001C078000E001C00070F3FFFFFFFFFF80038780781C0F00001C078000E001C00060F00007F600000;
char_line[ 14 ] = 384'h0018700780FFF00001C078080FFFFC00080F0000F7200000001C7007803FE00001C0781C0FFFFC00100F0001E7300000;
char_line[ 15 ] = 384'h000EF007800FC00001C07FFE0E001C00000F0003C71800000006E0078007800001C07BFF0E001180000F0003870C0000;
char_line[ 16 ] = 384'h0007E0078002000001C0791E0E0001C0000F0007070700000003C0078000008001C0781C0E0003E0000F000E07078000;
char_line[ 17 ] = 384'h0003C007800001C001FFF81C0E3007C0000F001C0703E0000003C00FFFFFFFE001FFF81C0E180F00000F00380701F800;
char_line[ 18 ] = 384'h0003E00FFFFFFFF001C0781C0E0E1C00000F00F007007F000007E007000001E001C0781C0E073000000F01C007003FF0;
char_line[ 19 ] = 384'h0007F002000001C001C0781C0E03E000000F038007001FC0000E7800000001C001C0781C0E01E000000F0E0007000780;
char_line[ 20 ] = 384'h000E7800000001C001C0781C0E00F000000F380187000300001C3C00000101C001C0781C0E007C00000F2001E8000000;
char_line[ 21 ] = 384'h001C3C00000381C003C0781C0E0C3E00000F0001F000000000381E000007C3C00380781C0E181F00000E0001C0000000;
char_line[ 22 ] = 384'h00301E7FFFFFE3C00380781C0E701F8000000001C000200000701E3FFFFFF3C00380781C0EE00F8000000001C0007000;
char_line[ 23 ] = 384'h00601E18000003C00380781C0FC0078000000001C000F80000C00F00000003C00380781C0F800380001FFFFFFFFFFC00;
char_line[ 24 ] = 384'h01C00F00000003C00300781C1F000380000FFFFFFFFFFE0001800F00000003C00300781C0F00010000040001C0000000;
char_line[ 25 ] = 384'h03000F00000003800700781C0600000000000001C000000007000E00000003800700783E0000000000000001C0000000;
char_line[ 26 ] = 384'h0600040000000780060078738000000000000001C00001000C00000000000780060079E1E000000000000001C0000380;
char_line[ 27 ] = 384'h1800000000600F800E007BC07800000000000001C00007C010000000007FDF000C1FFFC03FF003F800000001C0000FE0;
char_line[ 28 ] = 384'h20000000000FFF000C07FB801FFFFFF00FFFFFFFFFFFFFF0000000000003FE001801F18007FFFFC007FFFFFFFFFFFFF8;
char_line[ 29 ] = 384'h000000000000FC001000E000007FFF80030000000000000000000000000078001000C000000000000000000000000000;
char_line[ 30 ] = 384'h000000000000600020000000000000000000000000000000000000000000000000000000000000000000000000000000;
char_line[ 31 ] = 384'h000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000;
end
end
//实例化ROM
rom rom_inst (
.address ( rom_address ),
.clock ( vga_clk ),
.q ( rom_data )
);
endmodule // data_drive
3、按键消抖
module key_debounce(
input wire clk,
input wire rst_n,
input wire key,
output reg flag,// 0抖动, 1抖动结束
output reg key_value//key抖动结束后的值
);
parameter MAX_NUM = 20'd1_000_000;
reg [19:0] delay_cnt;//1_000_000
reg key_reg;//key上一次的值
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
key_reg <= 1;
delay_cnt <= 0;
end
else begin
key_reg <= key;
//当key为1 key 为0 表示按下抖动,开始计时
if(key_reg != key ) begin
delay_cnt <= MAX_NUM ;
end
else begin
if(delay_cnt > 0)
delay_cnt <= delay_cnt -1;
else
delay_cnt <= 0;
end
end
end
//当计时完成,获取key的值
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
flag <= 0;
key_value <= 1;
end
else begin
// 计时完成 处于稳定状态,进行赋值
if(delay_cnt == 1) begin
flag <= 1;
key_value <= key;
end
else begin
flag <= 0;
key_value <= key_value;
end
end
end
endmodule
4、顶层模块
module vga_top (input wire clk,
input wire rst_n,
input wire [ 2:0 ] key,
output wire vga_clk,
output wire h_sync,
output wire v_sync,
output wire [ 4:0 ] rgb_r,
output wire [ 5:0 ] rgb_g,
output wire [ 4:0 ] rgb_b,
output reg [ 3:0 ] led);
reg [ 27:0 ] cnt ;
wire [ 11:0 ] addr_h ;
wire [ 11:0 ] addr_v ;
wire [ 15:0 ] rgb_data ;
wire [ 2:0 ] key_flag ;
wire [ 2:0 ] key_value ;
//vga模块
vga_dirve u_vga_dirve(
.clk ( clk ),
.rst_n ( rst_n ),
.rgb_data ( rgb_data ),
.vga_clk ( vga_clk ),
.h_sync ( h_sync ),
.v_sync ( v_sync ),
.rgb_r ( rgb_r ),
.rgb_g ( rgb_g ),
.rgb_b ( rgb_b ),
.addr_h ( addr_h ),
.addr_v ( addr_v )
);
//数据模块
data_drive u_data_drive(
.vga_clk ( vga_clk ),
.rst_n ( rst_n ),
.addr_h ( addr_h ),
.addr_v ( addr_v ),
.key ( {key_value[ 2 ] && key_flag[ 2 ], key_value[ 1 ] && key_flag[ 1 ], key_value[ 0 ] && key_flag[ 0 ] } ),
.rgb_data ( rgb_data )
);
//按键消抖
key_debounce u_key_debounce0(
.clk ( vga_clk ),
.rst_n ( rst_n ),
.key ( key[ 0 ] ),
.flag ( key_flag[ 0 ] ),
.key_value ( key_value[ 0 ] )
);
key_debounce u_key_debounce1(
.clk ( vga_clk ),
.rst_n ( rst_n ),
.key ( key[ 1 ] ),
.flag ( key_flag[ 1 ] ),
.key_value ( key_value[ 1 ] )
);
key_debounce u_key_debounce2(
.clk ( vga_clk ),
.rst_n ( rst_n ),
.key ( key[ 2 ] ),
.flag ( key_flag[ 2 ] ),
.key_value ( key_value[ 2 ] )
);
// led
always @( posedge clk or negedge rst_n ) begin
if ( !rst_n ) begin
cnt <= 0;
end
else if ( cnt == 50_000_000 - 1 ) begin
cnt <= 0;
end
else begin
cnt <= cnt + 1;
end
end
always @( posedge clk or negedge rst_n ) begin
if ( !rst_n ) begin
led <= 4'b0000;
end
else if ( cnt == 50_000_000 -1 )begin
led <= ~led;
end
else begin
led <= led;
end
end
endmodule // vga_top
5、TCL引脚绑定脚本
package require ::quartus::project
set_location_assignment PIN_E1 -to clk
set_location_assignment PIN_E15 -to rst_n
set_location_assignment PIN_C16 -to h_sync
set_location_assignment PIN_D15 -to v_sync
set_location_assignment PIN_E16 -to key[0]
set_location_assignment PIN_M16 -to key[1]
set_location_assignment PIN_M15 -to key[2]
set_location_assignment PIN_G15 -to led[0]
set_location_assignment PIN_F16 -to led[1]
set_location_assignment PIN_F15 -to led[2]
set_location_assignment PIN_D16 -to led[3]
set_location_assignment PIN_A14 -to rgb_b[4]
set_location_assignment PIN_B14 -to rgb_b[3]
set_location_assignment PIN_A15 -to rgb_b[2]
set_location_assignment PIN_B16 -to rgb_b[1]
set_location_assignment PIN_C15 -to rgb_b[0]
set_location_assignment PIN_A11 -to rgb_g[5]
set_location_assignment PIN_B11 -to rgb_g[4]
set_location_assignment PIN_A12 -to rgb_g[3]
set_location_assignment PIN_B12 -to rgb_g[2]
set_location_assignment PIN_A13 -to rgb_g[1]
set_location_assignment PIN_B13 -to rgb_g[0]
set_location_assignment PIN_C8 -to rgb_r[4]
set_location_assignment PIN_A9 -to rgb_r[3]
set_location_assignment PIN_B9 -to rgb_r[2]
set_location_assignment PIN_A10 -to rgb_r[1]
set_location_assignment PIN_B10 -to rgb_r[0]
四、RTL原理图
五、实现效果
六、总结
本次实验学习了解了VGA协议的基础知识,并在FPGA开发板上实现了VGA协议,为后续FPGA学习以及图像显示打上了坚实的基础。
七、参考资料
https://blog.csdn.net/qq_47281915/article/details/125134764
https://blog.csdn.net/weixin_56102526/article/details/124964347