【Altera SOC体验之旅】(一)VGA的图片显示
[复制链接]
本帖最后由 Jackzhang1992 于 2015-4-17 15:14 编辑
(一)VGA的图片显示 一、目标 经过三个月来的对DE1-SOC基本操作的学习,现在开始慢慢进行收尾,结合以前所学的FPGA知识和基础,我准备在DE1-SOC上实现一个具有PS2键盘和VGA显示器进行I/O操作的计算机系统,具体功能是一个俄罗斯方块游戏。分为四个阶段:VGA显示,键盘与字符显示,键盘的移动。本篇实现第一步:VGA的图片显示。VGA是常用的计算机显示器之一,操作时序也不复杂,可是如何实现VGA的图片显示对于没有做过的人来说也完全没有概念,参考《计算机原理与设计—VerilogHDL》一书以及网上资料,我慢慢开始学习VGA显示,并最终实现了VGA的图片显示。
二、VGA时序 VGA接口信号HS(1bit),VS(1 bit), R(8bit), G(8bit), B(1bit)。VGA显示器常用的分辨率为640x480,即操作的时候FPGA想办法向VGA显示器输出640行480列个点位数据(共307200个数据)。那具体是如何操作的呢? 基本操作流程:VS叫场扫描信号或者帧扫描信号,首先VS发一个负脉冲,告诉显示器我要开始发送一副图片了,你准备接收。然后,HS行扫描信号,发一个负脉冲,告诉显示器我要开始发送第一行数据了,准备接受。跟在HS负脉冲信号后,接着就每个一个时钟周期发送一组R,G,B点位的数据,一直发送640组。接下来HS再发一个负脉冲,告诉显示器我要开始发送第二行数据了,准备接收,然后发送第2行的640组R,G,B数据,以此类推,第3行,第4行。一直发送到第480行结束后,所有307200个数据就发送完全了。然后VS信号再发一个负脉冲,发第二幅图片的信息。以此类推。
具体时序数据:下图就对应了具体行扫描和场扫描阶段每个操作具体所需要的周期数。一目了然。注意HS,VS负脉冲后要等待几个周期才正式发送数据这是为了满足VGA的器件要求而定的。下图给了周期数,然后计算VGA的时钟频率。一般VGA显示器的刷新频率为60帧/s,每一秒显示60幅图片,即一帧数据需要1/60s发送。由下图可知,完整发送一帧数据(一副图片)需要800*525=420000个周期。则VGA时钟频率为420000/(1/60)=25.2MHZ。近似等于25MHZ。开发板上的主频时钟为50MHZ,所以只要二分频就OK啦。
三、RTL code `timescale 1ns/1ns module vga_c( input clk, // 系统时钟50MHZ input clrn, //复位信号 input [23:0]datain, //输入R,G,B数据24bit output rdn, //读使能信号,低电平有效。 output [18:0]rd_a, //读地址,read_address={row(9),col(10bit)} output reg[9:0]h_count=10'd0 //列计数器 0-799 output reg[9:0]v_count=10'd0, //行计数器 0-524 output reg vga_clk=1'b0, //vga时钟 25MHZ
//VGA接口信号 output hs, //行扫描信号 output vs, //场扫描信号 output [7:0]r,g,b //R,G,B输出信号 );
//refreshrate=25*10^6/((96+48+640+16)*(480+2+33+10))=59.5=60
//1.VGA时钟生成,vga_clk:25MHZ always @( negedge clrn or posedge clk ) vga_clk<=(!clrn)?1'b0: ~vga_clk;
//2.计数器v_count(0-524) and h_count(0-799) always @( negedge clrn or posedge vga_clk ) if(!clrn) begin v_count<=10'd0;h_count<=10'd0;end else if(v_count==10'd524) v_count<=10'd0; else begin if(h_count==10'd799) begin h_count<=10'd0; v_count<=v_count+1'b1;end else h_count<=h_count+1'b1; end
// 3.锁存输入数据data_in,rdn=0时,外部有存储器有40ns的时间提供datain数据。 regvideo_out=1'b0; reg [23:0]data_reg=24'd0;
always @(negedge clrn or posedge vga_clk) if(!clrn) beginvideo_out<=1'b0;data_reg<=24'd0;end else beginvideo_out<=~rdn; data_reg<=datain;end
//4.接口信号生成 assign hs=(h_count>=96); //HS波形输出 assign vs=(v_count>=2); //VS波形输出 wire [9:0]rol=v_count-10'd35; //计算行地址 wire [9:0]col =h_count-10'd143; //计算列地址 assign rd_a={rol[8:0],col[9:0]}; //行列地址拼接成一个rd_a assign rdn=~(((h_count>=10'd143)&&(h_count<10'd783))&&((v_count>=10'd35)&&(v_count<10'd515))); //只有在有效的数据位rdn才置0 assign r=(video_out)?data_reg[23:16]:8'd0; assign g=(video_out)?data_reg[15:8]:8'd0; assign b=(video_out)?data_reg[7:0]:8'd0;
endmodule
这份代码核心思想是生成两个计数器h_count和v_count,作为绝对参考基准。 然后根据具体h_count和v_count的值生成端口信号(组合逻辑),因此没有太多的时序逻辑。结构简单,功能明确。
四、图片的存储 mif文件生成 首先在网上找一副640x480的彩色图片。 然后打开matlab,file—>importdata,导入数据。
接着运行如下代码,生成mif文件。为了存储不超过FPGA片内存储器极限,matlab代码中R,G,B的8位数据只取了其中高3位,使每个点位的r, gb数据总共占9位。即每幅图片占用640*480*9=2764800,小于FPGA内部最大的memorybit数目。
fh=fopen('jackVGA.mif','w+'); %打开建立文件 fprintf(fh,'\t WIDTH=9;\r\n'); fprintf(fh,'\t DEPTH=307200;\r\n\n'); fprintf(fh,'\tADDRESS_RADIX=UNS;\r\n'); fprintf(fh,'\tDATA_RADIX=UNS;\r\n\n'); fprintf(fh,'\t CONTENT BEGIN \r\n'); for i=1:480 for j=1:640 jackfilter(i,j,1)=bitand(jack(i,j,1),240); %(i,j)位数据的R通道数据&0xf0,取 jackfilter(i,j,2)=bitand(jack(i,j,2),240); jackfilter(i,j,3)=bitand(jack(i,j,3),240); end end
for i=1:480 for j=1:640
tempR=bitshift(uint32(jack(i,j,1)),-5); %(i,j)数据的R通道右移5位 tempG=bitshift(uint32(jack(i,j,2)),-5);%(i,j)数据的G通道右移5位 tempB=bitshift(uint32(jack(i,j,3)),-5);%(i,j)数据的B通道右移5位 tempR=bitshift(tempR,6); %R通道左移6位 tempG=bitshift(tempG,3);%G通道左移3位 data=bitor(bitor(tempR,tempG),tempB);%将三个3位数据拼接成一个9位数据
fprintf(fh,'\t %d :%d;\r\n',(i-1)*640+j-1,data); %打印数据,换行 end end fprintf(fh,'\t END; \r\n'); %打印结束符号END,换行
fclose(fh); %关闭
最后在verilog顶层模块建立一个ROM,相应端口信号链接起来即可。
//======================================================= // 顶层模块连线 //=======================================================
wire[8:0]q; rom2 rom_inst ( .address( address ), .clock( CLOCK_50 ), .rden( ~rdn ), .q ( q ) );
assign VGA_BLANK_N=~rdn; wire[23:0]datain={q[8:6],5'd0,q[5:3],5'd0,q[2:0],5'd0}; wire[18:0]rd_a; wire[18:0]address=(rd_a[18:10]*640)+rd_a[9:0]; wire rdn; wire vga_clk; assign VGA_CLK=vga_clk; vga_c u0( .clk(CLOCK_50),//50MHZ .clrn(KEY[0]), .datain(datain), .h_count(), .v_count(), .vga_clk(vga_clk), .rdn(rdn), //read enable"0"active .rd_a(rd_a),//read_address={row(9),col(10bit)} .hs(VGA_HS), .vs(VGA_VS), .r(VGA_R), .g(VGA_G), .b(VGA_B));
五、效果
|