VGA协议实现图片弹跳显示

本文介绍了VGA视频图形阵列协议的基本原理,包括RGB模拟信号和行场同步信号的细节。通过MATLAB生成初始化文件,实现图片数据转换,然后在FPGA中编写VGA控制模块和数据生成模块,展示了如何在VGA显示器上进行图像显示,包括行场计数、同步信号产生和图像数据处理。最后,给出了VGA显示的结果示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

VGA协议

在这里插入图片描述
  VGA(Video Graphics Array)视频图形阵列是IBM于1987年提出的一个使用模拟信号的电脑显示标准,VGA协议具有广泛兼容性,而且无需显卡驱动程序,同时VGA接口的插拔式设计也使它易于安装和更换。但其只能支持较低的分辨率(比如640×480),若要支持高分辨率,则需要消耗大量的计算机资源,同时不能传输声音、不支持热插拔技术。VGA接口传输红、绿、蓝三基色的模拟信号以及行场同步信号来控制和实现在显示屏上的显示,通过三基色的不同比例调和可以组成希望显示的色彩。虽然现在使用VGA协议的显示器的数量越来越少了,但是作为显示协议的基础,对它的学习和掌握都是十分必要的。

主要信号

  VGA的控制信号简单来说只需要关心RGB信号和行场同步信号。

RGB信号

  VGA传输的色彩是用RGB的模拟信号,但一般我们的计算机、FPGA输出的都是数字图像信号,这就需要把数字图像信号转换为VGA的模拟图像信号传输到VGA的接口上,一般来说有两种解决方案,一种是使用专门的图像数模转换芯片,如AD7123等;另一种是采用权电阻网络的形式,通过一定位宽的数字信号和不同大小的上拉电阻将VGA的R\G\B端口的电压控制在与数字信号相对应,常见的有RGB332、RGB565、RGB888等形式,数字代表该颜色的数字信号的位宽。下图左边是RGB332网络,右边是RGB565网络结构。
在这里插入图片描述

在这里插入图片描述

行场同步信号:

在这里插入图片描述
  乍看一下这个时序图可能有些糊涂,这张图规定了VGA的控制信号行同步信号HSYC和场同步信号VSYC的时序,这里是为了表示方便,实际上HSYC和VSYC并不是两个同步信号,HSYC的单位是像素、VSYC的单位是像素行,也就是说在行的同步(Sync)阶段,把HSYC信号拉高H_Sync个时钟周期,其他时刻拉低。
  而对于场同步信号,需要在场的同步阶段(Sync)阶段,把VSYC信号拉高V_Sync个像素行,其他时刻拉低。
  同时只有当每一行的像素行范围在“Addressable”Vedio范围内的像素,以及对应的行数在“Addressable”Vedio时,输出的RGB的模拟信号是有效的。在不同的显示模式下,上图的参数都是不同的,常见的显示的参数如下图所示:
在这里插入图片描述

VGA协议实现图片弹跳显示

生成ROM的初始化文件

  首先,需要根据xilinx的ROM的初始化文件要求,用MATLAB生成一个名为image.coe的初始化文件,这里是把野火的的mif初始化文件的代码作了一些修改,添加了写入图片的读取显示,防止写入的文件错误:

clear               %清理命令行窗口
clc                 %清理工作区

% 使用imread函数读取图片,并转化为三维矩阵
image_array = imread('image.bmp');

% 使用size函数计算图片矩阵三个维度的大小
% 第一维为图片的高度,第二维为图片的宽度,第三维为图片维度
[height,width,z]=size(image_array);   % 200*200*3
red   = image_array(:,:,1); % 提取红色分量,数据类型为uint8
green = image_array(:,:,2); % 提取绿色分量,数据类型为uint8
blue  = image_array(:,:,3); % 提取蓝色分量,数据类型为uint8

%显示原图和各个分量图
subplot(231);imshow(image_array	);title('RGB Image');
subplot(232);imshow(red			);title('R   Image');
subplot(233);imshow(green		);title('G   Image');
subplot(234);imshow(blue		);title('B   Image');


