LCD控制器

《嵌入式Linux应用完全开发手册》第2篇第13章总结归纳

本章目标

  1. 了解LCD显示器的接口及时序
  2. 掌握S3C2410/S3C2440LCD控制器的使用方法
  3. 了解帧缓冲区的概念,掌握如何设置帧缓冲区来显示图像

LCD和LCD控制器

LCD显示器

LCD的种类

LCD(Liquid Crystal Display),即液晶显示器,是一种采用了液晶控制透光度技术来实现色彩的显示器。它与传统的CRT显示器相比有很多优点:轻薄、能耗低、辐射小等,市场占有率越来越大。LCD有很多种类,比如STN、TFT、LTPS TFT、OLED等,各有优缺点。
STN(Super Twisted Nematic,超扭曲向列),有CSTN和DSTN之分,是4种LCD屏中最低端的一种,仅有的优点就是功耗低,在色彩鲜艳度和画面亮度上相对于TFT和其他LCD屏存在明显不足,在日光下几乎不能显示,而且响应时间长达200ms左右,播放动画或视频拖影明显不足。
TFT(Thin Film Transistor,薄膜晶体管)可以大大缩短屏幕响应时间,其响应时间已经小于80ms,并改善了STN连续显示时屏幕闪烁模糊,有效提供了动态画面的播放力,呈现画面色彩饱和度、真实效果和对比度都很不错,完全超越STN,只是功耗稍高,是目前最为主流的液晶显示器类型。在MP3、MP4产品上大量应用,在桌面液晶显示器、笔记本电脑、手机等产品上的应用也非常普遍。
LTPS(Low Temperature Polycrystalline Silicon,低温多晶硅)由TFT衍生的新一代的技术产品,可以获得更高的分辨率和更丰富的色彩。LTPS LCD可以提供170°的水平和垂直可视角度,显示响应仅12ms,显示亮度达到500Cd/m²,对比度可达500:1,这就是一些桌面液晶屏性能越来越出色的原因。虽然LTPS LCD已经出现很多年了,但由于LTPS TFT液晶屏的制造需要高于制造传统TFT屏的技术水平,目前仅有少数知名大厂能制造。
OLED(Organic Light Emitted Diode,有机发光二极管)各种物理特性都具备领先优势,色彩明亮、可视角度超大、非常省电,是未来发展的主流,只是目前受技术与成本限制,未能广泛普及。目前,彩色OLED比较广泛的存在中低端产品中。

LCD的接口

CPU或显卡发出的图像是TTL信号,LCD本身接收的也是TTL信号。但是由于TTL信号高速率的长距离传输性能不佳,抗干扰能力也比较差,后来又提出了多种接口,比如LVDS、TDMS、GVIF、P&D、DVI和DFP等。它们实际上只是将CPU或显卡发出的TTL信号编码成各种信号以便传输,在LCD那边将接收到的信号进行解码得到TTL信号。
由于数字接口标准尚未统一,所以使用LCD时需要根据其手册了解具体接口定义。也是基于数字接口标准尚未统一的原因,市场上大多LCD都采用模拟接口信号,LCD先通过ADC将模拟信号转换为数字信号才能显示。
但是不管采用何种数字接口,本质的TTL信号是一样的。

  1. 对于STN LCD
    STN LCD 的数据传输方式有3种:4位单扫(4-bit single scan)、4位双扫(4-bit dual scan)、8位单扫(8-bit single scan)。所谓“单扫”是指对于一整屏的数据,从上到下、从左到右,一个一个的发送出来;“双扫”是指将一整屏的数据分为上下两部分,同时从上到下、从左到右,一个一个的发送出来。“4位”、“8位”是指发送数据时使用多少个数据线;需要注意的是,4位双扫方式也是用到8根数据线,其中4根用于上半屏数据,另外4根用于下半屏数据。
    除数据信号外,还有其他控制信号,所有TTL信号如下表所示:

    信号名称 描述
    VFRAME 帧同步信号
    VLINE 行同步信号
    VCLK 像素时钟信号
    VD[7:0] 数据信号
    VM AV偏置信号
    PWREN 电源开关信号
  2. 对于TFT LCD
    TFT LCD的信号与STN类似,只是其数据信号多达24根,对应像素值的每一位。

    信号名称 描述
    VSYNC 垂直同步信号
    HSYNC 水平同步信号
    HCLK 像素时钟信号
    VD[23:0] 数据信号
    LEND 行结束信号
    PWREN 电源开关信号

S3C2410/S3C2440 LCD控制器介绍

S3C2410/S3C2440 LCD控制器的特性与结构

S3C2410/S3C2440 LCD控制器被用来向LCD传输图像数据,并提供必要的控制信号,比如VFRAME、VLINE、VCLK、VM等。可以支持STN LCD、TFT LCD,其特性如下(BPP 表示bit per pixel,即每个颜色像素点用多少位来表示)。
STN LCD

  1. 支持3种扫描方式:4位单扫,4位双扫和8位单扫
  2. 支持单色(1BPP)、4级灰度(2BPP)、16级灰度屏(4BPP)
  3. 支持256色(8BPP)和4096色(12BPP)彩色STN屏(CSTN)
  4. 支持分辨率为640x480、320x240、160x160以及其他规格的多种LCD
  5. 虚拟屏幕最大可达4MB
  6. 对于256色,分辨率有4094x1024、2048x2048、1024x4096等多种

