9. 基于rom的vga图像显示¶
在前面两个章节中,我们详细讲解了VGA的相关理论知识,通过“VGA彩条显示”和“VGA字符显示”两个实验工程分别实现了VGA等宽十色彩条显示和字符显示。但不论是彩条显示和字符显示,图像的色彩信息都是通过代码生成,那如果想要显示一幅已存在的图片,应该怎么做呢?
在本章节,我们就实现这一想法,在VGA显示器上实现已存在图片的显示,具体实现方法详见下文。
9.1. 理论学习¶
详见“VGA显示器驱动设计与验证”章节的“理论学习”小结。
9.2. 实战演练¶
9.2.1. 实验目标¶
实验目标:以十色等宽彩条做背景,将存储于ROM中的图片显示在VGA显示器的中心位置。图片分辨率为100*100,VGA显示模式为640x480@60。
实验效果,具体见图 38‑1。
图 38‑1 VGA彩条实验效果图
9.2.2. 硬件资源¶
详见“VGA显示器驱动设计与验证”章节的“硬件资源”介绍。
9.2.3. 程序设计¶
由实验目标可知,本实验想要将事先缓存在ROM中的图片显示在VGA显示器的中心位置,显示图片大小为100*100,VGA显示模式为640x480@60。实验的具体实现方法,我们会在本小节详细讲解。
本实验工程是在“VGA显示器驱动设计与验证”章节的实验工程基础上修改得到,功能模块中只修改了图像数据生成模块vga_pic。
在本小结的讲解中,我们先来说一下图片在存入ROM之前需要进行的预处理操作,再对工程整体架构进行说明,然后讲解一下改动较大的vga_pic模块,其他未改动或改动较小的模块,不再讲解。
9.2.3.1. 图片预处理¶
我们先来说一下图片在存入ROM之前需要进行的预处理操作。在本次实验中,我们用来存储图片的ROM是通过调用IP核,利用FPGA片上资源生成的只读存储器,但FPGA片上资源有限,生成ROM的存储空间也会受限,存储于ROM的图片大小也受到限制。
FPGA开发板上使用的芯片为Altera公司的EP4CE10F17C8芯片,存储空间为414Kbit;本次实验显示图片分辨率为100*100,色彩格式为RGB565,存储于ROM所占空间为100*100*16=160000bit=156.25Kbit。通过比较发现,芯片可生成满足要求的ROM 用来存储图片。
ROM作为只读存储器,在进行IP核设置时需要指定初始化文件, 即写入存储器中的图片数据,图片要以规定的格式才能正确写入ROM,这种格式就是MIF文件。MIF是Quartus规定的一种文件格式,文件格式示意图,具体见图 38‑2。
图 38‑2 MIF文件格式示意图
若存储数据量较小,我们可以参照上图,手动写入数据。但如果存储量较大,手动输入不太现实。本次实验中要写入100*100个数据,不可能使用手动输入,在这里我们使用Matlab软件将图片转化为MIF文件,具体步骤如下。
1、调整图片大小
考虑到ROM存储空间大小的问题,本次实验显示图片大小为100*100,但并不一定读者想要显示的图片恰好满足这一要求。若图片大小不满足要求,我们可以使用系统自带的画图软件进行调整。
以本次要显示的图片为例,进行讲解。
首先查看图片属性,检验图片大小是否满足要求,具体见图 38‑3。
图 38‑3 图片属性
由图可知,图片分辨率为103*103,不满足要求,需要进行大小调整;若图片大小满足要求,跳过此步骤。图片中的位深度位24,表示图片像素点数据位宽。
然后,右键选中图片,使用系统自带的画图工具打开图片,点击“重新调整大小”,在“调整大小和扭曲”的窗口中,去掉“保持纵横比”勾选,以“像素”为依据调整图片大小为实验要求的100*100,点击“确定”,保存图片并退出,具体见图 38‑4、图 38‑5。
图 38‑4 使用画图打开图片
图 38‑5 重新调整图片大小
2、使用Matlab软件将图片转化为MIF文件
图片大小调整完毕后,使用使用Matlab软件将图片转化为MIF文件。
首先,在工程目录下新建一个matlab文件夹,将调整好大小的图片复制到文件夹下。打开Matlab软件,新建一个*.m的脚本文件,保存到matlab文件夹中,文件名自定义,具体见图 38‑6。
图 38‑6 创建新的脚本文件
然后,在新建的脚本文件中写入以下代码,参考代码具体见代码清单 38‑1。代码中注释较为详细,理解较为容易,不再逐句讲解。
代码清单 38‑1 MIF生成代码(mif_gen.m)
1 clear %清理命令行窗口
2 clc %清理工作区
3
4 % 使用imread函数读取图片,并转化为三维矩阵
5 image_array = imread(‘logo.bmp’);
6
7 % 使用size函数计算图片矩阵三个维度的大小
8 % 第一维为图片的高度,第二维为图片的宽度,第三维为图片维度
9 [height,width,z]=size(image_array); % 100*100*3
10 red = image_array(:,:,1); % 提取红色分量,数据类型为uint8
11 green = image_array(:,:,2); % 提取绿色分量,数据类型为uint8
12 blue = image_array(:,:,3); % 提取蓝色分量,数据类型为uint8
13
14 % 使用reshape函数将各个分量重组成一个一维矩阵
15 %为了避免溢出,将uint8类型的数据扩大为uint32类型
16 r = uint32(reshape(red’ , 1 ,height*width));
17 g = uint32(reshape(green’ , 1 ,height*width));
18 b = uint32(reshape(blue’ , 1 ,height*width));
19
20 % 初始化要写入.mif文件中的RGB颜色矩阵
21 rgb=zeros(1,height*width);
22
23 % 导入的图片为24bit真彩色图片,每个像素占用24bit,RGB888
24 % 将RGB888转换为RGB565
25 % 红色分量右移3位取出高5位,左移11位作为ROM中RGB数据的第15bit到第11bit
26 % 绿色分量右移2位取出高6位,左移5位作为ROM中RGB数据的第10bit到第5bit
27 % 蓝色分量右移3位取出高5位,左移0位作为ROM中RGB数据的第4bit到第0bit
28 for i = 1:height*width
29 rgb(i) = bitshift(bitshift(r(i),-3),11)
30 + bitshift(bitshift(g(i),-2),5)
31 + bitshift(bitshift(b(i),-3),0);
32 end
33
34 fid = fopen( ‘image.mif’, ‘w+’ );
35
36 % .mif文件字符串打印
37 fprintf( fid, ‘WIDTH=16;n’);
38 fprintf( fid, ‘DEPTH=%d;nn’,height*width);
39
40 fprintf( fid, ‘ADDRESS_RADIX=UNS;n’);
41 fprintf( fid, ‘DATA_RADIX=HEX;nn’);
42
43 fprintf(fid,’%snt’,’CONTENT’);
44 fprintf(fid,’%sn’,’BEGIN’);
45
46 % 写入图片数据
47 for i = 1:height*width
48 fprintf(fid,’tt%dt:%xt;n’,i-1,rgb(i));
49 end
50
51 % 打印结束字符串
52 fprintf(fid,’tEND;’);
53
54 fclose( fid ); % 关闭文件指针
代码编写完成并保存,运行脚本文件后,会在matlab文件夹下生成一个MIF文件,查看文件,图片数据已按格式要求写入。
在调用IP核生成ROM时,将生成的MIF文件导入即可,具体方法在前面章节有详细介绍,在此不再过多叙述。但读者要注意的是,ROM深度一定要大于等于图片包含像素点个数。
9.2.3.2. 整体说明¶
在本小节,我们先要对整个实验工程有一个整体认识,首先来看一下基于ROM的VGA图像显示实验工程的整体框图,具体见图 38‑7。
图 38‑7 基于ROM的VGA图像显示实验整体框图
由上图可知,本实验工程包括4个模块,各模块简介,具体见表格 38‑1。
表格 38‑1 基于ROM的VGA图像显示工程模块简介
模块名称 |
功能描述 |
---|---|
vga_rom_pic |
顶层模块 |
clk_gen |
时钟生成模块,生成VGA驱动时钟 |
vga_ctrl |
VGA时序控制模块,控制VGA图像显示 |
vga_pic |
图像数据生成模块,生成VGA显示图像 |
结合图 38‑7和表格 38‑1,我们来说一下基于ROM的VGA图像显示工程的工作流程。
系统上电后,板卡传入系统时钟(sys_clk)和复位信号(sys_rst_n)到顶层模块;
系统时钟直接传入时钟生成模块(clk_gen),分频产生VGA工作时钟(vga_clk),作为图像数据生成模块(vga_pic)和VGA时序控制模块(vga_ctrl)的工作时钟;
图像数据生成模块以VGA时序控制模块传入的像素点坐标(pix_x,pix_y)为约束条件,生成背景信息的待显示图像的色彩信息(pix_data);在图片显示区域读取存储于ROM的图片数据;两者数据结合生成VGA显示图像数据pix_data_out传入VGA时序控制模块。
图像数据生成模块生成的图像色彩信息pix_data_out传入VGA时序控制模块,在模块内部使用使能信号滤除掉非图像显示有效区域的图像数据,产生RGB色彩信息(rgb),在行、场同步信号(hsync、vsync)的同步作用下,将RGB色彩信息扫描显示到VGA显示器,实现图片显示。
本小节以全局视角,对整个实验工程进行了概括,对各子功能模块做了简单介绍,简要说明了实验工程的工作流程。
9.2.3.3. 图像数据生成模块¶
本小节中我们开始图像数据生成模块(vga_pic)的介绍,接下来我们会通过模块框图、波形图绘制、代码编写、仿真分析这几个部分,对本模块的设计、实现、仿真验证过程做一下详细介绍。
模块框图
图像数据生成模块,设计本模块的目的是产生VGA彩条背景像素点色彩信息和读出ROM存储的图片数据。模块框图,具体见图 38‑8。
图 38‑8 图像数据生成模块框图
由图 38‑8可知,图像数据生成模块包含4路输入、1路输出,共5路信号。输入输出信号简介,具体见表格 38‑2。
表格 38‑2 图像数据生成模块输入输出端口功能描述
信号 |
位宽 |
类型 |
功能描述 |
---|---|---|---|
vga_clk |
1Bit |
Input |
工作时钟,频率25MHz |
sys_rst_n |
1Bit |
Input |
复位信号,低电平有效 |
pix_x |
10Bit |
Input |
VGA有效显示区域像素点X轴坐标 |
pix_y |
10Bit |
Input |
VGA有效显示区域像素点Y轴坐标 |
pix_data_out |
16Bit |
Output |
图像像素点色彩信息 |
输入信号中,时钟信号vga_clk,频率为25MHz,为VGA显示器工作时钟,由分频模块产生并输入;复位信号sys_rst_n为顶层模块的rst_n信号输入,低电平有效;(pix_x,pix_y)为VGA有效显示区域像素点坐标,由VGA时序控制模块生并输入。
输出信号pix_data_out为图像像素点色彩信息,在VGA有效显示区域像素点坐标(pix_x,pix_y)约束下生成,传输到VGA时序控制模块。
模块内部实例化ROM IP核,有三路输入,一路输出,输入信号为时钟、数据读使能和数据地址信号,输出为数据地址对应图片数据。
波形图绘制
在模块框图部分,我们介绍了图像数据生成模块的具体功能,对输入输出信号做了简单介绍,那么如何利用模块输入信号实现模块功能,输出我们想要得到的数据信号呢?在波形图绘制部分,我们会通过绘制波形图,并对各信号做详细讲解,带领读者学习掌握模块功能的实现方法。
图像数据生成模块波形图,具体见图 38‑9。
图 38‑9 图像数据生成模块波形图
图 38‑9是我们最终绘制生成的图像数据生成模块波形图,下面我们讲解一下波形图绘制的具体思路。
第一部分:彩条背景色彩信息(pi_data)波形图绘制思路
根据输入像素点坐标(pix_x,pix_y),在有效显示区域,将pix_x计数范围十等分,在不同的计数部分给pix_data赋值对应的色彩信息,因为采用时序逻辑的赋值方式,pix_data滞后pix_x、pix_y信号一个时钟周期。信号波形图如下:
图 38‑10 pix_data信号波形图
第二部分:ROM读使能(rd_en)、ROM地址(rom_addr)波形图绘制思路
我们将要显示的的图片数据是事先写入ROM,ROM为调用IP核生成,写入照片分辨率为100*100。要想将写入ROM的图片读取出来,使能信号和地址信号必不可少,所以模块内部要声明ROM读使能信号(rd_en)和ROM地址(rom_addr)信号。
我们可以在图片显示区域拉高使能信号,将要读取数据地址写入ROM地址端口,读取地址对应图像数据。但有一点要注意,自ROM读取的数据是滞后使能信号和地址信号一个时钟周期的,比如,当使能信号为高电平,地址写入为999,但与地址999同步输出的数据为地址998的数据,所以ROM读使能信号(rd_en)和RO M地址(rom_addr)信号均要超前图片显示区域一个时钟周期,信号波形图绘制如下:
图 38‑11 rd_en、rom_addr信号
第三部分:图片显示有效信号(pic_valid)、待显示图像数据(pic_data_out)波形图绘制思路
想要在彩条图像背景上显示要显示的图片,我们需要在图片显示区域,使用图片数据覆盖彩条背景,那么如何确定图像显示区域呢?我们需要声明一个内部信号,那就是图片显示有效信号(pic_valid),在有效信号为高电平时,将自ROM读出的图片数据赋值给待显示图像数据(pic_data_out),覆盖彩条背景。
上文中我们提到ROM读使能信号(rd_en)超前图片显示区域一个时钟周期,可以利用此信号 延迟一个时钟周期生成图片显示有效信号(pic_valid);在有效信号为高电平时,将自ROM读出的图片数据赋值给待显示图像数据(pic_data_out),覆盖彩条背景。信号波形图如下:
图 38‑12 pic_valid、pic_data_out信号波形图
各信号波形绘制思路讲解完毕,将所有信号整合后,就是本小节开始部分展示的模块波形图。
本设计思路只做参考,并非唯一方法,读者可利用所学知识,按照自己思路进行设计。
代码编写
模块波形图绘制完毕后,参照绘制波形图进行参考代码的编写。模块参考代码,具体见代码清单 38‑2。
代码清单 38‑2 图像数据生成模块参考代码(vga_pic.v)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 | module vga_pic
(
input wire vga_clk , //输入工作时钟,频率25MHz
input wire sys_rst_n , //输入复位信号,低电平有效
input wire [9:0] pix_x , //输入有效显示区域像素点X轴坐标
input wire [9:0] pix_y , //输入有效显示区域像素点Y轴坐标
output wire [15:0] pix_data_out //输出VGA显示图像数据
);
////
//\* Parameter and Internal Signal \//
////
parameter H_VALID = 10'd640 , //行有效数据
V_VALID = 10'd480 ; //场有效数据
parameter H_PIC = 10'd100 , //图片长度
W_PIC = 10'd100 , //图片宽度
PIC_SIZE= 14'd10000 ; //图片像素个数
parameter RED = 16'hF800 , //红色
ORANGE = 16'hFC00 , //橙色
YELLOW = 16'hFFE0 , //黄色
GREEN = 16'h07E0 , //绿色
CYAN = 16'h07FF , //青色
BLUE = 16'h001F , //蓝色
PURPPLE = 16'hF81F , //紫色
BLACK = 16'h0000 , //黑色
WHITE = 16'hFFFF , //白色
GRAY = 16'hD69A ; //灰色
//wire define
wire rd_en ; //ROM读使能
wire [15:0] pic_data ; //自ROM读出的图片数据
//reg define
reg [13:0] rom_addr ; //读ROM地址
reg pic_valid ; //图片数据有效信号
reg [15:0] pix_data ; //背景色彩信息
////
//\* Main Code \//
////
//rd_en:ROM读使能
assign rd_en = (((pix_x >= (((H_VALID - H_PIC)/2) - 1'b1))
&& (pix_x < (((H_VALID - H_PIC)/2) + H_PIC - 1'b1)))
&&((pix_y >= ((V_VALID - W_PIC)/2))
&& ((pix_y < (((V_VALID - W_PIC)/2) + W_PIC)))));
//pic_valid:图片数据有效信号
always@(posedge vga_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
pic_valid <= 1'b1;
else
pic_valid <= rd_en;
//pix_data_out:输出VGA显示图像数据
assign pix_data_out = (pic_valid == 1'b1) ? pic_data : pix_data;
//根据当前像素点坐标指定当前像素点颜色数据,在屏幕上显示彩条
always@(posedge vga_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
pix_data <= 16'd0;
else if((pix_x >= 0) && (pix_x < (H_VALID/10)*1))
pix_data <= RED;
else if((pix_x >= (H_VALID/10)*1) && (pix_x < (H_VALID/10)*2))
pix_data <= ORANGE;
else if((pix_x >= (H_VALID/10)*2) && (pix_x < (H_VALID/10)*3))
pix_data <= YELLOW;
else if((pix_x >= (H_VALID/10)*3) && (pix_x < (H_VALID/10)*4))
pix_data <= GREEN;
else if((pix_x >= (H_VALID/10)*4) && (pix_x < (H_VALID/10)*5))
pix_data <= CYAN;
else if((pix_x >= (H_VALID/10)*5) && (pix_x < (H_VALID/10)*6))
pix_data <= BLUE;
else if((pix_x >= (H_VALID/10)*6) && (pix_x < (H_VALID/10)*7))
pix_data <= PURPPLE;
else if((pix_x >= (H_VALID/10)*7) && (pix_x < (H_VALID/10)*8))
pix_data <= BLACK;
else if((pix_x >= (H_VALID/10)*8) && (pix_x < (H_VALID/10)*9))
pix_data <= WHITE;
else if((pix_x >= (H_VALID/10)*9) && (pix_x < H_VALID))
pix_data <= GRAY;
else
pix_data <= BLACK;
//rom_addr:读ROM地址
always@(posedge vga_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
rom_addr <= 14'd0;
else if(rom_addr == (PIC_SIZE - 1'b1))
rom_addr <= 14'd0;
else if(rd_en == 1'b1)
rom_addr <= rom_addr + 1'b1;
////
//\* Instantiation \//
////
//------------- rom_pic_inst -------------
rom_pic rom_pic_inst
(
.address (rom_addr ), //输入读ROM地址,14bit
.clock (vga_clk ), //输入读时钟,vga_clk,频率25MHz,1bit
.rden (rd_en ), //输入读使能,1bit
.q (pic_data ) //输出读数据,16bit
);
endmodule
|
模块参考代码是参照绘制波形图进行编写的,在波形图绘制小节已经对模块各信号有了详细的说明,对各信号介绍不再过多叙述。
仿真代码编写
不再对模块进行单独仿真,直接对实验工程进行整体仿真,仿真参考代码,具体见代码清单 38‑3。
代码清单 38‑3 顶层模块仿真参考代码(tb_vga_rom_pic.v)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | \`timescale 1ns/ 1ns
module tb_vga_rom_pic();
////
//\* Parameter and Internal Signal \//
////
//wire define
wire hsync ;
wire [15:0] rgb ;
wire vsync ;
//reg define
reg sys_clk ;
reg sys_rst_n ;
////
//\* Clk And Rst \//
////
//sys_clk,sys_rst_n初始赋值
initial
begin
sys_clk = 1'b1;
sys_rst_n <= 1'b0;
#200
sys_rst_n <= 1'b1;
end
//sys_clk:产生时钟
always #10 sys_clk = ~sys_clk ;
////
//\* Instantiation \//
////
//------------- vga_rom_pic_inst -------------
vga_rom_pic vga_rom_pic_inst
(
.sys_clk (sys_clk ), //输入晶振时钟,频率50MHz,1bit
.sys_rst_n (sys_rst_n ), //输入复位信号,低电平有效,1bit
.hsync (hsync ), //输出行同步信号,1bit
.vsync (vsync ), //输出场同步信号,1bit
.rgb (rgb ) //输出RGB图像信息,16bit
);
endmodule
|
顶层模块仿真参考代码内部实例化顶层模块,模拟产生50MHz时钟信号和复位信号,理解较为容易,不再讲解。
仿真波形分析
使用ModelSim软件对代码进行仿真,我们只经过改动的vga_pic模块的相关信号,仿真结果如下。
图 38‑13 图像数据生成模块整体仿真波形图
图 38‑14 图像数据生成模块局部仿真波形图(一)
图 38‑15 图像数据生成模块局部仿真波形图(二)
由仿真波形可以看出,图像数据生成模块各信号波形与绘制的参考波形图波形一致,模块通过仿真验证。
9.2.3.4. RTL视图¶
实验工程通过仿真验证后,使用Quartus软件对实验工程进行编译,编译完成后,我们查看一下RTL视图, RTL视图展示信息与顶层模块框图一致,各信号连接正确,具体见图 38‑16。
图 38‑16 实验工程RTL视图
9.3. 上板验证¶
9.3.1. 引脚约束¶
仿真验证通过后,准备上板验证,上板验证之前先要进行引脚约束。工程中各输入输出信号与开发板引脚对应关系如表格 38‑3所示。
表格 38‑3 引脚分配表
信号名 |
信号类型 |
对应引脚 |
备注 |
---|---|---|---|
sys_clk |
Input |
E1 |
输入系统时钟 |
sys_rst_n |
Input |
M15 |
复位信号 |
hsync |
Output |
C2 |
行同步信号 |
vsync |
Output |
D1 |
场同步信号 |
rgb[15] |
Output |
A5 |
RGB色彩信息(红) |
rgb[14] |
Output |
E6 |
RGB色彩信息(红) |
rgb[13] |
Output |
E7 |
RGB色彩信息(红) |
rgb[12] |
Output |
B8 |
RGB色彩信息(红) |
rgb[11] |
Output |
A8 |
RGB色彩信息(红) |
rgb[10] |
Output |
F8 |
RGB色彩信息(绿) |
rgb[9] |
Output |
E8 |
RGB色彩信息(绿) |
rgb[8] |
Output |
B7 |
RGB色彩信息(绿) |
rgb[7] |
Output |
A7 |
RGB色彩信息(绿) |
rgb[6] |
Output |
F7 |
RGB色彩信息(绿) |
rgb[5] |
Output |
F6 |
RGB色彩信息(绿) |
rgb[4] |
Output |
B6 |
RGB色彩信息(蓝) |
rgb[3] |
Output |
A6 |
RGB色彩信息(蓝) |
rgb[2] |
Output |
B5 |
RGB色彩信息(蓝) |
rgb[1] |
Output |
A2 |
RGB色彩信息(蓝) |
rgb[0] |
Output |
B4 |
RGB色彩信息(蓝) |
下面进行管脚分配,管脚的分配方法在前面章节已有所讲解,在此就不再过多叙述,管脚的分配如下图 38‑17所示。
图 38‑17 管脚分配
9.3.1.1. 结果验证¶
如图 38‑18所示,开发板连接12V直流电源、USB-Blaster下载器JTAG端口以及VGA显示器。线路正确连接后,打开开关为板卡上电。
图 38‑18 程序下载连线图
如图 38‑19所示,使用“Programmer”为开发板下载程序。
图 38‑19 程序下载图
程序下载完成后,如图 38‑20所示,VGA显示器正中间显示出野火标志,背景为十色彩条,和预期实验效果一致。
图 38‑20 基于ROM的VGA图像显示效果图
9.4. 章末总结¶
到这里,本章节讲解完毕,相信读者已经掌握了基于ROM的VGA图像显示方法。不过读者有一点需要注意,就是章节中提到的,普通ROM读出数据会滞后ROM读使能信号和地址信号一个时钟周期。读者在以后调用ROM时要记得这一点,防止出现错误。