跳到主要内容

14.4 实验原理

14.4.1 液晶屏介绍

查看底板上集成的2.0寸串行彩色液晶屏规格书,屏幕采用ST7789的驱动芯片,接下来我们主要根据ST7789的芯片手册来了解其工作原理和驱动方法。

ST7735S为262K 真彩控制器/驱动器,芯片可以直接跟外部处理器连接,支持串行SPI通信和8/9/16/18位并行通信(本液晶屏集成ST7735S时没有留并行接口,所以只能使用串行通信),显示数据存储在片上240x320x18 RAM块中,详细参数请参考数据手册。

alt text
液晶屏驱动芯片原理示意

ST7789支持不同位宽的并行通信格式。

alt text

在控制器给屏幕刷屏时,根据MV、MX、MY的配置支持8种不同方向的刷屏模式。

alt text

支持大量功能指令,部分系统功能指令列表如下:

alt text

更多的内容这里就不一一介绍了,感兴趣的同学可以详细阅读ST7789芯片手册。

14.4.2 液晶屏硬件连接

STEP BaseBoard V4.0底板上的2.0寸串行彩色液晶屏模块电路,其电路图如下:

alt text
2.0寸串行彩色液晶屏硬件电路

底板上的2.0寸串行彩色液晶屏电路和HDMI显示电路复用部分FPGA管脚,两者不能同时使用,当使用2.0寸串行彩色液晶屏时,DISP_SEL信号置高,驱动2.0寸串行彩色液晶屏使能同时点亮背光,DISP_2~ DISP_5分别对应RESET、D/C、SDA、SCK管脚,最后FPGA驱动2.0寸液晶屏完成屏显示控制即可。

14.4.3 液晶屏驱动设计

要驱动液晶屏需要先了解液晶屏的驱动流程,可以从液晶屏驱动芯片ST7735S的芯片手册上获取,也可以到网上找找有没有别人使用同类液晶屏的案例,或者向卖方问问有没有相关资料提供,这里我们找到了一个用C51单片机驱动的程序例程,例程仅供参考,需要根据例程中的配置到芯片手册中查找确认,不可以直接套用。 首先完成液晶屏初始化操作,51程序流程如下:

void Lcd_Init(void)
{
    LCD_RES_Clr();
    delay_ms(100);
    LCD_RES_Set();
    delay_ms(100);
    LCD_BLK_Set();
    delay_ms(100);
    //************* Start Initial Sequence **********//
    LCD_WR_REG(0x11); //Sleep out
    delay_ms(120);              //Delay 120ms
    //************* Start Initial Sequence **********//
    LCD_WR_REG(0x36);
    if(USE_HORIZONTAL==0)LCD_WR_DATA8(0x00);
    else if(USE_HORIZONTAL==1)LCD_WR_DATA8(0xC0);
    else if(USE_HORIZONTAL==2)LCD_WR_DATA8(0x70);
    else LCD_WR_DATA8(0xA0);

    LCD_WR_REG(0x3A);          
    LCD_WR_DATA8(0x05);

    LCD_WR_REG(0xB2);          
    LCD_WR_DATA8(0x0C);
    LCD_WR_DATA8(0x0C);
    LCD_WR_DATA8(0x00);
    LCD_WR_DATA8(0x33);
    LCD_WR_DATA8(0x33);            

    LCD_WR_REG(0xB7);          
    LCD_WR_DATA8(0x35);

    LCD_WR_REG(0xBB);          
    LCD_WR_DATA8(0x32); //Vcom=1.35V
                   
    LCD_WR_REG(0xC2);
    LCD_WR_DATA8(0x01);

    LCD_WR_REG(0xC3);          
    LCD_WR_DATA8(0x15); //GVDD=4.8V  ��ɫ���
               
    LCD_WR_REG(0xC4);          
    LCD_WR_DATA8(0x20); //VDV, 0x20:0v

    LCD_WR_REG(0xC6);          
    LCD_WR_DATA8(0x0F); //0x0F:60Hz        

    LCD_WR_REG(0xD0);          
    LCD_WR_DATA8(0xA4);
    LCD_WR_DATA8(0xA1);

    LCD_WR_REG(0xE0);
    LCD_WR_DATA8(0xD0);  
    LCD_WR_DATA8(0x08);  
    LCD_WR_DATA8(0x0E);  
    LCD_WR_DATA8(0x09);  
    LCD_WR_DATA8(0x09);  
    LCD_WR_DATA8(0x05);  
    LCD_WR_DATA8(0x31);  
    LCD_WR_DATA8(0x33);  
    LCD_WR_DATA8(0x48);  
    LCD_WR_DATA8(0x17);  
    LCD_WR_DATA8(0x14);  
    LCD_WR_DATA8(0x15);  
    LCD_WR_DATA8(0x31);  
    LCD_WR_DATA8(0x34);  

    LCD_WR_REG(0xE1);    
    LCD_WR_DATA8(0xD0);  
    LCD_WR_DATA8(0x08);  
    LCD_WR_DATA8(0x0E);  
    LCD_WR_DATA8(0x09);  
    LCD_WR_DATA8(0x09);  
    LCD_WR_DATA8(0x15);  
    LCD_WR_DATA8(0x31);  
    LCD_WR_DATA8(0x33);  
    LCD_WR_DATA8(0x48);  
    LCD_WR_DATA8(0x17);  
    LCD_WR_DATA8(0x14);  
    LCD_WR_DATA8(0x15);  
    LCD_WR_DATA8(0x31);  
    LCD_WR_DATA8(0x34);
    LCD_WR_REG(0x21);

    LCD_WR_REG(0x29);
}