TFT LCD

  1. 支持单色(1BPP)、4级灰度(2BPP)、16级灰度(4BPP)、256色(8BPP)的调色板显示模式
  2. 支持64K(16BPP)和16M(24BPP)色非调色板显示模式
  3. 支持分辨率为640x480、320x320及其他多种规格的LCD
  4. 虚拟屏幕最大可达4MB
  5. 对于64K色,分辨率有2048x1024等多种

S3C2410/S3C2440 LCD控制器提供了驱动STN LCD、TFT LCD所需的所有信号,另外,还特别提供额外的信号以支持SEC公司生产的TFT LCD。这3类信号中很大部分是复用的。
S3C2410/S3C2440 LCD控制器的内部结构如下图所示:
img not found
REGBANK是LCD控制器的寄存器组,含17个寄存器及一块256x16的调色板内存,用来设置各项参数。而LCDCDMA则是LCD控制器专用的DMA通道,可以自动地从系统总线(System Bus)上取到图像数据,这使得显示图像时不需要CPU的干涉。VIDPRCS将LCDCDMA中的数据组合成特定的格式,然后从VD[23:0]发送给LCD屏。同时TIMEGEN和LPC3600负责产生LCD屏所需要的控制时序,例如VSYNC、HSYNC、VCLK、VDEN,然后从VIDEO MUX送给LCD屏。其中LPC3600专用于SEC TFT LCD。
LCDCDMA中有2个FIFO:FIFOH容量为16(1个字为4个字节)个字,FIFOL容量为12个字。当使用“双扫”方式时,FIFOH、FIFIL分别用于传输上半屏、下班屏数据;当使用“单扫”方式时,只用到FIFOH。当FIFO为空或者其中的数据已经减少到设定的阈值时,LCDCDMA自动的发起DMA传输从内存中获得图像数据。

显示器上数据的组织格式

一幅图像被称为一帧,每帧由多行组成,每行由多个像素组成,每个像素的颜色用若干位的数据表示。对于单色显示器,每个像素用1位来表示,称为1BPP,对于256色显示器,每个像素使用8位来表示,称为8BPP。
显示器从屏幕的左上方开始,一行一行的取得每个像素的数据并显示出来,当显示当一行的最右边时,跳到下一行的最左边开始显示下一行;当显示完所有行后,跳到左上方开始显示下一帧。显示器沿着“Z”字行的路线进行扫描,使用HSYNC、VSYNC信号来控制扫描路线的跳转。HSYNC表示“是时候跳到最左边了”,VSYNC表示“是时候跳到最上边了”。
在工作中的显示器上,可以在四周看到黑色的边框。上方的黑框是因为显示完所有行的数据时,显示器还没有扫描到最下边(VSYNC信号还未发出),这时数据已经无效。左边的黑框是因为当发出HSYNC信号时,需要经过若干像素之后第一列数据才有效;右边的黑框是因为显示完一行的数据时,显示器还每扫描到最右边(HSYNC信号还没有发出),这时数据已经无效。显示器只会依据VSYNC、HSYNC信号来取得、显示数据,并不理会该数据是否有效,何时发出有效数据由显卡决定。
VSYNC信号出现的频率表示1秒内能显示多少帧的图像,称为垂直频率或者场频率,这就是我们常说的“显示器的频率”;HSYNC信号出现的频率称为水平频率。
显示器上,一帧数据的存放位置与VSYNC、HSYNC信号的关系如下图所示:
img not found
有效数据的行数、列数即分辨率,它与VSYNC、HSYNC信号之间的“距离”等,都是可以设置的,这由显卡完成。

TFT LCD的操作

目前市场上主流的LCD为TFT LCD,先了解TFT LCD的时序,这使得我们在设置各个寄存器时有个形象的概念。每个VSYNC信号表示一帧数据的开始;每个HSYNC信号表示一行数据的开始,无论这些数据是否有效;每个VCLK信号表示正在传输一个像素的数据,无论它是否有效。数据是否有效只是对CPU的LCD控制器来说的,LCD根据VSYNC、HSYNC、VCLK不停的读取总线数据、显示。
img not found

  1. VSYNC信号有效时,表示一帧数据的开始。
  2. VSPW表示VSYNC信号的脉冲宽度为(VSPW + 1)个HSYNC信号周期,即(VSPW + 1)行,这(VSPW + 1)行数据无效。
  3. VSYNC信号脉冲之后,还要经过(VBPD + 1)个HSYNC信号周期,有效的行数据才出现。所以在VSYNC信号有效之后,总共还要经过(VSPW + 1 + VBPD + 1)个无效的行,它对应图13.2上方的边框,第一个有效的行才出现。
  4. 随后即连续发出(LINEVAL + 1)行的有效数据。
  5. 最后是(VFPD + 1)个无效的行,它对应图13.2下方的边框,完整的一帧结束,紧接着就是下一帧的数据了(即下一个VSYNC信号)。

现在深入到一行像素的传输过程。类似于行数据的传输过程。

  1. HSYNC信号有效时,表示一行数据的开始。
  2. HSPW表示HSYNC信号的脉冲宽度为(HSPW + 1)个VCLK信号周期,即(HSPW + 1)个像素,这(HSPW + 1)个像素的数据无效。
  3. HSYNC信号脉冲无效之后,还要经过(HBPD + 1)个VCLK信号周期,有效的像素数据才出现。所以,在HSYNC信号有效之后,总共还要经过(HSPW + 1 + HBPD + 1)个无效的像素,它对应图13.2的左边框,第一个有效像素才出现。
  4. 随后即连续发出(HOZVAL + 1)个像素的有效数据。
  5. 最后是(HFPD + 1)个无效的像素,它对应图13.2的右边框,完整的一行结束,紧接着就是下一行的数据了(即下一个HSYNC信号)。

