1.14.ARM裸机第十四部分-LCD显示器

第一部分、章节目录
1.14.1.LCD简介
1.14.2.LCD的接口技术
1.14.3.LCD如何显示图像1
1.14.4.LCD如何显示图像2
1.14.5.LCD的六个主要时序参数1
1.14.6.LCD的六个主要时序参数2
1.14.7.LCD显示的主要相关概念
1.14.8.颜色在计算机中的表示
1.14.9.S5PV210的LCD控制器
1.14.10.LCD编程实战1-LCD控制器初始化
1.14.11.LCD编程实战2-显示像素&刷背景
1.14.12.LCD编程实战3-横线竖线斜线&画圆
1.14.13.LCD编程实战4-写英文中文字符
1.14.14.LCD编程实战5-画图
1.14.15.LCD编程实战6-画图测试及调试
1.14.16.未完成事项展望

第二部分、章节介绍
1.14.1.LCD简介
    本节简单介绍LCD的显示原理、其他主流显示技术以及LCD的发展史和分类。本节的内容虽然和编程无直接关联,但是对大家了解LCD的背景有极大意义。
1.14.2.LCD的接口技术
    本节主要介绍LCD的硬件接口,分析各种不同接口类型的差异,从而引出LCD的时序信号,为后面章节的正式展开打下基础。
1.14.3.LCD如何显示图像1
    本节讲述LCD显示图像的基本原理:像素的概念、扫描显示方式、LCD驱动器和LCD控制器的分别、显示缓存区的概念等,这些概念有助于我们从编程角度理解LCD如何工作。
1.14.4.LCD如何显示图像2
    本节讲述LCD显示图像的基本原理:像素的概念、扫描显示方式、LCD驱动器和LCD控制器的分别、显示缓存区的概念等,这些概念有助于我们从编程角度理解LCD如何工作。
    
1.14.5.LCD的六个主要时序参数1
    本节介绍LCD初始化过程中很重要的6个参数,这个六个参数是CRT年代继承而来,但是对于LCD来说却很重要,也是我们后面写程序初始化LCD时很重要的几个参数。
1.14.6.LCD的六个主要时序参数2
    本节介绍LCD初始化过程中很重要的6个参数,这个六个参数是CRT年代继承而来,但是对于LCD来说却很重要,也是我们后面写程序初始化LCD时很重要的几个参数。
1.14.7.LCD显示的主要相关概念
    本节讲解LCD显示中关键的几个概念,如像素、像素深度、清晰度、分辨率等,这些概念在后面的编程实践中用得上,而且对我们理解显示原理至关重要。

1.14.8.颜色在计算机中的表示
    本节主要讲了颜色相关的一些概念,颜色的物理含义、颜色的表达方式、颜色在计算机中如何展示等,这些基本概念对我们学习计算机的显示技术非常重要,对理解LCD显示也至关重要。
1.14.9.S5PV210的LCD控制器
    本节介绍SOC的LCD控制器,主要介绍了FIMD的结构框图、工作原理及主要寄存器,本节的关键点在于理解显存的意义和工作原理。
1.14.10.LCD编程实战1-LCD控制器初始化
    本节进入LCD编程实战,重点分析了LCD控制器初始化代码,整个初始化的目的就是完成LCD显示的第一个步骤:绑定显存和LCD屏幕,在两者之间建立一个映射。
1.14.11.LCD编程实战2-显示像素&刷背景
    本节是LCD编程实战的第二节,主要讲了2个显示函数。第一个是在LCD屏幕上绘制一个像素点的函数,第二个是绘制整个LCD屏幕背景色的函数。
1.14.12.LCD编程实战3-横线竖线斜线&画圆
    本节是LCD编程实战的第三节,主要讲了几个图形绘制函数,包括画横线、竖线、斜线、画圆等。通过这几个函数,希望大家进一步理解LCD中填充像素与绘制图形的对应关系。
1.14.13.LCD编程实战4-写英文中文字符
    本节是LCD编程实战的第四节,主要讲了在LCD上绘制中英文字的函数,通过实际分析ASCII码显示的函数,将文字的显示原理讲的清清楚楚。
1.14.14.LCD编程实战5-画图
    本节是LCD编程实战的第五节,主要讲了在LCD上绘制图像的函数,本节介绍了一个重要工具Image2LCD,讲了如何用该工具获取图像数据,以及在LCD中将之显示出来。
