片上系统设计思想与源代码分析
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

7.4 MCU接口LCD控制器

7.4.1 MCU接口LCD控制器介绍

MCU接口LCD的特点是其输入输出接口是标准的8位或16位单片机总线,其屏幕刷新和行列扫描过程在其内部完成,因此也被称作智能LCD(Smart LCD)。

这里以MzT24-1 TFT LCD为例。MzT24-1是一块高画质的TFT真彩LCD模块,具有丰富多样的接口、编程方便、易于扩展等良好性能。MzT24-1内置专用驱动和控制IC(SPFD5408),并且驱动IC自己集成显示缓存,无须外部显示缓存。

MzT24-01彩色TFT LCD显示模块的基本参数如表7-3所示。

表7-3 MzT24-1彩色TFT LCD显示模块的基本参数

MzT24-1彩色TFT LCD显示模块的输入输出如表7-4所示。

表7-4 MzT24-1彩色TFT LCD显示模块的引脚说明

MzT24-1彩色TFT LCD显示模块的LCD驱动控制IC为SPFD5408,用户在对MzT24-1模块进行操作时,实际上是对SPFD5408进行相关的控制寄存器、显示数据存储器进行操作。

MzT24-1模块的2.4英寸TFT-LCD显示面板上,共分布着240×320个像素点,而模块内部的TFT-LCD驱动控制芯片内置有与这些像素点对应的显示数据RAM(简称显存)。模块中每个像素点需要16位的数据(即2字节长度)来表示该点的RGB颜色信息,所以模块内置的显存共有240×320×16bit的空间,通常以字节(byte)来描述其大小。

MzT24-1模块的显示操作非常简便,需要改变某一个像素点的颜色时,只需要对该点所对应的2个字节的显存进行操作即可。而为了便于索引操作,模块将所有的显存地址分为X轴地址(X Address)和Y轴地址(Y Address),分别可以寻址的范围为X Address=0~239,Y Address= 0~319,X Address和Y Address交叉对应着一个显存单元(2byte);这样只要索引到了某一个X、Y轴地址时,并对该地址的寄存器进行操作,便可对TFT-LCD显示器上对应的像素点进行操作了。

MzT24-1模块的像素点与显存对应关系如图7-6所示:

图7-6 显存与像素点的对应关系

MzT24-1模块内部有一个显存地址累加器AC,用于在读写显存时对显存地址进行自动的累加,这在连续对屏幕显示数据操作时非常有用,特别是应用在图形显示、视频显示时。此外,AC累加器可以设置为各种方向的累加方式,如通常情况下为对X Address累加方式,具体为当累加到一行的尽头时,会切换到下一行的开始累加;还可以为对Y Address累加方式,具体为当累加到一列(垂直方向)的尽头时,会切换到下一个X Address所对应的列开始累加。

MzT24-1模块支持标准Intel8080总线,总线的最高速度可达8MHz,可支持视频。其接口时序如图7-7所示。

图7-7 MzT24-1模块接口时序

接口时序中的主要参数如表7-5所示。

表7-5 MzT24-1模块接口时序的主要时间参数

对MzT24-1模块的操作主要分为两种,一是对控制寄存器的读写操作,二是对显存的读写操作;而这两种操作实际上都是通过对LCD控制器(SPFD5408)的寄存器(Register)进行操作完成的,SPFD5408提供了一个索引寄存器(Index Register),对该Index Register寄存器的写入操作可以指定操作的寄存器索引,以便于完成控制寄存器、显存操作寄存器的读写操作。MzT24-1提供了RS(有些资料称A0)控制线,并以此线的高低电平状态来区别这对Index Register操作还是对所指向的寄存器进行操作,当RS为低电平时,表示当前的总线操作是对Index Register进行操作,即指明接下去的寄存器操作是针对哪一个寄存器的;当RS为高电平时,表示为对寄存器操作。

MzT24-1模块内部有控制寄存器,用户在使用MzT24-1之前及对其进行操作过程当中,需要对一些寄存器进行写操作以完成对LCD的初始化,或者是完成某些功能的设置(如当前显存操作地址设置等)。

对控制寄存器进行操作前,需要先对索引寄存器进行定入操作,以指明接下去的寄存器读写操作是针对哪一个寄存器的。操作的步骤如下:

①在RS为低电平的状态下,写入两个字节的数据,第一个字节为零,第二字节为寄存器索引值。

