本文演示如何用PL逻辑资源去驱动RGB888屏幕模块(以本站的5寸IPS 800X480 RGB接口LCD屏幕模块为例)
- 此章节内容适用于Smart ZYNQ SP SP2和 SL 版的板子 ( 不包含Smart ZYNQ 标准版 ),如是标准版或本站其他板子请看对应板子目录
- 本文在 vivado2018.3版本上演示
本次实验需要外接本站的5寸IPS 屏幕模块,有关本次实验的 RGB屏的原理图手册以及其他例程资料可以参看下面汇总贴:5寸IPS 800X480 RGB接口LCD屏幕模块
实验内容:本次实验我们将用Verilog设计一个符合RGB888输出的图像生成模块(分辨率800×480)(彩条纹生成模块),并借助板子上的排针接口点亮我们的5寸800×480分辨率的RGB屏,以此来熟悉我们板子的显示屏开发流程,为后续FPGA图像处理相关的开发做前期准备工作。
本文中RGB888驱动部分的内容和上一节HDMI的工程有部分类似,只是在分辨率和时序上做了调整。大家可以选看。其实关于RGB的时序都是一致的,只要设置好其中的参数就可以了。另外和HDMI 输出不同,这里的RGB 数据是可以直接点LCD屏幕的,不需要额外再增加RGB2DVI模块了。
RGB888时序简单介绍
RGB888 是一种常用的颜色编码格式,广泛应用于显示器和图像处理中。在 RGB888 中,每个像素的颜色信息由 红色 (R)、绿色 (G) 和 蓝色 (B) 三个分量组成,每个分量的位数为 8 位,因此总共是 24 位色深(8 位 × 3 通道 = 24 位)合计1600万种颜色。
RGB888 格式的时序可以理解为显示屏如何接收并显示像素数据的过程,具体的时序和信号包括数据通道、同步信号、时钟信号等。对于一个 RGB888 输出接口,时序结构大体可以分为以下几个部分:
- 像素数据:每个像素点的颜色由 24 位数据表示,其中:
- 红色分量 (R):8 位(位宽 8)
- 绿色分量 (G):8 位(位宽 8)
- 蓝色分量 (B):8 位(位宽 8)
- 同步信号:
- Vsync (垂直同步):用于标识显示器的每一帧的开始和结束。通常在一个完整的屏幕刷新周期内(垂直扫描)会发送一个 Vsync 信号。Vsync 的周期对应于一帧的时间。
- Hsync (水平同步):用于标识显示器当前行的开始和结束。在显示器的水平扫描过程中,Hsync 信号会定期产生,标识一行数据的开始。
- 像素时钟:
- Pixel Clock (像素时钟):用于同步像素数据的传输,通常通过高频时钟信号来控制每一像素的显示时间。像素时钟的频率与显示分辨率和刷新率相关。
通过掌握 RGB888 的时序和工作原理,您可以更好地设计和调试图像生成和显示模块。
下图是800×480屏手册上的时序表
一、Vivado工程创建
工程创建的过程可以参考实验一中的内容,这里不详细描述了。基于Smart ZYNQ (SP/SP2/SL 版) 的FPGA实验一 用ZYNQ的PL资源点亮一个LED(完整图文) (芯片型号选XC7Z020CLG484-1)
二、时钟模块设计
我们尝试输出800X480的分辨率的图像,根据上文中时序表上的内容可以知道,屏幕工作的DCLK范围是24M-27M,推荐工作频率是25M,而我们板子上焊接的有源晶体是50MHZ,这里就需要用时钟管理模块MMCM来生成我们要的25MHZ频率。
VIVADO系统给我们内置了很多功能强大的模块,包括我们要的时钟模块,这里我们只需要调用时钟模块输入我们要的参数就好
1)第一步,点击IP Catalog 打开模块选择器, 在里面的搜索栏输入 CLOCKING ,系统会自动跳出符合的 Clocking Wizard选项,双击它
2)在弹出的窗口中我们将input Frequence 输入频率修改为板子上焊接的50M时钟, 右边改为单端输入
3)在output Clocks选项中 将clk_out1改成27m (27是手册上推荐的DCLK 范围的上限)
4)将界面托到最下面,因为我们这里的要求并不高,所以把时钟的复位reset,和locked选项去除,最后点击ok生成模块
三、增加我们的代码内容
1)这里创建一个显示模块,显示模块负责输出标准的RGB时序,具体如下( color_bar.v)
//www.hellofpga.com//
`timescale 1ns / 1ps
module color_bar (
input wire clk,
input wire rst_n,
output reg hsync,
output reg vsync,
output reg de,
output reg [7:0] rgb_r,
output reg [7:0] rgb_g,
output reg [7:0] rgb_b
);
`define RES_800x480
`ifdef RES_800x480
parameter H_ACTIVE = 800;
parameter H_FRONT = 8;
parameter H_SYNC = 4;
parameter H_BACK = 8;
parameter V_ACTIVE = 480;
parameter V_FRONT = 8;
parameter V_SYNC = 4;
parameter V_BACK = 8;
`endif
reg [11:0] h_count = 0;
reg [11:0] v_count = 0;
reg pix_data_req;
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
h_count<=12'd0;
v_count<=12'd0;
pix_data_req<=1'b0;
de<=1'b0;
end
else begin
if (h_count < H_ACTIVE + H_FRONT + H_SYNC + H_BACK - 1'b1)
h_count <= h_count + 1;
else begin
h_count <= 0;
if (v_count < V_ACTIVE + V_FRONT + V_SYNC + V_BACK - 1'b1)
v_count <= v_count + 1;
else
v_count <= 0;
end
hsync <= (h_count < H_SYNC) ? 0 : 1;
vsync <= (v_count < V_SYNC) ? 0 : 1;
de <= ((h_count >= H_SYNC + H_BACK ) && (h_count < H_SYNC + H_BACK + H_ACTIVE)
&&(v_count >= V_SYNC + V_BACK ) && (v_count < V_SYNC + V_BACK + V_ACTIVE)) ? 1'b1:1'b0;
pix_data_req <= ((h_count >= H_SYNC + H_BACK -1'b1 ) && (h_count < H_SYNC + H_BACK + H_ACTIVE -1'b1)
&&(v_count >= V_SYNC + V_BACK ) && (v_count < V_SYNC + V_BACK + V_ACTIVE)) ? 1'b1:1'b0;
end
end
wire [11:0] pix_xpos = pix_data_req ? (h_count - (H_SYNC + H_BACK) ):12'd0;
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
rgb_r<=8'h00; rgb_g<=8'h00; rgb_b<=8'h00;
end
else if(pix_data_req)begin
if(pix_xpos==12'd0)
begin rgb_r<=8'hff; rgb_g<=8'hff; rgb_b<=8'hff; end //White
else if(pix_xpos ==( H_ACTIVE / 8 ) * 1)
begin rgb_r<=8'hff; rgb_g<=8'hff; rgb_b<=8'h00; end //Yellow
else if(pix_xpos ==( H_ACTIVE / 8 ) * 2)
begin rgb_r<=8'h00; rgb_g<=8'hff; rgb_b<=8'hff; end //Cyan
else if(pix_xpos ==( H_ACTIVE / 8 ) * 3)
begin rgb_r<=8'h00; rgb_g<=8'hff; rgb_b<=8'h00; end //Green
else if(pix_xpos ==( H_ACTIVE / 8 ) * 4)
begin rgb_r<=8'hff; rgb_g<=8'h00; rgb_b<=8'hff; end //Magenta
else if(pix_xpos ==( H_ACTIVE / 8 ) * 5)
begin rgb_r<=8'hff; rgb_g<=8'h00; rgb_b<=8'h00; end //Red
else if(pix_xpos ==( H_ACTIVE / 8 ) * 6)
begin rgb_r<=8'h00; rgb_g<=8'h00; rgb_b<=8'hff; end //Blue
else if(pix_xpos ==( H_ACTIVE / 8 ) * 7)
begin rgb_r<=8'h00; rgb_g<=8'h00; rgb_b<=8'h00; end //Black
end
else begin
rgb_r<=8'h00; rgb_g<=8'h00; rgb_b<=8'h00;
end
end
endmodule
2)创建一个顶层模块(top.v),分别调用时钟、以及彩条纹显示模块
//www.hellofpga.com//
`timescale 1ns / 1ps
module top(
input clk,
output [7:0] LCD_R,
output [7:0] LCD_G,
output [7:0] LCD_B,
output LCD_HS,
output LCD_VS,
output LCD_DE,
output LCD_CLK,
output LCD_BL
);
wire PCLK;
clk_wiz_0 u2(
.clk_in1(clk),
.clk_out1(PCLK)
);
assign LCD_CLK=PCLK;
assign LCD_BL=1;
color_bar u4 (
.clk(PCLK),
.rst_n(1'b1),
.hsync(LCD_HS),
.vsync(LCD_VS),
.de(LCD_DE),
.rgb_r(LCD_R),
.rgb_g(LCD_G),
.rgb_b(LCD_B)
);
endmodule
这里我们直接将LCD_BL设置为高电平,这样只要系统运行,屏幕的背光就会被点亮。
3)最后再增加约束文件 (PIN_XDC.XDC)( 对应Smart Zynq SP SP2 SL主板)
set_property PACKAGE_PIN M19 [get_ports clk]
set_property IOSTANDARD LVCMOS33 [get_ports clk]
set_property PACKAGE_PIN U22 [get_ports LCD_VS]
set_property PACKAGE_PIN T22 [get_ports LCD_DE]
set_property PACKAGE_PIN W22 [get_ports LCD_CLK]
set_property PACKAGE_PIN V22 [get_ports LCD_HS]
set_property PACKAGE_PIN Y16 [get_ports LCD_BL]
set_property PACKAGE_PIN Y20 [get_ports {LCD_B[7]}]
set_property PACKAGE_PIN Y21 [get_ports {LCD_B[6]}]
set_property PACKAGE_PIN AA22 [get_ports {LCD_B[5]}]
set_property PACKAGE_PIN AB22 [get_ports {LCD_B[4]}]
set_property PACKAGE_PIN AA21 [get_ports {LCD_B[3]}]
set_property PACKAGE_PIN AB21 [get_ports {LCD_B[2]}]
set_property PACKAGE_PIN AB20 [get_ports {LCD_B[1]}]
set_property PACKAGE_PIN AB19 [get_ports {LCD_B[0]}]
set_property PACKAGE_PIN Y19 [get_ports {LCD_G[7]}]
set_property PACKAGE_PIN AA19 [get_ports {LCD_G[6]}]
set_property PACKAGE_PIN AA16 [get_ports {LCD_G[5]}]
set_property PACKAGE_PIN AB16 [get_ports {LCD_G[4]}]
set_property PACKAGE_PIN AA18 [get_ports {LCD_G[3]}]
set_property PACKAGE_PIN Y18 [get_ports {LCD_G[2]}]
set_property PACKAGE_PIN AB15 [get_ports {LCD_G[1]}]
set_property PACKAGE_PIN AB14 [get_ports {LCD_G[0]}]
set_property PACKAGE_PIN AA13 [get_ports {LCD_R[7]}]
set_property PACKAGE_PIN Y13 [get_ports {LCD_R[6]}]
set_property PACKAGE_PIN W13 [get_ports {LCD_R[5]}]
set_property PACKAGE_PIN V13 [get_ports {LCD_R[4]}]
set_property PACKAGE_PIN W17 [get_ports {LCD_R[3]}]
set_property PACKAGE_PIN W18 [get_ports {LCD_R[2]}]
set_property PACKAGE_PIN AB17 [get_ports {LCD_R[1]}]
set_property PACKAGE_PIN AA17 [get_ports {LCD_R[0]}]
set_property IOSTANDARD LVCMOS33 [get_ports LCD_VS]
set_property IOSTANDARD LVCMOS33 [get_ports LCD_HS]
set_property IOSTANDARD LVCMOS33 [get_ports LCD_DE]
set_property IOSTANDARD LVCMOS33 [get_ports LCD_CLK]
set_property IOSTANDARD LVCMOS33 [get_ports LCD_BL]
set_property IOSTANDARD LVCMOS33 [get_ports {LCD_R[7]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LCD_R[6]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LCD_R[5]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LCD_R[4]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LCD_R[3]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LCD_R[1]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LCD_R[2]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LCD_R[0]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LCD_G[7]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LCD_G[6]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LCD_G[5]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LCD_G[4]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LCD_G[3]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LCD_G[2]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LCD_G[1]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LCD_G[0]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LCD_B[7]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LCD_B[6]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LCD_B[5]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LCD_B[4]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LCD_B[3]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LCD_B[2]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LCD_B[1]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LCD_B[0]}]
四、编译综合,并运行代码
最终显示效果如图(从左到右依次显示白黄青绿紫红蓝黑),可见我们的屏幕已经正常被点亮了 (照片拍出来效果差一些,实际显示颜色还是很鲜艳的)
五、代码解读:
1) 不同分辨率的不同参数
RGB的时序通常满足VGA的标准,以下是VGA分辨率的参数
时序图,以及消隐的概念,还有不同分辨率时,各个区域的参数
因为我们本次实验点亮的是LCD屏,所以这里的时序我们直接参考LCD屏幕手册中对应的LCD时序
下面是本次实验中根据LCD屏幕800X480 分辨率所添加的参数,通过 `define RES_800x480 来进行选择,其中的参数均和上述图表中的内容对应。
`ifdef RES_800x480
parameter H_ACTIVE = 800;
parameter H_FRONT = 8;
parameter H_SYNC = 4;
parameter H_BACK = 8;
parameter V_ACTIVE = 480;
parameter V_FRONT = 8;
parameter V_SYNC = 4;
parameter V_BACK = 8;
`endif
2)hsync 和vsync 以及de的逻辑如下:
if (h_count < H_ACTIVE + H_FRONT + H_SYNC + H_BACK - 1'b1)
h_count <= h_count + 1;
else begin
h_count <= 0;
if (v_count < V_ACTIVE + V_FRONT + V_SYNC + V_BACK - 1'b1)
v_count <= v_count + 1;
else
v_count <= 0;
end
hsync <= (h_count < H_SYNC) ? 0 : 1;
vsync <= (v_count < V_SYNC) ? 0 : 1;
de <= ((h_count >= H_SYNC + H_BACK ) && (h_count < H_SYNC + H_BACK + H_ACTIVE) &&(v_count >= V_SYNC + V_BACK ) && (v_count < V_SYNC + V_BACK + V_ACTIVE)) ? 1'b1:1'b0;
其中 h_count
和 v_count
分别是水平和垂直方向的计数器。
- h_count周期是从 H_SYNC 到 H_BACK(H_BP)到 H_ACTIVE 到 H_FRONT(H_FP)
- v_count周期是从V_SYNC 到 V_BACK(V_BP)到 V_ACTIVE 到 V_FRONT(H_FP)
- de 就是 H_ACTIVE 和V_ACTIVE 都有效的区域,即图像显示区域
下图是ST的一个LCD控制时序,我们也可以通过该图来对流程进行了解。
3) 彩条纹的显示部分:
a) 增加一个信号pix_data_req,这个信号和de相类似,都代表有效显示区域,但是pix_data_req在h计数上会早de一个像素点,用于在de信号之前就将待显示的颜色数据准备好
pix_data_req <= ((h_count >= H_SYNC + H_BACK -1'b1 ) && (h_count < H_SYNC + H_BACK + H_ACTIVE -1'b1)&&(v_count >= V_SYNC + V_BACK ) && (v_count < V_SYNC + V_BACK + V_ACTIVE)) ? 1'b1:1'b0;
b) 增加pix_xpos信号,该信号对应有效显示区域中x的坐标(从0开始计数)
wire [11:0] pix_xpos = pix_data_req ? (h_count - (H_SYNC + H_BACK) ):12'd0;
c) 彩条纹的逻辑也比较容易理解, 我们将H_ACTIVE 分割成8等份,每等份都显示不同颜色作演示,8等分通过下列方式来划分
(H_ACTIVE / 8 ) * n 这部分是8等分颜色的切分点,当我们当前的pix_xpos==(H_ACTIVE / 8 ) * n时,就将RGB输出对应的颜色。 (因为我们的H_ACTIVE这个值是确认的,所以这里的乘法和除法是在vivado 编译过程中就换算成对应的结果了,给进FPGA的值会是一个常数,而不是由FPGA进行运算的)
if(pix_data_req)begin
if(pix_xpos==12'd0)
begin rgb_r<=8'hff; rgb_g<=8'hff; rgb_b<=8'hff; end
else if(pix_xpos ==( H_ACTIVE / 8 ) * 1)
begin rgb_r<=8'hff; rgb_g<=8'hff; rgb_b<=8'h00; end
else if(pix_xpos ==( H_ACTIVE / 8 ) * 2)
begin rgb_r<=8'h00; rgb_g<=8'hff; rgb_b<=8'hff; end
else if(pix_xpos ==( H_ACTIVE / 8 ) * 3)
begin rgb_r<=8'h00; rgb_g<=8'hff; rgb_b<=8'h00; end
else if(pix_xpos ==( H_ACTIVE / 8 ) * 4)
begin rgb_r<=8'hff; rgb_g<=8'h00; rgb_b<=8'hff; end
else if(pix_xpos ==( H_ACTIVE / 8 ) * 5)
begin rgb_r<=8'hff; rgb_g<=8'h00; rgb_b<=8'h00; end
else if(pix_xpos ==( H_ACTIVE / 8 ) * 6)
begin rgb_r<=8'h00; rgb_g<=8'h00; rgb_b<=8'hff; end
else if(pix_xpos ==( H_ACTIVE / 8 ) * 7)
begin rgb_r<=8'h00; rgb_g<=8'h00; rgb_b<=8'h00; end
end
后续将会介绍LCD屏的更多玩法。
- 本文的完整工程下载:13_PL_5INCH_LCD_COLOR_BAR
- VIVADO的版本:2018.3
- 工程创建目录:E:\Smart_ZYNQ_SP_SL\FPGA\13_PL_5INCH_LCD_COLOR_BAR
- 工程适用主板: Smart ZYNQ (SP / SP2 / SL) (不适用于Smart ZYNQ 标准版 )以下是本次项目的完整工程 仅供参考