ARM 裸机之 LCD 显示器和触摸屏

本文介绍了LCD的显示原理、颜色的相关概念,以及 LCD 的编程实战

本文介绍了LCD的显示原理、颜色的相关概念,以及 LCD 的编程实战

ARM 裸机之 LCD 显示器和触摸屏

LCD简介

什么是LCD?

  • LCD(Liquid Crystal Display)俗称液晶.
  • 液晶是一种材料,液晶这种材料具有一种特点:可以在电信号的驱动下液晶分子进行旋转,旋转时会影响透光性,因此我们可以在整个液晶面板后面用白光照(称为背光),可以通过不同电信号让液晶分子进行选择性的透光,此时在液晶面板前面看到的就是各种各样不同的颜色,这就是LCD显示。
  • 被动发光和主动发光。有些显示器(譬如LED显示器、CRT显示器)自己本身会发光称为主动发光,有些(LCD)本身不会发光只会透光,需要背光的协助才能看起来是发光的,称为被动发光。
  • 液晶应用领域:电视机、电脑显示屏、手机显示屏、工业显示屏等····

其他主流显示设备(LED、CRT、等离子、OLED)

  • CRT:阴极摄像管显示器。
  • 等离子显示:未成为主流
  • OLED:目前未成为主流,但是很有市场潜力
  • LED:主要用在户外大屏幕
  • LCD:目前是主流显示器

LCD的接口技术