1.14.15.LCD编程实战6-画图测试及调试
    本节是LCD编程实战的最后一节,主要任务是调试上节所写的显示图片的函数,并且研究了RGB的像素顺序以及搞错像素顺序的实验现象,让大家学会调试程序。
1.14.16.未完成事项展望
    本节课对整个ARM裸机第十四部分进行总结,并且展望一些因为时间原因没有列出的LCD相关内容,我们希望以后在实战项目中用到这些内容并且给大家做详细的讲解和训练。
第三部分、随堂记录
1.14.1.LCD简介
1.14.1.1、什么是LCD?
(1)LCD(Liquid Crystal Display)俗称液晶.
(2)液晶是一种材料,液晶这种材料具有一种特点:可以在电信号的驱动下液晶分子进行旋转,旋转时会影响透光性,因此我们可以在整个液晶面板后面用白光照(称为背光),可以通过不同电信号让液晶分子进行选择性的透光,此时在液晶面板前面看到的就是各种各样不同的颜色,这就是LCD显示。
(3)被动发光和主动发光。有些显示器(譬如LED显示器、CRT显示器)自己本身会发光称为主动发光,有些(LCD)本身不会发光只会透光,需要背光的协助才能看起来是发光的,称为被动发光。
(4)液晶应用领域:电视机、电脑显示屏、手机显示屏、工业显示屏等····

1.14.1.2、其他主流显示设备(LED、CRT、等离子、OLED)
(1)CRT:阴极摄像管显示器。
(2)等离子显示:未成为主流
(3)OLED:目前未成为主流,但是很有市场潜力
(4)LED:主要用在户外大屏幕
(5)LCD:目前是主流显示器

1.14.1.3、LCD的显示原理和特点(液晶分子透光+背光)
(1)白光其实是由各种不同颜色的光组成的,所以白光被选择性透光之后可以产生各种不同颜色的光。

