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

被折叠的 条评论
为什么被折叠?



