weiqi7777

FPGA之显示图片

0
阅读(4864)

FPGA之显示图片

今天做了一个FPGA显示图片。特将过程记录如下:

FPGA显示图片的原理:将图片内容存在FPGA的内部block rom里面。然后写一个VGA控制模块,将rom的值依次发送,这样显示器就可以显示图片了。

显示的图片为:

clip_image002

这里要用到一个图片提取软件。pic2Mif

作用是将图片转化为mif文件,mif文件是altera的rom初始化文件。

clip_image004

打开图片,选择需要显示的图片,颜色类别选择彩色,因为我们是要显示彩色的,如果是现实黑白的,就选择黑白。彩色属性选择单一mif文件。即生成的文件包含RGB信息。然后点击生成mif文件。选择文件保存目录,并且填入文件名。这里保存的文件名是uestc.mif。

这样,就得到了mif文件。接下来要把这个mif文件转换为coe文件,即xilinx的rom初始化文件。

用excel打开生成的mif文件

clip_image006

使用替换,将得到的RGB数据的前面的无用数据给去掉。在替换分号,将分号替换成逗号,因为coe文件数据是以逗号分隔的。然后保存。

用文本软件打开保存后的mif文件。

clip_image008

使用替换,将引号全部去掉。并去掉数据之前的所有内容。在加入

MEMORY_INITIALIZATION_RADIX=2;

MEMORY_INITIALIZATION_VECTOR=

这里是coe文件的固定格式,2表示数据以二进制表示。

clip_image010

得到coe文件后,剩下就要用ISE来完成剩下的工作了。

ISE新建工程,加入rom的IP核。这里rom的设置为宽度为3,因为是RGB三个分量,每个分量以一位表示。深度是82295.这里的深度是由得到的mif文件决定的。在pic2Mif加入图片后,会显示图片的分辨率,分辨率的乘积就是rom的深度。

clip_image012

图片的分辨率是545*151。就是82295。

然后在载入coe文件,对rom进行初始化。

接下来就编写代码,实现VGA显示。

代码如下:

module VGA_sig(
clk_40M , //输入40M时钟
Reset_n , //复位信号
hsyncb , //行同步信号
vsyncb , //场同步信号
red_0 ,
red_1 ,
red_2 ,
gree_0 ,
gree_1 ,
gree_2 ,
blue_0,
blue_1
);
input clk_40M ;
input Reset_n ;
output reg hsyncb ;
output reg vsyncb ;
output red_0 ;
output red_1 ;
output red_2 ;
output gree_0 ;
output gree_1 ;
output gree_2 ;
output blue_0;
output blue_1;
reg red ;
reg gree ;
reg blue ;
//定义相关常量,参考VGA标准
//时钟频率40M ;
//行同步时间:128像素 ; 行消隐后沿:88像素 ;行消隐前沿 :40像素 ;
//场同步时间:4行;场消隐后沿:23行 ;场消隐前沿:1行;
parameter H_PIXELS = 800 ; //行显示点数
parameter H_FRONT = 40 ; //行消隐前沿点数
parameter H_BACK = 88 ; //行消隐后沿点数
parameter H_SYNCTIME = 128 ; //行同步点数
parameter H_PERIOD = H_PIXELS + H_FRONT + H_BACK + H_SYNCTIME;
//行周期 1056像素
parameter V_LINES = 600 ; //场显示行数
parameter V_FRONT = 1 ; //场消隐前沿行数
parameter V_BACK = 23 ; //场消隐后沿行数
parameter V_SYNCTIME = 4 ; //场同步行数
parameter V_PERIOD = V_LINES + V_FRONT + V_BACK + V_SYNCTIME;
//场周期 628行
reg [10:0] hcnt ;
reg [9:0] vcnt ;
//产生行计数输出
always@( posedge clk_40M )
begin
if( !Reset_n )
hcnt <= 0 ;
else if( hcnt < H_PERIOD - 1 )
hcnt <= hcnt + 1'b1 ;
else
hcnt <= 0 ;
end
//产生场计数输出
always@(posedge clk_40M )
begin
if( !Reset_n )
vcnt <= 0 ;
else
begin
if( hcnt >= H_PERIOD - 1 && vcnt >= V_PERIOD - 1 )
vcnt <= 0 ;
else if( hcnt >= H_PERIOD - 1 )
vcnt <= vcnt + 1'b1 ;
else
vcnt <= vcnt ;
end
end
//产生行同步信号 ,hsyncb低电平有效
always@( posedge clk_40M )
begin
if( !Reset_n )
hsyncb <= 0 ;
else
begin
if( hcnt < H_SYNCTIME -1 || hcnt == H_PERIOD - 1 )
//行计数值为0-95之间产生行同步信号,低电平有效
hsyncb <= 0 ;
else
hsyncb <= 1 ;
end
end
//产生场同步信号,vsyncb低电平有效
always@( posedge clk_40M )
begin
if( !Reset_n )
vsyncb <= 0 ;
else
begin
if( vcnt <= V_SYNCTIME - 1 )
//场计数值为0-1时产生场同步信号,低电平有效
vsyncb <= 0 ;
else
vsyncb <= 1 ;
end
end
reg enable ;
always@( posedge clk_40M )
begin
if( !Reset_n )
enable <= 0 ;
else
begin
//当行计数或场计数处于控制区时是RGB输出全为零,消隐
if( hcnt >= (H_SYNCTIME + H_BACK) -1 &&
hcnt < (H_SYNCTIME + H_BACK + H_PIXELS ) -1 &&
vcnt >= (V_SYNCTIME + V_BACK) &&
vcnt <= (V_SYNCTIME + V_BACK + V_LINES ) - 1 )
enable <= 1 ;
else
enable <= 0 ;
end
end
reg [10:0] address_x ;
reg [10:0] address_y ;
always@( posedge clk_40M )
begin
if( !Reset_n )
begin
address_x <= 0 ;
address_y <= 0 ;
end
else
begin
if( hcnt >= (H_SYNCTIME + H_BACK - 1 )&&
hcnt < (H_SYNCTIME + H_BACK + H_PIXELS ) -1 &&
vcnt >= (V_SYNCTIME + V_BACK) &&
vcnt <= (V_SYNCTIME + V_BACK + V_LINES ) - 1 )
begin
address_x <= hcnt - ( H_SYNCTIME + H_BACK - 1'b1 ) ;
address_y <= vcnt - ( V_SYNCTIME + V_BACK ) ;
end
else
begin
address_x <= address_x ;
address_y <= address_y ;
end
end
end
reg [16:0] addra;
always@(*) begin
if(address_x >=100 && address_x <= 644 &&
address_y >=100 && address_y <= 250 )
addra = ((address_y-100) << 9) + ((address_y-100) << 5) + (address_y-100) +address_x-100;
else
addra = 0;
end
//synthesis attribute box_type <rom> "rom_ceshi"
wire [2:0] douta ;
pictre_rom pictre_rom_u1 (
.clka(clk_40M), // input clka
.addra(addra), // input [16 : 0] addra
.douta(douta) // output [2 : 0] douta
);
always@(* )
begin
if(enable)
begin
case( douta )
3'b000 :
begin
red <= 0 ;
blue <= 0 ;
gree <= 0 ;
end
3'b001 :
begin
red <= 0 ;
blue <= 1 ;
gree <= 0 ;
end
3'b010 :
begin
red <= 0 ;
blue <= 0 ;
gree <= 1 ;
end
3'b011 :
begin
red <= 0 ;
blue <= 1 ;
gree <= 1 ;
end
3'b100 :
begin
red <= 1 ;
blue <= 0 ;
gree <= 0 ;
end
3'b101 :
begin
red <= 1 ;
blue <= 1 ;
gree <= 0 ;
end
3'b110 :
begin
red <= 1 ;
blue <= 0 ;
gree <= 1 ;
end
3'b111 :
begin
red <= 1 ;
blue <= 1 ;
gree <= 1 ;
end
default :
begin
red <= red ;
blue <= blue ;
gree <= gree ;
end
endcase
end
else
begin
red <= 0 ;
blue <= 0 ;
gree <= 0 ;
end
end
assign red_0 = red;
assign red_1 = red;
assign red_2 = red;
assign gree_0 = gree;
assign gree_1 = gree;
assign gree_2 = gree;
assign blue_0 = blue;
assign blue_1 = blue;
endmodule

VGA的控制这里就不详细说了,就说明实现显示图片的部分。其核心代码为:

reg [10:0] address_x ;
reg [10:0] address_y ;
always@( posedge clk_40M )
begin
if( !Reset_n )
begin
address_x <= 0 ;
address_y <= 0 ;
end
else
begin
if( hcnt >= (H_SYNCTIME + H_BACK - 1 )&&
hcnt < (H_SYNCTIME + H_BACK + H_PIXELS ) -1 &&
vcnt >= (V_SYNCTIME + V_BACK) &&
vcnt <= (V_SYNCTIME + V_BACK + V_LINES ) - 1 )
begin
address_x <= hcnt - ( H_SYNCTIME + H_BACK - 1'b1 ) ;
address_y <= vcnt - ( V_SYNCTIME + V_BACK ) ;
end
else
begin
address_x <= address_x ;
address_y <= address_y ;
end
end
end

这部分代码是得到显示部分的行列坐标。因为VGA显示有一些同步和消隐,这些地方是不显示内容的。其中列范围从0到799,行范围为0到499.

reg [16:0] addra;
always@(*) begin
if(address_x >=100 && address_x <= 644 &&
address_y >=100 && address_y <= 250 )
addra = ((address_y-100) << 9) + ((address_y-100) << 5) + (address_y-100) +address_x-100;
else
addra = 0;
end

得到了显示部分的行列坐标,接下来就要产生给rom的地址了。因为rom的地址是一维的,所以要将二维的行列地址转换为一维的地址。首先得确定图片显示的左上角的位置。这里定位100,100.那么对于坐标100,100,对于的rom的地址就是0。

图片的左上角的地址为100,100。依据这个地址,根据图片的分辨率,就可以得到图片的其他四个角的地址,由此确定需要转换的行列地址值。(有些地方是不显示图片,这部分的地址就不需要转化)也就是if中的部分。确定行列转换的地址后,接下来要转换地址了。因为像素是545*141。所以转化的地址值应该为

(行地址-100)*545+(列地址-100)。这里-100是因为我们显示图片的起始点是100,100.

这里将545变为512+32+1,这样就可以用移位操作来进行乘法计算了。

wire [2:0] douta ;
pictre_rom pictre_rom_u1 (
.clka(clk_40M), // input clka
.addra(addra), // input [16 : 0] addra
.douta(douta) // output [2 : 0] douta
);

这里就是一个rom的例化,将地址输入,得到输出的数据。

always@(* )
begin
if(enable)
begin
case( douta )
3'b000 :    
begin
red <= 0 ;
blue <= 0 ;
gree <= 0 ;
end
3'b001 :
begin
red <= 0 ;
blue <= 1 ;
gree <= 0 ;
end
3'b010 :
begin
red <= 0 ;
blue <= 0 ;
gree <= 1 ;
end
3'b011 :
begin
red <= 0 ;
blue <= 1 ;
gree <= 1 ;
end
3'b100 :
begin
red <= 1 ;
blue <= 0 ;
gree <= 0 ;
end
3'b101 :
begin
red <= 1 ;
blue <= 1 ;
gree <= 0 ;
end
3'b110 :
begin
red <= 1 ;
blue <= 0 ;
gree <= 1 ;
end
3'b111 :
begin
red <= 1 ;
blue <= 1 ;
gree <= 1 ;
end
default :
begin
red <= red ;
blue <= blue ;
gree <= gree ;
end
endcase
end
else
begin
red <= 0 ;
blue <= 0 ;
gree <= 0 ;
end
end

这里是一个译码的作用,对rom的数据进行译码,得到输出RGB的值。对于得到的coe文件的数据,按照红绿蓝的顺序存储的。根据这个顺序译码即可。

以000为例,最高位为红,中间位绿,最后为蓝。为000,那么红绿蓝都是0,即不显示三种颜色。那么VGA上显示的就是黑色。

assign red_0 = red;

assign red_1 = red;

assign red_2 = red;

assign gree_0 = gree;

assign gree_1 = gree;

assign gree_2 = gree;

assign blue_0 = blue;

assign blue_1 = blue;

至于后面的多个RGB,是因为我的开发板VGA是8位数据显示的。这里就简化为3位,所以都每个颜色都赋值为一样的。

对程序进行管脚约束,然后生成bit文件,下到FPGA中。即可得到效果了

clip_image014

这样,就显示了一副图片。