S5PV210裸机之LCD
LCD 简介
什么是 LCD ?
(1) LCD ( Liquid Crystal Display )俗称液晶.
(2)液晶是一种材料,液晶这种材料具有一种特点:可以在电信号的驱动下液晶分子进行旋转,旋转时会影响透光性,因此我们可以在整个液晶面板后面用白光照(称为背光),可以通过不同电信号让液晶分子进行选择性的透光,此时在液晶面板前面看到的就是各种各样不同的颜色,这就是 LCD 显示。
(3)被动发光和主动发光。有些显示器(譬如 LED 显示器、CRT 显示器)自己本身会发光称为主动发光,有些( LCD )本身不会发光只会透光,需要背光的协助才能看起来是发光的,称为被动发光。
(4)液晶应用领域:电视机、电脑显示屏、手机显示屏、工业显示屏等····
其他主流显示设备( LED、CRT、等离子、OLED )
(1) CRT :阴极摄像管显示器。
(2)等离子显示:未成为主流
(3) OLED :目前未成为主流,但是很有市场潜力
(4) LED :主要用在户外大屏幕
(5) LCD :目前是主流显示器
LCD 的显示原理和特点(液晶分子透光+背光)
(1)白光其实是由各种不同颜色的光组成的,所以白光被选择性透光之后可以产生各种不同颜色的光。
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)
LCD的接口技术
从电平角度来讲本质上都是 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 )
各种接口(TTL、LVDS、EDP、MIPI、)在传输速率、距离、适配性方面不同(参考资料:http://blog.csdn.net/wocao1226/article/details/23870149)
RGB接口详解(参考数据手册P1207页时序图)
(1)VD[23:0]:24 根数据线,用来传输图像信息。可见 LCD 是并行接口,速率才够快。
(2)HSYNC ( 水平同步信号 )
(3)VSYNC(垂直同步信号):时序信号线,为了让 LCD 能够正常显示给的控制信号
(4)VCLK(像素时钟):LCD 工作时需要主板控制器给 LCD 模组一个工作时钟信号,就是 VCLK。
(5)VDEN (数据有效标志):时序信号,和 HSYNC、VSYNC 结合使用。
(6)LEND(行结束标志,不是必须的):时序信号,非必须,譬如 X210 接口就没有。
LCD如何显示图像
像素(pixel)
(1)像素就是组成图像的最基本元素,或者说显示中可以被控制的最小单位,整个图像就是由很多个像素组成的。
(2)像素可以被单独控制,或控制其亮或不亮(单色屏)、或控制其亮度强弱(譬如亮 50%,35%,这样叫灰度屏,以前的黑白电视机)、或控制其显示一定的颜色(这就是我们现在最常用的彩色显示屏)。
总结:
像素很重要,整个显示图像是由一个个的像素组成的。我们要在显示器上显示一个图像,就是把这个图像离散化成一个一个的点,然后把各个点的颜色对应在显示器的像素上。
扫描
(1)扫描是一个动作而不是一个名字,扫描就是依次将颜色数值放入屏幕中所有的像素的这个过程。
(2)扫描这个词是由最早的 CRT 显示器遗留下来的,到 LCD 显示器的年代本来已经失去意义了,但是我们还是延续着这么叫。
(3)显示器的扫描显示原理依赖于人眼的视觉暂留。只要显示器扫描频率大于人眼的发现频率,人眼看到的图像就是恒定的。如果扫描频率偏小人眼就会看到闪动。(扫描频率的概念就叫做刷新率)
驱动器&控制器
(1) LCD 驱动器一般和 LCD 显示面板集成在一起(本来是分开的,做面板的是只做面板的,譬如说三星、LG、*的友达、奇美都是做面板的;驱动器也由专门的 IC 厂商生产;集成厂商买来面板和驱动器后集成在一起做成 LCD 屏幕),面板只负责里面的液晶分子旋转透光,面板需要一定的模拟电信号来控制液晶分子;LCD 驱动器芯片负责给面板提供控制液晶分子的模拟电信号,驱动器的控制信号(数字信号)来自于自己的数字接口,这个接口就是 LCD 屏幕的外部接口(第二节中讲到的接口)
(2) LCD 控制器一般集成在 SoC 内部,他负责通过数字接口向远端的 LCD 驱动器提供控制像素显示的数字信号。LCD 控制器的关键在于时序,它必须按照一定的时序和 LCD 驱动器通信;LCD 控制器受 SoC 控制,SoC 会从内存中拿像素数据给 LCD 控制器并最终传给 LCD 驱动器。
显示内存(简称:显存)
(1) SoC 在内存中挑选一段内存(一般来说是程序员随便挑选的,但是挑选的时候必须符合一定规矩),然后通过配置将 LCD 控制器和这一段内存(以后称为显存)连接起来构成一个映射关系。一旦这个关系建立之后,LCD 控制器就会自动从显存中读取像素数据传输给 LCD 驱动器。这个显示的过程不需要 CPU 的参与。
(2)显示体系建立起来后,CPU 就不用再管 LCD 控制器、驱动器、面板这些东西了;以后 CPU 就只关心显存了,因为我只要把要显示的图像的像素数据丢到显存中,硬件就会自动响应(屏幕上就能自动看到显示的图像了)。
总结:LCD 显示是分为 2 个阶段的:第一个阶段就是建立显示体系的过程,目的就是 CPU 初始化 LCD 控制器使其和显存联系起来构成映射;第二个阶段就是映射建立之后,此阶段主要任务是将要显示的图像丢到显存中去。
LCD的六个主要时序参数
LCD 显示单位:帧( frame )
(1)显示器上一整个画面的内容成为一个帧 ( frame ),整个显示器工作时是一帧一帧的在显示。
(2)电影实际就是以每秒种 24 帧的速度在播放图片。
(3)帧内数据:一帧分为多行,一行分为多像素,因此一帧图像其实就是多个像素组成的矩阵。
(4)帧外数据:整个视频由很多个帧构成,最终播放视频时逐个播放各个图像帧即可。
LCD 显示一帧图像的过程
(1)首先把帧分为行,然后再把行分为像素,然后逐个像素去显示。(显示像素:其实就是 LCD 驱动器按照接收到的 LCD 控制器给的显示数据,驱动一个像素的液晶分子旋转,让这个像素显示出相应的颜色值的过程)
(2)关键点:LCD 控制器和驱动器之间一次只能传一个像素点的显示数据。所以一帧图像在屏幕上其实是串行的依次被显示上去的,不是同一时间显示出来的。
为了向前兼容出现的六个时序参数
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、这些时序参数如果没设置好会影响什么?屏幕会跑偏。
LCD显示的主要相关概念
像素( pixel )
(1)整个图像是由一个个的像素组成的,像素就是一个显示点。
像素间距( pitch )
(1) pitch 是连续 2 个像素的像素中心的距离。一般的像素是方形的,所以横向 pitch 和纵向的 pitch 一样的。但是也有不一样的。
(2)像素间距会影响屏幕的最佳观看距离。像素间距大的适合远距离看,像素间距小的适合近距离看。
分辨率( resolution )
(1)整个屏幕的横向和纵向的像素个数就叫分辨率,譬如 X210 开发板用的屏幕是 800×480.
(2)屏幕尺寸和分辨率无关的,像开发板的屏幕尺寸是 7 寸的(纯屏幕对角线尺寸是 7 英寸)。
(3)屏幕尺寸和分辨率和像素间距三者之间有关联。
清晰度
(1)清晰度是一个主观概念,是人眼对显示效果的一个主观判断。说白了就是人看起来感觉清晰不清晰。
(2)客观来讲,清晰度由分辨率和像素间距共同决定。一般的,屏幕尺寸固定时分辨率越高越清晰,分辨率越低就越不清晰;分辨率固定下,屏幕尺寸越小越清晰,越大越不清晰。
(3)清晰度还由其他很多因素共同决定。
像素深度( bits per pixel,简称 bpp )
(1)一个像素在计算机中由多少个字节数据来描述。
(2)计算机中用二进制位来表示一个像素的数据,用来表示一个像素的数据位越多,则这个像素的颜色值更加丰富、分的更细,颜色深度就更深。
(3)一般来说像素深度有这么几种:1位、8位、16位、24位、32位。
颜色在计算机中的表示
颜色的本质
(1)颜色是主观存在,颜色其实是自然光在人的眼睛中和大脑中产生的一种映像。
(2)颜色的本质决定于光的波长。
自然光的颜色是连续的
(1)光的波长是连续的,导致颜色也是连续的。理论上,只要你的眼睛分辨能力足够好,可以在自然界中发现无数种颜色。
计算机中的颜色是离散的
(1)计算机中不可能存储无数种颜色,所以必须将颜色有限化,所以就用有限种颜色来代表自然界中的无限种颜色。这个理论非常类似于之前学过的 AD 转换。
(2)这种离散化表达颜色的缺点是不够真实,漏掉了很多种颜色。因此计算机中所能表达的颜色没有自然界中丰富(计算机屏幕上显示的图像和真实图像有差别)
(3)计算机所能表达的颜色种类个数,这个参数叫:像素深度 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 位色原理是一样的,只是 RG B三种颜色各自的精度都更高了( RGB 各 8 位),叫 RGB888。此时颜色比 RGB565 更加真实细腻,虽然说比自然界无数种颜色还是少了很多,不过由于人眼的不理想性所以人眼几乎不能区分 1677万种颜色和无数种颜色的差别了。于是乎就把这种 RGB888 的表示方法叫做真彩色。(RGB565就是假彩色)
32位:总共用32位二进制来表示颜色,其中 24 位表示红绿蓝三元色(还是RGB888分布),剩下8位表示透明度。这种显色方式就叫 ARGB(A是阿尔法,表示透明度),现在PC机中一般都用ARGB表示颜色。
补充:颜色的组成,三元色(三基色)是RGB,也就是说所有的颜色都可以由红绿蓝三种颜色组成。
S5PV210的LCD控制器
FIMD结构框图
(1) 210 的 LCD 控制器叫 FIMD,FIMD 是 210 内部和图像处理相关的一些部件,在摄像头等和图像处理有关的部分都可以有关联。
(2) FIMD 在内部与 AHB 总线等相连接,在外部提供 RGB 接口、I80 接口、YUV 接口与外部相连接,我们实际使用的是 RGB 接口。
虚拟屏幕叠加(数据手册 P1194)
(1)虚拟屏幕的意思是,我们平时看到的屏幕上显示出来的场景实际是很多个屏幕显示叠加在一起的效果(譬如新闻图像、电视台台标、下方飘动的字幕新闻)
(2)像 S5PV210 的 LCD 控制器中有 5 个虚拟屏幕 Window0 到 Window4,虚拟屏幕不存在于真实而存在于内存中。(之前讲过,LCd 显示时实际是显示的是对应的内存中的显存区域的数值)虚拟屏幕其实就是一个内存中的显存区域,有几个显存区域就有几个虚拟屏幕,但是这些虚拟屏幕都被映射到一个真实的显示屏上面,所以将来真实的现实效果实际是这几个虚拟屏幕的显示内容的叠加。(叠加时要注意上面一层会覆盖下面一层,所以要注意谁在前谁在后,设置寄存器时有这个选项)
(3)使用虚拟屏幕而不是整个 LCD 使用一个显存是有一定好处的:第一,可以保证不污染源图像,方便程序处理;第二,可以减少屏幕刷新,提高显示效率,减少 CPU 工作量。
虚拟显示(数据手册 P1206 )
(1)如何实现在小分辨率的屏幕上(真实)显示大分辨率的图像
(2)细节上,我们需要屏幕上看到不同图像时,需要对显存区域进行刷新。即使我们只需要屏幕显示移动一点点,整个屏幕对应的显存空间也需要整个重新刷新,工作量和完全重新显示一幅图像是一样的。这个显然不好,这样 CPU 刷新屏幕的工作量太大了,效率很低。
(3)如何能够在显示一个大图片的不同区域时让 CPU 刷新屏幕工作量减少?有,方法就是虚拟显示。具体做法就是在内存中建立显示缓存的时候实际建立一个很大的区域,然后让 LCD 去对应其中的一部分区域作为有效的显示区域。将来要显示大图像时,直接将大图像全部一次性加载入显示缓存区,然后通过移动有效显示区域就可以显示大图像的不同区域了。
LCD 编程实战 - LCD 控制器初始化
(1)要想 LCD 工作,必须给 LCD 屏幕和显存之间建立一个映射(映射是在 CPU 初始化 LCD 控制器来完成的)。本部分就是在完成这个过程(这也是 LCD 显示的 2 个阶段的第一阶段,第二阶段中我们只需要给显存中丢入相应的数据,LCD 屏幕就会自动显示相应内容)
#include "main.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 (480)
#define COL (800)
#define HOZVAL (COL-1)
#define LINEVAL (ROW-1)
#define XSIZE COL
#define YSIZE ROW
// 初始化LCD
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 799
#define RightBotY 479
// 设置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;
}
LCD 编程实战 - 显示像素&刷背景
// 在像素点(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();
}
LCD 编程实战 - 横线竖线斜线&画圆
// 绘制横线,起始坐标为(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);
}
}
/ 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;
}
}
}
}
}
//画圆函数,圆心坐标是(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);
}
}
}
LCD 编程实战 - 写英文中文字符
// 写字
// 写字的左上角坐标(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写color;如果是0直接跳过
if (data[count/8] & (1<<(count%8)))
lcd_draw_pixel(i, j, color);
}
count++;
}
}
}
// 写字符串
// 字符串起始坐标左上角为(x, y),字符串文字颜色是color,字符串内容为str
void draw_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; // 换行
}
}
}
// 画800×480的图,图像数据存储在pData所指向的数组中
void lcd_draw_picture(const unsigned char *pData)
{
u32 x, y, color, p = 0;
for (y=0; y<480; y++)
{
for (x=0; x<800; x++)
{
// 在这里将坐标点(x, y)的那个像素填充上相应的颜色值即可
color = (pData[p+0] << 0) | (pData[p+1] << 8) | (pData[p+2] << 16);
lcd_draw_pixel(x, y, color);
p += 3;
}
}
}
LCD 编程实战 - 画图
图片显示分析
(1)图像是彩色的,而之前的文字、图形都是单色的。之前的图形文字绘制函数都有个color参数,就是传给显存告诉它这个像素的显示颜色。
(2)一副分辨率是800×480,BPP是24的图片,实际上就是800×480×3字节的数据。将来写代码将图片显示到LCD中时,图片将会以 unsigned char pic_data[800×480×3]的形式出现。
#include "main.h"
#include "ascii.h"
#include "800480.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 (480)
#define COL (800)
#define HOZVAL (COL-1)
#define LINEVAL (ROW-1)
#define XSIZE COL
#define YSIZE ROW
u32 *pfb = (u32 *)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 799
#define RightBotY 479
// 设置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++);
}
// 绘制横线,起始坐标为(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);
}
}
// 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;
}
}
}
}
}
//画圆函数,圆心坐标是(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);
}
}
}
// 写字
// 写字的左上角坐标(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写color;如果是0直接跳过
if (data[count/8] & (1<<(count%8)))
lcd_draw_pixel(i, j, color);
}
count++;
}
}
}
// 写字符串
// 字符串起始坐标左上角为(x, y),字符串文字颜色是color,字符串内容为str
void draw_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; // 换行
}
}
}
// 画800×480的图,图像数据存储在pData所指向的数组中
void lcd_draw_picture(const unsigned char *pData)
{
u32 x, y, color, p = 0;
for (y=0; y<480; y++)
{
for (x=0; x<800; x++)
{
// 在这里将坐标点(x, y)的那个像素填充上相应的颜色值即可
color = ((pData[p+2] << 0) | (pData[p+1] << 8) | (pData[p+0] << 16));
lcd_draw_pixel(x, y, color);
p += 3;
}
}
}
void lcd_test(void)
{
lcd_init();
lcd_draw_picture(gImage_800480);
/*
// 测试写英文字母
lcd_draw_background(WHITE);
draw_ascii_ok(0, 0, RED, "aaa@qq.com#$%");
*/
/*
// 测试画斜线
lcd_draw_background(WHITE); // 如果没有加这一行,背景色是花花绿绿的
glib_line(0, 479, 799, 0, RED);
draw_circular(399, 239, 50, GREEN);
*/
/*
// 测试画横线、竖线
lcd_draw_background(WHITE);
lcd_draw_hline(350, 450, 240, RED);
lcd_draw_vline(400, 190, 290, GREEN);
*/
/*
// 测试绘制背景色,成功
while (1)
{
lcd_draw_background(RED);
delay();
lcd_draw_background(GREEN);
delay();
lcd_draw_background(BLUE);
delay();
}
*/
}
上一篇: 采坑-深入剖析Tomcat一书