详细的介绍资料:
【从零开始走进FPGA】 玩转VGA
http://www.cnblogs.com/spartan/archive/2011/08/16/2140546.html
【FPGA实验】基于DE2-115平台的VGA显示_vga接口实验 de2-115-CSDN博客
【FPGA】VGA显示文字、彩条、图片——基于DE2-115-CSDN博客
一.VCG原理
1.1 VCG引脚原理
1.2 VCG显示原理
VGA 显示器显示图像,并不是直接让图像在显示器上显示出来,而是采用扫描的方式,将构成图像的像素点,在行同步信号和场同步信号的同步下,按照从上到下、由左到右的顺序扫描到显示屏上。
详细介绍请参考:【FPGA】VGA显示文字、彩条、图片——基于DE2-115-CSDN博客
二.VCG显示自定义字符
2.1 点阵汉字生成
使用“PCtoLCD2002”生成汉字
2.2生成BMP文件
文件->保存为BMP格式,打开图片得到完整字符
2.3生成txt文件
保存字符,得到
三.VGA显示条纹
3.1实现流程
输出颜色竖条
// 状态输出逻辑,根据不同的状态输出不同的RGB数据
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;
// rgb_data = red;
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
3.2实现效果
四.VGA输出一幅彩色图像
4.1bmp图片转hex文件
使用BMP2Mif软件将bmp格式图片转换为mif文件
转换后的.mif文件:
4.2引入ROM ip核
新建Quartus工程,产生ROM IP核,将生成的mif文件保存在ROM中
双击选择ROM:1-PORT
更改设置,words大小设置要大于图片大小(50x49x24=58800< 65536),然后next
4.3代码实现
在data_drive.v文件里,从ikun_rom取出图片数据。
五.代码
5.1 时钟分频
分别使用640×480 60HZ和800×600 72HZ,对应时钟分别为25M和50M,需要使用PLL进行分频 时钟频率 = 行帧长 × 列帧长 * 刷新率
640 ×480 60HZ对应时钟频率= 800 ×525 × 60 = 25.2M
ip核里面找到ALTPLL
基础时钟选择50M
0默认输出50M即可, c1分频到25M,如需其他时钟频率可以自己进行设置
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]控制B
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 //蓝基色
);
// 定义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 ) 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 <= 1;
end
else if ( cnt_h == H_SYNC - 1 ) begin // 同步周期时为1
h_sync <= 0;
end
else if ( flag_clear_cnt_h ) begin // 其余为0
h_sync <= 1;
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 <= 1;
end
else if ( cnt_v == V_SYNC - 1 ) begin
v_sync <= 0;
end
else if ( flag_clear_cnt_v ) begin
v_sync <= 1;
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
// */
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 ) 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
// 状态输出逻辑,根据不同的状态输出不同的RGB数据
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;
rgb_data = red;
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
//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;
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
//李骏飞的居中显示参数
//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 ) begin
if ( !rst_n ) begin
rom_address <= 0;// 复位时清零ROM地址
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 || states_current != states_3;
// 初始化显示文字的逻辑
always@( posedge vga_clk or negedge rst_n ) begin
if ( !rst_n ) begin
//李菊芳-632109160602 ——32
//32*3+16*13 = 304 304/8 = 38
char_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