②然后在RS为主电平的状态下,写入两个字节数据,第一字节为高8位,第二字节为低8位;如要读出指定寄存器的数据,则需要连续三次读操作方能完成一次读出操作,第一个字节为无效数据,第二字节为高8位,第三字节为低8位。

MzT24-1的显存操作也是通过寄存器操作来完成的,即对0x22寄存器进行操作时,就是对当前位置点的显示进行读写操作。

MzT24-1模块的控制寄存器当中,最常被调用的是寄存器除了对显存操作的0x22寄存器外,还有当前显存地址的寄存器display RAM bus address counter(AC),一共由两个的寄存器组成,分别存放有X Address和Y Adderss,表示当前对显存数据的读写操作是针对于该地址所指向的显存单元;而每一个显存单元在前面已经用图示意过,每个单元有16位,最高的5位为R(红)的分量,最低的5位为B(蓝)的分量,中间6位为G(绿)分量。如图7-8所示。

图7-8 显存单元分量示意图

当需要对LCD显示面板上某一个坐标点(X,Y)进行操作时,需要先设置AC,以指向需要操作的点所对应的显存地址,然后连续写入或读出数据,才完成对该点的显存单元的数据操作。

而当对某一个显存单元完成写入数据操作后,AC会自动地进行调整,或者是不进行调整(根据控制寄存器中的设置而决定)保持原来指向。AC的这个特性对于MzT24-1模块来说非常有用,可以根据此特性设计出快速的LCD显示操作功能函数,以适应不同用户的需求。

关于该屏幕的更多信息请参考其用户手册。

7.4.2 MCU接口LCD控制器源代码分析

本章的MCU接口LCD针对MzT24-1而设计。其主体结构是一个状态机,该状态机的主要任务是完成对LCD指定寄存器的写操作。

查阅MzT24-1的模块编程手册可知,该模块内部有控制寄存器,用户在使用MzT24-1之前及对其进行操作过程中,需要对寄存器进行写操作以完成对LCD的初始化,或者是完成某些功能的设置(如当前显存操作地址设置等)。

对控制寄存器进行操作前,需要先对索引寄存器进行写入操作,以指明接下去的寄存器写操作是针对哪一个寄存器的。操作的步骤如下:

①在RS为低电平的状态下,写入两个字节的数据,第一个字节为零,第二个字节为寄存器索引。

②后在RS为高电平的状态下,写入两个字节数据,第一个字节为高8位,第二个字节为低8位。

smart_lcd完成以上4个字节的写操作及中间的等待过程。其源代码如下:

module smart_lcd(clk,rst,ack_o,err_o,rty_o,dat_i,adr_i,dat_o,sel_i,stb_i,cyc_i,we_i,
LCD_DATA,
LCD_CS, LCD_RS, LCD_RST, LCD_WR,LCD_RD);
……//此处忽略了输入输出定义
reg [7:0] lcd_data_out;
reg lcd_data_out_ena;
assign LCD_DATA = lcd_data_out_ena?lcd_data_out:8'bzzzz_zzzz;
wire write_op = adr_i == 32'hC0000000 && cyc_i && stb_i && we_i && sel_i == 4'hf;
wire read_op = adr_i == 4 && cyc_i && stb_i && sel_i == 4'hf;
parameter IDLE = 1, WRITE_INDEX_HIGH = 2, WAIT_1 = 3,     WRITE_INDEX_HIGH_CANCEL_WR
= 4, WRITE_INDEX_HIGH_END = 5,
        WRITE_INDEX_LOW = 6, WAIT_2 = 7,
        WRITE_INDEX_LOW_CANCEL_WR = 8, WRITE_INDEX_LOW_END = 9,
        WRITE_DATA_HIGH = 10, WAIT_3 = 11,
        WRITE_DATA_HIGH_CANCEL_WR = 12,
        WRITE_DATA_HIGH_END = 13, WRITE_DATA_LOW = 14,
        WAIT_4 = 15, WRITE_DATA_LOW_CANCEL_WR = 16,
        WRITE_DATA_LOW_END = 17, END_OPERATION = 18;
