FPGA之显示图片
0赞FPGA之显示图片
今天做了一个FPGA显示图片。特将过程记录如下:
FPGA显示图片的原理:将图片内容存在FPGA的内部block rom里面。然后写一个VGA控制模块,将rom的值依次发送,这样显示器就可以显示图片了。
显示的图片为:
这里要用到一个图片提取软件。pic2Mif
作用是将图片转化为mif文件,mif文件是altera的rom初始化文件。
打开图片,选择需要显示的图片,颜色类别选择彩色,因为我们是要显示彩色的,如果是现实黑白的,就选择黑白。彩色属性选择单一mif文件。即生成的文件包含RGB信息。然后点击生成mif文件。选择文件保存目录,并且填入文件名。这里保存的文件名是uestc.mif。
这样,就得到了mif文件。接下来要把这个mif文件转换为coe文件,即xilinx的rom初始化文件。
用excel打开生成的mif文件
使用替换,将得到的RGB数据的前面的无用数据给去掉。在替换分号,将分号替换成逗号,因为coe文件数据是以逗号分隔的。然后保存。
用文本软件打开保存后的mif文件。
使用替换,将引号全部去掉。并去掉数据之前的所有内容。在加入
MEMORY_INITIALIZATION_RADIX=2;
MEMORY_INITIALIZATION_VECTOR=
这里是coe文件的固定格式,2表示数据以二进制表示。
得到coe文件后,剩下就要用ISE来完成剩下的工作了。
ISE新建工程,加入rom的IP核。这里rom的设置为宽度为3,因为是RGB三个分量,每个分量以一位表示。深度是82295.这里的深度是由得到的mif文件决定的。在pic2Mif加入图片后,会显示图片的分辨率,分辨率的乘积就是rom的深度。
图片的分辨率是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中。即可得到效果了
这样,就显示了一副图片。