从电平角度来讲本质上都是TTL信号

  • 什么是TTL接口。
    • +5V表示逻辑1,0V表示逻辑0.这种就叫TTL电平,和CMOS电平相对比。
  • SoC的LCD控制器硬件接口是TTL电平的,LCD这边硬件接口也是TTL电平的。所以他们俩本来是可以直接对接的,手机、平板、开发板都是这样直接对接的(一般用软排线连接)。
  • TTL电平的缺陷就是不能传递太远,如果LCD屏幕和主板控制器太远(1米甚至更远)就不能直接TTL连接了,要进行转换。转换方式:主机SoC(TTL) ->VGA-> LCD屏幕(TTL)
  • 各种接口(TTL、LVDS、EDP、MIPI、)在传输速率、距离、适配性方面不同
    • (参考资料:http://blog.csdn.net/wocao1226/article/details/23870149)

RGB接口详解

信号引脚

信号 I/O 说明 引脚 类型
LCD_HSYNC Output 水平同步信号 XvHSYNC 复用
LCD_VSYNC Output 垂直同步信号 XvVSYNC 复用
LCD_VCLK Output LCD 视频时钟
LCD工作时需要主板控制器给LCD模组一个工作时钟信号,就是VCLK。
XvVCLK 复用
LCD_VDEN Output 数据使能,数据有效标志 XvVDEN 复用
LCD_VD[23:0] Output YCbCr 数据输出
24根数据线,用来传输图像信息。可见LCD是并行接口,速率才够快。
XvVD[23:0] 复用
LEND Output 行结束标志,不是必须的:时序信号,非必须,譬如X210接口就没有 - -

LCD如何显示图像

像素(pixel)

  • 像素就是组成图像的最基本元素,或者说显示中可以被控制的最小单位,整个图像就是由很多个像素组成的。
  • 像素可以被单独控制,或控制其亮或不亮(单色屏)、或控制其亮度强弱(譬如亮50%,35%,这样叫灰度屏,以前的黑白电视机)、或控制其显示一定的颜色(这就是我们现在最常用的彩色显示屏)。
  • 总结:像素很重要,整个显示图像是由一个个的像素组成的。我们要在显示器上显示一个图像,就是把这个图像离散化成一个一个的点,然后把各个点的颜色对应在显示器的像素上。

扫描

  • 扫描是一个动作而不是一个名字,扫描就是依次将颜色数值放入屏幕中所有的像素的这个过程。
  • 扫描这个词是由最早的CRT显示器遗留下来的,到LCD显示器的年代本来已经失去意义了,但是我们还是延续着这么叫。
  • 显示器的扫描显示原理依赖于人眼的视觉暂留。只要显示器扫描频率大于人眼的发现频率,人眼看到的图像就是恒定的。如果扫描频率偏小人眼就会看到闪动。(扫描频率的概念就叫做刷新率)

驱动器&控制器

  • LCD驱动器一般和LCD显示面板集成在一起
    • 本来是分开的,做面板的是只做面板的,譬如说三星、LG、台湾的友达、奇美都是做面板的;
    • 驱动器也由专门的IC厂商生产;
    • 集成厂商买来面板和驱动器后集成在一起做成LCD屏幕
    • 面板只负责里面的液晶分子旋转透光,面板需要一定的模拟电信号来控制液晶分子;
    • LCD驱动器芯片负责给面板提供控制液晶分子的模拟电信号,驱动器的控制信号(数字信号)来自于自己的数字接口,这个接口就是LCD屏幕的外部接口(第二节中讲到的接口)
  • LCD控制器一般集成在SoC内部,他负责通过数字接口向远端的LCD驱动器提供控制像素显示的数字信号。
    • LCD控制器的关键在于时序,它必须按照一定的时序和LCD驱动器通信;
    • LCD控制器受SoC控制,SoC会从内存中拿像素数据给LCD控制器并最终传给LCD驱动器。

显示内存(简称:显存)

SoC在内存中挑选一段内存(一般来说是程序员随便挑选的,但是挑选的时候必须符合一定规矩),然后通过配置将LCD控制器和这一段内存(以后称为显存)连接起来构成一个映射关系。一旦这个关系建立之后,LCD控制器就会自动从显存中读取像素数据传输给LCD驱动器。这个显示的过程不需要CPU的参与。

显示体系建立起来后,CPU就不用再管LCD控制器、驱动器、面板这些东西了;以后CPU就只关心显存了,因为我只要把要显示的图像的像素数据丢到显存中,硬件就会自动响应(屏幕上就能自动看到显示的图像了)。

LCD显示是分为2个阶段的

  • 第一个阶段就是建立显示体系的过程,目的就是CPU初始化LCD控制器使其和显存联系起来构成映射;
  • 第二个阶段就是映射建立之后,此阶段主要任务是将要显示的图像丢到显存中去。

LCD的六个主要时序参数

LCD显示单位:帧(frame)

  • 显示器上一整个画面的内容成为一个帧(frame),整个显示器工作时是一帧一帧的在显示。
  • 电影实际就是以每秒种24帧的速度在播放图片。
  • 帧内数据:一帧分为多行,一行分为多像素,因此一帧图像其实就是多个像素组成的矩阵。
  • 帧外数据:整个视频由很多个帧构成,最终播放视频时逐个播放各个图像帧即可。

LCD显示一帧图像的过程

  • 首先把帧分为行,然后再把行分为像素,然后逐个像素去显示。(显示像素:其实就是LCD驱动器按照接收到的LCD控制器给的显示数据,驱动一个像素的液晶分子旋转,让这个像素显示出相应的颜色值的过程)
  • 关键点:LCD控制器和驱动器之间一次只能传一个像素点的显示数据。所以一帧图像在屏幕上其实是串行的依次被显示上去的,不是同一时间显示出来的。

为了向前兼容出现的六个时序参数

参数 说明
HSPW 水平同步信号脉宽
HBPD 水平同步信号前肩
HFPD 水平同步信号后肩
VSPW 垂直同步信号脉宽
VBPD 垂直同步信号前肩
VFPD 垂直同步信号后肩

一行的通信过程

LCD RGB 接口时序图

bgr_it

  • LCD控制器先发送一个HSYNC高电平脉冲(脉冲宽度是HSPW),脉冲告诉驱动器下面的信息是一行信息。
  • 然后开始这一行信息,这一行信息包括3部分:
    • HBPD+有效行信息+HFPD。
    • 其中前肩和后肩都属于时序信息(和LCD屏幕具体有关),有效行信息就是横向分辨率。
    • 所以你可以认为一行总共包含4部分:HSPW+HBPD+有效行信息+HFPD。

一帧图像的通信过程

一帧图像其实就是一列,一列图像由多个行组成,每行都是上面讲的这个时序。

  • 整个帧图像信号分为4部分:VSPW+VBPD+帧有效信号+VFPD。
  • VSPW是帧同步信号宽度,用来告诉驱动器一帧图像要开始了;
  • VBPD和VFPD分别是垂直同步信号前后肩。

必须说明:这6个参数对于LCD显示器其实本来是没用的,这些信号其实是老式的CRT显示器才需要的,LCD本身不需要,但是出于历史兼容性要求,LCD选择了兼容CRT显示器的这些时序要求,所以理解LCD显示器时序和编程时,用CRT的方式来理解不会错。

要注意,这几个时序参数本身是LCD屏幕本身的参数,与LCD控制器无关。所以同一个主板如果接的屏幕不一样则时序参数设置也会不同。这些参数的来源一般是:第一,厂家会直接给出,一般以实例代码的形式给出;第二,来自于LCD的数据手册。

注意这些数字的单位。H开头的三个单位都是DCLK(像素时钟),V开头的三个单位是TH。这样设置的好处是我们改变了像素时钟的设置时,不用改变这里的时序参数。这些时序参数如果没设置好屏幕会跑偏。

分辨率(resolution)

整个屏幕的横向和纵向的像素个数就叫分辨率,譬如X210开发板用的屏幕是800×480.

像素深度(bits per pixel,简称bpp)

  • 一个像素在计算机中由多少个字节数据来描述。
  • 计算机中用二进制位来表示一个像素的数据,用来表示一个像素的数据位越多,则这个像素的颜色值更加丰富、分的更细,颜色深度就更深。
  • 一般来说像素深度有这么几种:1位、8位、16位、24位、32位

常见像素深度

像素深度 说明
1位 用1个二进制位来表示颜色,这种就叫单色显示。示例就是小饭店、理发店门口的LED屏。
8位 用8个二进制位来表示颜色,此时能表示256种颜色。这种叫灰度显示。这时候是黑白的,没有彩色,我们把纯白到纯黑分别对应255到0,中间的数值对应不同的灰。示例就是以前的黑白电视机
16位 用16个二进制位表示颜色,此时能表示65536种颜色。这时候就可以彩色显示了,一般是RGB565的颜色分布(用5位二进制表示红色、用6位二进制表示绿色、用5位二进制表示蓝色)。这种红绿蓝都有的颜色表示法就是一种模拟自然界中所有颜色的表示方式。但是因为RGB的颜色表达本身二进制位数不够多(导致红绿蓝三种颜色本身分的都不够细致),所以这样显示的彩色失真比较重,人眼能明显看到显示的不真实。
24位 用24个二进制位来表示颜色,此时能表示16777216种颜色。这种表示方式和16位色原理是一样的,只是RGB三种颜色各自的精度都更高了(RGB各8位),叫RGB888。此时颜色比RGB565更加真实细腻,虽然说比自然界无数种颜色还是少了很多,不过由于人眼的不理想性所以人眼几乎不能区分1677万种颜色和无数种颜色的差别了。于是乎就把这种RGB888的表示方法叫做真彩色。(RGB565就是假彩色)
32位 总共用32位二进制来表示颜色,其中24位表示红绿蓝三元色(还是RGB888分布),剩下8位表示透明度。这种显色方式就叫ARGB(A是阿尔法,表示透明度),现在PC机中一般都用ARGB表示颜色

S5PV210的LCD控制器

FIMD结构框图

fimd

  • 210的LCD控制器叫FIMD,FIMD是210内部和图像处理相关的一些部件,在摄像头等和图像处理有关的部分都可以有关联。
  • FIMD在内部与AHB总线等相连接,在外部提供RGB接口、I80接口、YUV接口与外部相连接,我们实际使用的是RGB接口。

虚拟屏幕叠加

虚拟屏幕的意思是,我们平时看到的屏幕上显示出来的场景实际是很多个屏幕显示叠加在一起的效果(譬如新闻图像、电视台台标、下方飘动的字幕新闻)

像S5PV210的LCD控制器中有5个虚拟屏幕Window0到Window4,虚拟屏幕不存在于真实而存在于内存中。(之前讲过,LCd显示时实际是显示的是对应的内存中的显存区域的数值)虚拟屏幕其实就是一个内存中的显存区域,有几个显存区域就有几个虚拟屏幕,但是这些虚拟屏幕都被映射到一个真实的显示屏上面,所以将来真实的现实效果实际是这几个虚拟屏幕的显示内容的叠加。(叠加时要注意上面一层会覆盖下面一层,所以要注意谁在前谁在后,设置寄存器时有这个选项)

使用虚拟屏幕而不是整个LCD使用一个显存是有一定好处的:第一,可以保证不污染源图像,方便程序处理;第二,可以减少屏幕刷新,提高显示效率,减少CPU工作量。

虚拟显示

  • 如何实现在小分辨率的屏幕上(真实)显示大分辨率的图像
  • 细节上,我们需要屏幕上看到不同图像时,需要对显存区域进行刷新。即使我们只需要屏幕显示移动一点点,整个屏幕对应的显存空间也需要整个重新刷新,工作量和完全重新显示一幅图像是一样的。这个显然不好,这样CPU刷新屏幕的工作量太大了,效率很低。
  • 如何能够在显示一个大图片的不同区域时让CPU刷新屏幕工作量减少?有,方法就是虚拟显示。具体做法就是在内存中建立显示缓存的时候实际建立一个很大的区域,然后让LCD去对应其中的一部分区域作为有效的显示区域。将来要显示大图像时,直接将大图像全部一次性加载入显示缓存区,然后通过移动有效显示区域就可以显示大图像的不同区域了。

LCD编程实战 - LCD控制器初始化

要想LCD工作,必须给LCD屏幕显存之间建立一个映射(映射是在CPU初始化LCD控制器来完成的)。本部分就是在完成这个过程(这也是LCD显示的2个阶段的第一阶段,第二阶段中我们只需要给显存中丢入相应的数据,LCD屏幕就会自动显示相应内容)。

lcd_init 函数详解

设置 GPIO 引脚

  • 确定引脚名称

    SoC 通过 GPIO 引脚向外输出控制信号,所以首先要设置用于控制 LCD 的输出引脚。在 X210 核心板原理图上可以找到 S5PV210 用于控制 LCD 的引脚(也可以在 S5PV210 的数据手册中找到引脚名称)GPF0、GPF1、GPF2 和 GPF3。

    lcdc

  • 查找 GPFn 对应的寄存器说明:

    GPF0CON Bit 说明 初始值
    GPF0CON[7] [31:28] 0010 = LCD_VD[3] 0000
    GPF0CON[6] [27:24] 0010 = LCD_VD[2] 0000
    GPF0CON[5] [23:20] 0010 = LCD_VD[1] 0000
    GPF0CON[4] [19:16] 0010 = LCD_VD[0] 0000
    GPF0CON[3] [15:12] 0010 = LCD_VCLK 0000
    GPF0CON[2] [11:8] 0010 = LCD_VDEN 0000
    GPF0CON[1] [7:4] 0010 = LCD_VSYNC 0000
    GPF0CON[0] [3:0] 0010 = LCD_HSYNC 0000

    GPF[1:3] 与上同,所以要把 GPF[0:3] = 0x22222222;

打开背光

  • 找到控制背光的引脚

    LCD 是通过选择性透光达到显示的效果。从主板原理图可以看到与 LCD 有关的引脚,其中 VLED+ 和 VLED- 就是用于 LCD 的背光的,将 VLED- 设置成低电平即可点亮背光

    lcd_bv

  • 查找核心板原理图看到对应的 PWMTOUT0 引脚为 GPD0[0]

    pwm

  • 数据手册说明

    只需要 GPD0[0] 输出为 0 即可点亮背光 LED 光源

    gpd0

    gpd0_dat

设置显示控制器

显示控制器由VSFR、VDMA、VPRCS、VTIME和视频时钟发生器组成。

为了配置显示控制器,VSFR有121个可编程寄存器组、一个伽马LUT寄存器组(64个寄存器)、一个i80命令寄存器组(12个寄存器)和5个256x32调色板存储器。

VDMA是一个专用的显示DMA,可以将帧存储器中的视频数据传输到VPRCS。通过使用这种专用的DMA,可以在没有CPU干预的情况下,在屏幕上显示视频数据。

VPRCS从VDMA接收视频数据,将视频数据改变为合适的数据格式后,通过数据端口(RGB_VD或SYS_VD)发送到显示设备(LCD)。例如,8位/像素模式(8 bpp模式)或16位/像素模式(16 bpp模式)。

VTIME由可编程逻辑组成,支持不同LCD驱动器中常见的接口定时和速率的可变要求。VTIME 模块生成RGB_VSYNC、RGB_VSYNC、RGB_VCLK、RGB_VDEN、VEN_VSYNC、VEN_VSYNC、VEN_FIELD、VEN_HrefSYS_CS0、SYS_CS1、SYS_WE等。

使用显示控制器数据,可以通过设置 DISPLAY_PATH_SEL[1:0](0xE010_7008)来选择上述数据路径中的一个。更多信息,请参阅 “02.03. 时钟控制器”。

DISPLAY_CONTROL Bit 说明 初始值
保留 [31:2] 保留 0x0000_0000
DISPLAY_PATH_SEL [1:0] 显示路径选择
00: RGB=— I80=FIMD ITU=FIMD
01: RGB=— I80=— ITU=FIMD
10: RGB=FIMD I80=FIMD ITU=FIMD
11: RGB=FIMD I80=FIMD ITU=FIMD
0

path

配置视频主控器 VIDCON0, R/W, Address = 0xF800_0000

VIDCON0 Bit 说明 初始值
VIDOUT [28:26] Video 控制器的输出格式, 000 = RGB interface 000
RGSPSEL [18] 选择显示模式:0 = RGB 平行格式;1 - RGB 串行模式 0
CLKVAL_F [13:6] 决定 VCLK 的速率和 CLKVAL[7:0] 的值
VCLK = HCLK / (CLKVAL+1), 当 CLKVAL >= 1
VCLK 的最大频率是 100MHz
0
CLKDIR [4] 使用 CLKVAL_F 寄存器选择时钟源为直接时钟或分频。
0 = 直接时钟(VCLK的频率 = 时钟源的频率)。
1 = 除以CLKVAL_F
0x00
CLKSEL_F [2] 选择 Video 的时钟源。 0 - HCLK;1 - SCLK_FIMD 0
ENVID [1] 立即启用/禁用视频输出和逻辑控制信号。
0=禁用视频输出和显示控制信号。
1=启用视频输出和显示控制信号。
0
ENVID_F [0] 启用/禁用在前帧结束时的视频输出和逻辑。
0=禁用视频输出和显示控制信号。
1=启用视频输出和显示控制信号。
如果该位设置为“开”和“关”,则读取“H”,并启用视频控制器,直到当前帧结束。
0

配置视频主控器 VIDCON1, R/W, Address = 0xF800_0004

从《HLY070ML226-12A》7” TFT LCD 数据手册可以看到时序图的电平和 S5PV210 控制器输出的电平相反

垂直输入时序图

S5PV210 控制器输出的电平

所以要反向 S5PV210 控制器输出的电平

VIDCON1 Bit 说明 初始值
IHSYNC [6] 确定 HSYNC 脉冲的极性。 0 - 正常;1 - 反向 0
IVSYNC [5] 确定 VSYNC 脉冲的极性。 0 - 正常;1 - 反向 0

调整屏幕大小和位置

  • 屏幕的位置由前后肩数值决定

    tcon0

    tcon1

  • 屏幕的大小由硬件决定

    tcon2

    HOZVAL = (水平显示尺寸)- 1,LINEVAL = (垂直显示尺寸)- 1

设置窗口 WINCON0, R/W, Address = 0xF800_0020

WINCON0 Bit 说明 初始值
WSWP_F [15] 是否自动换行(Word swap)。0 - 否;1 - 是 0
BPPMODE_F [5:2] 指定bpp(每像素比特位)
1011 = Unpacked 24 bpp ( non-palletized R:8-G:8-B:8 )
0
ENWIN_F [0] 立即启用/禁用视频输出和逻辑控制信号。0=禁用;1=启用 0

设置窗口位置 VIDOSD0A, R/W, Address = 0xF800_0040

VIDOSD0A Bit 说明 初始值
OSD_LeftTopX_F [21:11] 指定OSD图像左上像素的水平屏幕坐标 0
OSD_LeftTopY_F [10:0] 指定OSD图像左上像素的垂直屏幕坐标 0
VIDOSD0B Bit 说明 初始值
OSD_RightBotX_F [21:11] 指定OSD图像右下像素的水平屏幕坐标 0
OSD_RightBotY_F [10:0] 指定OSD图像右下像素的垂直屏幕坐标 0
VIDOSD0C Bit 说明 初始值
Reserved [25:24] Reserved (should be 0) 0
OSDSIZE [23:0] 指定窗口大小 0

设置帧缓冲区地址

VIDW00ADD0B0, R/W, Address = 0xF800_00A0

VIDWxxADD0 Bit 说明 初始值
VBASEU_F [31:0] 帧缓冲区起始地址 0

VIDW00ADD1B0, R/W, Address = 0xF800_00D0

VIDWxxADD0 Bit 说明 初始值
VBASEL_F [31:0] 帧缓冲区结束地址
VBASEL = VBASEU + (PAGEWIDTH+OFFSIZE) x (LINEVAL+1)
0

窗口阴影控制寄存器 VIDOSD0C, R/W, Address = 0xF800_0034

SHODOWCON Bit 说明 初始值
C0_EN_F [0] 启用通道0; 0 - 禁用;1 - 启用 0
void lcd_init(void)
{
	// 配置引脚用于LCD功能
	GPF0CON = 0x22222222; // LCD_VD[0:3]
	GPF1CON = 0x22222222; // LCD_VD[4:11]
	GPF2CON = 0x22222222; // LCD_VD[12:19]
	GPF3CON = 0x22222222; // LCD_VD[23:20]

	// 打开背光	GPD0_0(PWMTOUT0)
	GPD0CON &= ~(0xf<<0);
	GPD0CON |= (1<<0);			// output mode
	GPD0DAT &= ~(1<<0);			// output 0 to enable backlight

	// 10: RGB=FIMD I80=FIMD ITU=FIMD
	DISPLAY_CONTROL = 2<<0;

	// bit[26~28]:使用RGB接口
	// bit[18]:RGB 并行
	// bit[2]:选择时钟源为HCLK_DSYS=166MHz
	VIDCON0 &= ~( (3<<26)|(1<<18)|(1<<2) );

	// bit[1]:使能lcd控制器
	// bit[0]:当前帧结束后使能lcd控制器
	VIDCON0 |= ( (1<<0)|(1<<1) );

	// bit[6]:选择需要分频
	// bit[6~13]:分频系数为5,即VCLK = 166M/(4+1) = 33M
	VIDCON0 |= 4<<6 | 1<<4;

	// H43-HSD043I9W1.pdf(p13) 时序图:VSYNC和HSYNC都是低脉冲
	// s5pv210芯片手册(p1207) 时序图:VSYNC和HSYNC都是高脉冲有效,所以需要反转
	VIDCON1 |= 1<<5 | 1<<6;

	// 设置时序
	VIDTCON0 = VBPD<<16 | VFPD<<8 | VSPW<<0;
	VIDTCON1 = HBPD<<16 | HFPD<<8 | HSPW<<0;
	// 设置长宽(物理屏幕)
	VIDTCON2 = (LINEVAL << 11) | (HOZVAL << 0);

	// 设置window0
	// bit[0]:使能
	// bit[2~5]:24bpp(RGB888)
	WINCON0 |= 1<<0;
	WINCON0 &= ~(0xf << 2);
	WINCON0 |= (0xB<<2) | (1<<15);

	// 设置window0的上下左右 
	// 设置的是显存空间的大小 
    // LeftTopX=0, LeftTopY=0, RightBotX=799, RightBotY=479
	VIDOSD0A = (LeftTopX<<11) | (LeftTopY << 0);
	VIDOSD0B = (RightBotX<<11) | (RightBotY << 0);
	VIDOSD0C = (LINEVAL + 1) * (HOZVAL + 1);

	// 设置fb的地址
	VIDW00ADD0B0 = FB_ADDR;
	VIDW00ADD1B0 = (((HOZVAL + 1)*4 + 0) * (LINEVAL + 1)) & (0xffffff);

	// 使能channel 0传输数据
	SHADOWCON = 0x1;
}

LCD编程实战2-显示像素&刷背景

图像在内存中是以数组的方式保存的,常用的格式是RGB格式和ARGB格式。每个像素显示像素就是在 (x, y) 坐标处设置指定的颜色。

那么,我们假设:

  • 在内存中开辟的显存首地址是 0x2300_0000
  • 图片数据格式为 RGB888( (R« 16) (G « 8) (B « 0))。
  • TFT 显示屏的分辨率是 800 x 480
把 (x, y) 点设置成红色,就是将 (x + 800 x y) 这个 32 位的数据设置成 (R« 16) (0 « 8) (0 « 0)
#define FB_ADDR			(0x23000000)
#define ROW				(480)
#define COL				(800)
u32 *pfb = (u32 *)FB_ADDR;

static inline void lcd_draw_pixel(u32 x, u32, y, u32 color)
{
    *(pfb + COL*y + x) = color;
}

要刷背景颜色,就是把所有的像素都设置成指定的颜色

#define HOZVAL			(COL-1)
#define LINEVAL			(ROW-1)

static void lcd_draw_background(u32 color)
{
    u32 x, y;
    for (y = 0; y < ROW; y++)
        for (x = 0; x < COL; x++)
            lcd_draw_pixel(x, y, color);
}

LCD编程实战3-横线竖线斜线&画圆

划横线就是 y 不动,x 从 x1 增加到 x2。竖线、斜线、圆形原理一样

static void lcd_draw_hline(u32 x1, u32 x2, u32 y, u32 color)
{
    u32 x;
    for (x = x1; x < x2; x++)
        lcd_draw_pixel(x, y, color);
}

LCD编程实战4-写英文中文字符

绘制字符,其实就是在一个矩形中把相应坐标的点设置成指定颜色。比如,这里实在 8x16 的矩形中把颜色填充到相应的坐标,就组成了希望的字符。这种字符对应坐标的代码就成为点阵字模

我们从网上下载了 ASCII 可打印字符的字模,字符 A 对应的字模如下:

ascii_8_16['A' - 0x20] = {0x00,0x00,0x00,0x08,0x08,0x18,0x14,0x14,0x24,0x3C,0x22,0x42,0x42,0xE7,0x00,0x00}

16个数据对应16行,每一个数据对应8列;比如:0x00 对应第一行的8个坐标是否要填充数据

要在屏幕上显示对应的字符,就是判断某一位是否为1,是就填充颜色。

static void show_816(u32 x, u32 y, u32 color, unsigned char *data)
{
    int i, j; // 屏幕坐标
    int count = 0; // 每一行中第几个数据
    for (j = y; j < y+16; j++){
        for (i = x; i < x+8; i++, count++) {
            if (i < XSIZE && j < YSIZE){ // 防止画出屏幕边界
                if (data[count/8] & (1<<(count%8)))
                    lcd_draw_pixel(i, j, color);
            }
        }
    }
}


static void lcd_draw_ascii(u32 x, u32 y, u32 color, uchar *str)
{
    unsigned char *ch *p;
    
    for (p = str; *p; p++) {
        ch = (unsigned char *)ascii_8_16[(unsigned char*)p - 0x20];
        show_816(x, y, color, ch);
        x += 8;
        if (x >= XSIZE) {
            x -= XSIZE;	// 回车
            y += 16;	// 换行
        }
    }
}

LCD编程实战5-画图

图片显示分析

  • 图像是彩色的,而之前的文字、图形都是单色的。之前的图形文字绘制函数都有个color参数,就是传给显存告诉它这个像素的显示颜色。
  • 一副分辨率是800×480,BPP是24的图片,实际上就是800×480×3字节的数据。将来写代码将图片显示到LCD中时,图片将会以 unsigned char pic_data[800×480×3]的形式出现。
  • 如何由一副图片得到它对应的数据的数组?要用取模工具,如Image2LCD。

画图函数的显示效果测试

先通过 Image2LCD 工具把 800x480 的图片取模成数组形式,保存成 const unsigned char gImage_800480 变量。每一个像素点的颜色用3个字节表示。比如 gImage_800480[2:0] 三个字节分别表示坐标(0, 0) 处的 RGB 值,即 (0, 0) 处的颜色是

RGB = (gImage_800480[0] << 0 | gImage_800480[1] << 8 | gImage_800480[2] << 16)

我们把 800 x 480 的像素点都填充成 gImage_800480 指定的颜色即可

void lcd_draw_picture(const unsigned char *picture)
{
    u32 x, y, color, i = 0;
    for (y = 0; y < 480; y++){
        for (x = 0; x < 800; x++){
            color = ((picture[i+0] << 0) | (picture[i+1] << 8) | (picutre[i+2]<<16));
            lcd_draw_pixel(x, y, color);
            i += 3;
        }
    }
}

电容触摸屏的原理

人体电流感应

利用人体电流感应现象,在手指和屏幕之间形成一个电容,手指触摸时吸走一个微小电流,这个电流会导致触摸板上4个电极上发生电流流动,控制器通过计算这4个电流的比例就能算出触摸点的坐标(这个计算过程中涉及到AD转换)。

专用电路计算坐标

电阻式触摸屏本身是一个完全被动器件,里面没有任何IC和电路,它的工作逻辑完全在SoC控制器上;但是电容式触摸屏不同,电容式触摸屏需要自带一个IC进行坐标计算。因此电容式触摸屏工作时不需要主机SoC控制器参与。

为什么这样设计?主要原因是因为电容式触摸屏的坐标计算太复杂,普通程序员无法写出合适的代码解决这个问题,因此在电容式触摸屏中除了触摸板之外还附加了一个IC进行专门的坐标点计算和统计。这个IC全权负责操控触摸板得到触摸操作信息,然后再通过数字接口和主机SoC进行通信。

多个区块支持多点触摸

  • 电阻触摸屏不支持多点触摸,这是它本身的原理所限制,无法改变无法提升。
  • 电容式触摸屏可以支持多点触摸(也可以单点触摸)。按照之前讲的电容式触摸屏的原理,单个电容式触摸屏面板也无法支持多点触摸,但是可以将一个大的触摸面板分成多个小的区块,每个区块相当于是一个独立的小的电容式触摸屏面板。
  • 多个区块支持多点触摸让电容触摸屏坐标计算变复杂了,但是这个复杂性被电容触摸IC吸收了,还是通过数字接口和主机SoC通信报告触摸信息(触摸点数、每个触摸点的坐标等)

对外提供I2C的访问接口

  • 整个电容触摸屏包含2部分:触摸板和电容触摸IC。
    • 触摸板就是一个物理器件,电容触摸IC一般做到触摸屏的软排线(FPC)上面,
    • 电容触摸IC负责操控触摸板、通过AD转换和分析得到触摸点个数、触摸坐标等信息,然后以特定的数字接口与SoC通信。这个数字接口就是I2C。
  • 对于我们主机SoC来说,电容式触摸屏其实就是一个I2C从设备。
    • 主机只需要通过I2C总线对这个从设备进行访问即可(从设备有自己特定的从设备地址)。
    • 从这里来讲,其实电容式触摸屏和其他的传感器(gsensor等)并没有任何区别。