初始化过程中写的所有指令和数据存储,同时存储的还有指令或数据标志,例如初始化第1条指令为8'h11,我们增加最高位1‘b0组成9位位宽数据。存储器部分指令和数据如下:

7'd0:  init_data <= 9'h011 ;
7'd1:  init_data <= 9'h036 ;
7'd2:  init_data <= 9'h100 ;
7'd3 :  init_data <= 9'h03a ;
7'd4 :  init_data <= 9'h105 ;

FPGA驱动液晶屏的设计使用SPI总线,状态机完成,程序实现如下:

//mosi:SPI总线写数据信号
always@(posedge sys_clk_50MHz or negedge sys_rst_n)
    if(!sys_rst_n)
        mosi <= 1'b0;
    else if(state == STATE0)
        mosi <= 1'b0;
    else if(state == STATE1 && cnt_delay == CNT_SCLK_MAX)
        mosi <= data[7];
    else if(state == STATE2 && sclk_flag)
        case(cnt1)
            1 : mosi <= data[6];
            3 : mosi <= data[5];
            5 : mosi <= data[4];
            7 : mosi <= data[3];
            9 : mosi <= data[2];
            11: mosi <= data[1];
            13: mosi <= data[0];
            15: mosi <= 1'b0;
            default: mosi <= mosi;
        endcase
    else
        mosi <= mosi;

14.4.4 系统总体实现

液晶屏驱动模块的数据来源于图片数据的ram模块,这些数据由图片取模得到,使用图片取模软件,将图片载入软件,输出数据类型选择C语言数组,根据液晶屏驱动实际情况配置对应的扫描模式,输出灰度选择单色,调整最大宽度和高度符合液晶屏要求,最后点击保存生成需要的文件。此处我们以小脚丫的logo为例:

alt text
取模软件参数配置

打开生成的文件,数据格式如下,是C语言的格式