时序图中各信号的时间参数都可以在LCD控制寄存器中设置,VCLK作为时序图的基准信号,它的频率可由此计算:

1
VCLK(hz) = HCLK / [(CLKVAL + 1 x 2)]

VSYNC信号的频率又称为帧频率、垂直频率、场频率、显示器的频率,它可以如下计算:

1
Frame Rate = 1/[{(VSPW+1)+(VBPD+1)+(LINEVAL+1)+(VFPD+1)} x { (HSPW+1)+(HBPD+1)+(HFPD+1)+(HOZVAL+1) } x { 2 x ( CLKVAL+1) / (HCLK)}]

将VSYNC、HSYNC、VCLK等信号的时间参数设置好之后,并将帧内存(frame memory)的地址告诉LCD控制器,它即可自动发起DMA传输从帧内存得到图像数据,最终在上述信号的控制下出现在数据总线VD[23:0]上。用户只需要把要显示的图像数据写入帧内存中。
下面介绍各种图像的格式数据在内存中如何存储。
显示器上的每个像素的颜色都是由3个部分组成:红、绿、蓝。它们被称为三原色,这三者的混合几乎可以表示人眼所能识别到的所有颜色。比如可以根据颜色的浓烈程度将三原色都分为256个级别(0-255)。可以使用255级的红色、255级的绿色、255级的蓝色可以组成白色。0级的红色、0级的绿色、0级的蓝色可以组成黑色。
LCD控制器可以支持单色(1BPP)、4级灰度(2BPP)、16级灰度(8BPP)、256色(8BPP)的调色板显示模式,支持64K(16BPP)和16M(24BPP)非调色板显示模式。下面只介绍256色(8pp)、64K(16BPP)和16M(24BPP)色显示模式下,图像数据的存储格式。

  1. 16M(24BPP)色
    16M(24BPP)色的显示模式就是使用24位的数据来表示一个像素的颜色,每种原色使用8位。LCD控制器从内存中获得某个像素的24位颜色值后,直接通过VD[23:0]数据线发送给LCD。为了方便DMA传输,在内存中使用4个字节(32)位来表示一个像素,其中的3个字节从高到低分别表示红、绿、蓝,剩余的1个字节数据无效。是最低字节还是最高字节无效,这时可以选择的。
    img not found
  2. 64K(16BPP)色
    64K(16BPP)色的显示模式就是使用16位的数据来表示一个像素的颜色。这16位数据的格式又分为两种:5:6:5、5:5:5:1,前者使用高5位来表示红色,中间的6位来表示绿色,低5位来表示蓝色;后者的高15位从高到低分成3个5位来表示红色、绿色、蓝色,最低位表示透明度。5:5:5:1的格式也被称为RGBA(A表示Alpha,指代透明度)。
    一个4字节可以表示两个16BPP的像素,使用高2字节还是低2字节来表示第一个像素,这也是可以选择的。
    显示模式为16BPP时,内存数据与像素位置的关系如图所示:
    img not found
    在5:5:5:1的格式下,VD[18]、VD[10]、VD[2]数据线上的值是一样的,都表示透明度。图中的NC表示没有连接(not connect)。
  3. 256(8BPP)色
    256(8BPP)色的显示模式就是使用8位的数据来表示一个像素的颜色,但是对三种原色平均下来,每个原色只能使用不到3位的数据来表示,即每个原色最多不过8个级别,这不足以表示更丰富的颜色。
    为了解决8BPP模式显示能力太弱的问题,需要使用调色板。每个像素对应8位数据不再用来表示RGB三原色,而是表示它在调色板中的索引值;要显示这个像素时,使用这个索引值从调色板中取得其RGB颜色值。所谓调色板就是一块内存,可以对每个索引值设置其颜色,可以使用24BPP或16BPP。S3C2410/S3C2440中,调色板是一块256x16的内存,使用16BPP的格式来表示256色(8BPP)显示模式下各个索引值的颜色。这样即使使用256色(8BPP)的显示模式,最终在LCD数据总线上的仍是16BPP的数据。
    一个4字节可以表示4个8BPP的像素,字节与像素的对应顺序是可以选择的,如下图所示:
    img not found
    调色板中数据的存放格式与16BPP显示模式类似,也分为两种:5:6:5、5:5:5:1。调色板中数据的格式及与LCD数据线VD[23:0]的对应关系,如下表所示:

5:6:5格式下调色板的数据格式

序号 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 地址
00H R4 R3 R2 R1 R0 G5 G4 G3 G2 G1 G0 B4 B3 B2 B1 B0 0X4D000400
01H R4 R3 R2 R1 R0 G5 G4 G3 G2 G1 G0 B4 B3 B2 B1 B0 0X4D000404
… … … …
FFH R4 R3 R2 R1 R0 G5 G4 G3 G2 G1 G0 B4 B3 B2 B1 B0 0X4D0007FC
VD引脚号 23 22 21 20 19 15 14 13 12 11 10 7 6 5 4 3

5:5:5:1格式下调色板的数据格式

