FPGA 25 RGB888(兼容RGB565) 屏幕驱动设计
主要功能 :设计了RGB88 的通用驱动模块
实现(设计)流程:根据RGB888 屏幕的工作原理以及数据传输协议方式,编写输出信号的发送,进而将我们要显示的图像显示到屏幕上。
实验目的 : 直接使用fpga芯片设计RGB88 的驱动,而不是使用屏幕驱芯片来驱动。对于一般的单片机来说,要达到较好的显示效果,必定会占用大量的cpu资源,而fpga 的并行能力则很好的解决了这个问题,由于模块和模块之间的互不影响,所以使用fpga驱动 rgb屏幕的效果方面会比较好.
实验现象 : 通过内部设计一个屏幕驱动显示模块,实现屏幕2*4=8 区域颜色显示的效果
屏幕基础知识:
常用屏幕接口:
1、8080屏幕 (在 51 单片机上,使用最广泛的显示设备就是 1602、 12864 字符点阵 )
2、彩色液晶屏(内部带有显示缓存存储器,这样只要把待显示的内容写入到显存,使用 STM32 的 FSMC 总线,将液晶显示屏映射为一块存储区,直接使用 FSMC 总线对该存储区进行读写 )
3、RGB屏幕(WindowsCE、 Linux 等等。而且它们都能驱动很多的大屏幕 ,屏幕本身不含显存,需要驱动器能够带有显存,并按照 RGB 时序准确的将显存中的数据送到屏幕上显示,而一般的单片机STM32 由于工作速度、总线带宽、存储容量有限,很难支持如此高的数据刷新速率 。)
RGB 基本知识了解:
? 常用的 4.3/5 寸显示屏都是以模组的形式提供的,各大厂家生产的模组,其接口引脚顺序都是兼容的,而且接口时序也都是标准 的。
AT043TN24 模组的接口编号即功能描述如表 5.5.1 所示 :
原理图
接口功能定义
注: 背光要求供电【VLED+ VLED-】)电压为 18.6V 到 21V,电流为 36mA 到 44mA 之间。因此需要额外的升压电路 。
? 该屏幕的颜色数据支持 24 位输入,即每种颜色(RGB)有 8 位表示。由于在一般的数字系统中常常使用 16 位色(RGB565)进行图像显示,并且为了节约 FPGA引脚,本实验中,只选取了 24 位颜色数据中的一部分数据,将其精简为 16 位。为了保证精简数据后,图像颜色能够不失真,因此分别选取 R、 G、 B 数据线的高 5、 6、 5 位,组成新的 16 位数据线。此外,开发的驱动也可以很好的兼容RGB565屏幕,可以实现一个模块,两种屏幕的共同实验问题。 RGB 数据的格式如表 27.2 所示。
常见数据颜色:
2、RGB 接口 TFT 屏扫描方式
VGA原理:
液晶显示器原理:
VGA 和液晶屏驱动相同点:
3、RGB 接口 TFT 屏时序分析
实现行和场的方式:
实现方式:模块内使用两个计数器分别进行、场计数, 然后根据计数器的计数值确定像素数据内容和行、场同步信号的电平状态 。
模块接口设计 :
//TFT 行、场扫描时序参数表
parameter TFT_HS_end=10'd40,
hdat_begin=10'd42,
hdat_end=10'd522,
hpixel_end=10'd524,
TFT_VS_end=10'd9,
vdat_begin=10'd11,
vdat_end=10'd283,
vline_end=10'd285;
行扫描计数器即每个像素时钟自加 1,一旦加满到 524(刚好 525 个时钟周期),计数器清零并重新计数,该部分代码可如下设计:
reg [9:0] hcount_r; //TFT 行扫描计数器
//行扫描计数器
always@(posedge Clk9M or negedge Rst_n)
if(!Rst_n) //复位时,让行扫描计数器清零
hcount_r<=10'd0;
else if(hcount_r== hpixel_end) //当一行数据扫完后,再次清零行扫描计数器
hcount_r<=10'd0;
else //0~524 之间,每个像素时钟时行扫描计数器自加 1
hcount_r<=hcount_r+10'd1;
(很重要!!!)注 :只有每次一行扫描完成(即:行扫描计数器(hcount_r) == 最终的像素点hpixel_end的时候),场扫描计数器vcount_r才会开始变化(清零 或者 +1)
reg [9:0] vcount_r; //TFT 场扫描计数器
//场扫描计数器
always@(posedge Clk9M or negedge Rst_n)
if(!Rst_n) //复位时让场计数器清零
vcount_r<=10'd0;
else if(hcount_r== hpixel_end) begin //每次一行扫描完成,场扫描计数器信号才会开始变化(清零 或者 +1)
if(vcount_r== vline_end) //每次一场扫描结束,清零计数器
vcount_r<=10'd0;
else
vcount_r<=vcount_r+10'd1;//场计数器在 0~285,满足条件,自加 1
end
else //不满足行扫描结束条件器件,让场扫描计数器保持不变
vcount_r<=vcount_r;
行同步信号和场同步信号控制设计(设计接两个信号和屏幕计数器的高低电平的设置):
assign TFT_HS = (hcount_r>TFT_HS_end)?1'b1:1'b0;
assign TFT_VS = (vcount_r>TFT_VS_end)?1'b1:1'b0;
个人表述(屏幕工作流程描述):
对于一帧完整的图片(480*272 的图片)来说,我们每次接收显示,要执行以下流程:
? step1: 先发送一个行同步信号序列(525个时钟周期组成一个行同步信号序列),在 0-41的时钟周期下,给行同步信号引脚 【HS,发送低电平】,41-43时钟周期下,HS 引脚发送【高电平信号】,43-523时钟周期下,HS发送【高电平】,523-525时钟周期下,HS发送【高电平】。这就是一个完整的行时序实现。
? step2: 场扫描的实现,首先,我们要确定或者知道的是,在一帧的图片传输的过程中,只有1个场扫描序列(相对于行信号序列的 272行扫描序列),并且,我们也可以看到,对于一个输入的信号,只有每一个行信号序列的最后一个时钟周期(clk = 525)时,我们才会+1个场信号时钟周期,其余时间保持信号不变。
所以,显然可知,场信号的1个时钟周期是 == 525个行信号时钟周期。我们在【代码中】也可以看到,只有我们的行信号时钟计数器计数了525个时钟周期以后,我们才产生了给场时钟信号【计数器】+1。这就是一帧完整的时钟信号的变化。
数据(图像/图片)输出状态设计 :
首先我们要指导在整个一帧的图片中,只有中间时序的图像显示区域,我们输入的数据才是有效的,其余地方的数据都是无效的,所以,根据时序的情况,我们根据时序的变化来确定什么时候,我们的输入是可直接输出到屏幕,而其他的时刻,我们是不应该输入数据的,或者说让我们的输入数据无效.
//数据、同步信号输出
assign dat_act=((hcount_r>= hdat_begin)&&(hcount_r< hdat_end))
&&((vcount_r>= vdat_begin)&&(vcount_r< vdat_end));
数据选择:
利用二选一多路器实现消隐强制输出 0,代码如下所示。其中, TFT_RGB 是输出到 VGA接口上的数据,而 data_in 则是其他模块传递过来的正确的图像数据。dat_act =1 时,表示我们现在的数据可以直接输入到屏幕,反之我们输出的信号位16‘h0000.
assign TFT_RGB=(dat_act==1)?data_in:16'h0000;
行列扫描位置输出设计 (等价理解为 图片的 X轴 和 Y轴坐标)
这样的话我们可以通过: hcount ,hcount (X, Y)给信号控制模块,让信号控制模块输出在该点的图像数据
assign hcount = hcount_r- hdat_begin;
assign hcount = vcount_r- vdat_begin;
时钟及使能控制设计
assign TFT_CLK = Clk9M;
assign TFT_DE = dat_act;
assign TFT_PWM = Rst_n;
仿真及板级验证 :
module TFT_LCD
(
Clk9M,
Rst_n,
Data_in,
vcount,
hcount,
TFT_RGB,
TFT_VS,
TFT_HS,
TFT_CLK,
TFT_DE,
TFT_PWM
);
input Clk9M ;
input Rst_n ;
input [15:0]Data_in; // RGB 每个像素点的待显示的数据
output [9:0]vcount ; //行扫描地址(X 轴坐标)
output [9:0]hcount ; //场扫描地址(Y 轴坐标)
output [15:0]TFT_RGB ; //三原色数据输出 RGB565
output TFT_VS ; // 场同步输出信号
output TFT_HS ; // 行同步输出信号
output TFT_CLK ; // TFT像素时钟
output TFT_DE ; // 背光使能
output TFT_PWM ; // 背光控制
//TFT 行、场扫描时序参数表
parameter TFT_HS_end=10'd40, //行消隐时间数
hdat_begin=10'd42,
hdat_end=10'd522,
hpixel_end=10'd524,
TFT_VS_end=10'd9, //场消隐时间数
vdat_begin=10'd11,
vdat_end=10'd283,
vline_end=10'd285;
reg [9:0] hcount_r; //TFT 行扫描计数器
wire dat_act; //有效显示标定
//行扫描计数器
always@(posedge Clk9M or negedge Rst_n)
if(!Rst_n) //复位时,让行扫描计数器清零
hcount_r<=10'd0;
else if(hcount_r== hpixel_end) //当一行数据扫完后,再次清零行扫描计数器
hcount_r<=10'd0;
else //0~524 之间,每个像素时钟时行扫描计数器自加 1
hcount_r<=hcount_r+10'd1;
reg [9:0] vcount_r; //TFT 场扫描计数器
//场扫描计数器
always@(posedge Clk9M or negedge Rst_n)
if(!Rst_n) //复位时,让行扫描计数器清零
vcount_r<=10'd0;
else if(hcount_r== hpixel_end) begin //每次一行扫描完成
if(vcount_r == vline_end)
vcount_r<=10'd0;
else
vcount_r <= vcount_r + 1'd1;
end
else //0~524 之间,每个像素时钟时行扫描计数器自加 1
vcount_r <= vcount_r;
//行同步信号和场同步输出信号控制设计:
assign TFT_HS = (hcount_r > TFT_HS_end)? 1'b1:1'b0 ;
assign TFT_VS = (vcount_r > TFT_VS_end)? 1'b1:1'b0 ;
//像素点数据输出选择
// dat_act : 2选1 数据选择器,场消隐和行消隐信号在图像显示区,设置为1,选中1通道,否则选中0通道
assign dat_act=((hcount_r>= hdat_begin)&&(hcount_r< hdat_end))
&&((vcount_r>= vdat_begin)&&(vcount_r< vdat_end));
// TFT_RGB 数据输出: 由 dac_act 信号来选择
assign TFT_RGB = (dat_act)?Data_in:16'h0000;
// X// 实际TFT屏幕 X ,Y 轴坐标输出
//output [9:0]vcount ; //行扫描地址(y 轴坐标)
//output [9:0]hcount ; //场扫描地址(x 轴坐标)
assign vcount=(dat_act)?(vcount_r-vdat_begin):10'd0; // y轴
assign hcount=(dat_act)?(hcount_r-hdat_begin):10'd0; // x轴 ,Y 轴坐标输出
// TFT_CLK 时钟信号 ,这个是完成和外部TFT_LCD模块 通信的时钟
assign TFT_CLK = Clk9M;
// output TFT_DE ; // 背光使能
// TFT_DE 直接与 dat_act 信号相连,即在有效显示区域时使能 LED 屏幕
assign TFT_DE = dat_act ;
// output TFT_PWM ; // 背光控制
// TFT_PWM则直接与复位输入相连,即当复位结束后便一直开启背光
assign TFT_PWM = Rst_n;
endmodule
test_bench 文件:
`timescale 1ns/1ps
`define clk_period 120
module TFT_LCD_tb ;
reg Clk9M ;
reg Rst_n ;
reg [15:0]Data_in; // RGB 每个像素点的待显示的数据
wire [9:0]vcount ; //行扫描地址(X 轴坐标)
wire [9:0]hcount ; //场扫描地址(Y 轴坐标)
wire [15:0]TFT_RGB ; //三原色数据输出 RGB565
wire TFT_VS ; // 场同步输出信号
wire TFT_HS ; // 行同步输出信号
wire TFT_CLK ; // TFT像素时钟
wire TFT_DE ; // 背光使能
wire TFT_PWM ; // 背光控制
reg [7:0]v_cnt = 0; //扫描帧数统计计数器
TFT_LCD TFT_LCD_01
(
.Clk9M(Clk9M),
.Rst_n(Rst_n),
.Data_in(Data_in),
.vcount(vcount),
.hcount(hcount),
.TFT_RGB(TFT_RGB),
.TFT_VS(TFT_VS),
.TFT_HS(TFT_HS),
.TFT_CLK(TFT_CLK),
.TFT_DE(TFT_DE),
.TFT_PWM(TFT_PWM)
);
initial Clk9M = 0;
always #(`clk_period/2) Clk9M = ~Clk9M;
initial begin
Rst_n = 1'b0;
Data_in = 16'd0;
#(`clk_period*200 + 1);
Rst_n = 1'b1;
Data_in = 16'h1010;
end
always@(posedge TFT_VS) //统计总扫描帧数 TFT_VS:上升沿到来时,即代表一帧图像开始
v_cnt = v_cnt + 1'b1;
initial
begin
wait(v_cnt == 3); //等待扫描2帧后结束仿真
$stop;
end
endmodule
top 顶层显示设计文件:
module TFT_CTRL_test(
Clk, //50MHZ时钟
Rst_n,
TFT_RGB,//TFT数据输出
TFT_HS, //TFT行同步信号
TFT_VS, //TFT场同步信号
TFT_CLK,
TFT_DE,
TFT_PWM
);
input Clk;
input Rst_n;
output [15:0]TFT_RGB;
output TFT_HS;
output TFT_VS;
output TFT_CLK;
output TFT_DE;
output TFT_PWM;
reg [15:0]disp_data;
wire [9:0]hcount;
wire [9:0]vcount;
wire Clk33M;
TFT_pll TFT_pll_01 (
.inclk0(Clk),
.c0(Clk33M)
);
TFT_LCD TFT_LCD_01
(
.Clk33M(Clk33M),
.Rst_n(Rst_n),
.Data_in(disp_data),
.vcount(vcount),
.hcount(hcount),
.TFT_RGB(TFT_RGB),
.TFT_VS(TFT_VS),
.TFT_HS(TFT_HS),
.TFT_CLK(TFT_CLK),
.TFT_DE(TFT_DE),
.TFT_PWM(TFT_PWM)
);
//定义颜色编码
localparam
//BLACK = 16'h0000, //黑色
BLACK = 16'h4000, //棕色
BLUE = 16'h001F, //蓝色
RED = 16'hF800, //红色
PURPPLE = 16'hF81F, //紫色
GREEN = 16'h07E0, //绿色
CYAN = 16'h07FF, //青色
YELLOW = 16'hFFE0, //黄色
WHITE = 16'hFFFF; //白色
//定义每个像素块的默认显示颜色值
localparam
R0_C0 = BLACK, //第0行0列像素块
R0_C1 = BLUE, //第0行1列像素块
R1_C0 = RED, //第1行0列像素块
R1_C1 = PURPPLE,//第1行1列像素块
R2_C0 = GREEN, //第2行0列像素块
R2_C1 = CYAN, //第2行1列像素块
R3_C0 = YELLOW, //第3行0列像素块
R3_C1 = WHITE; //第3行1列像素块
wire R0_act = vcount >= 0 && vcount < 120; //正在扫描第0行
wire R1_act = vcount >= 120 && vcount < 240;//正在扫描第1行
wire R2_act = vcount >= 240 && vcount < 360;//正在扫描第2行
wire R3_act = vcount >= 360 && vcount < 480;//正在扫描第3行
wire C0_act = hcount >= 0 && hcount < 400; //正在扫描第0列
wire C1_act = hcount >= 400 && hcount < 800;//正在扫描第1列
wire R0_C0_act = R0_act & C0_act; //第0行0列像素块处于被扫描中标志信号
wire R0_C1_act = R0_act & C1_act; //第0行1列像素块处于被扫描中标志信号
wire R1_C0_act = R1_act & C0_act; //第1行0列像素块处于被扫描中标志信号
wire R1_C1_act = R1_act & C1_act; //第1行1列像素块处于被扫描中标志信号
wire R2_C0_act = R2_act & C0_act; //第2行0列像素块处于被扫描中标志信号
wire R2_C1_act = R2_act & C1_act; //第2行1列像素块处于被扫描中标志信号
wire R3_C0_act = R3_act & C0_act; //第3行0列像素块处于被扫描中标志信号
wire R3_C1_act = R3_act & C1_act; //第3行1列像素块处于被扫描中标志信号
always@(*)
case({R3_C1_act,R3_C0_act,R2_C1_act,R2_C0_act,
R1_C1_act,R1_C0_act,R0_C1_act,R0_C0_act})
8'b0000_0001:disp_data = R0_C0;
8'b0000_0010:disp_data = R0_C1;
8'b0000_0100:disp_data = R1_C0;
8'b0000_1000:disp_data = R1_C1;
8'b0001_0000:disp_data = R2_C0;
8'b0010_0000:disp_data = R2_C1;
8'b0100_0000:disp_data = R3_C0;
8'b1000_0000:disp_data = R3_C1;
default:disp_data = R0_C0;
endcase
endmodule
|