目录
1. 什么是LCD
- LCD:液晶显示器、面板,不会主动发光,可以通过不同电信号让液晶分子进行选择性的透光,此时在液晶面板前面看到的就是各种各样不同的颜色,LCD面板的比较对象是TN IPS VA屏!
- LED:主动发光、背光、我们平时所说的“LED屏”,全称LED-backlit LCD,看见后面那个大大的LCD了吗?翻译成中文是LED背光液晶显示器。我们所说的LED也是指的他的背光
- OLED,Organic Light-Emitting Diode,中文全称有机发光二极管,是一种全新的显示技术,可弯曲,OLED屏幕为自发光屏幕,无需背光照明,与LCD屏幕有着本质区别。
2. LCD接口技术
- 采用TTL接口。
- SoC的LCD控制器硬件接口是TTL电平的,LCD这边硬件接口也是TTL电平的
- TTL电平的缺陷就是不能传递太远,如果LCD屏幕和主板控制器太远,就不能直接TTL连接,转换方式:主机SoC(TTL) ->VGA-> LCD屏幕(TTL)
3. RGB接口详解
- VD[23:0]:24根数据线,并行
- HSYNC:水平同步信号
- VSYNC:垂直同步信号:时序信号线,为了让LCD能够正常显示给的控制信号
- VCLK(像素时钟):LCD工作时需要主板控制器给LCD模组一个工作时钟信号,就是VCLK。
- VDEN:数据有效标志:时序信号,和HSYNC、VSYNC结合使用。
- LEND:行结束标志,不是必须的,时序信号,非必须,譬如X210接口就没有。
4. LCD显示图像相关概念
- 像素pixel:是图像中最基本的元素、图像被操作的最小单位、可以是黑白(控制亮度)、可以是单色(控制亮、灭)、也可以是彩色
- 扫描:图像是一个像素一个像素扫描生成的,只是肉眼识别不出
- 驱动器:将控制器的数字信号转换成模拟信号发给LCD显示器、面板需要一定的模拟电信号来控制液晶分子
- 控制器:按时序发出数字信号、SoC会从内存中拿像素数据给LCD控制器并最终传给LCD驱动器。
- 显存:内存中的一块区域、先和LCD控制器建立映射、然后在显存中写入像素数据,然后就不需要关心LCD控制器、驱动器、LCD显示器,以后CPU就只关心显存了,因为我只要把要显示的图像的像素数据丢到显存中,硬件就会自动响应。
- 像素间距(pitch),连续2个像素的像素中心的距离。
- 分辨率:整个屏幕的横向和纵向的像素个数就叫分辨率,譬如X210开发板用的屏幕是800×480,分辨率不等于清晰度、
- 像素深度:一个像素在计算机中由多少个字节数据来描述,一般来说像素深度有这么几种:1位、8位、16位、24位、32位
5. LCD的六个主要时序参数
- LCD图像显示单位:帧,
- 帧内数据:一帧图像其实就是多个像素组成的矩阵。
- 帧外数据:整个视频由很多个帧构成,最终播放视频时逐个播放各个图像帧即可。
- 显示一帧图像其实是一个一个像素点显示的过程
6个主要时序参数:
HSPW 水平同步信号脉宽、HBPD 水平同步信号前肩、HFPD 水平同步信号后肩
VSPW 垂直同步信号脉宽、VBPD 垂直同步信号前肩、VFPD 垂直同步信号后肩、
- 前三个H开头的信号为一组横向的时序信号、HSPW告诉驱动器要发信息了,HBPD、HFPD调整时间,纵向V开头的信号同理。一行总共包含4部分:HSPW+HBPD+有效行信息+HFPD。
- 一帧图像其实就是一列,一列图像由多个行组成,每行都是上面讲的这个时序。
- 一帧图像的通信过程是这样的:整个帧图像信号分为4部分:VSPW+VBPD+帧有效信号+VFPD。VSPW是帧同步信号宽度,用来告诉驱动器一帧图像要开始了;VBPD和VFPD分别是垂直同步信号前后肩。
这些参数都是LCD本身的、与LCD控制器无关、这些参数大小来自LCD厂家提供的示例代码。
补充:H开头的三个单位都是DCLK(像素时钟),V开头的三个单位是TH。这样设置的好处是我们改变了像素时钟的设置时,不用改变这里的时序参数。
6. 颜色在计算机中的表示
- 自然光颜色连续、计算机颜色离散,当离散点足够多、颜色就足够丰富。
- 像素深度bpp与颜色丰富性有直接关系。
- 像素深度解析:
1位:0/1只能控制单色的亮灭
8位:2^8有256种颜色,灰度显示。没有彩色,我们把纯白到纯黑分别对应255到0,中间的数值对应不同的灰。
16位:用16个二进制位表示颜色,此时能表示65536种颜色。这时候就可以彩色显示了,一般是RGB565的颜色分布(用5位二进制表示红色、用6位二进制表示绿色、用5位二进制表示蓝色),失真严重。
24位:用24个二进制位来表示颜色,此时能表示16777216种颜色。这种表示方式和16位色原理是一样的,只是RGB三种颜色各自的精度都更高了(RGB各8位),叫RGB888。
32位:总共用32位二进制来表示颜色,其中24位表示红绿蓝三元色(还是RGB888分布),剩下8位表示透明度。这种显色方式就叫ARGB(A是阿尔法,表示透明度),现在PC机中一般都用ARGB表示颜色。
7. S5PV210的LCD控制器
-
数据手册:
时钟:AHB
外部接口选择:RGB -
虚拟屏幕:
开发板支持5个虚拟屏幕、每个虚拟屏幕对应信息保留在显存中 -
虚拟显示:
作用:需要显示的图片的分辨率大于屏幕支持的分辨率,先将整个大图片的像素信息以全部存放在显存中,然后让LCD去对应其中的一部分区域作为有效的显示区域,这个就叫虚拟显示
编程时,通过改变屏幕左上角的位置、来改变显示的图片、不需要额外写内存操作
8. 编程实战
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功能,LCD控制器连接的引脚要配置好工作模式、因为引脚复用,凡是有引脚的都要设置GPIO的工作模式。GPF0-GPF3都是如此
GPF0CON = 0x22222222;
GPF1CON = 0x22222222;
GPF2CON = 0x22222222;
GPF3CON = 0x22222222;
// 打开背光 GPD0_0(PWMTOUT0)
GPD0CON &= ~(0xf<<0);
GPD0CON |= (1<<0); // 输出模式
GPD0DAT &= ~(1<<0); // output 0 to enable backlight
将GPDODAT寄存器的bit[0]写0,就是GPDO_0输出低电平
// 10: RGB=FIMD I80=FIMD ITU=FIMD
DISPLAY_CONTROL = 2<<0;
// bit[26~28]:使用RGB接口 写100
// bit[18]:RGB 并行 写0
// bit[2]:选择时钟源为HCLK_DSYS=166MHz 写0
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;
//以上的VIDCON1设置控制器内部的一些时钟、分频、接口类型RGB等等
// 设置时序 横+竖
VIDTCON0 = VBPD<<16 | VFPD<<8 | VSPW<<0;
VIDTCON1 = HBPD<<16 | HFPD<<8 | HSPW<<0;
// 设置长宽(物理屏幕)
VIDTCON2 = (LINEVAL << 11) | (HOZVAL << 0);
//以上的VIDTCONn设置一些和时序、长宽、这类设计到具体的数据。
// 设置window0,只使用虚拟屏幕0
// 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的上下左右
// 设置的是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);
//一行的像素个数*4(采用RGB888,一个像素3字节但是4字节对齐)
// 使能channel 0传输数据
SHADOWCON = 0x1;
}
demo1:用一种颜色填充屏幕
RGB,全1为白,全0为黑
#define BLUE 0x0000FF
#define RED 0xFF0000
#define GREEN 0x00FF00
#define WHITE 0xFFFFFF
// 在像素点(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();
}
}
demo2:画横线、竖线
// 绘制横线,起始坐标为(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);
}
}
demo3:写字符、字符串
// 写字
// 写字的左上角坐标(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直接跳过
data是想要写的字符以数组方式存储的字符模
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; // 换行
}
}
}
demo4:画图
// 画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);
//RGB888
lcd_draw_pixel(x, y, color);
p += 3;
}
}
}