1.14.1.4、LCD的发展史和种类(TN/STN/TFT)
(1)TN最早。坏处是响应性不够好,有拖尾现象。
(2)STN是TN的升级版。有效解决拖尾现象,显示更清晰。
(3)TFT的最大特点就是超薄。
(4)TFT技术之上发展出来很多更新的技术。
(参考资料一:http://blog.163.com/tao198352__4232/blog/static/85020645201062285210682/)
(参考资料二:http://display.ofweek.com/2013-12/ART-8321301-8300-28763136.html)

1.14.2.LCD的接口技术
1.14.2.1、从电平角度来讲本质上都是TTL信号
(1)什么是TTL接口。+5V表示逻辑1,0V表示逻辑0.这种就叫TTL电平,和CMOS电平相对比。
(2)SoC的LCD控制器硬件接口是TTL电平的,LCD这边硬件接口也是TTL电平的。所以他们俩本来是可以直接对接的,手机、平板、开发板都是这样直接对接的(一般用软排线连接)。
(3)TTL电平的缺陷就是不能传递太远,如果LCD屏幕和主板控制器太远(1米甚至更远)就不能直接TTL连接了,要进行转换。转换方式:主机SoC(TTL) ->VGA-> LCD屏幕(TTL)

1.14.2.2、各种接口(TTL、LVDS、EDP、MIPI、)在传输速率、距离、适配性方面不同(参考资料:http://blog.csdn.net/wocao1226/article/details/23870149)

1.14.2.3、RGB接口详解(参考数据手册P1207页时序图)。 RGB接口如图:


(1)VD[23:0]:24根数据线,用来传输图像信息。可见LCD是并行接口,速率才够快。如图:


(2)HSYNC(水平同步信号) 
(3)VSYNC(垂直同步信号):时序信号线,为了让LCD能够正常显示给的控制信号
(4)VCLK(像素时钟):LCD工作时需要主板控制器给LCD模组一个工作时钟信号,就是VCLK。
(5)VDEN(数据有效标志):时序信号,和HSYNC、VSYNC结合使用。
(6)LEND(行结束标志,不是必须的):时序信号,非必须,譬如X210接口就没有。


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

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

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

1.14.3.4、显示内存(简称:显存)
(1)SoC在内存中挑选一段内存(一般来说是程序员随便挑选的,但是挑选的时候必须符合一定规矩),然后通过配置将LCD控制器和这一段内存(以后称为显存)连接起来构成一个映射关系。一旦这个关系建立之后,LCD控制器就会自动从显存中读取像素数据传输给LCD驱动器。这个显示的过程不需要CPU的参与。
(2)显示体系建立起来后,CPU就不用再管LCD控制器、驱动器、面板这些东西了;以后CPU就只关心显存了,因为我只要把要显示的图像的像素数据丢到显存中,硬件就会自动响应(屏幕上就能自动看到显示的图像了)。

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


1.14.5.LCD的六个主要时序参数
1.14.5.1、LCD显示单位:帧(frame)
(1)显示器上一整个画面的内容成为一个帧(frame),整个显示器工作时是一帧一帧的在显示。
(2)电影实际就是以每秒种24帧的速度在播放图片。
(3)帧内数据:一帧分为多行,一行分为多像素,因此一帧图像其实就是多个像素组成的矩阵。
(4)帧外数据:整个视频由很多个帧构成,最终播放视频时逐个播放各个图像帧即可。

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

1.14.5.3、为了向前兼容出现的六个时序参数
HSPW 水平同步信号脉宽
HBPD 水平同步信号前肩
HFPD 水平同步信号后肩
VSPW 垂直同步信号脉宽
VBPD 垂直同步信号前肩
VFPD 垂直同步信号后肩

如图;

(1)一行的通信过程是这样的:LCD控制器先发送一个HSYNC高电平脉冲(脉冲宽度是HSPW),脉冲告诉驱动器下面的信息是一行信息。然后开始这一行信息,这一行信息包括3部分:HBPD+有效行信息+HFPD。其中前肩和后肩都属于时序信息(和LCD屏幕具体有关),有效行信息就是横向分辨率。所以你可以认为一行总共包含4部分:HSPW+HBPD+有效行信息+HFPD。
(2)一帧图像其实就是一列,一列图像由多个行组成,每行都是上面讲的这个时序。
(3)一帧图像的通信过程是这样的:整个帧图像信号分为4部分:VSPW+VBPD+帧有效信号+VFPD。VSPW是帧同步信号宽度,用来告诉驱动器一帧图像要开始了;VBPD和VFPD分别是垂直同步信号前后肩。
(4)必须说明:这6个参数对于LCD显示器其实本来是没用的,这些信号其实是老式的CRT显示器才需要的,LCD本身不需要,但是出于历史兼容性要求,LCD选择了兼容CRT显示器的这些时序要求,所以理解LCD显示器时序和编程时,用CRT的方式来理解不会错。
(5)要注意,这几个时序参数本身是LCD屏幕本身的参数,与LCD控制器无关。所以同一个主板如果接的屏幕不一样则时序参数设置也会不同。这些参数的来源一般是:第一,厂家会直接给出,一般以实例代码的形式给出;第二,来自于LCD的数据手册。
第一种方式,查看九鼎的210裸机教程(x210v3裸机开发教程\src\template-framebuffer-font\source\hardware\s5pv210-fb.c的第774行)
        .h_fp            = 210,        // 160-210-354
        .h_bp            = 38,        // 46
        .h_sw            = 10,        // 1-40
        .v_fp            = 22,        // 7-22-147
        .v_fpe            = 1,
        .v_bp            = 18,        // 23
        .v_bpe            = 1,        
        .v_sw            = 7,        // 1-20
第二种方式,查看LCD数据手册(X210光盘资料\A盘\DataSheet\AT070TN92.pdf)

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


1.14.7.LCD显示的主要相关概念
1.14.7.1、像素(pixel)
(1)整个图像是由一个个的像素组成的,像素就是一个显示点。
1.14.7.2、像素间距(pitch)
(1)pitch是连续2个像素的像素中心的距离。一般的像素是方形的,所以横向pitch和纵向的pitch一样的。但是也有不一样的。
(2)像素间距会影响屏幕的最佳观看距离。像素间距大的适合远距离看,像素间距小的适合近距离看。

1.14.7.3、分辨率(resolution)
(1)整个屏幕的横向和纵向的像素个数就叫分辨率,譬如X210开发板用的屏幕是800×480.
(2)屏幕尺寸和分辨率无关的,像开发板的屏幕尺寸是7寸的(纯屏幕对角线尺寸是7英寸)。
(3)屏幕尺寸和分辨率和像素间距三者之间有关联。

1.14.7.4、清晰度
(1)清晰度是一个主观概念,是人眼对显示效果的一个主观判断。说白了就是人看起来感觉清晰不清晰。
(2)客观来讲,清晰度由分辨率和像素间距共同决定。一般的,屏幕尺寸固定时分辨率越高越清晰,分辨率越低就越不清晰;分辨率固定下,屏幕尺寸越小越清晰,越大越不清晰。
(3)清晰度还由其他很多因素共同决定。

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


1.14.8.颜色在计算机中的表示
1.14.8.1、颜色的本质
(1)颜色是主观存在,颜色其实是自然光在人的眼睛中和大脑中产生的一种映像。
(2)颜色的本质决定于光的波长。
1.14.8.2、自然光的颜色是连续的
(1)光的波长是连续的,导致颜色也是连续的。理论上,只要你的眼睛分辨能力足够好,可以在自然界中发现无数中颜色。

1.14.8.3、计算机中的颜色是离散的
(1)计算机中不可能存储无数种颜色,所以必须将颜色有限化,所以就用有限种颜色来代表自然界中的无限种颜色。这个理论非常类似于之前学过的AD转换。
(2)这种离散化表达颜色的缺点是不够真实,漏掉了很多种颜色。因此计算机中所能表达的颜色没有自然界中丰富(计算机屏幕上显示的图像和真实图像有差别)
(3)计算机所能表达的颜色种类个数,这个参数叫:像素深度bpp。

1.14.8.4、常见像素深度: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表示颜色。

补充:颜色的组成,三元色(三基色)是RGB,也就是说所有的颜色都可以由红绿蓝三种颜色组成。


1.14.9.S5PV210的LCD控制器
1.14.9.1、FIMD结构框图

FIMD结构图:


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

1.14.9.2、虚拟屏幕叠加(数据手册P1194)


(1)虚拟屏幕的意思是,我们平时看到的屏幕上显示出来的场景实际是很多个屏幕显示叠加在一起的效果(譬如新闻图像、电视台台标、下方飘动的字幕新闻)
(2)像S5PV210的LCD控制器中有5个虚拟屏幕Window0到Window4,虚拟屏幕不存在于真实而存在于内存中。(之前讲过,LCd显示时实际是显示的是对应的内存中的显存区域的数值)虚拟屏幕其实就是一个内存中的显存区域,有几个显存区域就有几个虚拟屏幕,但是这些虚拟屏幕都被映射到一个真实的显示屏上面,所以将来真实的现实效果实际是这几个虚拟屏幕的显示内容的叠加。(叠加时要注意上面一层会覆盖下面一层,所以要注意谁在前谁在后,设置寄存器时有这个选项)
(3)使用虚拟屏幕而不是整个LCD使用一个显存是有一定好处的:第一,可以保证不污染源图像,方便程序处理;第二,可以减少屏幕刷新,提高显示效率,减少CPU工作量。

1.14.9.3、虚拟显示(数据手册P1206)

显示大图像策略如图:


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

1.14.9.4、主要寄存器简介

如图:


1.14.10.LCD编程实战1-LCD控制器初始化
参考代码lcd_init函数详解

GPF的引脚原理图:


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

 


1.14.11.LCD编程实战2-显示像素&刷背景
(1)显示像素
(2)刷背景色

#include "main.h"
#include "stdio.h"


#define GPF0CON            (*(volatile unsigned long *)0xE0200120)
#define GPF1CON            (*(volatile unsigned long *)0xE0200140)
#define GPF2CON            (*(volatile unsigned long *)0xE0200160)
#define GPF3CON            (*(volatile unsigned long *)0xE0200180)

#define GPD0CON            (*(volatile unsigned long *)0xE02000A0)
#define GPD0DAT            (*(volatile unsigned long *)0xE02000A4)

#define CLK_SRC1        (*(volatile unsigned long *)0xe0100204)
#define CLK_DIV1        (*(volatile unsigned long *)0xe0100304)
#define DISPLAY_CONTROL    (*(volatile unsigned long *)0xe0107008)

#define VIDCON0            (*(volatile unsigned long *)0xF8000000)
#define VIDCON1            (*(volatile unsigned long *)0xF8000004)
#define VIDTCON2        (*(volatile unsigned long *)0xF8000018)
#define WINCON0         (*(volatile unsigned long *)0xF8000020)
#define WINCON2         (*(volatile unsigned long *)0xF8000028)
#define SHADOWCON         (*(volatile unsigned long *)0xF8000034)
#define VIDOSD0A         (*(volatile unsigned long *)0xF8000040)
#define VIDOSD0B         (*(volatile unsigned long *)0xF8000044)
#define VIDOSD0C         (*(volatile unsigned long *)0xF8000048)

#define VIDW00ADD0B0     (*(volatile unsigned long *)0xF80000A0)
#define VIDW00ADD1B0     (*(volatile unsigned long *)0xF80000D0)

#define VIDTCON0         (*(volatile unsigned long *)0xF8000010)
#define VIDTCON1         (*(volatile unsigned long *)0xF8000014)

#define HSPW             (40)                // 1~40 DCLK
#define HBPD            (10 - 1)            // 46
#define HFPD             (240 - 1)            // 16 210 354
#define VSPW            (20)                // 1~20 DCLK
#define VBPD             (10 - 1)            // 23
#define VFPD             (30 - 1)            // 7 22 147

//FB地址
#define FB_ADDR            (0x23000000)
#define ROW                (600)
#define COL                (1024)
#define HOZVAL            (COL-1)
#define LINEVAL            (ROW-1)


#define XSIZE            COL
#define YSIZE            ROW

u32 *pfb = (u32 *) FB_ADDR;
//unsigned int *fb_buf = (unsigned int*)FB_ADDR;


//常用颜色定义
#define BLUE        0x0000FF
#define RED            0xFF0000
#define GREEN        0x00FF00
#define WHITE        0xFFFFFF


//初始化LCD控制器的函数
static void lcd_init(void)
{
    //配置引脚用于LCD功能
    GPF0CON = 0x22222222;
    GPF1CON = 0x22222222;
    GPF2CON = 0x22222222;
    GPF3CON = 0x22222222;


    //打开背光 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);


#define LeftTopX     0
#define LeftTopY     0
#define RightBotX   (1024-1)
#define RightBotY   (600-1)

    // 设置window0的上下左右
    // 设置的是显存空间的大小
    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;

}

//在像素点(x,y)处填充为color颜色
static inline void lcd_draw_pixel(u32 x,u32 y,u32 color)
{
    *(pfb + COL * y + x) = color;
}

//把整个屏幕全部填充成一个颜色color
static void lcd_draw_background(u32 color)
{
    u32 i,j;
    for (j = 0; j < ROW; j++)
    {
        for (i = 0; i < COL; i++)
        {
            lcd_draw_pixel(i,j,color);
        }
    }
}


static void delay(void)
{
    volatile u32 i,j;
    for (i = 0; i < 4000; i++)
    {
        for (j = 0; j < 1000; j++);
    }
}


void lcd_test(void)
{
    lcd_init();

    // 测试绘制背景色,成功
    while(1)
    {
        lcd_draw_background(RED);
        delay();

        lcd_draw_background(GREEN);
        delay();

        lcd_draw_background(BLUE);
        delay();
    }
    
}


 

1.14.12.LCD编程实战3-横线竖线斜线&画圆
(1)画横线&竖线
(2)斜线

关键代码:

//绘制横线,其实坐标为(x1,y)到(x2,y),颜色是color
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);
    }
}


