1 功能
使用 FPGA 驱动 TFT-LCD 彩屏 R61509V3 显示彩色图片,屏幕分辨率为240*400,刷新速度100Hz。
2 环境
硬件:
[tr]项目说明[/tr]
FPGA | xc7a35tcsg324-1 |
TFT-LCD驱动IC | R61509V |
软件: [tr]项目说明[/tr]
vivado | Vivado v2017.1 (64-bit) |
matlab | R2018b 64-bit |
modelsim | ModelSim SE-64 10.5 Revision: 2016.02 |
3 程序说明
系统框图:
fpga开发板输入时钟为100MHz,通过MMCM输出30MHz时钟作为工作时钟。R61509V3驱动模块完成器件的初始化和显示。R61509V 显示模块例化ROM IP核 ,利用片内存储资源存储图片信息,并根据驱动模块的请求输出数据。80-16bit并口模块将驱动模块输出的指令按照80系统16位并口的时序输出。
3.1 .coe 文件的产生
xilinx 家的 FPGA 例化单端口 ROM 时,要指定对应的初始化文件。产生coe文件的matlab代码(源文件在相关资料里面工程目录下的matlab文件夹):
clear all;
close all;
%lcd参数
P_lcd_X = 240; %宽
P_lcd_Y = 400; %高
%读取原图片
img_rgb = imread("./pic.jpg");
% figure;imshow(img_rgb);
%根据lcd的参数剪切与压缩图片
img_rgb_size = size(img_rgb);
img_rgb_cut = img_rgb(1:floor(img_rgb_size(1)/P_lcd_Y):400*floor(img_rgb_size(1)/P_lcd_Y),...
floor(img_rgb_size(2)/2-(P_lcd_X/2)*(img_rgb_size(1)/P_lcd_Y)):(img_rgb_size(1)/P_lcd_Y):floor(img_rgb_size(2)/2+(P_lcd_X/2)*(img_rgb_size(1)/P_lcd_Y))-1,:);
figure;imshow(img_rgb_cut);
%RGB数据转为2进制
img_rgb_cut_R = img_rgb_cut(:,:,1);
img_rgb_cut_G = img_rgb_cut(:,:,2);
img_rgb_cut_B = img_rgb_cut(:,:,3);
img_rgb_cut_bin_R = dec2bin(img_rgb_cut_R');
img_rgb_cut_bin_G = dec2bin(img_rgb_cut_G');
img_rgb_cut_bin_B = dec2bin(img_rgb_cut_B');
img_rgb_cut_bin_R = img_rgb_cut_bin_R(:,1:5);
img_rgb_cut_bin_G = img_rgb_cut_bin_G(:,1:6);
img_rgb_cut_bin_B = img_rgb_cut_bin_B(:,1:5);
%写到.coe文件,用来初始化rom
if(0)
coef_bin = [img_rgb_cut_bin_R img_rgb_cut_bin_G img_rgb_cut_bin_B];
fid = fopen('.coepic1.coe','w');
fprintf(fid,'MEMORY_INITIALIZATION_RADIX=2;n');
fprintf(fid,'MEMORY_INITIALIZATION_VECTOR=n');
for i = 1:1:length(coef_bin)
fprintf(fid,'%s',coef_bin(i,:));
if i==length(coef_bin)
fprintf(fid,';');
else
fprintf(fid,',n');
end
end
fclose(fid);
end
首先导入需要转换的图片,然后根据 LCD 的尺寸剪切与压缩图片,然后分别将范围为 0~255 的 RGB 数据转换为 8 位二进制数,最后根据 RGB565 的格式取 R 分量的高 5 位、 G 分量的高 6 位与B分量的高 5 位组合成对应像素点的数据,240*400 像素点对应 coe 文件初始化向量的长度位 96000。在本示例中,原图片,剪切后的图片,生成的 .coe 文件为:
3.2 模块设计
3.2.1 顶层模块
顶层模块对应的 RTL 原理图为:
时钟管理器模块(mmcm_100M) 通过例化 MMCM 来实现。由后面 3.2.4 80-16bit并口模块 的说明可知本设计最大工作时钟受限于 写入低电平脉冲宽度(一个时钟周期,min(PWLW)=30ns),取工作时钟为 30MHz。
R61509V3 驱动模块(R61509V3_driver) 在上电后首先完成 R61509V3 的配置,配置完成后根据设置的刷新频率,输出对应像素点的坐标并且根据对应的 RGB 数据输入刷新屏幕。本模块只是输出80并口的 数据,数据类型,读写类型,触发信号。80并口的时序转换由“80-16bit并口模块”完成。
80-16bit并口模块(lcd_80_16b_dri) 根据用户接口输入的数据类型按照 80系统16位并口 的时序输出。
图片显示模块(pic_disp_rom) 例化rom IP核存储图片的 RGB 数据,根据输入的坐标将对应的数据输出。
顶层模块的代码如下:
`timescale 1ns / 1ps
//
// Company:
// Engineer: w0shishabi
//
// Create Date: 2021/02/09 19:54:44
// Design Name:
// Module Name: top
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//
module top(
input I_sys_clk ,
input I_reset_n ,
output O_lcd_cs ,
output O_lcd_rs ,
output O_lcd_wr ,
output O_lcd_rd ,
output O_lcd_reset_n ,
inout [15:0] IO_lcd_data
);
//wire define
wire W_reset_n ;
wire W_mmcm_locked ;
wire W_clk_100M ;
wire W_clk_30M ;
wire [15:0] W_data_w ;
wire W_datah_instl ;
wire W_rh_wl ;
wire W_80_exec ;
wire W_80_done ;
wire W_lcd_init_done ;
wire [15:0] W_rgb_data ;
wire [7:0] W_pixel_xpos ;
wire [8:0] W_pixel_ypos ;
wire W_pos_en ;
//main
assign W_reset_n = I_reset_n && W_mmcm_locked;
mmcm_100M mmcm_100M_u
(
// Clock out ports
.clk_out100M(W_clk_100M ), // output clk_out100M
.clk_out30M (W_clk_30M ), // output clk_out20M
// Status and control signals
.reset (~I_reset_n ), // input reset
.locked (W_mmcm_locked), // output locked
// Clock in ports
.clk_in1 (I_sys_clk ));// input clk_in1
pic_disp_rom pic_disp_rom_u(
.I_sys_clk (W_clk_30M ),
.I_reset_n (W_reset_n ),
.I_pos_x (W_pixel_xpos),
.I_pos_y (W_pixel_ypos),
.I_pos_en (W_pos_en ),
.O_data (W_rgb_data )
);
R61509V3_driver R61509V3_driver_u(
.I_sys_clk (W_clk_30M ),
.I_reset_n (W_reset_n ),
.I_rgb_data (W_rgb_data ),
.O_pixel_xpos (W_pixel_xpos ),
.O_pixel_ypos (W_pixel_ypos ),
.O_pos_en (W_pos_en ),
.O_data_w (W_data_w ),
.O_datah_instl (W_datah_instl ),
.O_rh_wl (W_rh_wl ),
.O_80_exec (W_80_exec ),
.I_80_done (W_80_done ),
.O_lcd_init_done(W_lcd_init_done)
);
lcd_80_16b_dri lcd_80_16b_dri_u(
.I_sys_clk (W_clk_30M ),
.I_reset_n (W_reset_n ),
.I_data_w (W_data_w ),
.O_data_r ( ),
.I_datah_instl (W_datah_instl),
.I_rh_wl (W_rh_wl ),
.I_80_exec (W_80_exec ),
.O_80_done (W_80_done ),
.O_lcd_cs (O_lcd_cs ),
.O_lcd_rs (O_lcd_rs ),
.O_lcd_wr (O_lcd_wr ),
.O_lcd_rd (O_lcd_rd ),
.O_lcd_reset_n (O_lcd_reset_n),
.IO_lcd_data (IO_lcd_data )
);
endmodule
顶层模块完成其余模块的例化,其中各模块的复位信号为 MMCM 的 locked 端口输出与系统复位的逻辑与,确保其他模块在时钟稳定后开始工作。
3.2.2 R61509V3 驱动模块
R61509V3 驱动模块的代码如下:
`timescale 1ns / 1ps
//
// Company:
// Engineer: w0shishabi
//
// Create Date: 2021/02/09 09:56:33
// Design Name:
// Module Name: R61509V3_driver
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//
module R61509V3_driver(
input I_sys_clk ,
input I_reset_n ,
input [15:0] I_rgb_data ,
output reg [7:0] O_pixel_xpos ,
output reg [8:0] O_pixel_ypos ,
output reg O_pos_en ,
output [15:0] O_data_w ,
output O_datah_instl ,
output O_rh_wl ,
output O_80_exec ,
input I_80_done ,
output O_lcd_init_done
);
localparam P_60FPS_CNT = 20'd333333 ;
localparam P_65FPS_CNT = 20'd307692 ;
localparam P_100FPS_CNT = 20'd290912 ;
localparam P_SIM_CNT = 20'd20000 ;//simulation
localparam P_FPS_CNT = P_100FPS_CNT ;
//reg define
reg [19:0] R_clk_div_cnt ;
reg [15:0] R_disp_data_w ;
reg R_disp_datah_instl ;
reg R_disp_rh_wl ;
reg R_disp_80_exec ;
reg R_disp_ena ;
//wire define
wire [15:0] W_init_data_w ;
wire W_init_datah_instl ;
wire W_init_rh_wl ;
wire W_init_80_exec ;
wire W_lcd_init_done ;
wire W_Refresh_frame_req;
//main
assign O_data_w = W_lcd_init_done ? R_disp_data_w : W_init_data_w ;
assign O_datah_instl = W_lcd_init_done ? R_disp_datah_instl : W_init_datah_instl;
assign O_rh_wl = W_lcd_init_done ? R_disp_rh_wl : W_init_rh_wl ;
assign O_80_exec = W_lcd_init_done ? R_disp_80_exec : W_init_80_exec ;
assign O_lcd_init_done = W_lcd_init_done;
R61509V3_config R61509V3_config_u(
.I_sys_clk (I_sys_clk ),
.I_reset_n (I_reset_n ),
.O_data_w (W_init_data_w ),
.O_datah_instl (W_init_datah_instl),
.O_rh_wl (W_init_rh_wl ),
.O_80_exec (W_init_80_exec ),
.I_80_done (I_80_done ),
.O_lcd_init_done(W_lcd_init_done )
);
always @ (posedge I_sys_clk or negedge I_reset_n)
begin
if(~I_reset_n)
begin
R_clk_div_cnt <= 20'd0;
end
else
begin
if (R_clk_div_cnt == P_FPS_CNT)
R_clk_div_cnt <= 20'd0;
else
R_clk_div_cnt <= R_clk_div_cnt + 1'b1;
end
end
assign W_Refresh_frame_req = (R_clk_div_cnt == P_FPS_CNT);
always @ (posedge I_sys_clk or negedge I_reset_n)
begin
if(~I_reset_n)
begin
R_disp_data_w <= 1'b0;
R_disp_datah_instl <= 1'b0;
R_disp_rh_wl <= 1'b0;
R_disp_80_exec <= 1'b0;
end
else
begin
if (W_Refresh_frame_req && W_lcd_init_done)
begin
{R_disp_rh_wl,R_disp_datah_instl,R_disp_data_w} <= {1'b0,1'b0,16'h0202};
R_disp_80_exec <= 1'b1;
end
else if (R_disp_ena && I_80_done)
begin
{R_disp_rh_wl,R_disp_datah_instl,R_disp_data_w} <= {1'b0,1'b1,I_rgb_data};
R_disp_80_exec <= 1'b1;
end
else
begin
{R_disp_rh_wl,R_disp_datah_instl,R_disp_data_w} <= {1'b0,1'b0,16'h0000};
R_disp_80_exec <= 1'b0;
end
end
end
always @ (posedge I_sys_clk or negedge I_reset_n)
begin
if(~I_reset_n)
R_disp_ena <= 1'b0;
else if (W_Refresh_frame_req && W_lcd_init_done)
R_disp_ena <= 1'b1;
else if ((O_pixel_xpos == 8'd239) && (O_pixel_ypos == 9'd399))
R_disp_ena <= 1'b0;
end
always @ (posedge I_sys_clk or negedge I_reset_n)
begin
if(~I_reset_n)
begin
O_pixel_xpos <= 8'd0;
O_pixel_ypos <= 9'd0;
O_pos_en <= 1'b0;
end
else
begin
if (W_lcd_init_done && W_Refresh_frame_req)
begin
O_pos_en <= 1'b1;
O_pixel_xpos <= 8'd0;
O_pixel_ypos <= 9'd0;
end
else if (W_lcd_init_done && R_disp_80_exec)
begin
O_pos_en <= 1'b1;
O_pixel_xpos <= O_pixel_xpos + 1'b1;
if (O_pixel_xpos == 8'd239 && O_pixel_ypos <= 9'd399)
begin
O_pixel_xpos <= 8'd0;
O_pixel_ypos <= O_pixel_ypos + 1'b1;
end
end
else
begin
O_pos_en <= 1'b0;
O_pixel_xpos <= O_pixel_xpos;
O_pixel_ypos <= O_pixel_ypos;
end
end
end
R61509V3 驱动模块 例化了 R61509V3 配置模块 完成器件的配置,根据配置完成信号(W_lcd_init_done)输出配置数据或者刷新图像数据。完成一次图片刷新需要写入一个指令(GRAM Data Read (R202h))以及240*400 = 96000 个 RGB 数据,80-16bit并口模块 完成一次数据写入需要3个时钟周期(输入30MHz),则刷新率最高为
1/[(1/30000000)∗
3∗
96001]≈
104Hz
取刷新率为 100Hz,则每 290912 个时钟周期产生一个刷新请求信号。
3.2.3 R61509V3 配置模块
R61509V3 配置模块的代码如下:
`timescale 1ns / 1ps
//
// Company:
// Engineer: w0shishabi
//
// Create Date: 2021/02/09 18:21:11
// Design Name:
// Module Name: R61509V3_config
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//
module R61509V3_config(
input I_sys_clk ,
input I_reset_n ,
output reg [15:0] O_data_w ,
output reg O_datah_instl ,
output reg O_rh_wl ,
output reg O_80_exec ,
input I_80_done ,
output reg O_lcd_init_done
);
localparam P_CONFIG_NUM = 82;
localparam P_DELAY_1MS_CNT = 16'd30003;
reg [15:0] R_delay_cnt;
reg [7:0] R_config_cnt;
always @ (posedge I_sys_clk or negedge I_reset_n)
begin
if(~I_reset_n)
R_delay_cnt <= 16'd0;
else if (((R_config_cnt == 7'd0)||(R_config_cnt == 7'd4)||(R_config_cnt == 7'd44)||(R_config_cnt == 7'd76)||(R_config_cnt == 7'd78)) && I_80_done)
R_delay_cnt <= 16'd0;
else if (R_delay_cnt < P_DELAY_1MS_CNT)
R_delay_cnt <= R_delay_cnt + 1'b1;
end
always @ (posedge I_sys_clk or negedge I_reset_n)
begin
if(~I_reset_n)
R_config_cnt <= 7'd0;
else if (O_80_exec)
R_config_cnt <= R_config_cnt + 1'b1;
end
always @ (posedge I_sys_clk or negedge I_reset_n)
begin
if(~I_reset_n)
O_80_exec <= 1'b0;
else if (R_delay_cnt == P_DELAY_1MS_CNT - 1'b1)
O_80_exec <= 1'b1;
else if (I_80_done && (R_config_cnt != 7'd0) && (R_config_cnt != 7'd4) && (R_config_cnt != 7'd44) && (R_config_cnt != 7'd76) && (R_config_cnt != 7'd78) && (R_config_cnt < P_CONFIG_NUM))
O_80_exec <= 1'b1;
else
O_80_exec <= 1'b0;
end
always @ (posedge I_sys_clk or negedge I_reset_n)
begin
if(~I_reset_n)
O_lcd_init_done <= 1'b0;
else if ((R_config_cnt == P_CONFIG_NUM) && I_80_done)
O_lcd_init_done <= 1'b1;
end
always @ (posedge I_sys_clk or negedge I_reset_n)
begin
if(~I_reset_n)
begin
O_data_w <= 16'd0;
O_datah_instl <= 1'b0;
O_rh_wl <= 1'b0;
end
else
begin
case (R_config_cnt)
8'd0 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b0,16'h0000};
8'd1 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b0,16'h0000};
8'd2 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b0,16'h0000};
8'd3 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b0,16'h0000};
8'd4 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b0,16'h0400};
8'd5 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b1,16'h6200};
8'd6 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b0,16'h0008};
8'd7 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b1,16'h0808};
8'd8 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b0,16'h0301};
8'd9 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b1,16'h4C06};
8'd10 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b0,16'h0302};
8'd11 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b1,16'h0602};
8'd12 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b0,16'h0303};
8'd13 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b1,16'h050C};
8'd14 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b0,16'h0304};
8'd15 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b1,16'h3300};
8'd16 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b0,16'h0305};
8'd17 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b1,16'h0C05};
8'd18 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b0,16'h0306};
8'd19 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b1,16'h4206};
8'd20 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b0,16'h0307};
8'd21 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b1,16'h060C};
8'd22 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b0,16'h0308};
8'd23 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b1,16'h0500};
8'd24 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b0,16'h0309};
8'd25 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b1,16'h0033};
8'd26 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b0,16'h0010};
8'd27 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b1,16'h0014};
8'd28 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b0,16'h0011};
8'd29 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b1,16'h0101};
8'd30 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b0,16'h0012};
8'd31 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b1,16'h0000};
8'd32 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b0,16'h0013};
8'd33 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b1,16'h0001};
8'd34 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b0,16'h0100};
8'd35 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b1,16'h0330};
8'd36 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b0,16'h0101};
8'd37 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b1,16'h0247};
8'd38 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b0,16'h0103};
8'd39 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b1,16'h1000};
8'd40 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b0,16'h0280};
8'd41 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b1,16'hDE00};
8'd42 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b0,16'h0102};
8'd43 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b1,16'hD1B0};
8'd44 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b0,16'h0001};
8'd45 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b1,16'h0100};
8'd46 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b0,16'h0002};
8'd47 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b1,16'h0100};
8'd48 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b0,16'h0003};
8'd49 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b1,16'h1030};
8'd50 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b0,16'h0009};
8'd51 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b1,16'h0001};
8'd52 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b0,16'h000C};
8'd53 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b1,16'h0000};
8'd54 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b0,16'h0090};
8'd55 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b1,16'h8000};
8'd56 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b0,16'h000F};
8'd57 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b1,16'h0000};
8'd58 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b0,16'h0210};
8'd59 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b1,16'h0000};
8'd60 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b0,16'h0211};
8'd61 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b1,16'h00EF};
8'd62 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b0,16'h0212};
8'd63 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b1,16'h0000};
8'd64 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b0,16'h0213};
8'd65 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b1,16'h018F};
8'd66 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b0,16'h0500};
8'd67 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b1,16'h0000};
8'd68 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b0,16'h0501};
8'd69 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b1,16'h0000};
8'd70 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b0,16'h0502};
8'd71 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b1,16'h005F};
8'd72 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b0,16'h0401};
8'd73 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b1,16'h0001};
8'd74 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b0,16'h0404};
8'd75 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b1,16'h0000};
8'd76 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b0,16'h0007};
8'd77 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b1,16'h0100};
8'd78 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b0,16'h0200};
8'd79 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b1,16'h0000};
8'd80 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b1,16'h0201};
8'd81 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b1,16'h0000};
default : ;
endcase
end
endmodule
根据【TFT-LCD学习记录1】 R61509V3 彩屏显示原理中主要指令的说明,在第1,5,45,77,79个指令之前需要延时等待,这里统一设置成延时1ms,所以写这些指令时的触发信号(O_80_exec)根据延时计数器(R_delay_cnt)的值产生,其余则根据上一个指令的完成信号(I_80_done)产生。
3.2.4 80-16bit并口模块
80-16bit并口模块的代码如下:
`timescale 1ns / 1ps
//
// Company:
// Engineer: w0shishabi
//
// Create Date: 2021/02/09 18:26:06
// Design Name:
// Module Name: lcd_80_16b_dri
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//
module lcd_80_16b_dri(
input I_sys_clk ,
input I_reset_n ,
input [15:0] I_data_w ,
output reg [15:0] O_data_r ,
input I_datah_instl ,
input I_rh_wl ,
input I_80_exec ,
output reg O_80_done ,
output O_lcd_cs ,
output reg O_lcd_rs ,
output reg O_lcd_wr ,
output reg O_lcd_rd ,
output O_lcd_reset_n ,
inout [15:0] IO_lcd_data
);
//parameter define
//reg define
reg [15:0] R_data_out;
reg R_data_dir;
reg R_flag;
//wire define
wire [15:0] W_data_in;
//main code
assign O_lcd_cs = 1'b0;
assign O_lcd_reset_n = 1'b1;
assign IO_lcd_data = R_data_dir ? R_data_out : 1'bz;
assign W_data_in = IO_lcd_data;
always @ (posedge I_sys_clk or negedge I_reset_n)
begin
if(~I_reset_n)
begin
R_flag = 1'b0;
end
else
begin
if (I_80_exec)
begin
R_data_dir <= I_rh_wl ? 1'b0 : 1'b1;
R_data_out <= I_rh_wl ? 16'bz : I_data_w;
O_lcd_rs <= I_datah_instl ? 1'b1 : 1'b0;
O_lcd_wr <= I_rh_wl ? 1'b1 : 1'b0;
O_lcd_rd = I_rh_wl ? 1'b0 : 1'b1;
R_flag <= 1'b1;
end
else if (R_flag == 1'b1)
begin
O_data_r <= I_rh_wl ? W_data_in : 1'bz;
O_lcd_wr <= 1'b1;
O_lcd_rd <= 1'b1;
O_80_done <= 1'b1;
R_flag <= 1'b0;
end
else
begin
O_80_done <= 1'b0;
end
end
endmodule
数据/指令(I_datah_instl)以及触发信号(I_80_exec)以80系统16位并口的时序完成数据的写入或者读取。
3.2.5 图片显示模块
图片显示模块的代码如下:
`timescale 1ns / 1ps
//
// Company:
// Engineer: w0shishabi
//
// Create Date: 2021/02/10 22:29:39
// Design Name:
// Module Name: pic_disp_rom
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//
module pic_disp_rom(
input I_sys_clk ,
input I_reset_n ,
input [ 7:0] I_pos_x ,
input [ 8:0] I_pos_y ,
input I_pos_en ,
output [15:0] O_data
);
localparam P_PIXEL_NUM_Y = 9'd400 ;
localparam P_PIXEL_NUM_X = 8'd240 ;
localparam P_PIC_POS_X = 8'd0 ;
localparam P_PIC_POS_Y = 9'd0 ;
localparam P_PIC_WIDTH = 8'd240 ;
localparam P_PIC_HEIGTH = 9'd400 ;
localparam P_PIC_TOTAL = 17'd96000 ;
localparam P_COLOR_BLACK = 16'b00000_000000_00000;
wire W_rom_en;
reg [16:0] R_rom_addr;
reg R_rom_valid;
reg R_pos_en;
reg [ 7:0] R_pos_x;
wire [15:0] W_rom_data;
wire [16:0] W_mult_addr;
always @ (posedge I_sys_clk or negedge I_reset_n)
begin
if(~I_reset_n)
begin
R_pos_en <= 1'b0;
R_pos_x <= 8'd0;
end
else
begin
R_pos_en <= I_pos_en;
R_pos_x <= I_pos_x;
end
end
assign O_data = R_rom_valid ? W_rom_data : P_COLOR_BLACK;
assign W_rom_en = (I_pos_x >= P_PIC_POS_X) && (I_pos_x < P_PIC_POS_X + P_PIC_WIDTH) && (I_pos_y >= P_PIC_POS_Y) && (I_pos_y < P_PIC_POS_Y + P_PIC_HEIGTH) ? 1'b1 : 1'b0;
always @ (posedge I_sys_clk or negedge I_reset_n)
begin
if(~I_reset_n)
begin
R_rom_addr <= 17'd0;
end
else
begin
if (R_pos_en)
begin
R_rom_addr <= W_mult_addr + R_pos_x;
end
else
R_rom_addr <= R_rom_addr;
end
end
always @ (posedge I_sys_clk or negedge I_reset_n)
begin
if(~I_reset_n)
begin
R_rom_valid <= 1'b0;
end
else
begin
R_rom_valid <= W_rom_en;
end
end
mult_addr mult_addr_u (
.CLK(I_sys_clk), // input wire CLK
.A(8'd240), // input wire [7 : 0] A
.B(I_pos_y), // input wire [8 : 0] B
.P(W_mult_addr) // output wire [16 : 0] P
);
pic_rom pic_rom_u (
.clka(I_sys_clk), // input wire clka
.ena(W_rom_en), // input wire ena
.addra(R_rom_addr), // input wire [16 : 0] addra
.douta(W_rom_data) // output wire [15 : 0] douta
);
endmodule
4 显示效果
5 相关资料
链接:https://pan.baidu.com/s/1t66akpZ1VL5EOTJ5zQc8KA
提取码:1234
1 功能
使用 FPGA 驱动 TFT-LCD 彩屏 R61509V3 显示彩色图片,屏幕分辨率为240*400,刷新速度100Hz。
2 环境
硬件:
[tr]项目说明[/tr]
FPGA | xc7a35tcsg324-1 |
TFT-LCD驱动IC | R61509V |
软件: [tr]项目说明[/tr]
vivado | Vivado v2017.1 (64-bit) |
matlab | R2018b 64-bit |
modelsim | ModelSim SE-64 10.5 Revision: 2016.02 |
3 程序说明
系统框图:
fpga开发板输入时钟为100MHz,通过MMCM输出30MHz时钟作为工作时钟。R61509V3驱动模块完成器件的初始化和显示。R61509V 显示模块例化ROM IP核 ,利用片内存储资源存储图片信息,并根据驱动模块的请求输出数据。80-16bit并口模块将驱动模块输出的指令按照80系统16位并口的时序输出。
3.1 .coe 文件的产生
xilinx 家的 FPGA 例化单端口 ROM 时,要指定对应的初始化文件。产生coe文件的matlab代码(源文件在相关资料里面工程目录下的matlab文件夹):
clear all;
close all;
%lcd参数
P_lcd_X = 240; %宽
P_lcd_Y = 400; %高
%读取原图片
img_rgb = imread("./pic.jpg");
% figure;imshow(img_rgb);
%根据lcd的参数剪切与压缩图片
img_rgb_size = size(img_rgb);
img_rgb_cut = img_rgb(1:floor(img_rgb_size(1)/P_lcd_Y):400*floor(img_rgb_size(1)/P_lcd_Y),...
floor(img_rgb_size(2)/2-(P_lcd_X/2)*(img_rgb_size(1)/P_lcd_Y)):(img_rgb_size(1)/P_lcd_Y):floor(img_rgb_size(2)/2+(P_lcd_X/2)*(img_rgb_size(1)/P_lcd_Y))-1,:);
figure;imshow(img_rgb_cut);
%RGB数据转为2进制
img_rgb_cut_R = img_rgb_cut(:,:,1);
img_rgb_cut_G = img_rgb_cut(:,:,2);
img_rgb_cut_B = img_rgb_cut(:,:,3);
img_rgb_cut_bin_R = dec2bin(img_rgb_cut_R');
img_rgb_cut_bin_G = dec2bin(img_rgb_cut_G');
img_rgb_cut_bin_B = dec2bin(img_rgb_cut_B');
img_rgb_cut_bin_R = img_rgb_cut_bin_R(:,1:5);
img_rgb_cut_bin_G = img_rgb_cut_bin_G(:,1:6);
img_rgb_cut_bin_B = img_rgb_cut_bin_B(:,1:5);
%写到.coe文件,用来初始化rom
if(0)
coef_bin = [img_rgb_cut_bin_R img_rgb_cut_bin_G img_rgb_cut_bin_B];
fid = fopen('.coepic1.coe','w');
fprintf(fid,'MEMORY_INITIALIZATION_RADIX=2;n');
fprintf(fid,'MEMORY_INITIALIZATION_VECTOR=n');
for i = 1:1:length(coef_bin)
fprintf(fid,'%s',coef_bin(i,:));
if i==length(coef_bin)
fprintf(fid,';');
else
fprintf(fid,',n');
end
end
fclose(fid);
end
首先导入需要转换的图片,然后根据 LCD 的尺寸剪切与压缩图片,然后分别将范围为 0~255 的 RGB 数据转换为 8 位二进制数,最后根据 RGB565 的格式取 R 分量的高 5 位、 G 分量的高 6 位与B分量的高 5 位组合成对应像素点的数据,240*400 像素点对应 coe 文件初始化向量的长度位 96000。在本示例中,原图片,剪切后的图片,生成的 .coe 文件为:
3.2 模块设计
3.2.1 顶层模块
顶层模块对应的 RTL 原理图为:
时钟管理器模块(mmcm_100M) 通过例化 MMCM 来实现。由后面 3.2.4 80-16bit并口模块 的说明可知本设计最大工作时钟受限于 写入低电平脉冲宽度(一个时钟周期,min(PWLW)=30ns),取工作时钟为 30MHz。
R61509V3 驱动模块(R61509V3_driver) 在上电后首先完成 R61509V3 的配置,配置完成后根据设置的刷新频率,输出对应像素点的坐标并且根据对应的 RGB 数据输入刷新屏幕。本模块只是输出80并口的 数据,数据类型,读写类型,触发信号。80并口的时序转换由“80-16bit并口模块”完成。
80-16bit并口模块(lcd_80_16b_dri) 根据用户接口输入的数据类型按照 80系统16位并口 的时序输出。
图片显示模块(pic_disp_rom) 例化rom IP核存储图片的 RGB 数据,根据输入的坐标将对应的数据输出。
顶层模块的代码如下:
`timescale 1ns / 1ps
//
// Company:
// Engineer: w0shishabi
//
// Create Date: 2021/02/09 19:54:44
// Design Name:
// Module Name: top
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//
module top(
input I_sys_clk ,
input I_reset_n ,
output O_lcd_cs ,
output O_lcd_rs ,
output O_lcd_wr ,
output O_lcd_rd ,
output O_lcd_reset_n ,
inout [15:0] IO_lcd_data
);
//wire define
wire W_reset_n ;
wire W_mmcm_locked ;
wire W_clk_100M ;
wire W_clk_30M ;
wire [15:0] W_data_w ;
wire W_datah_instl ;
wire W_rh_wl ;
wire W_80_exec ;
wire W_80_done ;
wire W_lcd_init_done ;
wire [15:0] W_rgb_data ;
wire [7:0] W_pixel_xpos ;
wire [8:0] W_pixel_ypos ;
wire W_pos_en ;
//main
assign W_reset_n = I_reset_n && W_mmcm_locked;
mmcm_100M mmcm_100M_u
(
// Clock out ports
.clk_out100M(W_clk_100M ), // output clk_out100M
.clk_out30M (W_clk_30M ), // output clk_out20M
// Status and control signals
.reset (~I_reset_n ), // input reset
.locked (W_mmcm_locked), // output locked
// Clock in ports
.clk_in1 (I_sys_clk ));// input clk_in1
pic_disp_rom pic_disp_rom_u(
.I_sys_clk (W_clk_30M ),
.I_reset_n (W_reset_n ),
.I_pos_x (W_pixel_xpos),
.I_pos_y (W_pixel_ypos),
.I_pos_en (W_pos_en ),
.O_data (W_rgb_data )
);
R61509V3_driver R61509V3_driver_u(
.I_sys_clk (W_clk_30M ),
.I_reset_n (W_reset_n ),
.I_rgb_data (W_rgb_data ),
.O_pixel_xpos (W_pixel_xpos ),
.O_pixel_ypos (W_pixel_ypos ),
.O_pos_en (W_pos_en ),
.O_data_w (W_data_w ),
.O_datah_instl (W_datah_instl ),
.O_rh_wl (W_rh_wl ),
.O_80_exec (W_80_exec ),
.I_80_done (W_80_done ),
.O_lcd_init_done(W_lcd_init_done)
);
lcd_80_16b_dri lcd_80_16b_dri_u(
.I_sys_clk (W_clk_30M ),
.I_reset_n (W_reset_n ),
.I_data_w (W_data_w ),
.O_data_r ( ),
.I_datah_instl (W_datah_instl),
.I_rh_wl (W_rh_wl ),
.I_80_exec (W_80_exec ),
.O_80_done (W_80_done ),
.O_lcd_cs (O_lcd_cs ),
.O_lcd_rs (O_lcd_rs ),
.O_lcd_wr (O_lcd_wr ),
.O_lcd_rd (O_lcd_rd ),
.O_lcd_reset_n (O_lcd_reset_n),
.IO_lcd_data (IO_lcd_data )
);
endmodule
顶层模块完成其余模块的例化,其中各模块的复位信号为 MMCM 的 locked 端口输出与系统复位的逻辑与,确保其他模块在时钟稳定后开始工作。
3.2.2 R61509V3 驱动模块
R61509V3 驱动模块的代码如下:
`timescale 1ns / 1ps
//
// Company:
// Engineer: w0shishabi
//
// Create Date: 2021/02/09 09:56:33
// Design Name:
// Module Name: R61509V3_driver
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//
module R61509V3_driver(
input I_sys_clk ,
input I_reset_n ,
input [15:0] I_rgb_data ,
output reg [7:0] O_pixel_xpos ,
output reg [8:0] O_pixel_ypos ,
output reg O_pos_en ,
output [15:0] O_data_w ,
output O_datah_instl ,
output O_rh_wl ,
output O_80_exec ,
input I_80_done ,
output O_lcd_init_done
);
localparam P_60FPS_CNT = 20'd333333 ;
localparam P_65FPS_CNT = 20'd307692 ;
localparam P_100FPS_CNT = 20'd290912 ;
localparam P_SIM_CNT = 20'd20000 ;//simulation
localparam P_FPS_CNT = P_100FPS_CNT ;
//reg define
reg [19:0] R_clk_div_cnt ;
reg [15:0] R_disp_data_w ;
reg R_disp_datah_instl ;
reg R_disp_rh_wl ;
reg R_disp_80_exec ;
reg R_disp_ena ;
//wire define
wire [15:0] W_init_data_w ;
wire W_init_datah_instl ;
wire W_init_rh_wl ;
wire W_init_80_exec ;
wire W_lcd_init_done ;
wire W_Refresh_frame_req;
//main
assign O_data_w = W_lcd_init_done ? R_disp_data_w : W_init_data_w ;
assign O_datah_instl = W_lcd_init_done ? R_disp_datah_instl : W_init_datah_instl;
assign O_rh_wl = W_lcd_init_done ? R_disp_rh_wl : W_init_rh_wl ;
assign O_80_exec = W_lcd_init_done ? R_disp_80_exec : W_init_80_exec ;
assign O_lcd_init_done = W_lcd_init_done;
R61509V3_config R61509V3_config_u(
.I_sys_clk (I_sys_clk ),
.I_reset_n (I_reset_n ),
.O_data_w (W_init_data_w ),
.O_datah_instl (W_init_datah_instl),
.O_rh_wl (W_init_rh_wl ),
.O_80_exec (W_init_80_exec ),
.I_80_done (I_80_done ),
.O_lcd_init_done(W_lcd_init_done )
);
always @ (posedge I_sys_clk or negedge I_reset_n)
begin
if(~I_reset_n)
begin
R_clk_div_cnt <= 20'd0;
end
else
begin
if (R_clk_div_cnt == P_FPS_CNT)
R_clk_div_cnt <= 20'd0;
else
R_clk_div_cnt <= R_clk_div_cnt + 1'b1;
end
end
assign W_Refresh_frame_req = (R_clk_div_cnt == P_FPS_CNT);
always @ (posedge I_sys_clk or negedge I_reset_n)
begin
if(~I_reset_n)
begin
R_disp_data_w <= 1'b0;
R_disp_datah_instl <= 1'b0;
R_disp_rh_wl <= 1'b0;
R_disp_80_exec <= 1'b0;
end
else
begin
if (W_Refresh_frame_req && W_lcd_init_done)
begin
{R_disp_rh_wl,R_disp_datah_instl,R_disp_data_w} <= {1'b0,1'b0,16'h0202};
R_disp_80_exec <= 1'b1;
end
else if (R_disp_ena && I_80_done)
begin
{R_disp_rh_wl,R_disp_datah_instl,R_disp_data_w} <= {1'b0,1'b1,I_rgb_data};
R_disp_80_exec <= 1'b1;
end
else
begin
{R_disp_rh_wl,R_disp_datah_instl,R_disp_data_w} <= {1'b0,1'b0,16'h0000};
R_disp_80_exec <= 1'b0;
end
end
end
always @ (posedge I_sys_clk or negedge I_reset_n)
begin
if(~I_reset_n)
R_disp_ena <= 1'b0;
else if (W_Refresh_frame_req && W_lcd_init_done)
R_disp_ena <= 1'b1;
else if ((O_pixel_xpos == 8'd239) && (O_pixel_ypos == 9'd399))
R_disp_ena <= 1'b0;
end
always @ (posedge I_sys_clk or negedge I_reset_n)
begin
if(~I_reset_n)
begin
O_pixel_xpos <= 8'd0;
O_pixel_ypos <= 9'd0;
O_pos_en <= 1'b0;
end
else
begin
if (W_lcd_init_done && W_Refresh_frame_req)
begin
O_pos_en <= 1'b1;
O_pixel_xpos <= 8'd0;
O_pixel_ypos <= 9'd0;
end
else if (W_lcd_init_done && R_disp_80_exec)
begin
O_pos_en <= 1'b1;
O_pixel_xpos <= O_pixel_xpos + 1'b1;
if (O_pixel_xpos == 8'd239 && O_pixel_ypos <= 9'd399)
begin
O_pixel_xpos <= 8'd0;
O_pixel_ypos <= O_pixel_ypos + 1'b1;
end
end
else
begin
O_pos_en <= 1'b0;
O_pixel_xpos <= O_pixel_xpos;
O_pixel_ypos <= O_pixel_ypos;
end
end
end
R61509V3 驱动模块 例化了 R61509V3 配置模块 完成器件的配置,根据配置完成信号(W_lcd_init_done)输出配置数据或者刷新图像数据。完成一次图片刷新需要写入一个指令(GRAM Data Read (R202h))以及240*400 = 96000 个 RGB 数据,80-16bit并口模块 完成一次数据写入需要3个时钟周期(输入30MHz),则刷新率最高为
1/[(1/30000000)∗
3∗
96001]≈
104Hz
取刷新率为 100Hz,则每 290912 个时钟周期产生一个刷新请求信号。
3.2.3 R61509V3 配置模块
R61509V3 配置模块的代码如下:
`timescale 1ns / 1ps
//
// Company:
// Engineer: w0shishabi
//
// Create Date: 2021/02/09 18:21:11
// Design Name:
// Module Name: R61509V3_config
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//
module R61509V3_config(
input I_sys_clk ,
input I_reset_n ,
output reg [15:0] O_data_w ,
output reg O_datah_instl ,
output reg O_rh_wl ,
output reg O_80_exec ,
input I_80_done ,
output reg O_lcd_init_done
);
localparam P_CONFIG_NUM = 82;
localparam P_DELAY_1MS_CNT = 16'd30003;
reg [15:0] R_delay_cnt;
reg [7:0] R_config_cnt;
always @ (posedge I_sys_clk or negedge I_reset_n)
begin
if(~I_reset_n)
R_delay_cnt <= 16'd0;
else if (((R_config_cnt == 7'd0)||(R_config_cnt == 7'd4)||(R_config_cnt == 7'd44)||(R_config_cnt == 7'd76)||(R_config_cnt == 7'd78)) && I_80_done)
R_delay_cnt <= 16'd0;
else if (R_delay_cnt < P_DELAY_1MS_CNT)
R_delay_cnt <= R_delay_cnt + 1'b1;
end
always @ (posedge I_sys_clk or negedge I_reset_n)
begin
if(~I_reset_n)
R_config_cnt <= 7'd0;
else if (O_80_exec)
R_config_cnt <= R_config_cnt + 1'b1;
end
always @ (posedge I_sys_clk or negedge I_reset_n)
begin
if(~I_reset_n)
O_80_exec <= 1'b0;
else if (R_delay_cnt == P_DELAY_1MS_CNT - 1'b1)
O_80_exec <= 1'b1;
else if (I_80_done && (R_config_cnt != 7'd0) && (R_config_cnt != 7'd4) && (R_config_cnt != 7'd44) && (R_config_cnt != 7'd76) && (R_config_cnt != 7'd78) && (R_config_cnt < P_CONFIG_NUM))
O_80_exec <= 1'b1;
else
O_80_exec <= 1'b0;
end
always @ (posedge I_sys_clk or negedge I_reset_n)
begin
if(~I_reset_n)
O_lcd_init_done <= 1'b0;
else if ((R_config_cnt == P_CONFIG_NUM) && I_80_done)
O_lcd_init_done <= 1'b1;
end
always @ (posedge I_sys_clk or negedge I_reset_n)
begin
if(~I_reset_n)
begin
O_data_w <= 16'd0;
O_datah_instl <= 1'b0;
O_rh_wl <= 1'b0;
end
else
begin
case (R_config_cnt)
8'd0 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b0,16'h0000};
8'd1 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b0,16'h0000};
8'd2 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b0,16'h0000};
8'd3 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b0,16'h0000};
8'd4 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b0,16'h0400};
8'd5 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b1,16'h6200};
8'd6 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b0,16'h0008};
8'd7 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b1,16'h0808};
8'd8 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b0,16'h0301};
8'd9 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b1,16'h4C06};
8'd10 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b0,16'h0302};
8'd11 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b1,16'h0602};
8'd12 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b0,16'h0303};
8'd13 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b1,16'h050C};
8'd14 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b0,16'h0304};
8'd15 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b1,16'h3300};
8'd16 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b0,16'h0305};
8'd17 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b1,16'h0C05};
8'd18 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b0,16'h0306};
8'd19 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b1,16'h4206};
8'd20 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b0,16'h0307};
8'd21 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b1,16'h060C};
8'd22 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b0,16'h0308};
8'd23 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b1,16'h0500};
8'd24 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b0,16'h0309};
8'd25 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b1,16'h0033};
8'd26 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b0,16'h0010};
8'd27 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b1,16'h0014};
8'd28 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b0,16'h0011};
8'd29 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b1,16'h0101};
8'd30 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b0,16'h0012};
8'd31 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b1,16'h0000};
8'd32 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b0,16'h0013};
8'd33 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b1,16'h0001};
8'd34 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b0,16'h0100};
8'd35 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b1,16'h0330};
8'd36 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b0,16'h0101};
8'd37 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b1,16'h0247};
8'd38 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b0,16'h0103};
8'd39 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b1,16'h1000};
8'd40 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b0,16'h0280};
8'd41 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b1,16'hDE00};
8'd42 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b0,16'h0102};
8'd43 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b1,16'hD1B0};
8'd44 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b0,16'h0001};
8'd45 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b1,16'h0100};
8'd46 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b0,16'h0002};
8'd47 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b1,16'h0100};
8'd48 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b0,16'h0003};
8'd49 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b1,16'h1030};
8'd50 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b0,16'h0009};
8'd51 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b1,16'h0001};
8'd52 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b0,16'h000C};
8'd53 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b1,16'h0000};
8'd54 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b0,16'h0090};
8'd55 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b1,16'h8000};
8'd56 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b0,16'h000F};
8'd57 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b1,16'h0000};
8'd58 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b0,16'h0210};
8'd59 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b1,16'h0000};
8'd60 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b0,16'h0211};
8'd61 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b1,16'h00EF};
8'd62 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b0,16'h0212};
8'd63 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b1,16'h0000};
8'd64 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b0,16'h0213};
8'd65 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b1,16'h018F};
8'd66 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b0,16'h0500};
8'd67 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b1,16'h0000};
8'd68 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b0,16'h0501};
8'd69 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b1,16'h0000};
8'd70 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b0,16'h0502};
8'd71 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b1,16'h005F};
8'd72 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b0,16'h0401};
8'd73 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b1,16'h0001};
8'd74 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b0,16'h0404};
8'd75 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b1,16'h0000};
8'd76 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b0,16'h0007};
8'd77 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b1,16'h0100};
8'd78 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b0,16'h0200};
8'd79 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b1,16'h0000};
8'd80 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b1,16'h0201};
8'd81 :{O_rh_wl,O_datah_instl,O_data_w} <= {1'b0,1'b1,16'h0000};
default : ;
endcase
end
endmodule
根据【TFT-LCD学习记录1】 R61509V3 彩屏显示原理中主要指令的说明,在第1,5,45,77,79个指令之前需要延时等待,这里统一设置成延时1ms,所以写这些指令时的触发信号(O_80_exec)根据延时计数器(R_delay_cnt)的值产生,其余则根据上一个指令的完成信号(I_80_done)产生。
3.2.4 80-16bit并口模块
80-16bit并口模块的代码如下:
`timescale 1ns / 1ps
//
// Company:
// Engineer: w0shishabi
//
// Create Date: 2021/02/09 18:26:06
// Design Name:
// Module Name: lcd_80_16b_dri
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//
module lcd_80_16b_dri(
input I_sys_clk ,
input I_reset_n ,
input [15:0] I_data_w ,
output reg [15:0] O_data_r ,
input I_datah_instl ,
input I_rh_wl ,
input I_80_exec ,
output reg O_80_done ,
output O_lcd_cs ,
output reg O_lcd_rs ,
output reg O_lcd_wr ,
output reg O_lcd_rd ,
output O_lcd_reset_n ,
inout [15:0] IO_lcd_data
);
//parameter define
//reg define
reg [15:0] R_data_out;
reg R_data_dir;
reg R_flag;
//wire define
wire [15:0] W_data_in;
//main code
assign O_lcd_cs = 1'b0;
assign O_lcd_reset_n = 1'b1;
assign IO_lcd_data = R_data_dir ? R_data_out : 1'bz;
assign W_data_in = IO_lcd_data;
always @ (posedge I_sys_clk or negedge I_reset_n)
begin
if(~I_reset_n)
begin
R_flag = 1'b0;
end
else
begin
if (I_80_exec)
begin
R_data_dir <= I_rh_wl ? 1'b0 : 1'b1;
R_data_out <= I_rh_wl ? 16'bz : I_data_w;
O_lcd_rs <= I_datah_instl ? 1'b1 : 1'b0;
O_lcd_wr <= I_rh_wl ? 1'b1 : 1'b0;
O_lcd_rd = I_rh_wl ? 1'b0 : 1'b1;
R_flag <= 1'b1;
end
else if (R_flag == 1'b1)
begin
O_data_r <= I_rh_wl ? W_data_in : 1'bz;
O_lcd_wr <= 1'b1;
O_lcd_rd <= 1'b1;
O_80_done <= 1'b1;
R_flag <= 1'b0;
end
else
begin
O_80_done <= 1'b0;
end
end
endmodule
数据/指令(I_datah_instl)以及触发信号(I_80_exec)以80系统16位并口的时序完成数据的写入或者读取。
3.2.5 图片显示模块
图片显示模块的代码如下:
`timescale 1ns / 1ps
//
// Company:
// Engineer: w0shishabi
//
// Create Date: 2021/02/10 22:29:39
// Design Name:
// Module Name: pic_disp_rom
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//
module pic_disp_rom(
input I_sys_clk ,
input I_reset_n ,
input [ 7:0] I_pos_x ,
input [ 8:0] I_pos_y ,
input I_pos_en ,
output [15:0] O_data
);
localparam P_PIXEL_NUM_Y = 9'd400 ;
localparam P_PIXEL_NUM_X = 8'd240 ;
localparam P_PIC_POS_X = 8'd0 ;
localparam P_PIC_POS_Y = 9'd0 ;
localparam P_PIC_WIDTH = 8'd240 ;
localparam P_PIC_HEIGTH = 9'd400 ;
localparam P_PIC_TOTAL = 17'd96000 ;
localparam P_COLOR_BLACK = 16'b00000_000000_00000;
wire W_rom_en;
reg [16:0] R_rom_addr;
reg R_rom_valid;
reg R_pos_en;
reg [ 7:0] R_pos_x;
wire [15:0] W_rom_data;
wire [16:0] W_mult_addr;
always @ (posedge I_sys_clk or negedge I_reset_n)
begin
if(~I_reset_n)
begin
R_pos_en <= 1'b0;
R_pos_x <= 8'd0;
end
else
begin
R_pos_en <= I_pos_en;
R_pos_x <= I_pos_x;
end
end
assign O_data = R_rom_valid ? W_rom_data : P_COLOR_BLACK;
assign W_rom_en = (I_pos_x >= P_PIC_POS_X) && (I_pos_x < P_PIC_POS_X + P_PIC_WIDTH) && (I_pos_y >= P_PIC_POS_Y) && (I_pos_y < P_PIC_POS_Y + P_PIC_HEIGTH) ? 1'b1 : 1'b0;
always @ (posedge I_sys_clk or negedge I_reset_n)
begin
if(~I_reset_n)
begin
R_rom_addr <= 17'd0;
end
else
begin
if (R_pos_en)
begin
R_rom_addr <= W_mult_addr + R_pos_x;
end
else
R_rom_addr <= R_rom_addr;
end
end
always @ (posedge I_sys_clk or negedge I_reset_n)
begin
if(~I_reset_n)
begin
R_rom_valid <= 1'b0;
end
else
begin
R_rom_valid <= W_rom_en;
end
end
mult_addr mult_addr_u (
.CLK(I_sys_clk), // input wire CLK
.A(8'd240), // input wire [7 : 0] A
.B(I_pos_y), // input wire [8 : 0] B
.P(W_mult_addr) // output wire [16 : 0] P
);
pic_rom pic_rom_u (
.clka(I_sys_clk), // input wire clka
.ena(W_rom_en), // input wire ena
.addra(R_rom_addr), // input wire [16 : 0] addra
.douta(W_rom_data) // output wire [15 : 0] douta
);
endmodule
4 显示效果
5 相关资料
链接:https://pan.baidu.com/s/1t66akpZ1VL5EOTJ5zQc8KA
提取码:1234
举报