always @ (posedge clk or posedge rst) begin
    if(rst) begin
        ack_o <= 1'b0;  LCD_RS <= 1;LCD_CS <= 1;LCD_WR <= 1;LCD_RD <= 1;
        lcd_data_out_ena <= 0;lcd_data_out <= 0;wait_cnt <= 0;state <= IDLE;
    end
    else begin
        case(state)
        IDLE:begin
            if(write_op) begin //开始进行写操作
                state <= WRITE_INDEX_HIGH;LCD_RS <= 0;  LCD_CS <= 0;
                lcd_data_out <= 0;
            end
            else begin //空闲
                ack_o <= 1'b0;  LCD_RS <= 1;LCD_CS <= 1;LCD_WR <= 1;
                LCD_RD <= 1;lcd_data_out_ena <= 0;    lcd_data_out <= 0; wait_cnt <= 0;
            end
        end
        WRITE_INDEX_HIGH:begin //写INDEX高字节
            ack_o <= 1;data_buf <= dat_i[23:0];
            LCD_WR<=0;LCD_RD<=1;state<=WAIT_1;wait_cnt<=0;lcd_data_out_ena <= 1;
        end
        WAIT_1:begin
            ack_o <= 0;wait_cnt <= wait_cnt + 6'b1;
            if(wait_cnt > 3) state <= WRITE_INDEX_HIGH_CANCEL_WR;
        end
        WRITE_INDEX_HIGH_CANCEL_WR:begin
            LCD_WR <= 1;wait_cnt <= 0;
            state <= WRITE_INDEX_HIGH_END;
        end
        WRITE_INDEX_HIGH_END:begin
            wait_cnt <= wait_cnt + 1;
            if(wait_cnt > 4) begin
                state <= WRITE_INDEX_LOW;   lcd_data_out_ena <= 0;
            end
        end
        WRITE_INDEX_LOW:begin
            LCD_WR <= 0; LCD_RD <= 1; lcd_data_out <= data_buf[23:16];
            lcd_data_out_ena <= 1; wait_cnt <= 0; state <= WAIT_2;
        end
        WAIT_2:begin
            wait_cnt <= wait_cnt + 1;
            if(wait_cnt > 3)state <= WRITE_INDEX_LOW_CANCEL_WR;
        end
        WRITE_INDEX_LOW_CANCEL_WR:begin
            LCD_WR <= 1;wait_cnt <= 0;state <= WRITE_INDEX_LOW_END;
        end
        WRITE_INDEX_LOW_END:begin
            wait_cnt <= wait_cnt + 1;
            if(wait_cnt > 4) begin state <= WRITE_DATA_HIGH;lcd_data_out_ena<=0;end
        end
        WRITE_DATA_HIGH:begin
            LCD_RS <= 1; LCD_WR <= 0; lcd_data_out <= data_buf[15:8];
            lcd_data_out_ena <= 1; wait_cnt <= 0; state <= WAIT_3;
        end
        WAIT_3:begin
            wait_cnt <= wait_cnt + 1;
            if(wait_cnt > 3) state <= WRITE_DATA_HIGH_CANCEL_WR;
        end
        WRITE_DATA_HIGH_CANCEL_WR:begin
            LCD_WR <= 1;wait_cnt <= 0;
            state <= WRITE_DATA_HIGH_END;
        end
        WRITE_DATA_HIGH_END:begin
            wait_cnt <= wait_cnt + 1;
            if(wait_cnt > 4) begin state <= WRITE_DATA_LOW;lcd_data_out_ena <= 0;end
        end
        WRITE_DATA_LOW:begin
            LCD_RS <= 1;LCD_WR <= 0;lcd_data_out <= data_buf[7:0];
            lcd_data_out_ena <= 1;wait_cnt <= 0;state <= WAIT_4;
        end
        WAIT_4:begin
            wait_cnt <= wait_cnt + 1;
            if(wait_cnt > 3) state <= WRITE_DATA_LOW_CANCEL_WR;
        end
        WRITE_DATA_LOW_CANCEL_WR:begin
            LCD_WR <= 1; wait_cnt <= 0; state <= WRITE_DATA_LOW_END;
        end
        WRITE_DATA_LOW_END:begin
            wait_cnt <= wait_cnt + 1;
            if(wait_cnt > 4) begin state <= END_OPERATION; lcd_data_out_ena <= 0;end
        end
        END_OPERATION:begin state <= IDLE; ack_o <= 1'b0;end
        default:begin state <= IDLE;ack_o <= 1'b0;end
        endcase
    end//else
end//always
endmodule

7.4.3 MCU接口LCD控制器的验证

MCU接口LCD控制器设计已经通过FPGA验证,本书将在第20章中讲述如何在屏幕上显示图片、实时时钟。