RGB TFT-LCD 简介
TFT-LCD 的全称是 Thin Film Transistor-Liquid Crystal Display,即薄膜晶体管液晶显示屏,它显示的每个像素点都是由集成在液晶后面的薄膜晶体管独立驱动,因此 TFT-LCD 具有较高的响应速度以及较好的图像质量。液晶显示器是现在最常用的显示器,如手机、电脑等各种人机交互设备基本都用到了 LCD。在使用 TFT-LCD 时需要重点关注以下参数:
- 分辨率
LCD 显示器是由一个一个的像素点组成,一个像素点就类似一个 RGB 小灯(也就是由 R(红色)、G(绿色)、B(蓝色)这三种颜色组成,而 RGB 就是光的三原色,通过不同比例的组合可以程任意颜色),如 1080P 的意思就是一个 LCD 屏幕上的像素数量是 19201080 个,即屏幕一列有 1080 个像素点,一共有 1920 列,如下是一个 1080P 显示器的像素示意图,X 轴就是 LCD 显示器的横轴,Y 轴就是显示器的竖轴,图中的小方块就是像素点,一共有 19201080=2073600 个像素点,左上角的 A 点是第一个像素点,右下角的 C 点就是最后一个像素点。
- 像素格式
上面提到一个像素点就相当于一个 RGB 小灯,通过控制 R、G、B 这三种颜色的亮度就可以显示出各种各样的色彩。一般一个像素点的 R、G、B 这三部分分别使用 8bit 的数据描述其亮度,那么一个像素点就是 8bit*3=24bit,也就是说一个像素点 3 个字节,这种像素格式称为RGB888。当然常用的像素点格式还有 RGB565,只需要两个字节,但在色彩鲜艳度上较差一些。如下是一个 RGB888 像素格式示意图,其中 bit23~bit16 是 RED 通道,bit15~bit8 是 GREEN 通道,bit7~bit0 是 BLUE 通道。
- LCD 屏幕接口
LCD 屏幕或者说显示器有很多种接口,常见的 VGA、HDMI、DP、RGB等, VGA、HDMI、DP 常用于桌面显示器, RGB 常用于各自嵌入式设备,RGB LCD 接口的信号线如下表所示:
- LCD 时间参数
如果将 LCD 显示一帧图像的过程想象成绘画,那么在显示的过程中就是用一根“笔”在不同的像素点画上不同的颜色。这根笔按照从左至右、从上到下的顺序扫描每个像素点,并且在像素画上对应的颜色,当画到最后一个像素点的时候一幅图像就绘制好了,如下是一个 LCD 显示一帧图像的扫描示意图:
其中有几个重要参数:
HSYNC :行同步信号,当产生此信号的话就表示开始显示新的一行了。
VSYNC : 帧同步信号,当产生此信号的话就表示开始显示新的一帧图像了。
HBP :行显示后沿,左边黑框宽度。
HFP:行显示前沿,右边黑框宽度。
VBP:帧显示后沿,上边黑框宽度。
VFP:帧显示前沿,下边黑框宽度。 - RGB LCD 屏幕时序
如下是显示一行的时序图:
HSYNC:行同步信号,当此信号有效的时候就表示开始显示新的一行数据。
HSPW:行同步信号宽度,也就是 HSYNC 信号持续时间。HSYNC 信号不是一个脉冲,而是需要持续一段时间才是有效的,单位为 CLK(如果是 DE 同步的 LCD 则不需控制 HSYNC ,但是计时时序时要考虑其所占时间)。
HBP:行显示后沿,单位是 CLK。
HOZVAL:行有效显示区域,假如屏幕分辨率为 1024600,那么 HOZVAL 就是 1024,单位为 CLK。
HFP:行显示前沿,单位是 CLK。
当 HSYNC 信号发出以后,需要等待 HSPW+HBP 个 CLK 时间才会接收到真正有效的像素数据。当显示完一行数据以后需要等待 HFP 个 CLK 时间才能发出下一个 HSYNC 信号,所以显示一行所需要的时间就是:HSPW + HBP + HOZVAL + HFP。
如下是显示一帧的时序:
VSYNC:帧同步信号,当此信号有效的时候就表示开始显示新的一帧数据。
VSPW:帧同步信号宽度,也就是 VSYNC 信号持续时间,单位为 1 行的时间(如果是 DE 同步的 LCD 则不需控制 VSYNC ,但是计时时序时要考虑其所占时间)。
VBP:帧显示后沿,单位为 1 行的时间。
LINE:帧有效显示区域,假如屏幕分辨率为 1024600,那么 LINE 就是 600 行的时间。
VFP:帧显示前沿,单位为 1 行的时间。
显示一帧所需要的时间就是:VSPW+VBP+LINE+VFP 个行时间,最终的计算公式:T = (VSPW+VBP+LINE+VFP) * (HSPW + HBP + HOZVAL + HFP)。
在使用一款 RGB LCD 屏的时候需要知道:HSPW(行同步)、HBP(行显示后沿)、HOZVAL(行有效显示区域)、HFP(行显示前沿)、VSPW(场同步)、VBP(场显示后沿)、LINE(场有效显示区域)和 VFP(场显示后沿)。 - LCD 同步模式
RGB LCD 液晶屏一般有两种数据同步方式:行场同步模式(HV Mode)、数据使能同步模式(DE Mode);行场同步模式采用行同步信号(HSYNC)和场同步信号(VSYNC)作为数据的同步信号,有些 LCD 还需要在数据有效时设置 DE 为有效(高电平),如上两幅图就是行场同步模式;数据使能同步模式使用 DE 信号进行数据同步,当同时扫描到帧有效显示区域和行有效显示区域时(无效显示区域包括了 HSPW、VSPW 在内),DE 信号才设置为有效(高电平),选择 DE 同步模式时,行场同步信号 VS 和 HS 一般保持为高电平。
硬件设计
RGB-LCD 接口部分的原理图如下图所示:
系统框图
系统框图分为4个部分:
- 全局分为,因为系统中存在多个时钟,所以需要对复位信号进行延长,以确保不同时钟域的模块均能收到复位信号。
- 时钟分频模块,负责将系统时钟分频,得到合适的 LCD 时钟。
- LCD 显示模块,根据 LCD 接口模块提供的坐标输出对应的像素颜色,从而在 LCD 上显示相应的图案。
- LCD 接口驱动模块,按照 LCD 接口时序控制 LCD 显示。
代码编写
全局复位模块
全局复位模块可以参考14 FIFO IP核实验中的“全局复位模块”部分。
时钟分频模块
时钟分频模块参考06 分频器设计实验中的“任意分频器实现”部分。
LCD屏幕接口驱动模块设计
LCD屏幕接口驱动模块主要是根据 LCD 屏幕接口时序驱动 RGB-LCD 显示屏,将 LCD 显示模块输出的像素信息按照 RGB-LCD 的 DE 同步时序显示到 LCD 显示器上,如下是 LCD 接口模块的框图:
LCD 接口驱动模块一共包含3路输入信号和9路输出信号;其中输入信号的功能如下:
- lcd_pclk:时钟信号 ,为 RGB-LCD 显示屏接口模块的工作时钟。
- g_rst_n :复位信号,低电平有效,由全局复位模块提供。
- pixel_data:像素点色彩信息数据,由图像显示模块提供。
输出信号的功能如下: - pixel_xpos:输出下一个时钟所显示的像素点x轴位置。
- pixel_ypos:输出下一个时钟所显示的像素点y轴位置。
- data_req:像素数据请求信号,同pixel_xpos和pixel_ypos一起输出。
- lcd_de:RGB数据有效信号。
- lcd_hs: LCD 行同步信号,因为采用 DE 模式,所以可直接赋值为1。
- lcd_vs: LCD 场同步信号,因为采用 DE 模式,所以可直接赋值为1。
- lcd_bl: LCD 背光控制信号,直接赋值为 1。
- lcd_clk:LCD 时钟信号。
- lcd_rst:LCD 复位信号,直接赋值为 1。
- lcd_rgb:输出给 LCD 屏幕的 RGB 数据。
这里假设 LCD 屏幕的参数如下:
对应的时序图如下:
屏幕行周期为1056个像素,所以行计数器范围为0~1055,其中0~215为行同步+行显示后沿,216~1015为行显示区域,1016~1055为行显示前沿。
屏幕帧显示周期为525个行,所以帧计数器范围为0~524,其中0~34为帧同步+帧显示后沿,35~514为帧显示有效区域,515~524为帧显示前沿。
lcd_de 信号在行和帧同时有效时拉高。
data_req、pixel_xpos、pixel_ypos 信号提前一个时钟周期给出。
LCD屏幕接口模块代码如下:
module lcd_driver #(
parameter HSPW = 128, //行同步信号宽度
parameter HBP = 88, //行显示后沿
parameter HOZVAL = 800, //行有效显示区域
parameter HFP = 40, //行显示前沿
parameter VSPW = 2, //帧同步信号宽度
parameter VBP = 33, //帧显示后沿
parameter LINE = 480, //帧有效显示区域
parameter VFP = 10, //帧显示前沿
parameter DATA_REQ_ADVANCE = 1 //数据请求提前量
)(
input g_rst_n, //复位
input lcd_pclk, //时钟
//显示控制模块接口
output [32:0] pixel_xpos, //接下来要显示的横坐标
output [32:0] pixel_ypos, //接下来要显示的纵坐标
output data_req, //显示数据请求
input [23:0] pixel_data, //显示数据
//RGB屏幕接口
output lcd_de, //LCD 数据使能信号
output lcd_hs, //LCD 行同步信号
output lcd_vs, //LCD 场同步信号
output lcd_bl, //LCD 背光控制信号
output lcd_clk, //LCD 像素时钟
output lcd_rst, //LCD 复位
output [23:0] lcd_rgb //LCD RGB888 颜色数据
);
//行扫描周期
localparam HTOTAL = HSPW + HBP + HOZVAL + HFP;
//帧扫描周期
localparam VTOTAL = VSPW + VBP + LINE + VFP;
//行计数器
reg [31:0] h_cnt;
//场计数器
reg [31:0] v_cnt;
//RGB LCD 采用DE模式时,行场同步信号拉高即可
assign lcd_hs = 1'b1;
assign lcd_vs = 1'b1;
//LCD背光控制信号
assign lcd_bl = 1'b1;
//LCD复位
assign lcd_rst= 1'b1;
//LCD时钟
assign lcd_clk = lcd_pclk;
//LCD 数据使能信号
assign lcd_de = ((h_cnt >= (HSPW + HBP)) && (h_cnt < (HSPW + HBP + HOZVAL))) &&
((v_cnt >= (VSPW + VBP)) && (v_cnt < (VSPW + VBP + LINE))) ? 1 : 0;
//RGB888数据输出
assign lcd_rgb = lcd_de ? pixel_data : 24'd0;
//像素数据请求信号
assign data_req = ((h_cnt >= (HSPW + HBP - DATA_REQ_ADVANCE)) && (h_cnt < (HSPW + HBP + HOZVAL - DATA_REQ_ADVANCE))) &&
((v_cnt >= (VSPW + VBP)) && (v_cnt < (VSPW + VBP + LINE))) ? 1 : 0;
//像素请求坐标
assign pixel_xpos = data_req ? (h_cnt + DATA_REQ_ADVANCE - HSPW - HBP) : 0;
assign pixel_ypos = data_req ? (v_cnt - VSPW - VBP) : 0;
//行计数器对像素计数
always @(posedge lcd_pclk) begin
if(!g_rst_n)
h_cnt <= 0;
else if(h_cnt < (HTOTAL - 1))
h_cnt <= h_cnt + 1;
else
h_cnt <= 0;
end
//帧计数器对行计数
always @(posedge lcd_pclk) begin
if(!g_rst_n)
v_cnt <= 0;
else if(h_cnt == (HTOTAL - 1)) begin
if(v_cnt < (VTOTAL - 1))
v_cnt <= v_cnt + 1;
else
v_cnt <= 0;
end
end
endmodule
LCD彩条显示模块设计
LCD彩条显示模块根据LCD接口驱动模块给的像素坐标和像素请求信号输出像素的颜色,其系统框图如下:
其时序图如下:
它将一个800*480的LCD分为了5个部分,0~159显示WHITE,160~319列显示BLACK,320~479显示RED,480~639显示GREEN,640~799显示BLUE,其对应的代码如下:
module lcd_display #(
parameter HOZVAL = 800, //行有效显示区域
parameter LINE = 480 //帧有效显示区域
)(
input g_rst_n, //复位
input lcd_pclk, //时钟
input [32:0] pixel_xpos, //接下来要显示的横坐标
input [32:0] pixel_ypos, //接下来要显示的纵坐标
input data_req, //显示数据请求
output reg [23:0] pixel_data //显示数据
);
//颜色定义
localparam WHITE = 24'hFFFFFF; //白色
localparam BLACK = 24'h000000; //黑色
localparam RED = 24'hFF0000; //红色
localparam GREEN = 24'h00FF00; //绿色
localparam BLUE = 24'h0000FF; //蓝色
always @(posedge lcd_pclk) begin
if(!g_rst_n)
pixel_data <= BLACK;
else if(data_req == 1'b1) begin
if((pixel_xpos >= 0) && (pixel_xpos < (HOZVAL / 5 * 1)))
pixel_data <= WHITE;
else if((pixel_xpos >= (HOZVAL / 5 * 1)) && (pixel_xpos < (HOZVAL / 5 * 2)))
pixel_data <= BLACK;
else if((pixel_xpos >= (HOZVAL / 5 * 2)) && (pixel_xpos < (HOZVAL / 5 * 3)))
pixel_data <= RED;
else if((pixel_xpos >= (HOZVAL / 5 * 3)) && (pixel_xpos < (HOZVAL / 5 * 4)))
pixel_data <= GREEN;
else
pixel_data <= BLUE;
end
else
pixel_data <= BLACK;
end
endmodule
顶层模块设计
顶层模块用于例化这些子模块,并将这些子模块联系到一起,形成一个完整的系统,其代码如下:
module lcd_rgb_colorbar #(
parameter HSPW = 128, //行同步信号宽度
parameter HBP = 88, //行显示后沿
parameter HOZVAL = 800, //行有效显示区域
parameter HFP = 40, //行显示前沿
parameter VSPW = 2, //帧同步信号宽度
parameter VBP = 33, //帧显示后沿
parameter LINE = 480, //帧有效显示区域
parameter VFP = 10, //帧显示前沿
parameter PCLK_DIV = 2, //像素时钟分频系数
parameter RESET_KEEP = 20 //复位信号保持多少个像素时钟周期
)(
input sys_clk, //时钟信号
input sys_rst_n, //复位信号
//RGB屏幕接口
output lcd_de, //LCD 数据使能信号
output lcd_hs, //LCD 行同步信号
output lcd_vs, //LCD 场同步信号
output lcd_bl, //LCD 背光控制信号
output lcd_clk, //LCD 像素时钟
output lcd_rst, //LCD 复位
output [23:0] lcd_rgb //LCD RGB888 颜色数据
);
//数据请求提前一个像素时钟给出
localparam DATA_REQ_ADVANCE = 1;
//接下来要显示的横坐标,提前DATA_REQ_ADVANCE个像素时钟输出
wire [32:0] pixel_xpos;
//接下来要显示的纵坐标,提前DATA_REQ_ADVANCE个像素时钟输出
wire [32:0] pixel_ypos;
//显示数据请求,提前DATA_REQ_ADVANCE个像素时钟输出
wire data_req;
//需要显示数据
wire [23:0] pixel_data;
//像素时钟
wire pclk;
//全局复位信号
wire g_rst_n;
//复位忙标志
wire rst_busy;
//将系统时钟分频,得到像素时钟
clk_div #(
//参数列表
.DIV(PCLK_DIV) //分频系数
)
u_clk_div_inst0(
.sys_clk(sys_clk), //系统时钟
.sys_rst_n(sys_rst_n), //外部输入的复位信号
.out_clk(pclk)
);
//将复位信号延长,以确保所有模块都能复位
global_rst #(
.RESET_KEEP_CYCLE(RESET_KEEP * PCLK_DIV), //复位信号最小保持周期
.BUSY_KEEP_CYCLE(10) //复位忙标志保持周期
)
u_global_rst_inst0(
.sys_clk(sys_clk), //系统时钟
.sys_rst_n(sys_rst_n), //外部输入的复位信号
.global_rst_n(g_rst_n), //全局复位信号
.rst_busy(rst_busy) //复位忙标志
);
lcd_display #(
.HOZVAL(HOZVAL), //行有效显示区域
.LINE(LINE) //帧有效显示区域
)
u_lcd_display_inst0(
.g_rst_n(g_rst_n), //全局复位
.lcd_pclk(pclk), //像素时钟
.pixel_xpos(pixel_xpos), //接下来要显示的横坐标
.pixel_ypos(pixel_ypos), //接下来要显示的纵坐标
.data_req(data_req), //显示数据请求
.pixel_data(pixel_data) //显示数据
);
//LCD屏幕接口
lcd_driver #(
.HSPW(HSPW), //行同步信号宽度
.HBP(HBP), //行显示后沿
.HOZVAL(HOZVAL), //行有效显示区域
.HFP(HFP), //行显示前沿
.VSPW(VSPW), //帧同步信号宽度
.VBP(VBP), //帧显示后沿
.LINE(LINE), //帧有效显示区域
.VFP(VFP), //帧显示前沿
.DATA_REQ_ADVANCE(DATA_REQ_ADVANCE) //数据请求提前量
)
u_lcd_driver_inst0(
.g_rst_n(g_rst_n), //全局复位
.lcd_pclk(pclk), //像素时钟
//显示控制模块接口
.pixel_xpos(pixel_xpos), //接下来要显示的横坐标
.pixel_ypos(pixel_ypos), //接下来要显示的纵坐标
.data_req(data_req), //显示数据请求
.pixel_data(pixel_data), //显示数据
//RGB屏幕接口
.lcd_de(lcd_de), //LCD 数据使能信号
.lcd_hs(lcd_hs), //LCD 行同步信号
.lcd_vs(lcd_vs), //LCD 场同步信号
.lcd_bl(lcd_bl), //LCD 背光控制信号
.lcd_clk(lcd_clk), //LCD 像素时钟
.lcd_rst(lcd_rst), //LCD 复位
.lcd_rgb(lcd_rgb) //LCD RGB888 颜色数据
);
endmodule
仿真激励代码编写
仿真激励代码用于产生仿真所需的激励信号,它主要分为3部分:初始化复位、周期时钟产生、例化仿真IP核,其代码如下:
`timescale 1ns / 1ps
module tb_lcd_rgb_colorbar();
reg sys_clk;
reg sys_rst_n;
wire lcd_de;
wire lcd_hs;
wire lcd_vs;
wire lcd_bl;
wire lcd_clk;
wire lcd_rst;
wire [23:0] lcd_rgb;
initial begin
sys_clk = 1'b0;
sys_rst_n = 1'b0;
#200
sys_rst_n = 1'b1;
end
always #10 sys_clk = ~sys_clk;
lcd_rgb_colorbar u_lcd_rgb_colorbar(
.sys_clk(sys_clk),
.sys_rst_n(sys_rst_n),
.lcd_de(lcd_de),
.lcd_hs(lcd_hs),
.lcd_vs(lcd_vs),
.lcd_bl(lcd_bl),
.lcd_clk(lcd_clk),
.lcd_rst(lcd_rst),
.lcd_rgb(lcd_rgb)
);
endmodule
约束输入
create_clock -period 20.000 -name sys_clk -waveform {0.000 10.000} [get_ports sys_clk]
set_property -dict {PACKAGE_PIN R4 IOSTANDARD LVCMOS15} [get_ports sys_clk]
set_property -dict {PACKAGE_PIN U7 IOSTANDARD LVCMOS15} [get_ports sys_rst_n]
set_property -dict {PACKAGE_PIN L14 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb[0]}]
set_property -dict {PACKAGE_PIN L13 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb[1]}]
set_property -dict {PACKAGE_PIN M13 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb[2]}]
set_property -dict {PACKAGE_PIN K19 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb[3]}]
set_property -dict {PACKAGE_PIN K18 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb[4]}]
set_property -dict {PACKAGE_PIN K16 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb[5]}]
set_property -dict {PACKAGE_PIN L16 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb[6]}]
set_property -dict {PACKAGE_PIN M16 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb[7]}]
set_property -dict {PACKAGE_PIN H14 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb[8]}]
set_property -dict {PACKAGE_PIN J14 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb[9]}]
set_property -dict {PACKAGE_PIN H15 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb[10]}]
set_property -dict {PACKAGE_PIN J15 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb[11]}]
set_property -dict {PACKAGE_PIN J16 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb[12]}]
set_property -dict {PACKAGE_PIN K14 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb[13]}]
set_property -dict {PACKAGE_PIN K13 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb[14]}]
set_property -dict {PACKAGE_PIN L15 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb[15]}]
set_property -dict {PACKAGE_PIN G17 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb[16]}]
set_property -dict {PACKAGE_PIN G18 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb[17]}]
set_property -dict {PACKAGE_PIN G15 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb[18]}]
set_property -dict {PACKAGE_PIN G16 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb[19]}]
set_property -dict {PACKAGE_PIN H19 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb[20]}]
set_property -dict {PACKAGE_PIN J19 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb[21]}]
set_property -dict {PACKAGE_PIN G13 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb[22]}]
set_property -dict {PACKAGE_PIN H13 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb[23]}]
set_property -dict {PACKAGE_PIN H18 IOSTANDARD LVCMOS33} [get_ports lcd_hs]
set_property -dict {PACKAGE_PIN J17 IOSTANDARD LVCMOS33} [get_ports lcd_vs]
set_property -dict {PACKAGE_PIN K17 IOSTANDARD LVCMOS33} [get_ports lcd_de]
set_property -dict {PACKAGE_PIN W9 IOSTANDARD LVCMOS15} [get_ports lcd_bl]
set_property -dict {PACKAGE_PIN H17 IOSTANDARD LVCMOS33} [get_ports lcd_clk]
set_property -dict {PACKAGE_PIN Y9 IOSTANDARD LVCMOS15} [get_ports lcd_rst]