const unsigned char gImage_11[1990] = { 0X10,0X01,0X00,0X80,0X00,0X7C,
0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,
0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,
0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,
0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0XF8,0X00,0X00,0X00,0X00,0X00,0X00,0X00,
0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X07,0XFF,0X00,0X00,0X00,0X00,0X00,0X00,0X00,

使用编辑器的查找替换功能,将数据处理成下图格式

240'h00000000000000000000000000000000000000000000000000000000000;
240'h00000000000000000000000000000000000000000000000000000000000;
240'h00000000000000000000000000000000000000000000000000000000000;
240'h00000000000000000000000000000000000000000000000000000000000;
240'h00000000000000000000000000000000000000000000000000000000000;

创建ram模块,将图片数据初始化到ram中,程序实现图下:

module pic_ram (address, q);

    input wire [8:0] address;
    output reg [239:0] q;
     
    always @ (*)
        case(address)
            9'd0:q = 240'h00000000000000000000000000000000000000000000000000000000000;
            9'd1  : q = 240'h00000000000000000000000000000000000000000000000000000000000;
            9'd2  : q = 240'h00000000000000000000000000000000000000000000000000000000000;
            9'd3  : q = 240'h00000000000000000000000000000000000000000000000000000000000;
            9'd4  : q = 240'h00000000000000000000000000000000000000000000000000000000000;

存储图片数据的ram本实验采用分布式ram搭建,前面波形信号发生器实验中讲过ram IP核的例化及使用方法,有兴趣的同学可以自己尝试一下。

完成温湿度数据的获取后,即可将数据显示在液晶屏上,首先确定要显示的字符以及是否有正负号,以及当要显示“小数”字符时读取温度和湿度的BCD码。

reg     [15:0]      T_data_r;
reg     [15:0]      H_data_r;
reg                 fuhao;

always@(posedge clk)begin
    if(next_char_flag) begin
        if(T_data[15:12] == 4'ha)
            fuhao <= 1'b1;
        else
            fuhao <= 1'b0;
    end
    else
        fuhao <= fuhao;
end

always@(posedge clk or negedge rst_n)
begin
    if(!rst_n)  begin
        T_data_r <= 16'd0;
        H_data_r <= 16'd0;
    end
    else if(next_char_flag && (the_char == XIAOSHU))    begin
        T_data_r <= T_data;
        H_data_r <= H_data;
    end
    else begin
        T_data_r <= T_data_r;
        H_data_r <= H_data_r;
    end
End

//the_char decide which char to display
always@(posedge clk or negedge rst_n)
begin
    if(!rst_n)  begin
        the_char <= FIRST;
    end
    else if(next_char_flag) begin
        case (the_char)
            FIRST    :begin
                the_char <= COLON;
            end
            COLON   :begin
                the_char <= BAI;
            end
            BAI     :begin
                the_char <= SHI;
            end
            SHI     :begin
                the_char <= GE;
            end
            GE      :begin
                the_char <= DOT;
            end
            DOT     :begin
                the_char <= XIAOSHU;
            end
            XIAOSHU :begin
                the_char <= (fuhao)?BAI:SHI;
            end
            default: the_char <= FIRST;
        endcase
    end
    else
        the_char <= the_char;
end

根据字符和温度湿度确定rom的读取地址,液晶屏显示窗口的起始位置和长度。

// rom address begin
always@(*)
begin
    case (the_char)
        FIRST   : addr_start <= (select)?9'd352:9'd320;
        COLON   : addr_start <= 'd430;
        DOT     : addr_start <= 'd418;
        BAI     : addr_start <= (select)?((!fuhao)?9'd480:9'd448):9'd441;
        SHI     : addr_start <= (select)?({T_data_r[11:8],5'b00000}):({H_data_r[11:8],5'b00000});
        GE      : addr_start <= (select)?({T_data_r[7:4],5'b00000}):({H_data_r[7:4],5'b00000});
        XIAOSHU : addr_start <= (select)?({T_data_r[3:0],5'b00000}):({H_data_r[3:0],5'b00000});
        default: addr_start <= 'd0;
    endcase
end
//generate the window depend on the_char
always@(*)
begin
    case (the_char)
        FIRST   :begin
            window_x0 <= 16'd20;
        end
        COLON   :begin
            window_x0 <= 16'd60;
        end
        BAI     :begin
            window_x0 <= 16'd90;
        end
        SHI     :begin
            window_x0 <= (fuhao)?16'd130:16'd90;
        end
        GE      :begin
            window_x0 <= (fuhao)?16'd170:16'd130;
        end
        DOT     :begin
            window_x0 <= (fuhao)?16'd210:16'd170;
        end
        XIAOSHU :begin
            window_x0 <= (fuhao)?16'd230:16'd190;
        end
        default: begin
            window_x0 <= 16'd0;
        end
    endcase
end
always@(*)
begin
    case (the_char)
        FIRST   : char_length <= 6'd32;
        COLON   : char_length <= 6'd32;
        BAI     : char_length <= 6'd32;
        SHI     : char_length <= 6'd32;
        GE      : char_length <= 6'd32;
        DOT     : char_length <= 6'd32;
        XIAOSHU : char_length <= 6'd32;
        default: char_length <= 6'd32;
    endcase
end
always@(posedge clk or negedge rst_n)
begin
    if(!rst_n)  begin
        x_size <= 6'd31;
    end
    else if(the_char == COLON || the_char == DOT)   begin
        x_size <= 6'd12;
    end
    else begin
        x_size <= 6'd31;
    end
end
endmodule

在顶层模块中,将两个模块例化并连接,最终完成图片显示系统的总体设计。

综合后的设计框图如下:

alt text
RTL设计框图