//绘制竖线,其实坐标为(x,y1)到(x,y2),颜色是color
static void lcd_draw_vline(u32 x,u32 y1,u32 y2,u32 color)
{
    u32 y;
    for(y = y1; y < y2; y++)
    {
        lcd_draw_pixel(x,y,color);
    }
}

//绘制横线和竖线
static void draw_h_v_delay(void)
{
    lcd_draw_background(WHITE);
    lcd_draw_hline(412,612,300,RED);
    lcd_draw_vline(512,200,400,GREEN);
}

画斜线关键代码:

// glib库中的画线函数,可以画斜线,线两端分别是(x1, y1)和(x2, y2)
void glib_line(unsigned int x1, unsigned int y1, unsigned int x2, unsigned int y2, unsigned int color)
{
    int dx,dy,e;
    dx=x2-x1; 
    dy=y2-y1;
    
    if(dx>=0)
    {
        if(dy >= 0) // dy>=0
        {
            if(dx>=dy) // 1/8 octant
            {
                e=dy-dx/2;  
                while(x1<=x2)
                {
                    lcd_draw_pixel(x1,y1,color);
                    if(e>0){y1+=1;e-=dx;}    
                    x1+=1;
                    e+=dy;
                }
            }
            else        // 2/8 octant
            {
                e=dx-dy/2;
                while(y1<=y2)
                {
                    lcd_draw_pixel(x1,y1,color);
                    if(e>0){x1+=1;e-=dy;}    
                    y1+=1;
                    e+=dx;
                }
            }
        }
        else           // dy<0
        {
            dy=-dy;   // dy=abs(dy)

            if(dx>=dy) // 8/8 octant
            {
                e=dy-dx/2;
                while(x1<=x2)
                {
                    lcd_draw_pixel(x1,y1,color);
                    if(e>0){y1-=1;e-=dx;}    
                    x1+=1;
                    e+=dy;
                }
            }
            else     // 7/8 octant
            {
                e=dx-dy/2;
                while(y1>=y2)
                {
                    lcd_draw_pixel(x1,y1,color);
                    if(e>0){x1+=1;e-=dy;}    
                    y1-=1;
                    e+=dx;
                }
            }
        }    
    }
    else //dx<0
    {
        dx=-dx;        //dx=abs(dx)
        if(dy >= 0) // dy>=0
        {
            if(dx>=dy) // 4/8 octant
            {
                e=dy-dx/2;
                while(x1>=x2)
                {
                    lcd_draw_pixel(x1,y1,color);
                    if(e>0){y1+=1;e-=dx;}    
                    x1-=1;
                    e+=dy;
                }
            }
            else        // 3/8 octant
            {
                e=dx-dy/2;
                while(y1<=y2)
                {
                    lcd_draw_pixel(x1,y1,color);
                    if(e>0){x1-=1;e-=dy;}    
                    y1+=1;
                    e+=dx;
                }
            }
        }
        else           // dy<0
        {
            dy=-dy;   // dy=abs(dy)

            if(dx>=dy) // 5/8 octant
            {
                e=dy-dx/2;
                while(x1>=x2)
                {
                    lcd_draw_pixel(x1,y1,color);
                    if(e>0){y1-=1;e-=dx;}    
                    x1-=1;
                    e+=dy;
                }
            }
            else        // 6/8 octant
            {
                e=dx-dy/2;
                while(y1>=y2)
                {
                    lcd_draw_pixel(x1,y1,color);
                    if(e>0){x1-=1;e-=dy;}    
                    y1-=1;
                    e+=dx;
                }
            }
        }    
    }
}

