LCD分类和特点
LCD背光是白色的,白光的像素值是255,255,255,通过白光可以显示任何其他颜色的光,LCD分很多种:
- TN面板:最早的面板,响应性不够强,有拖尾现象
- STN面板:解决了拖尾现象
- TFT面板:超薄
- -
其他显示技术
- CRT阴极摄像管显示器,寿命短,体积较大,、
- 等离子显示器,未成 主流
- OLED,可以实现柔性 显示
- LED,户外大屏幕,照明
LCD接口
LCD的数据量比较大,本质上LCD都是TTL接口,+5V表示逻辑1,0V或者GND表示逻辑0,SoC的LCD控制器的硬件接口是TTL电平,LCD硬件接口也是TTL电平,所以是可以直接对接通信的,用软排线连接
TTL不能传递太远的距离,如果LCD和控制器距离过远,则不能直接使用TTL连接,需要进行转换,从主机控制器转换成别的接口(VGA,HDMI等),然后由别的接口在转换成TTL电平到LCD
RGB接口
- VD[0-23]:24根数据线,用于传输图像信息,所以LCD是24根线的并行接口,这样才能快速显示
- HSYNC&VSYNC:时序信号线,控制时序
- VCLK:像素时钟,LCD控制器工作需要主板控制器给他一个工作时钟,从而驱动LCD工作
- VDEN:数据有效标志,时序信号,和HSYNC&VSYNC结合使用
- LEND:行结束标示,时序信号
LCD图像显示
像素
组成图像的最基本的元素称为像素,在显示中可以被控制最小单位,整个图像就是由很多歌像素一个一个组成的,每个像素可以被单独控制
扫描
扫描是一个动作,扫描依次将颜色数值放入屏幕中所有像素里的一个过程,像素以2D平面的方式摆放在屏幕中,扫描就一行一行的从左到右,从上到下将像素数据依次放入到每一个像素中
驱动器&控制器
LCD驱动器产生复杂的模拟信号,驱动LCD中的液晶分子旋转,从而产生图像,LCD控制器通过接口传递数字信号到驱动器,从而告诉驱动器要产生什么样的图像,控制器和驱动器之间使用RGB接口进行通信,驱动器负责数字信号转换为模拟信号的过程,Soc从内存中将像素数据给到LCD控制器。控制器将数据传送到驱动器从而驱动屏幕显示图像
显示内存
Soc在内存中挑选一段满足要求的内存,通过配置将LCD控制器和这段内存连接起来,构成映射关系,LCD控制器就会从这段显存中读取像素数据传输给LCD驱动器来显示,显示体系建立起来后CPU就不用再管驱动器,控制器以及面板的事情了,只需要把图像数据放到显存中去就可显示了
LCD主要时序参数
LCD显示的单位为帧(Frame),表示显示器上一整个画面的内容,显示器工作时一帧一帧的在显示,一帧分为多行,每一行有多个像素,因此一帧图像就是多个像素组成的方形的矩阵,这些数据称为帧外数据,一个视频是由多个帧组成的,在播放的时候逐个播放各个帧,LCD控制器和驱动器一次只能传递一个像素点,一帧图像在屏幕上以串行的方式显示到屏幕上
主要的时序参数有:
- HSPW:HSYNC脉冲持续时间的长短
- HBPD:水平前肩,在水平开始的时候的等待时间
- HFPD:水平后肩,在水平结束的时候的等待时间
- VSPW:VSYNC脉冲持续时间的长短
- VBPD:水平前肩,在水平开始的时候的等待时间
- VFPD:水平后肩,在水平结束的时候的等待时间
每一行的开始,先发送一个HSYNC的高电平脉冲,宽度为HSPW,脉冲告诉驱动器下面的数据是一行信息,信息包含三部分:HBPD+有效信息+HFPD,其中HBPD和HFPD属于时序信息,有效信息就是横向分辨率
帧图像信号分为四部分,VSPW+VBPD+帧有效数据+VFPD,VSPW是帧同步脉冲信号宽度,VBPD和VFPD是帧垂直同步信号的前后肩,帧有效数据
LCD显示概念
像素间距
分为纵向和横向间距,一般像素都是矩形的,所以横向和纵向间距是一样的,像素间距影响最佳观看距离
屏幕分辨率
屏幕横向和纵向的像素个数叫做屏幕的分辨率,分辨率和屏幕尺寸无关
像素深度
bit per pixel,每个像素的数据大小,计算机中使用二进制位表示像素数据,像素位的数据越多,像素值更丰富,颜色更细腻
颜色在计算机中的表示
颜色的本质
颜色是主观存在的,是自然光在眼睛和大脑中产生的映像,颜色的本质在于光,取决于光的波长,光的波长是连续的,所以自然光的颜色也是连续的
颜色的组成
所有的颜色都由红绿蓝三原色组成,来模拟自然界中的颜色,但是受限于计算机中的二进制位数限制,所以颜色分的不够细,有点失真
计算机中的颜色
计算机中的颜色是有限的,类似于模拟信号和数字信号的区别,计算机中需要使用有限的颜色来表示自然界中的颜色,这种情况的缺点是不够真实,会漏掉一部分颜色,所以计算机中能表达的颜色没有自然界丰富,计算机能表达的颜色种类个数,叫做像素深度
常见的像素深度
- 1bit:用1个二进制位来表示颜色,单色显示
- 8bit:能表示256种颜色,但是不能表示彩色,只能以灰度显示
- 16bit:能表示25536种颜色,此时就可以显示颜色了,一般是RGB565颜色分布,用5bit表示红色,6bit表示绿色,剩余5bit表示蓝色,
- 24bit:可以表示2的24次方种颜色,使用RGB888颜色分布,这时颜色显示比较丰富和细腻,图像看起来就比较真实,最起码人眼看起来和真实颜色相差无几了,我们称这种颜色分布为真彩色
- 32bit:使用24bit表示RGB三原色,剩下8bit表示透明度,这种颜色我们成为ARGB8888,颜色的整体个数没变,增加了8bit的透明显示
LCD控制器
S5PV210中的LCD控制器叫做FIMD,与AHB总线相连接,外部提供RGB,Y80,YUV接口,实际使用的是RGB
虚拟屏幕叠加
平时屏幕上看到的场景实际上是多个屏幕显示叠加在一起的效果,S5PV210中有Window0-4总共5个虚拟屏幕,对应的是内存中的不同显存区域,都被映射到一个真实的显示屏上边,所以将来真实的显示效果实际上是这几个虚拟屏幕显示内容的叠加,上面一层会覆盖下面一层,虚拟屏幕可以保证不污染原图像,处理起来比较简单,减少屏幕的刷新,减少CPU的工作量,提高效率
虚拟显示
在内存中显示一块比屏幕大的区域,LCD可以显示其中的一部分,LCD可以在这个大内存区域中移动来显示其中的哪一部分,适合于图片放大等场景
LCD编程
LCD控制器初始化
LCD控制器初始化用于建立显存和LCD之间的映射关系,LCD工作时需要和显存之间建立一个映射,需要使用CPU初始化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;
}
初始化GPIO
根据用户手册,将GPF0-3全部设置为0x22222222
打开背光
将PWMTOUT0设置为波形输出或者输出0来常量,所以设置GPD0CON寄存器
设置显示路径
DISPLAY_CONTROL对应显示路径,有RGB-I80-ITU,这里要设置为ITU就是FIMD路径
配置LCD控制器
配置VIDCON0寄存器:
- bit[26~28]用于设置输出接口
- bit[18]用于RGB并行
- bit[2]设置要使用的时钟源
- bit[1]设置使能或者禁止LCD控制器
- bit[0]设置当前帧结束后是否马上使能LCD控制器
- bit[6]设置是否需要分频
- bit[6~13]设置分频系数
- bit[4]设置是否使能分频
配置VIDCON1寄存器:
- bit[5-6]设置信号极性,说明HSYNC和VSYNC是否反转
配置VIDTCON0和VIDTCON1:
- bit[0]配置xSPW
- bit[8]配置xFPD
- bit[16]配置xBPD
配置VIDTCON2:
- bit[0]配置HOZVAL
- bit[11]配置LINEVAL
配置第一个window-WINCON0:
- bit[0]配置是否使能该window
- bit[2~5]配置颜色深度
下面是设置虚拟显示,控制显存空间
- VIDOSD0A的bit[0]设置左上角的X坐标,bit[11]设置左上角Y坐标
- VIDOSD0B的bit[0]设置右下角角的X坐标,bit[11]设置右下角Y坐标
- VIDOSD0C设置 横向和纵向尺寸
- VIDW00ADD0B0设置为显存空间的地址,最好对齐使用
- VIDW00ADD1B0设置屏幕大小
配置SHADOWCON寄存器
- 使能window0通过channel0传输数据
绘制简单元素
// 把整个屏幕填充为纯色
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);
}
}
}
void lcd_draw_pixel(u32 x,u32 y,u32 color) {
// 基地址加上偏移量得到目标地址,设置颜色
*(pfb + COL * y + x) = color;
}
//绘制一个横线,x变化y不变
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);
}
}
//绘制一个竖线,y变化x不变
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);
}
}
//绘制一个斜线,x变化y变化,需要使用到算法
// glib库中的画线函数,可以画斜线,线两端分别是(x1, y1)和(x2, y2)
void glib_line(u32 x1, u32 y1, u32 x2, u32 y2, u32 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(u32 centerX, u32 centerY, u32 radius, u32 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中
void show_8_16(u32 x, u32 y, u32 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(u32 x, u32 y, u32 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_pic(const unsigned char *pimg_data) {
u32 x,y,color, p = 0;
for(y = 0; y < ROW; y++) {
for (x = 0; x < COL; x++) {
// 计算颜色
color = (pimg_data[p + 0] << 0) | (pimg_data[p + 1] << 8) | (pimg_data[p + 2] << 16);
// 计算坐标点并填充颜色
lcd_draw_pixel(x, y, color);
p += 3;
}
}
}