FPGA——TFT显示屏驱动
一、TFT时序
视频电子标准协会(Video Electronics Standards Association)显示器时序标准(Monitor Timing Standard)
https://vesa.org/vesa-standards/
二、设计思路
- 800 * 480 60Hz的意义
该显示屏能显示800 * 480个像素点,一秒钟能刷新60张这样的图片 - TFT显示屏时钟的计算方法
800 * 480是有效像素点,总共像素由上图可知,H Total Time = 1056;V Total Time = 525;
所以一张图片要有1056 * 525个像素,1秒钟显示60张图片,那么一张图片的显示时间是t=(1/60)s,一个像素的的时间是t_pix=t/(1056 * 525),那么TFT时钟的频率就是f = 1/(t_pix)
以上就是TFT显示屏时钟频率的推导方法,更为简便的时钟频率计算是:f = 1056 * 525 * 60 = 33264000Hz = 33MHz - verilog实现
行同步信号,场同步信号只要找到对应的点即可完成
最重要的是对于数据输出的控制,即vcount,hcount,dout
vcount(行有效数据计数),hcount(场有效数据计数)要比输出提前一拍到来
三、TFT驱动代码
module tft_ctr(
clk , //TTF驱动时钟
rst_n , //复位电路
data_in , //RGB三基色输入
data_out , //RGB三基色输出
hsyc , //行同步信号
vsyc , //场同步信号
data_count_vld , //数据传完标志信号
data_vld ,
hcount , //当前扫描点的h坐标
vcount , //当前扫描点的v坐标
blk //数据有效输出信号
);
`include "tft_parameter.v"
localparam H_SYNC_END = `H_SYNC_TIME;
//数据开始设置在数据左黑边过去之后,而不是时序图上的数据开始
localparam H_DATA_BEGIN = `H_SYNC_TIME + `H_BACK_PORCH + `H_LEFT_BORDER;
//数据结束设置在数据右黑边来到之前,而不是时序图上的数据结束
localparam H_DATA_END = `H_SYNC_TIME + `H_BACK_PORCH + `H_LEFT_BORDER + `H_DATA_TIME;
localparam H_SYNC_BEGIN = `H_SYNC_TIME + `H_BACK_PORCH + `H_LEFT_BORDER + `H_DATA_TIME + `H_RIGHT_BORDER + `H_FRONT_PORCH;
localparam V_SYNC_END = `V_SYNC_TIME;
//数据开始设置在数据左黑边过去之后,而不是时序图上的数据开始
localparam V_DATA_BEGIN = `V_SYNC_TIME + `V_BACK_PORCH + `V_TOP_BORDER;
//数据结束设置在数据右黑边来到之前,而不是时序图上的数据结束
localparam V_DATA_END = `V_SYNC_TIME + `V_BACK_PORCH + `V_TOP_BORDER + `V_DATA_TIME;
localparam V_SYNC_BEGIN = `V_SYNC_TIME + `V_BACK_PORCH + `V_TOP_BORDER + `V_DATA_TIME + `V_BOTTOM_BORDER + `V_FRONT_PORCH;
//输入输出
localparam DATA_W = 24;
//计数器
localparam CNT_HSYC_N = `H_TOTAL_TIME;
localparam CNT_HSYC_W = `CNT_HSYC_W;
localparam CNT_VSYC_N = `V_TOTAL_TIME;
localparam CNT_VSYC_W = `CNT_VSYC_W;
input clk;
input rst_n;
input [DATA_W-1:0] data_in;
output [DATA_W-1:0] data_out;
output hsyc;
output vsyc;
output blk;
output data_count_vld;
output data_vld;
output [CNT_HSYC_W-1:0] hcount;
output [CNT_VSYC_W-1:0] vcount;
reg data_count_vld;
reg [DATA_W-1:0] data_out;
reg hsyc;
reg vsyc;
reg blk;
reg data_vld;
reg [CNT_HSYC_W-1:0] hcount;
reg [CNT_VSYC_W-1:0] vcount;
//计数器
reg [CNT_HSYC_W-1:0] cnt_hsyc;
wire add_cnt_hsyc;
wire end_cnt_hsyc;
reg [CNT_VSYC_W-1:0] cnt_vsyc;
wire add_cnt_vsyc;
wire end_cnt_vsyc;
//行同步计数器
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
cnt_hsyc <= 0;
else if(add_cnt_hsyc)begin
if(end_cnt_hsyc)
cnt_hsyc <= 0;
else
cnt_hsyc <= cnt_hsyc + 1'b1;
end
end
assign add_cnt_hsyc = 1;
assign end_cnt_hsyc = add_cnt_hsyc && cnt_hsyc == CNT_HSYC_N - 1;
//场同步计数器
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
cnt_vsyc <= 0;
else if(add_cnt_vsyc)begin
if(end_cnt_vsyc)
cnt_vsyc <= 0;
else
cnt_vsyc <= cnt_vsyc + 1'b1;
end
end
assign add_cnt_vsyc = end_cnt_hsyc;
assign end_cnt_vsyc = add_cnt_vsyc && cnt_vsyc == CNT_VSYC_N - 1;
//输出比输入慢一拍,hcount,vcount要先于输出一拍产生数据
//行有效数据计数器
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
hcount <= 0;
else if(data_count_vld)
hcount <= (cnt_hsyc - (H_DATA_BEGIN - 2));
end
//场有效数据计数器
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
vcount <= 0;
else if(data_count_vld)
vcount <= cnt_vsyc - V_DATA_BEGIN;
end
//行同步信号
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
hsyc <= 0;
else if(cnt_hsyc == H_SYNC_END - 1 && add_cnt_hsyc)
hsyc <= 1;
else if(cnt_hsyc == H_SYNC_BEGIN -1 && add_cnt_hsyc)
hsyc <= 0;
end
//场同步信号
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
vsyc <= 0;
else if(cnt_vsyc == V_SYNC_END - 1 && add_cnt_vsyc)
vsyc <= 1;
else if(cnt_vsyc == V_SYNC_BEGIN - 1 && add_cnt_vsyc)
vsyc <= 0;
end
//数据传输标志位
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
blk <= 0;
else if(data_vld)
blk <= 1;
else
blk <= 0;
end
//数据传输使能信号,比blk提前一拍,为了数据传输与blk同步
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
data_vld <= 0;
else if(data_count_vld)
data_vld <= 1;
else
data_vld <= 0;
end
//有效数据指示信号
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
data_count_vld <= 0;
else if((cnt_hsyc >= H_DATA_BEGIN - 3 && cnt_hsyc < H_DATA_END - 3) && (cnt_vsyc >= V_DATA_BEGIN && cnt_vsyc < V_DATA_END))
data_count_vld <= 1;
else
data_count_vld <= 0;
end
//数据传输
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
data_out <= 0;
else if(data_vld)
data_out <= data_in;
else
data_out <= 0;
end
endmodule
四、条件编译代码
`define resolution_800x480;
`ifdef resolution_800x480
`define H_RIGHT_BORDER 12'd0
`define H_FRONT_PORCH 12'd40
`define H_SYNC_TIME 12'd128
`define H_BACK_PORCH 12'd88
`define H_LEFT_BORDER 12'd0
`define H_DATA_TIME 12'd800
`define H_TOTAL_TIME 12'd1056
`define V_BOTTOM_BORDER 12'd8
`define V_FRONT_PORCH 12'd2
`define V_SYNC_TIME 12'd2
`define V_BACK_PORCH 12'd25
`define V_TOP_BORDER 12'd8
`define V_DATA_TIME 12'd480
`define V_TOTAL_TIME 12'd525
`define CNT_HSYC_W 11
`define CNT_VSYC_W 10
// `elsif
`endif
五、仿真代码
`timescale 1ns / 1ns
module ttf_ctr_tb();
//输入输出
localparam DATA_W = 24;
reg clk;
reg rst_n;
reg [DATA_W-1:0] data_in;
wire [DATA_W-1:0] data_out;
wire hsyc;
wire vsyc;
wire data_vld;
wire blk;
wire [11-1:0] hcount;
wire [10-1:0] vcount;
ttf_ctr ttf_ctr(
.clk (clk), //TTF驱动时钟
.rst_n (rst_n), //复位电路
.data_in (data_in), //RGB三基色输入
.data_out (data_out), //RGB三基色输出
.hsyc (hsyc), //行同步信号
.vsyc (vsyc), //场同步信号
.hcount (hcount), //行数据计数器
.vcount (vcount), //场数据计数器
.data_vld (data_vld),
.blk (blk) //数据有效输出信号
);
//时钟周期,单位ns,在这里修改时钟周期
parameter CYCLE = 20;
//复位时间,此时表示复位3个时钟周期的时间
parameter RST_TIME = 3;
//生成本地时钟50M
initial begin
clk = 0;
forever
#(CYCLE/2)
clk=~clk;
end
//产生复位信号
initial begin
rst_n = 1;
#2;
rst_n = 0;
#(CYCLE * RST_TIME);
rst_n = 1;
end
//输入信号din0赋值方式
// initial begin
// #1;
// //赋初值
// data_in = 0;
// #(10*CYCLE);
// forever begin
// @(posedge clk)begin
// if(data_vld)
// data_in = data_in + 1;
// end
// end
// @(negedge blk)
// #1000;
// $stop;
// end
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
data_in <= 0;
else if(data_vld)
data_in <= data_in + 1;
end
endmodule
六、板级验证代码
注:板级验证使用的是RGB565模式,16位输出端口
module tft_test(
clk ,
rst_n ,
rgb_out ,
de ,
hsyc ,
vsyc ,
bl ,
tft_clk
);
//定义颜色编码
localparam
BLACK = 24'h000000, //黑色
BLUE = 24'h0000F8, //蓝色
RED = 24'hFF0000, //红色
PURPPLE =24'hFF00FF, //紫色
GREEN = 24'h00FF00, //绿色
CYAN = 24'h00FFFF, //青色
YELLOW = 24'hFFFF00, //黄色
WHITE = 24'hFFFFFF; //白色
localparam OUT_W = 16;
input clk;
input rst_n;
output [OUT_W-1:0] rgb_out;
output de;
output hsyc;
output vsyc;
output bl;
output tft_clk;
wire [OUT_W-1:0] rgb_out;
wire de;
wire hsyc;
wire vsyc;
wire bl;
wire tft_clk;
wire [24-1:0] data_out;
wire [11-1:0] hcount;
wire [10-1:0] vcount;
reg [24-1:0] data_in;
tft_ctr tft_ctr(
.clk (tft_clk), //TTF驱动时钟
.rst_n (rst_n), //复位电路
.data_in (data_in), //RGB三基色输入
.data_out (data_out), //RGB三基色输出
.hsyc (hsyc), //行同步信号
.vsyc (vsyc), //场同步信号
.hcount (hcount), //当前扫描点的h坐标
.vcount (vcount), //当前扫描点的v坐标
.blk (de) //数据有效输出信号
);
disp_clk disp_clk(
.clk_out1(tft_clk),
.clk_in1(clk)
);
always @(*)begin
if((hcount >= 0 && hcount < 400) && (vcount >= 0 && vcount < 120))
data_in <= RED;
else if((hcount >= 400 && hcount < 800) && (vcount >= 0 && vcount < 120))
data_in <= BLUE;
else if((hcount >= 0 && hcount < 400) && (vcount >= 120 && vcount < 240))
data_in <= BLACK;
else if((hcount >= 400 && hcount < 800) && (vcount >= 120 && vcount < 240))
data_in <= WHITE;
else if((hcount >= 0 && hcount < 400) && (vcount >= 240 && vcount < 360))
data_in <= PURPPLE;
else if((hcount >= 400 && hcount < 800) && (vcount >= 240 && vcount < 360))
data_in <= GREEN;
else if((hcount >= 0 && hcount < 400) && (vcount >= 360 && vcount < 480))
data_in <= CYAN;
else if((hcount >= 400 && hcount < 800) && (vcount >= 360 && vcount < 480))
data_in <= YELLOW;
else
data_in <=0;
end
assign rgb_out = {data_out[23:19],data_out[15:10],data_out[7:3]};
assign bl = 1'b1;
endmodule
七、板级验证仿真代码
`timescale 1ns / 1ns
module tf_test_tb();
localparam OUT_W = 16;
reg clk;
reg rst_n;
wire [OUT_W-1:0] rgb_out;
wire de;
wire hsyc;
wire vsyc;
wire bl;
wire tft_clk;
tft_test tft_test(
.clk (clk),
.rst_n (rst_n),
.rgb_out (rgb_out),
.de (de),
.hsyc (hsyc),
.vsyc (vsyc),
.bl (bl),
.tft_clk (tft_clk)
);
//时钟周期,单位ns,在这里修改时钟周期
parameter CYCLE = 20;
//复位时间,此时表示复位3个时钟周期的时间
parameter RST_TIME = 3;
//生成本地时钟50M
initial begin
clk = 0;
forever
#(CYCLE/2)
clk=~clk;
end
//产生复位信号
initial begin
rst_n = 1;
#2;
rst_n = 0;
#(CYCLE * RST_TIME);
rst_n = 1;
#1547850;
end
endmodule