//绘制斜线
static void draw_glib(void)
{
    lcd_draw_background(WHITE);
    glib_line(0,599,1023,0,RED);
    glib_line(0,0,1023,599,RED);
}

绘制圆的关键代码:

//画圆函数,圆心坐标是(centerX, centerY),半径是radius,圆的颜色是color
void draw_circular(unsigned int centerX, unsigned int centerY, unsigned int radius, unsigned int color)
{
    int x,y ;
    int tempX,tempY;;
    int SquareOfR = radius*radius;

    for(y=0; y<XSIZE; y++)
    {
        for(x=0; x<YSIZE; x++)
        {
            if(y<=centerY && x<=centerX)
            {
                tempY=centerY-y;
                tempX=centerX-x;                        
            }
            else if(y<=centerY&& x>=centerX)
            {
                tempY=centerY-y;
                tempX=x-centerX;                        
            }
            else if(y>=centerY&& x<=centerX)
            {
                tempY=y-centerY;
                tempX=centerX-x;                        
            }
            else
            {
                tempY = y-centerY;
                tempX = x-centerX;
            }
            if ((tempY*tempY+tempX*tempX)<=SquareOfR)
                lcd_draw_pixel(x, y, color);
        }
    }
}

//绘制圆
static void set_circular(void)
{
    //lcd_draw_background(WHITE);
    draw_circular(512,300,50,GREEN);
}

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

