FPGA实现8080接口驱动TFL LCD屏(ILI9486芯片)

来源:互联网 发布:暗黑3装备数据库 编辑:程序博客网 时间:2024/05/29 19:20

1.简介

1.1 ILI9486

实际驱动LCD的芯片,最大可驱动320*480的LCD屏,自带GRAM,作为FPGA的协处理器,FPGA利用8080接口向GRAM中写数据,ILI9486负责把GRAM的数据显示在屏幕上。成功驱动的最关键一步也就是配置它的工作方式,也就是根据它的芯片手册初始化,芯片手册可以百度到。

手册很重要!手册很重要!手册很重要!

基本会碰到的坑在手册里都有提及,之前没有成功的主要原因就是没有发现里面提及的在芯片软复位后,需要等待至少5ms才能读取其他指令,且软复位后进入睡眠模式,并默认关闭显示功能,退出睡眠模式后又需等待5ms才能读取其他指令

1.2 8080接口

使用一种接口协议最重要的就是要知道它的时序,下面附上利用8080接口操作ILI9486的时序图

写时序(命令、数据)
写时序

读时序(数据)
读时序

CSX:片选线
RESX:硬件复位线
D/CX:指令数据选择线 (低为指令,高为数据)
WRX:写命令线
RDX:读命令线
D[17:0]:数据线
所有控制线皆为低有效
可以看到,在8080接口中,在正常的驱动过程中,WRX充当了时钟的作用,在上升沿读取数据,所以它的速度决定了写RAM的速度(不是屏幕绘制速度)。

时序特征

这是具体时序的时间约束,比较重要的有最小读写周期、最小数据建立和保持时间,决定了FPGA的驱动时钟频率,另外注意到DCX没有最小时间要求,所以它的信号可以和读写信号在同一时钟周期变化

2.代码实现

项目实现按钮切换显示三原色和背光pwm调节

顶层模块
主要实现按钮检测和子模块调用