序号 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 地址
00H R4 R3 R2 R1 R0 G4 G3 G2 G1 G0 B4 B3 B2 B1 B0 0X4D000400
01H R4 R3 R2 R1 R0 G4 G3 G2 G1 G0 B4 B3 B2 B1 B0 0X4D000404
… … … …
FFH R4 R3 R2 R1 R0 G4 G3 G2 G1 G0 B4 B3 B2 B1 B0 0X4D0007FC
VD引脚号 23 22 21 20 19 15 14 13 12 11 7 6 5 4 3 2
注:
①0X4D000400是调色板的起始地址。
②5:5:5:1格式下,VD18、VD10和VD2三个数据线中都是亮度值1,即最低位的值。
③当LCDCCON5寄存器中的VSTATUS、HSTATUS有效时,不能读写调色板。

各模式下用来传输红、绿、蓝三种原色的颜色值VD数据线如下表所示:

24BPP 16BPP/8BPP 5:6:5格式 16BPP/8BPP 5:5:5:1格式
红色 VD[23:16] VD[23:19]
绿色 VD[15:8] VD[15:10]
蓝色 VD[7:0] VD[7:3]

没有用到的数据线其电平为0,从这个观点来看,无论是24BPP模式还是16BPP、8BPP模式,24根数据线VD[23:0]都被用到了。事实上,一个TFT LCD能处理的像素位宽是固定的,即它的数据线的数目是固定的。红、绿、蓝3类信号线总是连接到各字节中的高位;软件设置24BPP、16BPP、8BPP以及调色板等,只会影响到色值的精度。

使用TFT LCD时LCD控制器的寄存器设置

LCD控制器中REGBANK的17个寄存器可以分为6种,如下表所示:

名称 说明
LCDCON1-LCDCON5 用于选择LCD类型,设置各类控制信号的时间特性等
LCDSADDR1-LCDSADDR3 用于设置帧内存的地址
TPAL 临时调色板寄存器,可以快速的输出一帧单色的图像
LCDINTPND
LCDSRCPND
LCDINTMSK
用于LCD的中断,在一般应用中无需中断
REDLUT
GREENLUT
BLUELUT
DITHMODE
专用于STNLCD
TCONSEL 专用于SEC TFT LCD

对于TFT LCD,一般情况下只需要设置前两种寄存器;在8BPP模式下,如果想快速的输出一帧单色图,可以借助TPAL寄存器。

  1. LCD控制寄存器LCDCON1
    用于选择LCD类型、设置像素时钟、使能LCD信号的输出等,格式如下表:

    功能 说明
    LINECNT [27:18] 只读,每输出一个有效行其值减一,从LINEVAL减到0
    CLKVAL [17:8] 用于设置VCLK(时钟)
    对于TFT LCD,VCLK = HCLK / [(CLKVAL + 1) x 2](CLKVAL >= 0)
    MMODE [7] 设置VM信号的反转效率,用于STN LCD
    PNRMODE [6:5] 设置LCD的类型,对于TFT LCD 设为0b11
    BPPMODE [4:1] 设置BPP,对于TFT LCD:
    0b1000 = 1bpp
    0b1001 = 2bpp
    0b1010 = 4bpp
    0b1011 = 8bpp
    0b1100 = 16bpp
    0b1101 = 24bpp
    ENVID [0] LCD信号输出使能位,0:禁止,1:使能
  2. LCD控制寄存器LCDCON2
    用于设置垂直方向各信号的时间参数,格式如下表:

    功能 说明
    VBPD [31:24] VSYNC信号脉冲之后,还要经过(VBPD + 1)个HSYNC信号周期,有效的行数据才出现
    LINEVAL [23:14] LCD的垂直宽度:(LINEVAL + 1)行
    VFPD [13:6] 一帧中的有效行数据完结后,到下一个VSYNC信号有效前的无效行数目:VFPD + 1
    VSPW [5:0] 表示VSYNC信号的脉冲宽度为(VSPW + 1)个HSYNC信号周期,即(VSPW + 1)行,这(VSPW + 1)行的数据无效
  3. LCD控制器LCDCON3
    用于设置水平方向信号的时间参数,格式如下表所示:

    功能 说明
    HBPD [25:19] HSYNC信号脉冲之后,还要经过(HBPD + 1)个VCLK信号周期,有效的行数据才出现
    HOZVAL [18:8] LCD的水平宽度:(HOZVAL + 1)列(像素点)
    HFPD [7:0] 一行中的有效数据完结后,到下一个HSYNC信号有效前的无效像素数目:HFPD + 1
  4. LCD控制寄存器LCDCON4
    对于TFT LCD,这个寄存器只用来设置HSYNC信号的脉冲宽度,位[7:0]的数值称为HSPW,表示脉冲宽度为(HSPW + 1)个VCLK周期。

  5. LCD控制寄存器LCDCON5
    用于设置各个控制信号的极性,并可以从中读到一些状态信息。如下表所示:

    功能 说明
    VSTATUS [16:15] 只读,垂直状态。
    00:正处于VSYNC信号脉冲期间
    01:正处于VSYNC信号结束到行有效期间
    10:正处于行有效期间
    11:正处于行有效结束到下一个VSYNC之间
    HSTATUS [14:13] 只读,水平状态。
    00:正处于HSYNC信号脉冲期间
    01:正处于HSYNC信号结束到像素有效期间
    10:正处于像素有效期间
    11:正处于像素有效结束到下一个HSYNC之间
    BPP24BL [12] 设置TFT LCD的显示模式为24BPP时,一个4字节中哪3个字节有效。
    0:LSB有效(低地址的3字节);1:MSB(高地址的3字节);
    FRM565 [11] 设置TFT LCD的显示模式为16BPP时,使用的数据格式。
    0表示5:5:5:1格式;1表示5:6:5 格式
    INVVCLK [10] 设置VCLK信号有效沿的极性。
    0:在VCLK的下降沿读取数据;1:在VCLK的上升沿读取数据。
    INVVLINE [9] 设置VLINE/HSYNC脉冲的极性。
    0:正常的极性;1:反转的极性
    INVVFRAME [8] 设置VFRAME/VSYNC脉冲的极性。
    0:正常的极性;1:反转的极性
    INVVD [7] 设置VD数据线表示数据(0/1)的极性。
    0:正常的极性;1:反转的极性
    INVVDEN [6] 设置VDEN信号的极性。
    0:正常的极性;1:反转的极性
    INVPWREN [5] 设置PWREN信号的极性。
    0:正常的极性;1:反转的极性
    INVLEND [4] 设置LEND信号的极性。
    0:正常的极性;1:反转的极性
    PWREN [3] LCD_PWREN信号输出使能。
    0:禁止;1:输出
    ENLEND [2] LEND信号输出使能。
    0:禁止;1:输出
    BSWP [1] 字节交换使能
    0:禁止;1:输出
    HWSWP [1] 半字(2字节)交换使能
    0:禁止;1:输出
  6. 帧内存地址寄存器LCDSADDR1-LCDSADDR3
    帧内存可以很大,而真正要显示的区域被称为视口(view point),它处于帧内存之内。这3个寄存器用于确定帧内存的起始地址,定位视口在帧内存的位置。下图给出了帧内存和视口之间的关系:
    img not found
    各寄存器格式如下表所示:

LCDSADDR1 功能 说明
LCDBANK [29:21] 用来保存帧内存起始地址A[30:22],帧内存起始地址为4MB对齐。
LCDBASEU [20:0] 对于TFT LCD,用来保存视口(view point),所对应的内存起始地址A[21:1],这块内存地址也称为LCD的帧缓冲区(frame buffer)
LCDSADDR2 功能 说明
LCDBASEL [20:0] 对于TFT LCD,用来保存LCD的帧缓冲区结束地址A[21:1],其值可如下计算:LCDBASEL = LCDBASEU + (PAGEWIDTH + OFFSICE)x (LINEVAL + 1)

注:可以修改LCDBASEU、LCDBASEL的值来实现图像的移动,不过不能在一帧图像的结束阶段(LCDCON1寄存器的LINECNT为0时)进行修改,因为此时LCD控制器会优先取得下一帧的数据,之后才会改变这些值,这样的话,这些数据就与新的帧缓冲区不一致。

LCDSADDR3 功能 说明
OFFSIZE [21:11] 表示上一行最后一个数据与下一行第一个数据间地址差值的一半,即以半字为单位的地址差(0表示两行数据是紧接着的,1表示它们之间相差2个字节,依次类推)
PAGEWIDTH [10:0] 视口的宽带,以半字为单位

注:OFFSIZE、PAGEWIDTH的值只能在ENVID(LCDCON1寄存器的信号输出使能)为0时修改。

  1. 临时调色板寄存器TPAL
    如果要输出一帧单色的图像,可以在TPAL寄存器中设定这个颜色值,然后使能TPAL寄存器,这种方法可以避免修改整个调色板或帧缓冲区。TPAL寄存器格式如下表所示:
    功能 说明
    TPALEN [24] 调色板寄存器使能位。
    0:禁止 1:使能
    TPALVAL [23:0] 颜色值。
    TPALVAL[23:16]:红色
    TPALVAL[15:8]:绿色
    TPALVAL[7:0]:蓝色

注:临时调色板寄存器TPAL可以用在任何显示模式下,并非只能用在8BPP模式下。

TFT LCD显示实例

程序设计

本实例的目的是从串口输出一个菜单,从中选择各种方法进行测试,比如画线、画圆、显示单色、使用调色板等。

代码设计

与LCD相关的文件有3个:lcddrv.c、framebuffer.c和lcdlib.c(及相应的头文件)。
img not found

  1. lcddrv.c封装了对LCD控制器、调色板的访问函数,可以设置LCD的显示模式、开启/关闭LCD、设置调色板等。
  2. framebuffer.c直接操作帧缓冲区(frame buffer),实现了画点、画线、画圆、清屏等操作。
  3. lcdlib.c调用前两个文件提供的函数在LCD上进行各种操作。

main.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
switch(getc())
{
case '1':
{
Test_Lcd_Tft_8Bit_240320();
break;
}
case '2':
{
Test_Lcd_Tft_16Bit_240320();
break;
}
case '3':
{
Test_Lcd_Tft_8Bit_640480();
break;
}
case '4':
{
Test_Lcd_Tft_16Bit_640480();
break;
}
}

它根据串口的输入选择是以”240x320、8bpp“、”240x320、16bpp“、”640x480、8bpp“或”640x480、16bpp“的显示模式来操作LCD,所调用的4个函数都在lcdlib.c中实现。

lcdlib.c

8BPP模式将用到调色板,其操作比16BPP模式稍为复杂,但是大部分仍是相似的。下面以Test_Lcd_Tft_8Bit_240320()为例进行说明:

1
2
3
4
5
6
7
8
9
10
/*
以240x320、8bpp的显示模式测试TFT LCD
*/
void Test_Lcd_Tft_8Bit_240320(void)
{
Lcd_Port_Init(); //设置LCD引脚
Tft_Lcd_Init(MODE_TFT_8BIT_240320); //初始化LCD控制器
Lcd_PowerEnable(0,1); //设置LCD_PWREN有效,它用于打开LCD的电源
Lcd_EnvidOnOff(1); //使能LCD控制器输出信号
}

第6行所涉及的GPIO引脚用于LCD功能。
第7行调用Tft_Lcd_Init函数初始化LCD控制器,即设置各个控制信号的时间特性、设置LCD的显示模式、设置帧缓冲区的地址等,它是lcddrv.c中最复杂的函数,在后面会详细分析这个函数。
进行第6、7行的初始化之后,只要打开LCD,帧缓冲区中的数据就会被LCD控制器自动地发送到LCD上去显示。打开操作由8、9行来完成。
第8行发出LCD_PWREN信号。对于有电源开关控制引脚的LCD,可以使用LCD_PWREN来打开或关闭LCD。LCD_PWREN信号的极性可以设置。
第9行使能LCD控制器输出信号,这时,帧缓冲区中的数据就开始在LCD上显示出来了。
接下来就是按照设定的流程进行各类操作了,比如画线、清屏等,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
Lcd_Palette8Bit_Init();                     //初始化调色板
ClearScr(0x0); //清屏

getc();
Draw_Line(0,0,100,0,0); //颜色为DEMO256pal[0]
Draw_Line(0,0,100,10,1); //颜色为DEMO256pal[1]
Draw_Line(0,0,100,20,2); //颜色为DEMO256pal[2]
Draw_Line(0,0,100,40,4); //颜色为DEMO256pal[4]
Draw_Line(0,0,100,80,8); //颜色为DEMO256pal[8]
Draw_Line(0,0,100,160,16); //颜色为DEMO256pal[16]

getc();
Mire(); //画圆

getc();
ClearScr(128); //输出单色图像,颜色为DEMO256pal[128]

getc();
ClearScrWithTmpPlt(0x0000ff); //输出单色图像,颜色为蓝色

getc();
DisableTmpPlt(); //关闭临时调色板寄存器
ChangePalette(0xffff00); //改变整个调色板为黄色,输出单色图像

getc();
Lcd_EnvidOnOff(0); //停止

上述函数分3类:

  1. 清屏函数ClearScr、画线函数DrawLine,都是通过framebuffer.c中的PutPixel函数来设置帧缓冲区中的数据,以像素为单位修改颜色来实现的。
  2. Lcd_Palette8Bit_Init函数设置调色板,ChangePalette函数通过设置调色板来实现清屏功能,不涉及帧缓冲区,它在lcddrv.c中实现。
  3. ClearScrWithTmpPlt函数则是通过临时调色板寄存器来快速的输出单色的图像,也不涉及帧缓冲区,它在lcddrv.c中实现。
    lcddrv.c、framebuffer.c文件中的各个函数才是本实例的关键。可以认为lcddrv.c是对操作各寄存器的封装,framebuffer.c则是对操作图像数据的封装。先看lcddrv.c文件。

lcddrv.c