写文字字符的关键代码:

//写字
//写字的左上角坐标(x,y),字的颜色是color,字的字模信息存储在data中
static void show_8_16(unsigned int x,unsigned int y,unsigned int color,unsigned char *data)
{
    //count记录当前正在绘制的像素的次序
    int i,j,count = 0;
    for(j = y;j<(y+16);j++)
    {
        for(i = x;i < (x+8);i++)
        {
            if (i<XSIZE && j <YSIZE)
            {
                //在坐标(i,j)这个像素处判断是0还是1,如果是1,就把颜色写入,如果是0,就直接跳过
                if (data[count/8] & (1<<(count %8)))
                {
                    lcd_draw_pixel(i,j,color);
                }
            }
            count++;
        }
    }
}

//写字符串
//字符串起始左上角的坐标(x,y),字符串的颜色为color,字符串的内容是str
static void ascii_ok(unsigned int x,unsigned int y,unsigned int color,unsigned char *str)
{
    int i;
    unsigned char *ch;
    for(i = 0; str[i]!='\0';i++)
    {
        ch = (unsigned char *)ascii_8_16[(unsigned char)str[i]-0x20];
        show_8_16(x,y,color,ch);
        x +=8;
        if(x >= XSIZE)
        {
            x -= XSIZE; //相当于回车
            y +=16;        //相当于换行
        }
    }
}

