1、lcd基本知识
今天这篇文章来介绍使用s3c2440来操作lcd的过程,在开始之前首先来介绍关于lcd的一些概念。
lcd就是我们常说的显示设备,常用的是TFT lcd和STN lcd,lcd能显示一张图片就是因为在它内部存在一个电子枪,
电子枪把缓存里的数据拿出来一个一个打到显示屏上,根据显示屏的分辨率来决定发出多少数据,
关于更多它内部的工作细节我们在此不去深究,了解它的大概的工作原理就行,但是我们还需要明确几点才能往下继续
1、电子枪如何移动,也就是打下一个点?
答:lcd根据外部提供的脉冲信号vclk,每来一个vclk,电子枪就移动到下一个位置
2、电子枪如何跳到下一行?
答:电子枪移动到一行的最后一个位置时,就会接受到一个信号(HSYNC),也就是水平
同步信号,移动到下一行
3、电子枪如何跳到原点?
答:当电子枪移动到最后一行的最后一个点时,就需要移动到原点去,这时候会收到一个
垂直同步信号VSYNC,电子枪才会移动到原点,我们常说的一帧数据就是“一屏”像素,那么,
VSYNC是在一帧数据显示之前就必须有效的。
4、显示的颜色如何确定?
答:显示屏上显示的是缓存中的数据,这些数据是有格式的,叫做bpp,就是每一个像素的格式,
由红绿蓝三种颜色组成,如果显示的数据格式是5-6-5(16)bpp,那么每一个像素就由16bit数据组成。
好了,到这里我们就已经了解了lcd要显示数据的基本概念了,接下来就来看看s3c2440上面的lcd控制
2、S3C2440 lcd
我们使用的是TFT lcd,它支持很多种显示格式,在下面的测试里用的是16bpp,下面这张图列举的是lcd的外接信号,这些信号我们会在下面
详细介
来简单说一下显示格式,就拿16BPP来说,那么一个像素在内存里所占的地址空间是16bit,2个字节的空间,可以通过配置BSWP和HWSWP的值来决定他们在缓存里存放的格式,第一种是低字节存放在高地址处,第二种是第字节存放在地地址处,显然第二中更符合我们的编程习惯。
好了,接下来我们来看看重要的内容,就是lcd的信号时序,决定着lcd控制器能否正常的工作
这个时序图里包含了我们要处理的一些信号,这个图刚开始比较难懂,需要仔细的看才能看清楚它的内容
1、在前面我们知道 VSYNC 是垂直同步信号,它必须在显示一帧数据之前有效,在它变为高电平(有效之后)
开始显示数据。
2、VSPW+1表示VSYNC的脉冲宽度为VSPW+1个HSYNC信号周期,HSYNC信号是行同步信号,所以,也就是说,在
VSYNC信号有效之后,(VSPW+1)行个数据是无效的,
3、接着看,经过VBPD+1个HSYNC信号之后,有效的数据行才出现,也就是在VSYNC信号有效之后,总共经历了
(VBPD+1 + VSPW+1)行才开始显示数据,这也就是显示屏上面的黑框
4、随后发出LINEVAL+1行有效数据。
5、在有效数据显示完之后,还要经过VFPD+1个无效的行,VSYNC信号才来显示下一帧数据,也就是显示器下边的黑框。
下面的图是显示行的具体过程
1、行(水平)同步信号HSYNC有效之后,表示一行数据显示的开始,
2、HSYNC信号脉冲的宽度是HSPW+1个VCLK信号周期,也就是HSPW+1个像素无效,
3、紧接着HBPD+1个VCLK信号周期之后,有效数据才开始显示,也就是显示屏左边的黑框一共是(HBPD+1+HSPW+1)个像素
4、随后发出HOZVAL+1个有效像素的数据
5、显示完一行有效的数据之后,还要显示HFPD+1个无效的数据,下一个HSYNC信号才来到,也就是右边的黑框
实际的显示效果如下图所示那样
3、编程前准备
这一节的内容为编写程序做准备,我们需要确定lcd的时序要求和引脚的配置,因为s3c2440上面的lcd控制器和4.3寸的lcd数据手册中的时序可能是不一样的,所以,要进行对比之后才可以进行编写程序
上面的图片是我从数据手册中截取出来的,左边的是s3c2440数据手册中的lcd时序图,右边的是4.3寸lcd数据手册的时序图
我们先来解决第一个问题
1、信号的极性
s3c2440
VSYNC : 高电平有效 HSYNC : 高电平有效
VCLK : 下降沿读取数据 VDEN : 高电平有效
4.3寸lcd
VSYNC : 下降沿有效 HSYNC : 下降沿有效
VCLK : 下降沿读取数据 VDEN : 高电平有效
通过对比我们得出结论,s3c2440的lcd时序VSYNC和HSYNC信号需要反转,其他的不需要
2、信号持续时间
对比上面两张图中相同的部分,可以得出信号的脉冲时间
VSPW+1 = tvp VBPD+1 = tvb
VFPD+1 = tvf LINEVAL+1 = tvd
HSPW+1 = thp HBPD+1 = thb
HFPD+1 = thf HOZVAL+1 = thd
在4.3寸lcd的数据手册中寻找上面的信号的值
tvp典型值为10,VSPW = tvp- 1 = 9 tvb典型值为2, VBPD = tvb - 1 = 1
tvf典型值为2, VFPD = tvf - 1 = 1 tvd表示行数为272,LINEVAL = tvd - 1 = 271
thp典型值为41,HSPW = thp - 1 = 40
thb和thf没有给出典型值,但是thf+ thp+ thb﹥44 CLK,所以取他们的最小值2
HBPD = thb - 1 = 1 HFPD = thf - 1 = 1
HOZVAL = thd - 1 = 480 - 1 = 479
接下来需要处理lcd的引脚问题了,在s3c2440原理图上找到cd,看它的引脚要满足那些条件
其中VD是RGB数据引脚,可将这些引脚都配置为lcd专用引脚,VCLK也接到了普通GPIO上了,将VCLK和VFRAME都配置为lcd使用引脚,这些不再多说,需要注意的是Von和Voff,从名字上来看好像是开关,没错,它们接到了lcd的电源电路
该电源电路有效的前提是LCD_PWREN引脚输入高电平,LCD_PWREN接到了GPG4引脚上,将其配置为lcd使用引脚,需要注意的是这个引脚不用我们配置输出是高还是低,它是由lcd控制寄存器LCDCON5的bit3和bit5控制,
最后一个引脚是lcd的背光驱动引脚,KEYBOARD接入到了GPB0引脚上,该引脚输出高点平打开lcd背光,输出低电平关闭lcd背光,需要注意的是,KEYBOARD是lcd的背光电路的开关,这个引脚在使能和禁止的函数里可以进行输出设置,上一个引脚LCD_PWER是lcd设备的电源引脚,不要混淆了。
4、编写程序
接下来,我们来编写S3c2440的lcd程序,我们打开s3c2440的数据手册,来配置所有的lcd的寄存器,寄存器的原始内容在这里就不写了
我只列举出我们需要处理的位就行了
lcd.c
#define LCD_FB_BASE 0x33c00000 //缓存的地址
#define XRES 480 //列
#define YRES 272 //行
//lcd使用到的引脚初始化
void lcd_pin_init()
{
/*GPD2 - 12 VD10-20 */
GPDCON = 0xaaaaaaaa;
/*GPC11-15 VD3-7 */
/* GPC1 -> vclk 这些引脚都配置为lcd专用引脚 */
GPCCON = 0xaaaaaaaa;
/* GPB0 -> 背光KEYBOARD 输出(使能1)*/
GPBCON &= ~(3 << 0);
GPBCON |= (1 << 0);
/* GPG4 -> LCD_PWREN 输出(使能1) */
GPGCON |= (3 << 8);
}
/* 配置lcd寄存器 */
void lcd_init()
{
unsigned int addr;
/*
LCDCON1
[17:8] CLKVAL 计算公式VCLK = HCLK / [(CLKVAL+1) x 2]
时钟配置HCLK是100Mhz VCLK在lcd数据手册中找到典型值是9Mhz
9 = 100 / [(CLKVAL + 1 ) *2] CLKVAL = 4.5取5
[6:5] PNRMODE 11对应的是tft显示器
[4:1] BPPMODE 1100 对应16 bpp的TFT显示格式
[0] ENVID 输出和逻辑启用 0-disable 这一位在下面的使能和禁用函数里用到
*/
LCDCON1 = (5 << 8) | (3 << 5) | (0xc << 1);
/*
LCDCON2
[31:24] VBPD tvb-1
[23:14] LINEVAL YRES-1
[13:6] VFPD tvf-1
[5:0] VSPW tvp-1
*/
LCDCON2 = (1 << 24) | (1 << 6) | (9 << 0) | ((272-1) << 14);
/*
[25:19] HBPD thb-1
[18:8] HOZVAL XRES-1
[7:0] HFPD thf-1
*/
LCDCON3 = (1 << 19) | ((480-1) << 8) | (1 << 0);
/*
[7:0] HSPW thp-1
*/
LCDCON4 = (40 << 0);
/*
[11] FRM565 1 对应 5:6:5 的显示格式
[10] INVVCLK 设置vclk的信号极性 0 对应vclk下降沿读取数据 (我们使用下降沿)
[9] HSYNC信号极性 1 需要反转
[8] VSYNC信号 1需要反转
[7] VD(RGB)信号 0 = Normal,一般是常规的,高电平表示1,
[6] VDEN 0 normal
[5] PWREN power_enable信号极性,0-正常 高电平有效
[3] PWREN power_enable信号启用0-disable 该位有效时,bit5输出高电平,在下边的使能函数里用到
*/
LCDCON5 = (1 << 11) | (0 << 10) | (1 << 9) | (1 << 8) | (0 << 7) | (0 << 6) | (0 << 5);
/*
* [29:21] : LCDBANK, A[30:22] framdbuffer地址的22-30位
* [20:0] : LCDBASEU, A[21:1] framebuffer地址的1-21位
*/
addr = LCD_FB_BASE & (~(1<<31));
LCDSADDR1 = (addr >> 1);
/*
* [20:0] : LCDBASEL, A[21:1] framebuffer的结束地址的1-21位
*/
addr = LCD_FB_BASE + XRES * YRES * 16 / 8;
addr >>= 1;
addr &= 0x1fffff;
LCDSADDR2 = addr;//
}
//lcd使能配置
void lcd_enable()
{
/* 背光引脚 : GPB0 输出高电平*/
GPBDAT |= (1<<0);
//lcd的输出和逻辑启用
LCDCON1 |= (1 << 0);
/* pwren 该位为1,powe_enable输入高电平,有效 */
LCDCON5 |= (1<<3);
}
void lcd_disable()
{
/* 背光引脚 :关闭背光*/
GPBDAT &= ~(1<<0);
/* 该位为0,power_enable输出低电平,无效 */
LCDCON5 &= ~(1<<3);
/* LCDCON1'BIT 0 : 设置LCD控制器是否输出信号 */
LCDCON1 &= ~(1<<0);
}
void lcd_test()
{
int i,j;
unsigned short *p;
//红色
p = (unsigned short *)LCD_FB_BASE;
for (i = 0; i < XRES; i++)
for (j = 0; j < YRES; j++)
*p++ = 0xf800;
//绿色
p = (unsigned short *)LCD_FB_BASE;
for (i = 0; i < XRES; i++)
for (j = 0; j < YRES; j++)
*p++ = 0x7e0;
//蓝色
p = (unsigned short *)LCD_FB_BASE;
for (i = 0; i < XRES; i++)
for (j = 0; j < YRES; j++)
*p++ = 0x1f;
}
ok,lcd的函数已经写完,在主函数里一次调用lcd_init(),lcd_enable, lcd_test();就可以看完lcd显示屏上显示出的画面