【TFT-LCD学习记录2】 R61509V3 彩屏 FPGA 驱动程序设计

本文详细介绍了使用FPGA驱动TFT-LCD彩屏显示彩色图片的过程,包括硬件环境、软件工具、系统框图设计、模块详细设计等。通过MMCM产生30MHz工作时钟,R61509V3驱动模块完成配置和显示,80-16bit并口模块实现并口时序转换,最终达到240*400分辨率、100Hz刷新率的显示效果。
摘要由CSDN通过智能技术生成

1 功能

 使用 FPGA 驱动 TFT-LCD 彩屏 R61509V3 显示彩色图片,屏幕分辨率为240*400,刷新速度100Hz。

2 环境

硬件:

项目说明
FPGAxc7a35tcsg324-1
TFT-LCD驱动ICR61509V
软件:
项目说明
vivadoVivado v2017.1 (64-bit)
matlabR2018b 64-bit
modelsimModelSim 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('.\coe\pic1.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 文件为:

原图
剪切后的图片
生成的 .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

endmodule

R61509V3 驱动模块 例化了 R61509V3 配置模块 完成器件的配置,根据配置完成信号(W_lcd_init_done)输出配置数据或者刷新图像数据。完成一次图片刷新需要写入一个指令(GRAM Data Read (R202h))以及240*400 = 96000 个 RGB 数据,80-16bit并口模块 完成一次数据写入需要3个时钟周期(输入30MHz),则刷新率最高为
1 / [ ( 1 / 30000000 ) ∗ 3 ∗ 96001 ] ≈ 104 H z 1/[(1/30000000)*3*96001]\approx104Hz 1/[(1/30000000)396001]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
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
end

endmodule

 此模块根据输入数据读/写(I_rh_wl)、数据/指令(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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lu-ming.xyz

觉得有用的话点个赞吧 :)

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值