在上图中,我们可以看到VGA_HS(HSYNC)信号是一个周期信号,在一个周期内,VGA_HS的低电平时间为96个VGA_CLK信号周期,高电平时间为704个VGA_CLK信号周期。VGA的数据信号在VGA_HS高电平的第49个VGA_CLK信号周期开始有效,一直持续到VGA_HS高电平的第688个VGA_CLK信号周期。
VGA_VS(VSYNC)信号也是一个周期信号,在一个周期内,VGA_VS的低电平时间为2个VGA_HS信号周期,高电平时间为523个VGA_HS信号周期。VGA的数据信号在VGA_VS高电平的第34个VGA_HS信号周期开始有效,一直持续到高电平的第513个VGA_HS信号周期。为了更直观的表达这一时序,我们用下面这个图表示。
/* 时序逻辑,用来给hsync_cnt寄存器赋值 */
always @ (posedge CLK_VGA or negedge RST_N)
begin
if(!RST_N)
hsync_cnt <= 16'b0;
else
hsync_cnt <= hsync_cnt_n;
end
/* 组合逻辑,水平扫描计数器,在启动标志拉高后再计数,每个时钟循环递增 */
always @ (*)
begin
if(hsync_cnt == `HSYNC_D - 16'h1)
hsync_cnt_n = 16'b0;
else
hsync_cnt_n = hsync_cnt + 1'b1;
end
/* 时序逻辑,用来给vsync_cnt寄存器赋值 */
always @ (posedge CLK_VGA or negedge RST_N)
begin
if(!RST_N)
vsync_cnt <= 16'b0;
else
vsync_cnt <= vsync_cnt_n;
end
/* 组合逻辑,垂直扫描计数器,每次水平扫描计数器计满时循环递增 */
always @ (*)
begin
if((vsync_cnt == `VSYNC_R - 16'h1) && (hsync_cnt == `HSYNC_D - 16'h1))
vsync_cnt_n = 16'b0;
else if(hsync_cnt == `HSYNC_D - 16'h1)
vsync_cnt_n = vsync_cnt + 1'b1;
else
vsync_cnt_n = vsync_cnt;
end
/* 时序逻辑,用来给VGA_HSYNC寄存器赋值 */
always @ (posedge CLK_VGA or negedge RST_N)
begin
if(!RST_N)
VGA_HSYNC <= 1'b0;
else
VGA_HSYNC <= VGA_HSYNC_N;
end
/* 组合逻辑,在B C D区间拉高水平扫描信号 */
always @ (*)
begin
if(hsync_cnt == `HSYNC_A - 16'h1)
VGA_HSYNC_N = 1'b1;
else if(hsync_cnt == `HSYNC_D - 16'h1)
VGA_HSYNC_N = 1'b0;
else
VGA_HSYNC_N = VGA_HSYNC;
end
/* 时序逻辑,用来给VGA_VSYNC寄存器赋值 */
always @ (posedge CLK_VGA or negedge RST_N)
begin
if(!RST_N)
VGA_VSYNC <= 1'b0;
else
VGA_VSYNC <= VGA_VSYNC_N;
end
/* 组合逻辑,在P Q R区间拉高垂直扫描信号 */
always @ (*)
begin
if(vsync_cnt == `VSYNC_O - 16'h1 && hsync_cnt == `HSYNC_D - 16'h1)
VGA_VSYNC_N = 1'b1;
else if((vsync_cnt == `VSYNC_R - 16'h1) && (hsync_cnt == `HSYNC_D - 16'h1))
VGA_VSYNC_N = 1'b0;
else
VGA_VSYNC_N = VGA_VSYNC;
end
2 网络图片和VGA显示有何区别
在第一个问题中,我们知道了VGA显示分辨率为640*480,颜色深度为24位真彩色。但是,网络图片一般不会完全符合这两个参数,因此,我们需要借助一个软件工具转换一下。
1、 首先,我们在网上随意找到一副图片。
2、 可以看到这个图片的参数为分辨率为700*718,颜色深度为8位。我们用软件Image2Lcd打开该图片并设置相应参数,可以发现我们得到一张分辨率为174*179,颜色深度为8位的bmp图片。由于我使用的FPGA芯片的片内存储器资源较少,而为了将生成的mif文件顺利导入Rom IP核中,我们需要压缩图片。这里为什么要转换成bmp图片呢,因为bmp格式是非压缩的,数据格式比较简单容易处理,方便我们将这个图片存取FPGA中。
3、我们知道FPGA不能直接读取图片,我们要将图片转换成mif文件,存入FPGA的rom中。因此,这里我们将制作一个包含图片全部有效数据的mif文件。这里,我们要使用另一个常用的工具
matlab,用m语言来实现。代码如下:
clear;
clc;
n=31146;%174*179
mat = imread('tu1.bmp');%读取.bmp文件
mat = double(mat);
fid=fopen('bmp_data.mif','w');%打开待写入的.mif文件
fprintf(fid,'WIDTH=8;
');%写入存储位宽8位
fprintf(fid,'DEPTH=31146;
');%写入存储深度31146
fprintf(fid,'ADDRESS_RADIX=UNS;
');%写入地址类型为无符号整型
fprintf(fid,'DATA_RADIX=HEX;');%写入数据类型为无符号整型
fprintf(fid,'CONTENT BEGIN
');%起始内容
for i=0:n-1
x = mod(i,174)+1; %174为bmp图片的水平分辨率
y = fix(i/174)+1;
k = mat(y,x);
fprintf(fid,' %d:%x;
',i,k);
end
fprintf(fid,'END;
');
fclose(fid);%关闭文件
3 VGA如何显示图片
解决了上述两个问题之后,终于可以显示图片了。
首先,我们调用一个Rom IP核,由于我们显示的bmp图片分辨率为174*179,颜色深度为8位,所以我们设置Rom IP核如下图所示。
如图中所示,我们设置了一个32768*8bit大小的Rom,并且使输出q受时钟控制,加入mif文件。
VGA显示图片的代码也十分简单,我们以VGA显示有效区设置了vga_x和vga_y坐标变量,定义了两个信号用来控制产生Rom表的地址信号,与VGA显示的数据信号。由于bmp图的深度为8bit,所以我们按照332来分配红绿蓝三色数据,并将末尾置1。具体代码如下:
assign vga_x = hsync_cnt - `HSYNC_B;
assign vga_y = vsync_cnt - `VSYNC_P;
Rom Rom_init
(
.clock (CLK_25M ),
.address (bmp_rom_add ),
.q (bmp_rom_data )
);
//组合电路,用于生成图片位置信号
assign bmp_add = (vga_x >= `BMP1_X - 8'h3) && (vga_x < `BMP1_X + `BMP1_W - 8'h3) && (vga_y >= `BMP1_Y) && (vga_y < `BMP1_Y + `BMP1_H);
//组合电路,用于生成图片使能信号
assign bmp_en = (vga_x >= `BMP1_X) && (vga_x < `BMP1_X + `BMP1_W) && (vga_y >= `BMP1_Y) && (vga_y < `BMP1_Y + `BMP1_H);
//时序电路,用来给bmp_rom_add寄存器赋值
always @ (posedge CLK_25M or negedge RST_N)
begin
if(!RST_N)
bmp_rom_add <= 1'h0;
else
bmp_rom_add <= bmp_rom_add_n;
end
//组合电路,用于生成bmp_rom_add
always @ (*)
begin
if((vga_x == `BMP1_X - 8'h3) && (vga_y == `BMP1_Y) && bmp_add)
bmp_rom_add_n = 1'h0;
else if(bmp_add)
bmp_rom_add_n = bmp_rom_add + 1'b1;
else
bmp_rom_add_n = bmp_rom_add;
end
/* 时序电路,用来给VGA_DATA寄存器赋值 */
always @ (posedge CLK_25M or negedge RST_N)
begin
if(!RST_N)
VGA_DATA <= 1'b0;
else
VGA_DATA <= VGA_DATA_N;
end
/* 组合电路,用来生成VGA_DATA */
always @ (*)
begin
if(bmp_en)
VGA_DATA_N = {bmp_rom_data[7:5],5'b11111,bmp_rom_data[4:2],5'b11111,bmp_rom_data[1:0],6'bb111111};
else if(hsync_cnt > `HSYNC_B && hsync_cnt <= `HSYNC_B + 16'd128 && vsync_cnt > `VSYNC_P && vsync_cnt < `VSYNC_Q)
VGA_DATA_N = 24'hFF0000;
else if(hsync_cnt > `HSYNC_B + 16'd128 && hsync_cnt <= `HSYNC_B + 16'd256 && vsync_cnt > `VSYNC_P && vsync_cnt < `VSYNC_Q)
VGA_DATA_N = 24'hFFFF00;
else if(hsync_cnt > `HSYNC_B + 16'd256 && hsync_cnt <= `HSYNC_B + 16'd384 && vsync_cnt > `VSYNC_P && vsync_cnt < `VSYNC_Q)
VGA_DATA_N = 24'h00FF00;
else if(hsync_cnt > `HSYNC_B + 16'd384 && hsync_cnt <= `HSYNC_B + 16'd512 && vsync_cnt > `VSYNC_P && vsync_cnt < `VSYNC_Q)
VGA_DATA_N = 24'h00FFFF;
else if(hsync_cnt > `HSYNC_B + 16'd512 && hsync_cnt <= `HSYNC_B + 16'd640 && vsync_cnt > `VSYNC_P && vsync_cnt < `VSYNC_Q)
VGA_DATA_N = 24'h0000FF;
else
VGA_DATA_N = 24'd0;
end
大家可能会比较疑惑,为什么用来控制产生Rom表的地址信号bmp_add会比控制VGA显示图片的信号bmp_en提前三个时钟。那是因为我们在读取Rom表数据时存在延时,经过我们signaltap采集后发现,原本作为地址70的输出数据FF比地址70慢两个时钟,即bmp_rom_data的输出会比bmp_rom_add延迟两个时钟,而VGA_DATA又比bmp_rom_data延迟1个时钟,因此bmp_add信号需要比bmp_en提前三个时钟。