//设置文字字符绘制
void lcd_draw_askii_ok(void)
{
    lcd_draw_background(WHITE);//先绘制背景
    ascii_ok(0,0,RED,"ABCDabcd1234!@#$%");
}


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

绘制图片核心代码:

//画1024*600的图,图像数据存储在pData所指向的数组中
static void draw_picture(const unsigned char *pData)
{
    u32 x,y,color,p = 0;
    for(y = 0; y <YSIZE; y++)
    {
        for(x = 0; x <XSIZE; x++)
        {
            //在这里将坐标点(x,y)的那个像素填充上相应的颜色值即可
            color = ((pData[p+2] <<0) | (pData[p+1] <<8) | (pData[p+0] <<16));
            lcd_draw_pixel(x,y,color);
            p += 3;
        }
    }
}

//画图片
    draw_picture(gImage_1024600);


1.14.15.LCD编程实战6-画图测试及调试
1.14.15.1、画图函数的显示效果测试
1.14.15.2、RGB的像素顺序调整
(1)图片中红色的变成了蓝色、蓝色的变成了红色,这就说明RGB顺序反了。
(2)解决方案一:重新使用Image2LCD来取模,取模时RGB顺序对调。
(3)解决方案二:不重新取模,改代码,在color形成的时候调换顺序。

1.14.15.3、小分辨率图片显示
(1)本质上大小分辨率的图像显示都是一样的,都是:都是在图像的每个像素点对应的显存中填入相应的颜色值即可。


1.14.16.未完成事项展望
1.14.16.1、RGB565和RGB888
(1)单片机等性能和资源有限的平台会用RGB565,嵌入式平台习惯用RGB888.
(2)有时候手头只有RGB888的颜色值,但是显示部分却只接受RGB565的(有时候反过来),这时候就需要在2种颜色之间做转换。

1.14.16.2、不同分辨率、不同起点坐标下的显示
(1)屏幕分辨率和图像分辨率不同时的显示。我们现在写的代码都是没有考虑的。
(2)图像起点坐标(左上角坐标)不一定在屏幕左上角,也可能在屏幕的任何其他区域,这种情况下画图的函数要做处理来支持。
(3)分辨率不同而且起始坐标不同下,这两个问题组合起来也对画图函数提出了更高要求。

1.14.16.3、bmp图片格式解析及显示
(1)我们当前的图像数据是用Image2LCD工具转过来的。实践中是不可能手工用工具来转的(你想一下Image2LCD也是一个软件,既然它能转,我们也应该可以用软件来转)
(2)bmp格式的图片本身遵照一定的格式来存储的,我们只需按照bmp的解析格式来读取文件就可得到这幅图片的图像数据的二进制,直接拿去显示。(这就是图片文件的本质)

1.14.16.4、jgp、png、gif等压缩图片的解码和显示
(1)对于以上压缩格式的图片是不能直接解析的,必须用相应的库来解压缩图片之后才能拿来显示。
(2)不同的格式的图片其压缩算法不同,解压缩算法自然也不同。开源社区中每种压缩格式都有个对应的库,库里提供了这种格式的压缩和解压缩函数,我们只需要移植这个库并且调用它来压缩或解压缩即可。

1.14.16.5、图片缩小和放大显示
(1)图片数据源本来是400×200的,但是显示时我希望显示成100×50的,这时候就要显示函数后台对源图像进行先缩小,然后再显示。这时候就需要一个缩小算法。(譬如最简单的算法就是抽点采样)
(2)图片数据源是100×50的,但是显示时希望全屏显示(800×480),这时候就需要对源图像进行放大然后再显示(当然了显示出来图像清晰度肯定比较低),这时候就需要一种放大算法(譬如插点方式)

1.14.16.6、显示动画
(1)动态刷屏形成动画
(2)立体3D图像显示(伪3D)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值