基于RS232的VGA图像显示
基于ROM的VGA图像显示方式,需要提前将图片数据转换成.coe文件,然后初始化ROM-IP,其实还是占用FPGA内部的RAM存储资源,能够显示的图像数据大小是有限的,并且每次只能显示一张图片,想要显示不同的图片就得重新用.coe文件初始化ROM-IP
改进一下,把串口RS232也加上,通过串口发送图像数据到FPGA内部,然后再用VGA显示出来,想要显示不同的图片,就只需要通过串口发送不同的图像数据就可以了,这样的显示方式可以显示更大的图片
图像数据是不能用串口直接进行收发的,需要处理成像素信息,然后再由串口收发
串口数据帧中有8bit有效数据,需要将原来的RGB444转成RGB332格式,再由串口进行发送,一次发送一个像素点信息
1. matlab工具使用
matlab图片转换为RGB332格式,也可以参照之前生成.coe文件的.m文件,改一下将字符打印到txt文件的部分,也能生成对应的.txt文件
clear all;
RGB24 = imread('dog_100x100.jpg'); %读取图片文件
R = bitshift(RGB24(1:end,1:end,1),-5); %取R高3位,{5'b0,R[7:5]}
G = bitshift(RGB24(1:end,1:end,2),-5); %取G高3位,{5'd0,G[7:5]}
B = bitshift(RGB24(1:end,1:end,3),-6); %取B高2位,{6'd0,B[7:6]}
rgb332 = bitshift(R,5) + bitshift(G,2) + B; %拼接{R[7:5],G[7:5],B[7:6]}
fid=fopen('D:\wwww\mine_2\mine\dog_RGB332.txt','w+'); %打开文件
fprintf(fid,'%02x ',rgb332'); %将字符打印到txt文件中
2. 实验目标
通过上位机的串口调试助手发送图片数据给FPGA,FPGA接收到后会缓存在RAM中,当VGA显示器扫描到图像数据后就会显示
RS232工作时钟为50MHz,VGA640*480@60工作时钟为25MHz,中间作为缓存的RAM需要处理跨时钟域
3. 代码实现
25MHz和50MHz的工作时钟由PLL锁相环生成,uart_rx接收模块复用RS232的接收模块,vga_ctrl也是复用VGA显示模块,RAM调用IP核,需要修改的就是vga_pic模块
block RAM有三种:单口RAM、伪双口RAM和真双口RAM。
- 单口RAM只有一个端口(A端口),可以对A端口进行读写。
- 伪双口RAM有两个端口(A和B端口),但是A端口只能进行写入操作,不能进行读出操作,而B端口则只能进行读出操作,不能进行写入操作。
- 真双口RAM有两个端口(A和B端口),A和B端口都能进行读写操作。
这里创建伪双端口RAM,50MHz下写,25MHz下读
module vga_pic (
input wire rx_clk , // 50MHz
input wire vga_clk , // 25MHz
input wire sys_rst_n ,
input wire [ 9: 0] pix_x ,
input wire [ 9: 0] pix_y ,
input wire [ 7: 0] pi_data , // rx
input wire pi_flag ,
output wire [ 7: 0] pix_data
);
// 分辨率 也就是坐标信号的最大值
parameter H_VALID = 10'd640,
V_VALID = 10'd480;
// 颜色参数定义
// B G R
parameter RED = 8'b00_000_111,
GREEN = 8'b00_111_000,
BLUE = 8'b11_000_000,
WHITE = 8'b111_111_11,
BLACK = 8'b000_000_00;
// 图片大小参数定义
parameter H_PIC = 10'd100,
V_PIC = 10'd100;
parameter PIC_SIZE= 14'd10000;
reg [ 7: 0] data_pix ; // 彩条颜色
reg pic_valid ; // ROM图像有效信号
wire rd_en ; // RAM读写使能信号
reg [13: 0] rd_addr ; // RAM读地址
reg [13: 0] wr_addr ; // RAM写地址
wire [ 7: 0] pic_data ; // RAM读数据
wire [ 7: 0] real_rgb_data;
assign real_rgb_data = {pic_data[1:0], pic_data[4:2], pic_data[7:5]};
assign pix_data = (pic_valid == 1'b1) ? real_rgb_data : data_pix;
// rd_en:
// 横向:269 ≤ pix_x < 369 需要提前一拍准备好读ROM使能
// 纵向:190 ≤ pix_y < 290
assign rd_en = (((pix_x >= (((H_VALID - H_PIC) / 2) - 1'b1)) // pix_x ≥ (((640-100)/2))-1'b1 = 269
&& (pix_x < (((H_VALID - H_PIC) / 2) + H_PIC - 1'b1))) // pix_x < (((640-100)/2)+100)-1'b1= 369
&& ((pix_y >= ((V_VALID - V_PIC) / 2)) // pic_y ≥ (((480-100)/2)) = 190
&& ((pix_y < (((V_VALID - V_PIC) / 2) + V_PIC))))); // pix_x < (((480-100)/2)+100) = 290
// pic_valid: 将rd_en打一拍
always @ (posedge vga_clk or negedge sys_rst_n)
if (sys_rst_n == 1'b0)
pic_valid <= 1'b0;
else
pic_valid <= rd_en;
// rd_addr 25MHz读时钟
always @ (posedge vga_clk or negedge sys_rst_n)
if (sys_rst_n == 1'b0)
rd_addr <= 14'd0;
else if (rd_addr == PIC_SIZE - 1'b1)
rd_addr <= 14'd0;
else if (rd_en)
rd_addr <= rd_addr + 1'b1;
else
rd_addr <= rd_addr;
// wr_addr 50MHz写时钟
always @ (posedge rx_clk or negedge sys_rst_n)
if (sys_rst_n == 1'b0)
wr_addr <= 14'd0;
else if ((wr_addr == PIC_SIZE - 1'b1) && (pi_flag == 1'b1))
wr_addr <= 14'd0;
else if (pi_flag == 1'b1)
wr_addr <= wr_addr + 1'b1;
else
wr_addr <= wr_addr;
// data_pix: 彩条颜色信号赋值
always @ (posedge vga_clk or negedge sys_rst_n)
if (sys_rst_n == 1'b0)
data_pix <= BLACK; // 默认显示黑色
else if (pix_x >= 0 && pix_x < (H_VALID / 10) * 1)
data_pix <= RED;
else if (pix_x >= (H_VALID / 10) * 1 && pix_x < (H_VALID / 10) * 2)
data_pix <= GREEN;
else if (pix_x >= (H_VALID / 10) * 2 && pix_x < (H_VALID / 10) * 3)
data_pix <= BLUE;
else if (pix_x >= (H_VALID / 10) * 3 && pix_x < (H_VALID / 10) * 4)
data_pix <= WHITE;
else if (pix_x >= (H_VALID / 10) * 4 && pix_x < (H_VALID / 10) * 5)
data_pix <= BLACK;
else if (pix_x >= (H_VALID / 10) * 5 && pix_x < (H_VALID / 10) * 6)
data_pix <= RED;
else if (pix_x >= (H_VALID / 10) * 6 && pix_x < (H_VALID / 10) * 7)
data_pix <= GREEN;
else if (pix_x >= (H_VALID / 10) * 7 && pix_x < (H_VALID / 10) * 8)
data_pix <= BLUE;
else if (pix_x >= (H_VALID / 10) * 8 && pix_x < (H_VALID / 10) * 9)
data_pix <= WHITE;
else if (pix_x >= (H_VALID / 10) * 9 && pix_x < (H_VALID / 10) * 10)
data_pix <= BLACK;
else
data_pix <= BLACK;
ram_pic ram_pic_inst (
.clka (rx_clk ), // input wire clka 写时钟
.ena (pi_flag ), // input wire ena 写使能
.wea (1'b1 ), // input wire [0 : 0] wea 写字节使能
.addra (wr_addr ), // input wire [13 : 0] addra写地址
.dina (pi_data ), // input wire [7 : 0] dina 写数据
.clkb (vga_clk ), // input wire clkb 读时钟
.enb (1'b1 ), // input wire enb 读使能
.addrb (rd_addr ), // input wire [13 : 0] addrb读地址
.doutb (pic_data ) // output wire [7 : 0] doutb读数据
);
endmodule
vga_pic模块输出的彩条像素数据、图像像素数据都是8bit格式,在顶层模块,需要扩展成12bitRGB444格式
assign rgb = {2'b0, rgb_332[7:6], 1'b0, rgb_332[5:3], 1'b0, rgb_332[2:0]};
或者尝试直接将332格式用于管脚绑定 用不到的管脚就空着,这样也能正常显示图像
set_property -dict {PACKAGE_PIN F5 IOSTANDARD LVCMOS33} [get_ports rgb_332[0]]
set_property -dict {PACKAGE_PIN C6 IOSTANDARD LVCMOS33} [get_ports rgb_332[1]]
set_property -dict {PACKAGE_PIN C5 IOSTANDARD LVCMOS33} [get_ports rgb_332[2]]
#set_property -dict {PACKAGE_PIN B7 IOSTANDARD LVCMOS33} [get_ports rgb[3]]
set_property -dict {PACKAGE_PIN B6 IOSTANDARD LVCMOS33} [get_ports rgb_332[3]]
set_property -dict {PACKAGE_PIN A6 IOSTANDARD LVCMOS33} [get_ports rgb_332[4]]
set_property -dict {PACKAGE_PIN A5 IOSTANDARD LVCMOS33} [get_ports rgb_332[5]]
#set_property -dict {PACKAGE_PIN D8 IOSTANDARD LVCMOS33} [get_ports rgb[7]]
set_property -dict {PACKAGE_PIN C7 IOSTANDARD LVCMOS33} [get_ports rgb_332[6]]
set_property -dict {PACKAGE_PIN E6 IOSTANDARD LVCMOS33} [get_ports rgb_332[7]]
#set_property -dict {PACKAGE_PIN E5 IOSTANDARD LVCMOS33} [get_ports rgb[10]]
#set_property -dict {PACKAGE_PIN E7 IOSTANDARD LVCMOS33} [get_ports rgb[11]]
4. 遇到的问题
-
Matlab生成的像素点信息文件用串口传送后,等宽彩条应该是“红绿蓝白黑”,实际显示的是下面这个样子,不太清楚RGB332转RGB444的bit对应关系
修改一下对应bit关系,重新下板
-
自己仿真出来的波形,vga_pic模块中的wr_addr信号没有归零,po_flag少拉高一次,下板后可以看到显示器显示对应的图像,影响不太大