这个文件的重点在于Tft_Lcd_Init、Lcd_Palette8Bit_Init。

  1. Lcd_Port_Init函数
    设置所涉及的GPIO引脚用于LCD功能。GPIO功能的设置对读者来说已经很熟悉了,不再赘述。

  2. Tft_Lcd_Init函数
    用于初始化LCD控制器,即设置各个控制信号的时间特性、设置LCD的显示模式、设置帧缓冲区的地址等。
    首先是对5个控制寄存器LCDCON1-LCDCON5的设置,代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    /*
    初始化LCD控制器
    输入参数:
    type:显示模式
    MODE_TFT_8BIT_240320 : 240*320 8bpp 的TFT LCD
    MODE_TFT_16BIT_240320 : 240*320 16bpp 的TFT LCD
    MODE_TFT_8BIT_640480 : 640*480 8bpp 的TFT LCD
    MODE_TFT_16BIT_640480 : 640*480 16bpp 的TFT LCD
    */
    void Tft_Lcd_Init(int type)
    {
    switch(type)
    {
    case MODE_TFT_8BIT_240320:
    /*
    设置LCD控制器的控制寄存器LCDCON1-LCDCON5
    1. LCDCON1
    设置VCLK的频率:VCLK(Hz)= HCLK/[(CLKVAL+1)x2]
    选择LCD类型:TFT LCD
    设置显示模式:8BPP
    先禁止LCD信号输出
    2. LCDCON2/3/4
    设置控制信号的时间参数
    设置分辨率,即行数及列数
    现在,可以根据公式计算出显示器的频率
    当HCLK = 100MHz时,
    Frame Rate = 1/[{(VSPW+1)+(VBPD+1)+(LINEVAL+1)+(VFPD+1)} x { (HSPW+1)+(HBPD+1)+(HFPD+1)+(HOZVAL+1) } x { 2 x ( CLKVAL+1) / (HCLK)}] = 60Hz
    3. LCDCON5
    设置显示模式为8BPP时,调色板中的数据格式为5:6:5
    设置HSYNC、VSYNC脉冲的极性:反转字节交换使能
    */
    LCDCON1 = (CLKVAL_TFT_240320 << 8) | (LCDTYPE_TFT << 5) | (BPPMODE_8BPP << 1) | (ENVID_DISABLE << 0);
    LCDCON2 = (VBPD_240320 << 24) | (LINEVAL_TFT_240320 << 14) | (VFPD_240320 << 6) | (VSPW_240320);
    LCDCON3 = (HBPD_240320 << 19) | (HOZVAL_TFT_240320 << 8) | (HFPD_240320);
    LCDCON4 = (HSPW_240320);
    LCDCON5 = (FORMAT8BPP_565 << 11) | (HSYNC_INV << 9) | (VSYNC_INV << 8) | (BSWP << 1);
    }
    }

    代码中的注释可以帮助读者理解这些代码,比较困难的是时间参数VSPW、VBPD、VFPD、HSPW、HBPD、HFPD、CLKVAL的设置。对于CRT显示器,当它的频率在60Hz时,人眼会感到明显的闪烁;而对于LCD,在60Hz时显示效果就很好。

    1
    Frame Rate = 1/[{(VSPW+1)+(VBPD+1)+(LINEVAL+1)+(VFPD+1)} x { (HSPW+1)+(HBPD+1)+(HFPD+1)+(HOZVAL+1) } x { 2 x ( CLKVAL+1) / (HCLK)}];

    接下来是对地址寄存器LCDSADDR1-LCDSADDR3的设置。本程序中,帧内存与视图吻合,即图中的OFFSIZE为0,LCDBANK、LCDBASEU指向同一个地址(同一地址的不同位)。
    需要注意的是,8BPP的显示模式要用到调色板,帧缓冲区中的数据不是像素的颜色值,而是调色板中的索引值,真正的颜色值在调色板中。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    /*
    设置LCD控制器的地址寄存器LCDSADDR1-LCDSADDR3
    帧内存与视口(view point)完全吻合,
    图像数据格式如下(8BPP时,帧缓存区中的数据为调色板中的索引值):
    |----width----|
    y/x 0 1 2 239
    0 idx idx idx idx
    1 idx idx idx idx
    1. LCDSADDR1
    设置LCDBANK、LCDBASEU
    2. LCDSADDR2
    设置LCDBASEL:帧缓冲区的结束地址A[21:1]
    3. LCDSADDR3
    OFFSIZE等于0,PAGEWIDTH等于(240/2)
    */
    LCDSADDR1 = ((LCDFRAMEBUFFER >> 22) << 21) | LOWER21BITS(LCDFRAMEBUFFER >> 1);
    LCDSADDR2 = LOWER21BITS((LCDFRAMEBUFFER + (LINEVAL_TFT_240320 + 1) x (HOZVAL_TFT_240320+1) x1) >> 1);
    LCDSADDR3 = (0 <<11 ) | (LCD_XSIZE_TFT_240320/2);

    第16行将帧缓冲区的起始地址写入LCDSADDR1寄存器。
    第17行先计算帧缓冲区的结束地址,再取其位[21:1]存入LCDSADDR2中。这个地址值在本实例中即是“LCDFRAMEBUFFER + 320 x 240 x 1”,其中的“x1”表示在8BPP中一个像素使用1个字节来表示(对于16BPP,则是“x2”)。
    在设置寄存器的最后,禁止临时调色板寄存器,现在还没有用到它。

    1
    2
    /*禁止临时调色板寄存器*/
    TPAL = 0;

    最后,将显示模式的主要参数记录下来,在framebuffer.c中需要用到。

    1
    2
    3
    4
    fb_base_addr = LCDFRAMEBUFFER;
    bpp = 8;
    xsize = 240;
    ysize = 320;

    需要说明的是,显示模式为8BPP时,LCDCON5中BSWAP位设为1,表示“字节交换使能”,这时帧缓存区中的数据与屏幕上的像素位置关系如上图13.6所示;显示模式为16BPP时,LCDCON5中HWSWAP位设为1,表示“半字交换使能”,这时帧缓冲区的数据与屏幕上的像素位置关系如图13.5所示。他们都是“低地址的数据”对应“位置靠前”的像素。

  3. Lcd_Palette8Bit_Init函数
    设置调色板中的数据:调试板大小为256x16,而8BPP模式中每个像素的索引值占据8位,刚好有256个索引值。代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    /*
    设置调色板
    */
    void Lcd_Palette8Bit_Init(void)
    {
    int i;
    volatile unsigned int *palette;

    LCDCON5 |= (FORMAT8BPP_565 << 11); //设置调色板中数据格式为5:6:5

    palette = (volatile unsigned int *)PALETTE;
    for(i = 0;i < 256;i++)
    *palette++ = DEMO256pal[i];
    }

    调色板中用16BPP的格式来表示颜色,第9行设置调色板中数据的格式为5:6:5。
    第12、13行将数组DEMO256pal中的数据写入调色板中。

  4. ChangePalette函数
    以给定的颜色填充整个调色板。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    /*
    改变调色板为一种颜色
    输入参数:
    color:颜色值,格式为0xRRGGBB
    */
    void ChangePalette(UINT32 color)
    {
    int i;
    unsigned char red,green,blue;
    UNT32 *palette;

    palette = (UINT32 *)PALETTE;
    for(i = 0;i < 256;i++)
    {
    red = (color >> 19) & 0xff;
    green = (color >> 10) & 0xff;
    blue = (color >> 3) & 0xff;

    color = (red << 11) | (greee << 5) | (blue); //格式5:6:5

    while((LCDCON5 >> 16) == 2); //等待直到VSTATUS不为“有效”
    *palette++ = color;
    }
    }

    第15-19行从0xRRGGBB格式的变量中,提取8位红色值的高5位,8位绿色值的高6位,8位蓝色值的高5位组成5:6:5格式的16BPP颜色值。
    第21行检测当前VSYNC信号的状态,如果它处于有效的状态,则等待。前面说过,读写调色板时,VSTATUS、HSTATUS不能处于有效状态。这里当VSTATUS不是“有效”状态时,HSTATUS也不能是“有效”状态。
    第22行将新数据写入调色板。

  5. Lcd_PowerEnable函数
    用于是否控制发出LCD_PWREN信号。对于有电源开关控制引脚的LCD,可以使用LCD_PWREN来打开或关闭LCD。LCD_PWREN信号的极性可以设置。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    /*
    设置是否输出LCD电源开关信号LCD_PWREN
    输入参数:
    invpwren:
    0表示LCD_PWREN有效时为正常极性
    1表示LCD_PWREN有效时为反转极性
    pwren:
    0表示LCD_PWREN输出有效
    1表示LCD_PWREN输出无效
    */
    void Lcd_PowerEnable(int invpwren,int pwren)
    {
    GPGCON = (GPGCON & (~ (3 << 8))) | (3 << 8); //GPG4用作LCD_PWREN
    GPGUP = (GPGUP & (~ (1 << 4))) | (1 << 4); //禁止内部上拉

    LCDCON5 = (LCDCON5 & (~ (1 << 5))) | (invpwrwen << 5); //设置LCD_PWREN的极性:正常/反转
    LCDCON5 = (LCDCON5 & (~ (1 << 3))) | (pwren << 3); //设置是否输出LCD_PWREN
    }
  6. Lcd_EnvidOnOff函数
    用于控制是否使能LCD控制器输出各个LCD信号,当设置如控制寄存器、地址寄存器之后,即可调用此函数输出各个LCD信号,这样,帧缓冲区中的数据即发送给LCD。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    /*
    设置LCD控制器是否输出信号
    输入参数:
    onoff:
    0:关闭
    1:打开
    */
    void Lcd_EnvidOnOff(int onoff)
    {
    if(onoff == 1)
    LCDCON1 |= 1; //ENVID ON
    else
    LCDCON1 &= 0x3fffe; //ENVID OFF
    }
  7. ClearScrWithTmpPlt、DisableTmpPlt函数
    ClearScrWithTmpPlt函数设置颜色值并使能TPAL寄存器,这使得LCD上显示单一颜色的图像。DisableTmpPlt函数停止TPAL寄存器的功能,继续输出帧缓存区的图像。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    /*
    使用临时调色板寄存器输出单色图像
    输入参数:
    color:颜色值,格式为0xRRGGBB
    */

    void ClearScrWithTmpPlt(UINT32 color)
    {
    TPAL = (1 << 24) | ((color & 0xffffff) << 0);
    }

    /*
    停止使用临时调色板寄存器
    */
    void DisableTmpPlt(void)
    {
    TPAL = 0;
    }