% 使用reshape函数将各个分量重组成一个一维矩阵
%为了避免溢出,将uint8类型的数据扩大为uint32类型
r = uint32(reshape(red'   , 1 ,height*width));
g = uint32(reshape(green' , 1 ,height*width));
b = uint32(reshape(blue'  , 1 ,height*width));

% 初始化要写入.coe文件中的RGB颜色矩阵
rgb=zeros(1,height*width);

% 导入的图片为24bit真彩色图片,每个像素占用24bit,RGB888
% 将RGB888转换为RGB332
% 红色分量右移5位取出高3位,左移5位作为ROM中RGB数据的第8bit到第6bit
% 绿色分量右移5位取出高3位,左移2位作为ROM中RGB数据的第5bit到第2bit
% 蓝色分量右移6位取出高2位,左移0位作为ROM中RGB数据的第1bit到第0bit
for i = 1:height*width
    rgb(i) = bitshift(bitshift(r(i),-5),5)+ bitshift(bitshift(g(i),-5),2)+ bitshift(bitshift(b(i),-6),0);
end

%读取即将存入的数据
for i = 1:height*width
	r_load(i) = bitshift(bitshift(r(i),-5),5);
	g_load(i) = bitshift(bitshift(g(i),-5),6);
	b_load(i) = bitshift(bitshift(b(i),-6),6);
end
r_color = reshape(transpose(r_load)   ,height,width);
g_color = reshape(transpose(g_load)   ,height,width);
b_color = reshape(transpose(b_load)   ,height,width);
load_array = zeros(height,width,3);

load_array(:,:,1) = r_color;
load_array(:,:,2) = g_color;
load_array(:,:,3) = b_color;

subplot(235);imshow(image_array		);title('load Image');

fid = fopen( 'image.coe', 'w+' );

% .coe文件字符串打印
fprintf( fid, 'MEMORY_INITIALIZATION_RADIX=16;\n');
fprintf( fid, 'MEMORY_INITIALIZATION_VECTOR=%h;\n\n',height*width);

% 写入图片数据
% \t:水平制表符,表示为--i-1--rgb(i-1)
%for i = 1:height*width
%    fprintf(fid,'\t\t%d\t:%x\t;\n',i-1,rgb(i));
%end
for i = 1:height*width
	if(i ~= height*width)
		fprintf(fid,'%02x,\n',rgb(i));
	else
		fprintf(fid,'%02x',rgb(i));
    end
end

% 打印结束字符串
fprintf(fid,';');

fclose( fid ); % 关闭文件指针

初始化写入的图片如下:
在这里插入图片描述

VGA控制模块代码

  因为在数据生成模块中慢一拍和从ROM中读取数据慢一拍,所以输出的显示坐标信号是提前两拍的信号:

// =============================================================================
// ---名称    : vga_ctrl.v
// ---作者    : 彼方云城
// ---日期    : 2023-05-01
// ---功能    : 实现在VGA显示器上的显示控制,640*480@60hz
// =============================================================================
module vga_ctrl(
    input   wire            pixel_clk   ,//像素时钟25Mhz
    input   wire            rst_n       ,//复位信号,低有效
    input	wire	[7:0]	rgb_data 	,//输入的图像数据
//请求图像数据
	output	wire	[9:0]	pix_x		,//请求的横坐标
	output	wire	[9:0]	pix_y		,//请求的纵坐标
	output	reg   			frame_end	,//一帧数据结束的脉冲信号
//控制VGA显示
    output  wire            hsync       ,//行同步信号
    output  wire            vsync       ,//场同步信号
    output  wire    [7:0]   rgb          //输出图像数据
    );

//========================< 参数 >==========================================
//----------------640*480@60hz---------------------------------------------
//行参数
parameter   H_TOTAL = 800;
parameter   H_SYNC  = 96;
parameter   H_BACK  = 40;
parameter   H_LEFT  = 8;
parameter   H_ADDR  = 640;
parameter   H_RIGHT = 8;
parameter   H_FRONT = 8;
//场参数
parameter   V_TOTAL = 525;
parameter   V_SYNC  = 2;
parameter   V_BACK  = 25;
parameter   V_TOP   = 8;
parameter   V_ADDR  = 480;
parameter   V_BOTTOM= 8;
parameter   V_FRONT = 2;

//========================< 信号 >==========================================
//行场计数器
reg     [9:0]   cnt_h;//行计数器
reg     [9:0]   cnt_v;//场计数器
//图像数据有效信号
wire			rgb_valid;//图像数据有效信号
wire			rgb_valid_early;//图像数据有效信号提前一拍

//==========================================================================
//==    行场计数器
//==========================================================================
always @(posedge pixel_clk or negedge rst_n) begin
    if (!rst_n) begin
        cnt_h <= 'd0;
    end
    else if (cnt_h == H_TOTAL - 1'b1) begin
        cnt_h <= 'd0;
    end
    else begin
        cnt_h <= cnt_h + 1'b1;
    end
end

always @(posedge pixel_clk or negedge rst_n) begin
    if (!rst_n) begin
        cnt_v <= 'd0;
    end
    else if(cnt_h == H_TOTAL - 1'b1 && cnt_v == V_TOTAL - 1'b1) begin
        cnt_v <= 'd0;
    end
    else if (cnt_h == H_TOTAL - 1'b1 ) begin
        cnt_v <= cnt_v + 1'b1;
    end
end

//==========================================================================
//==  控制VGA显示的行场控制及图像数据信号输出  
//==========================================================================

assign hsync = (cnt_h < H_SYNC)?1'b1:1'b0;
assign vsync = (cnt_v < V_SYNC)?1'b1:1'b0;
assign rgb = rgb_valid?rgb_data:8'd0;

//此信号与坐标信号对齐
assign  rgb_valid =   (cnt_h >= H_SYNC + H_BACK + H_LEFT)
                    && (cnt_h < H_SYNC + H_BACK + H_LEFT + H_ADDR)
                    && (cnt_v >= V_SYNC + V_BACK + V_TOP)
                    && (cnt_v < V_SYNC + V_BACK + V_TOP + V_ADDR);

//==========================================================================
//==    图像坐标信号的生成,以及一帧结束的脉冲信号(便于数据生成端以帧为单位操作数据)
//==    由于本身的延迟和从ROM读数据的延迟,所以提前两拍输出
//==========================================================================
//提前2拍的rgb_req信号,用于抵消数据生成模块的影响
assign rgb_valid_early =   (cnt_h >= H_SYNC + H_BACK + H_LEFT - 2'd2)
                    && (cnt_h < H_SYNC + H_BACK + H_LEFT + H_ADDR - 2'd2) 
                    && (cnt_v >= V_SYNC + V_BACK + V_TOP)
                    && (cnt_v < V_SYNC + V_BACK + V_TOP + V_ADDR);

//生成提前2拍的坐标信号(x坐标需要针对提前的信号修改计数(-1),y坐标不需要)
assign pix_x = rgb_valid_early ? cnt_h-(H_SYNC + H_BACK + H_LEFT - 1'b1) : 10'd0;
assign pix_y = rgb_valid_early ? cnt_v-(V_SYNC + V_BACK + V_TOP) : 10'd0;

//帧结束脉冲,同样提前2拍
always @(posedge pixel_clk or negedge rst_n) begin
	if (rst_n == 1'b0) begin
		frame_end <= 1'b0;
	end
	else if (cnt_h == H_TOTAL-2'd3 && cnt_v == V_TOTAL-1'b1) begin
		frame_end <= 1'b1;
	end
	else begin
		frame_end <= 1'b0;
	end
end

endmodule

数据生成模块:

// =============================================================================
// ---名称	: gen_data.v
// ---作者	: 彼方云城
// ---日期	: 2023-05-04
// ---功能	: 从ROM中读取数据生成RGB332的图像数据,用于VGA弹跳显示
// =============================================================================
module gen_data(
	input	wire			pixel_clk	,//像素时钟
	input	wire			rst_n		,//系统复位,低有效
	input	wire	[9:0]	pix_x		,//像素横坐标x
	input	wire	[9:0]	pix_y		,//像素纵坐标y
	input	wire			frame_end	,//控制端帧结束脉冲
	output	reg 	[7:0]	rgb_data	//图像数据
	);

//========================< 参数 >==========================================
//--------显示区域参数----------------
parameter	SHOW_LENGTH = 640;
parameter	SHOW_WIDTH  = 480;

//---------色彩参数-------------------
parameter   RED     = 8'b111_000_00;
parameter   GREEN   = 8'b000_111_00;
parameter   BLUE    = 8'b000_000_11;
parameter   BLACK   = 8'b000_000_00;
parameter	WHITE   = 8'b111_111_11;

//---------移动显示块参数-------------
parameter	BLOCK_LEN = 200;//图像块边长
parameter 	LEFT = 1;//向左移动
parameter 	RIRHT = 0;//向右移动
parameter	UP = 1;//向上移动
parameter	DOWN = 0;//向下移动

//========================< 信号 >==========================================
//-----背景区域显示标志----------------
wire            red_area;//显示红色
wire			green_area;//显示绿色
wire			blue_area;//显示蓝色
//-----ROM数据------------------------
wire	[7:0]	pic_data;//ROM中读取的图像数据
wire	[15:0]	rom_addr;//ROM的读取地址
//-----移动图像框信号------------------
wire			pic_block;//显示图像块
wire	[7:0]	pic_x;//要显示的图像的横坐标,最大为(图片长度-1)
wire 	[7:0]	pic_y;//要显示的图像的纵坐标,最大为(图片宽度-1)
(* KEEP="TRUE" *)reg		[8:0]	shift_x;//白色方块在x方向上偏移量
(* KEEP="TRUE" *)reg		[8:0]	shift_y;//白色方块在y方向上偏移量
reg				shift_direction_x;//白色方块在x方向上移动方向,0-->右移;1-->左移
reg				shift_direction_y;//白色方块在y方向上移动方向,0-->下移;1-->上移

//==========================================================================
//==    背景图像数据生成,形成以下图案
//==    红==绿==蓝
//==    蓝==红==绿
//==========================================================================
assign red_area = (pix_x < SHOW_LENGTH/3 && pix_y < SHOW_WIDTH/2)
                    || (pix_x >= SHOW_LENGTH/3 && pix_x < ((SHOW_LENGTH/3)<<1) && pix_y >= SHOW_WIDTH/2);

assign green_area = (pix_x >= SHOW_LENGTH/3 && pix_x < ((SHOW_LENGTH/3)<<1) && pix_y < SHOW_WIDTH/2
                    || pix_x >= ((SHOW_LENGTH/3)<<1) && pix_y >= SHOW_WIDTH/2);

assign blue_area = (pix_x >= ((SHOW_LENGTH/3)<<1) && pix_y < SHOW_WIDTH/2
                    || pix_x < SHOW_LENGTH/3 && pix_y >= SHOW_WIDTH/2);

//==========================================================================
//==    从ROM中读取图像数据(200*200*8bit)
//==========================================================================
assign pic_x = pic_block ? (pix_x - shift_x) : 'd0;
assign pic_y = pic_block ? (pix_y - shift_y) : 'd0;

assign rom_addr = pic_block ? pic_x + pic_y * BLOCK_LEN : 'd0;

//==========================================================================
//==    移动的图像框生成,开始向右下方移动,到达显示边缘时朝反方向移动
//==========================================================================

//----------移动方向---------------
always @(posedge pixel_clk or negedge rst_n) begin
	if (rst_n == 1'b0) begin
		shift_direction_x <= RIRHT;
	end
	else if (shift_x == SHOW_LENGTH-BLOCK_LEN && shift_direction_x == RIRHT) begin
		shift_direction_x <= LEFT;
	end
	else if(shift_x == 'd0 && shift_direction_x == LEFT) begin
		shift_direction_x <= RIRHT;
	end
end

always @(posedge pixel_clk or negedge rst_n) begin
	if (rst_n == 1'b0) begin
		shift_direction_y <= DOWN;
	end
	else if (shift_y == SHOW_WIDTH-BLOCK_LEN && shift_direction_y == DOWN) begin
		shift_direction_y <= UP;
	end
	else if(shift_y == 'd0 && shift_direction_y == UP) begin
		shift_direction_y <= DOWN;
	end
end

//-----------偏移量---------------
always @(posedge pixel_clk or negedge rst_n) begin
	if (rst_n == 1'b0) begin
		shift_x <= 9'd0;
	end
	else if (shift_direction_x == RIRHT && frame_end == 1'b1) begin
		shift_x <= shift_x + 1'b1;
	end
	else if(shift_direction_x == LEFT && frame_end == 1'b1) begin
		shift_x <= shift_x - 1'b1;
	end
end

always @(posedge pixel_clk or negedge rst_n) begin
	if (rst_n == 1'b0) begin
		shift_y <= 9'd0;
	end
	else if (shift_direction_y == DOWN && frame_end == 1'b1) begin
		shift_y <= shift_y + 1'b1;
	end
	else if(shift_direction_y == UP && frame_end == 1'b1) begin
		shift_y <= shift_y - 1'b1;
	end
end

assign pic_block = (pix_x >= shift_x && pix_x < shift_x + BLOCK_LEN - 1'b1)
						&& (pix_y >= shift_y && pix_y < shift_y + BLOCK_LEN -1'b1);

//test
//assign pic_block = pix_x < BLOCK_LEN && pix_y < BLOCK_LEN;

//==========================================================================
//==    总体图像的显示
//==========================================================================
always@(posedge pixel_clk or negedge rst_n)begin
    if(rst_n == 1'b0)begin
    	rgb_data <= BLACK;
    end
    else if(pic_block) begin
    	rgb_data <= pic_data;
    end
    else if(red_area)begin
        rgb_data <= RED;
    end
    else if (green_area) begin
        rgb_data <= GREEN;
    end
    else if(blue_area) begin
        rgb_data <= BLUE;
    end
    else begin
        rgb_data <= BLACK;
    end
end

//==========================================================================
//==    实例化
//==========================================================================
ROM ROM_inst (
  .clka(pixel_clk), // input clka
  .addra(rom_addr), // input [15 : 0] addra
  .douta(pic_data) // output [7 : 0] douta
);

endmodule

  顶层模块以及PLL、ROM的ip核调用比较简单,这里就不再赘述了,最终的现象如下:

最终结果

总结

  通过上述代码已经实现了固定的图片的VGA显示,后续可以结合视频流数据、实现相机图像的采集和显示等,敬请期待!

参考:
[1] 咸鱼FPGA.VGA协议 cnblogs
[2] V3学院FPGA教程
[3] 刘火良、杨森、张硕. FPGA开发实战指南[M],机械工业出版社, 2021

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值