module LCD_Top(    input wire clk,    //用来调试的按钮    input wire btn_pwm_on,    input wire btn_light_up,    input wire btn_light_down,    input wire btn_rst,    input wire btn_swap,    //控制信号线    output wire cs,    output wire rs,    output wire wr,    output wire rd,    output wire lcd_rst_n,    //数据线    output wire [15:0] data,    //pwm调光    output wire lcd_pwm,    output wire pwm_stat    );    reg btn_ltup;    reg btn_ltdn;    reg btn_swp;    reg btn_pwmon;    reg is_pwm=0;    reg [4:0] red=0;    reg [5:0] green=0;    reg [4:0] blue=0;    reg [3:0] bright=0;    reg [2:0] color=0;    //使用xilinx开发板,检测按钮下降沿    always@ (posedge CLK_50M)    begin        if({btn_ltup,btn_light_up} == 2'b10)            if(bright < 4'd15)                bright <= bright + 1'd1;        if({btn_ltdn,btn_light_down} == 2'b10)            if(bright > 1)                bright <= bright - 1'd1;        if({btn_swp,btn_swap} == 2'b10)            if(color == 2'd2)                color <= 0;            else                color <= color + 1'd1;        if({btn_pwmon,btn_pwm_on} == 2'b10)            is_pwm <= ~is_pwm;        btn_ltup <= btn_light_up;        btn_ltdn <= btn_light_down;        btn_swp <= btn_swap;        btn_pwmon <= btn_pwm_on;        end    always@ (posedge CLK_5M)    begin        case(color)        0:        begin            red = 5'b11111;            green = 0;            blue = 0;        end        1:        begin            red = 0;            green = 6'b111111;            blue = 0;        end        2:        begin            red = 0;            green = 0;            blue = 5'b11111;        end        endcase    end    assign pwm_stat = is_pwm;    assign lcd_rst_n = 1;    //时钟分频IP核    clk_wizard clkw   (// Clock in ports    .CLK_IN1(clk),      // IN 50Mhz    // Clock out ports    .CLK_50M(CLK_50M),     // OUT    .CLK_5![这里写图片描述](http://img.blog.csdn.net/20170919110910711?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvdTAxMTQ1Mjc0Nw==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)M(CLK_5M),     // OUT    .CLK_20M(CLK_20M),     // OUT    // Status and control signals    .RESET(~btn_rst)     );            //背光控制模块     LCD_BL_Control bl_ctl     (        .clk_5M(CLK_5M),        .isPWM(is_pwm),        .brt(bright),        .pwm(lcd_pwm)     );     //初始化及显示模块     LCD_Config cfg     (        .clk(CLK_20M),        .rst_n(btn_rst),        .data_in({red,green,blue}),        .cs(cs),        .rs(rs),        .wr(wr),        .rd(rd),        .data_out(data)     );endmodule

背光控制模块
根据输入亮度生成pwm波
输入时钟5MHz,能产生略低于20KHz的pwm信号

module LCD_BL_Control(    input wire clk_5M,    input wire isPWM,       //pwm状态指示灯    input wire [3:0] brt,       //亮度    output wire pwm     //pwm信号    );    parameter unit = 'd16;    parameter total = 'd256;    reg [18:0]cnt = 0;    reg r_pwm = 0;    always@ (posedge clk_5M)    begin        if(cnt == total-1)            cnt <= 0;        else            cnt <= cnt +1'd1;    end    always@ (posedge clk_5M)    begin        if(cnt < unit*brt-1)            r_pwm <= 1;        else            r_pwm <= 0;    end    assign pwm = isPWM?r_pwm:1'b1;  //根据是否选择pwm模式输出pwm波或高点平endmodule

配置及显示模块
为了偷懒用一个巨长的状态机操作ILI9486,其实不太利于扩展,之后如果想显示图片或者搭配摄像头最好能分立模块再搭配FIFO
输入20MHz时钟,每两个时钟完成一次完整读或写操作,所以写数据的频率为10MHz,写满320*480的RAM的频率大概为65Hz。也就是说屏幕的刷新率不能高于65Hz,在后面初始化配置参数时需要用到。
其实ILI9486的配置选项很多,这里只配置了我认为最基础的,各位看官可以按需配置。

 module LCD_Config(    input wire clk,    input wire rst_n,    input wire [15:0] data_in,    output reg cs,    output reg rs,    output reg wr,    output reg rd,    output reg [15:0] data_out    );    parameter total_px = 18'd153600;    parameter total_dl = 13'd5120;    reg [5:0] cnt=0;    reg [12:0] delay=0;    reg [17:0] px_cnt=0;    always@ (posedge clk)    begin        if(~rst_n)        begin            cnt <= 0;            delay <= 0;            px_cnt <= 0;        end        rd <= 1;        cs <= 0;        case(cnt)        //cmd//sw reset        0:        begin            rs <= 0;            wr <= 0;            data_out[7:0] <= 8'h1;              cnt <= cnt + 1'd1;        end        1:        begin            wr <= 1'd1;            cnt <= cnt + 1'd1;        end        //delay ~5ms        2:        begin            if(delay == total_dl - 1'd1)            begin                delay <= 13'd0;                cnt <= cnt + 1'd1;            end            else                delay <= delay + 1'd1;        end        //cmd//Sleep OUT        3:              begin            rs <= 0;            wr <= 0;            data_out[7:0] <= 8'h11;             cnt <= cnt + 1'd1;        end        4:              begin            wr <= 1'd1;            cnt <= cnt + 1'd1;        end        //delay ~5ms        5:              begin            if(delay == total_dl - 1'd1)            begin                delay <= 13'd0;                cnt <= cnt + 1'd1;            end            else                delay <= delay + 1'd1;        end        //cmd//Normal Display Mode ON        6:              begin            rs <= 0;            wr <= 0;            data_out[7:0] <= 8'h13;             cnt <= cnt + 1'd1;        end        7:        begin            wr <= 1'd1;            cnt <= cnt + 1'd1;        end        //cmd//Display ON        8:              begin            rs <= 0;            wr <= 0;            data_out[7:0] <= 8'h29;             cnt <= cnt + 1'd1;        end        9:        begin            wr <= 1'd1;            cnt <= cnt + 1'd1;        end        //cmd//Column Address Set        10:         begin            rs <= 0;            wr <= 0;            data_out[7:0] <= 8'h2A;             cnt <= cnt + 1'd1;        end        11:        begin            wr <= 1'd1;            cnt <= cnt + 1'd1;        end         12: //arg//sc[15:8]        begin            rs <= 1;            wr <= 0;            data_out[7:0] <= 8'h0;              cnt <= cnt + 1'd1;        end        13:        begin            wr <= 1'd1;            cnt <= cnt + 1'd1;          end        14: //arg//sc[7:0]        begin            rs <= 1;            wr <= 0;            data_out[7:0] <= 8'h0;              cnt <= cnt + 1'd1;        end        15:        begin            wr <= 1'd1;            cnt <= cnt + 1'd1;          end        16: //arg//ec[15:8]        begin            rs <= 1;            wr <= 0;            data_out[7:0] <= 8'h1;              cnt <= cnt + 1'd1;        end        17:        begin            wr <= 1'd1;            cnt <= cnt + 1'd1;          end        18: //arg//ec[7:0]        begin            rs <= 1;            wr <= 0;            data_out[7:0] <= 8'h3F;             cnt <= cnt + 1'd1;        end        19:        begin            wr <= 1'd1;            cnt <= cnt + 1'd1;          end        //cmd//Page Address Set        20:         begin            rs <= 0;            wr <= 0;            data_out[7:0] <= 8'h2B;             cnt <= cnt + 1'd1;        end        21:        begin            wr <= 1'd1;            cnt <= cnt + 1'd1;          end        22: //arg//sp[15:8]        begin            rs <= 1;            wr <= 0;            data_out[7:0] <= 8'h0;              cnt <= cnt + 1'd1;        end        23:        begin            wr <= 1'd1;            cnt <= cnt + 1'd1;          end        24: //arg//sp[7:0]        begin            rs <= 1;            wr <= 0;            data_out[7:0] <= 8'h0;              cnt <= cnt + 1'd1;        end        25:        begin            wr <= 1'd1;            cnt <= cnt + 1'd1;          end        26: //arg//ep[15:8]        begin            rs <= 1;            wr <= 0;            data_out[7:0] <= 8'h1;              cnt <= cnt + 1'd1;        end        27:        begin            wr <= 1'd1;            cnt <= cnt + 1'd1;          end        28: //arg//ec[7:0]        begin            rs <= 1;            wr <= 0;            data_out[7:0] <= 8'hDF;             cnt <= cnt + 1'd1;        end        29:        begin            wr <= 1'd1;            cnt <= cnt + 1'd1;          end        //cmd//Memory Access Contrl        30:         begin            rs <= 0;            wr <= 0;            data_out[7:0] <= 8'h36;             cnt <= cnt + 1'd1;        end        31:        begin            wr <= 1'd1;            cnt <= cnt + 1'd1;          end        32: //arg        begin            rs <= 1;            wr <= 0;            data_out[7:0] <= 8'h0;              cnt <= cnt + 1'd1;        end        33:        begin            wr <= 1'd1;            cnt <= cnt + 1'd1;          end        //cmd//Interface Pixel Format        34:         begin            rs <= 0;            wr <= 0;            data_out[7:0] <= 8'h3A;             cnt <= cnt + 1'd1;        end        35:        begin            wr <= 1'd1;            cnt <= cnt + 1'd1;          end        36: //arg        begin            rs <= 1;            wr <= 0;            data_out[7:0] <= 8'b0101_x_101;             cnt <= cnt + 1'd1;        end        37:        begin            wr <= 1'd1;            cnt <= cnt + 1'd1;          end        //cmd//Frame Rate Control        38:         begin            rs <= 0;            wr <= 0;            data_out[7:0] <= 8'hB1;             cnt <= cnt + 1'd1;        end        39:        begin            wr <= 1'd1;            cnt <= cnt + 1'd1;          end        40: //arg        begin            rs <= 1;            wr <= 0;            data_out[7:0] <= 8'hB0;             cnt <= cnt + 1'd1;        end        41:        begin            wr <= 1'd1;            cnt <= cnt + 1'd1;          end        42: //arg        begin            rs <= 1;            wr <= 0;            data_out[7:0] <= 8'h11;             cnt <= cnt + 1'd1;        end        43:        begin            wr <= 1'd1;            cnt <= cnt + 1'd1;          end        //cmd//Display Function Control        44:         begin            rs <= 0;            wr <= 0;            data_out[7:0] <= 8'hB6;             cnt <= cnt + 1'd1;        end        45:        begin            wr <= 1'd1;            cnt <= cnt + 1'd1;          end        46: //arg1        begin            rs <= 1;            wr <= 0;            data_out[7:0] <= 8'h0;              cnt <= cnt + 1'd1;        end        47:        begin            wr <= 1'd1;            cnt <= cnt + 1'd1;          end        48: //arg2        begin            rs <= 1;            wr <= 0;            data_out[7:0] <= 8'h1;              cnt <= cnt + 1'd1;        end        49:        begin            wr <= 1'd1;            cnt <= cnt + 1'd1;          end        50: //arg3        begin            rs <= 1;            wr <= 0;            data_out[7:0] <= 8'h3B;             cnt <= cnt + 1'd1;        end        51:        begin            wr <= 1'd1;            cnt <= cnt + 1'd1;          end        //cmd//Memory Write         52:         begin            rs <= 0;            wr <= 0;            data_out[7:0] <= 8'h2C;             cnt <= cnt + 1'd1;        end        53:        begin            wr <= 1'd1;            cnt <= cnt + 1'd1;          end        54: //arg        begin            rs <= 1;            wr <= 0;            data_out <= data_in;                cnt <= cnt + 1'd1;        end        55:        begin            wr <= 1'd1;            //memory is full            if(px_cnt == total_px - 1'd1)            begin                px_cnt <= 0;                cnt <= 6'd52;             end            else            begin                px_cnt <= px_cnt + 1'd1;                cnt <= 6'd54;             end         end        endcase    endendmodule

3.实际效果

蓝色

绿色

红色