framebuffer.c

此文件中有4个函数:画点函数PutPixel、画线函数DrawLine、绘制同心圆函数Mire、清屏函数ClearScr。后3个函数都是基于PutPixel函数实现的。PutPixel函数是framebuffer.c的核心,它在缓冲区中找到给定坐标的像素的内存,然后修改它的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
extern unsigned int fb_base_addr;
extern unsigned int bpp;
extern unsigned int xsize;
extern unsigned int ysize;

/*
画点
输入参数:
x,y:像素坐标
color:颜色值
对于16BPP:color的格式为0xAARRGGBB AA是透明度,0-256,需要转换为5:6:5格式
对于8BPP:color为调色板的索引值,颜色取决于调色板中的数值
*/
void PutPixel(UINT32 x,UINT32 y,UINT32 color)
{
UINT8 red,green,blue;

switch(bpp)
{
case 16:
{
UINT16 *addr = (UINT16 *)fb_base_addr + (y * xsize + x);
red = (color >> 19) & 0xff;
green = (color >> 10) & 0xff;
blue = (color >> 3) & 0xff;

color = (red << 11) | (greee << 5) | (blue); //格式5:6:5
*addr = (UINT16) color;
break;
}

case 8:
{
UINT8 *addr = (UINT8 *)fb_base_addr + (y * xsize + x);
*addr = (UINT8) color
break;
}

default:
break;
}
}

第1-4行的4个变量在lcddrv.c中的Tft_Lcd_Init函数中设置,PutPixel函数根据它们来确定给定坐标的像素在缓冲区中的地址。
第22、34行分别计算16BPP、8BPP模式下给定坐标的像素在帧缓冲中的地址。对于16BPP模式,每个像素占据2字节;对于8BPP模式,每个像素占据1字节。
对于16BPP的显示模式,第22-25行从0xAARRGGBB中提取8位红色值的高5位,8位绿色值的高6位,8位蓝色值的高5位组成5:6:5格式的16BPP颜色值。
最后,第28、35行将颜色值(8BPP模式下为调色板索引值)写入帧缓冲区中,这样下一次显示的时候,新颜色就可以显示出来了。