s3c2440片上集成了LCD控制器,用于驱动外接LCD屏,LCD屏的硬件特性是固定的,LCD裸板编程重点是根据外接LCD的硬件特性和用户需求配置LCD控制器的寄存器组。而配置寄存器的重点又是理解LCD的工作时序,本文重点分析了LCD的工作时序和与时序相关的寄存器的配置,也结合代码简单介绍LCD使用思路。
LCD控制器提供了驱动外接LCD屏所需的所有控制信号,REGBANK是LCD控制器的寄存器组,含17个寄存器及一块256*16的调色板内存,用来设置各项参数,LCDCDMA是LCD控制器专用的DMA信道,可以自动的从系统总线上取到图像数据。将相关寄存器设置好后,并将帧内存(frame memory)的地址告诉LCD控制器,它即可自动的发起DMA传输,从帧内存中得到图像数据,最终在上述信号的控制下出现在数据总线,VD[23:0]上。用户只需要把要显示的图像数据写入帧内存中。
LCD相关名词解释:
数据传输方式有4位单扫,4位双扫,8位单扫等
单扫:从上到下,从左到右,一个一个地发送数据。
双扫:整屏分上下两部分,每一部分单扫
4位、8位:多少位表示多少根数据线,4位双扫亦是8根数据线
BPP:bit per piexl,表示每个像素使用多少位来表示其颜色。
1BPP:单色(黑白)
2BPP:4级灰度(共四种颜色)
4BPP:16级灰度(共16种颜色)
8BPP:256色
2BPP:4096色
16BPP:64k色
调色板:颜色库,一块存着RGB颜色值的内存。调色板颜色值一般为16BPP,565格式或5551格式。一般8BPP的显示模式要用到调色板,此时帧缓冲区中的数据不是像素的颜色值,而是调色板中颜色值的索引值,真正的颜色值是调色板中的颜色值。这样8BPP显示模式的像素实际是16BPP的。
TFT LCD Timing Example:
名词解释:
VSYNC:垂直同步信号
VSYNC频率:帧/秒,垂直频率或场频率,相当于显示器的频率
VDEN:数据有效使能
HSYNC:水平同步信号
VCLK:像素时钟
VD:数据信号
LEND:行结束信号(行有效数据结束信号)(这不是必须的)
VSPW (vertical sync pulse width):表示VSYNC信号脉冲宽度值为(VSPW+1)个HSYNC信号周期。(若放在一周期最后,则可形象理解为电子枪回扫所需的时间),即(VSPW+1)行,这(VSPW+1)行数据无效。
VBPD (vertical back porch):表示VSYNC脉冲信号结束以后还要经过(VBPD+1)个HSYNC信号周期,有效的行数据才出现。所以,在VSYNC信号刚刚开始之后,总共要经过(VSPW+1 +VBPD+1)个无效的行,第一个有效的行才出现。
LINEVAL:表示(LINEVAL+1)个有效行。
VFPD (vertical front porch):表示有效行结束后要经过(VFPD+1)个无效行。
HSPW (hertical sync pulse width):概念与VSPW相似,表示HSYNC信号脉冲宽度值为(HSPW+1)个VCLKC信号周期。(若放在一HSYNC周期最后,则可形象理解为电子枪行回扫所需的时间),即(HSPW+1)个像素,这(HSPW+1)个像素数据无效。
HBPD (hertical back porch):表示HSYNC脉冲信号结束以后还要经过(HBPD+1)个VCLK信号周期,有效的像素数据才出现。所以,在HSYNC信号刚刚开始之后,总共要经过(HSPW+1 +HBPD+1)个无效的像素,第一个有效的像素才出现。
HOZVAL:表示一行中的(HOZVAL+1)个有效像素。
HFPD (vertical front porch):表示有效像素结束后要经过(HFPD+1)个无效像素。
反映在屏幕上的关系示意图:
具体结合2440开发板上日立液晶模组TX09D70VM1CBA的时序图分析:
时序图原图中T5的相对长度与实际不符,我用红色部分把它投影到VSYNC周期里面,相对关系就非常清晰明了了。
解释其中四个单词:
Vertical Sync Start:有效行开始到下一个VSYNC开始的行数
Vertical Sync end:有效行开始到下一个VSYNC结束的行数
则有 Vertical Sync end - Vertical Sync Start = 1个VSYNC信号脉冲宽度(VSPW+1 行)
Vertical Blank Time:总黑框行数,值为(VSPW+1+VBPD+1+VFPD+1)
Vertical Display End:有效行。
对于s3c2440控制器,与时序相关的要配置的项是VBPD、LINEVAL、VFPD、VSPW、HBPD、HOZVAL、HFPD。
结合8.2时序图和表8.1数据分析得:
VSPW +1= T1=1, VSPW=0
VBPD+1 = T0-T2-T1=327 - 322 - 1 = 4, VBPD =3
VFPD+1 = T2-T5= 322 -320 = 2, VFPD = 1
LINEVAL +1=T5 =320, LINEVAL = 319
同理:
HSPW +1= T7=5, HSPW=4
HBPD+1 = T6-T7-T8=273 - 5 - 251 = 17, HBPD =16
HFPD+1 = T8-T11= 251 -240 = 11, HFPD = 10
HOZVAL +1=T5 =240, HOZVAL = 239
其他寄存器的配置及寄存器的位操作技巧,由于内容多繁杂,就不展开讨论了。也不难,只是需要大家花时间细心去推敲。
裸板LCD的使用步骤:
1、配置引脚用于LCD,函数:Lcd_Port_Init()
2、根据显示模式,配置LCD控制寄存器组,函数:void Tft_Lcd_Init(int type)
3、打开LCD电源
4、使能LCD控制器输出信号
5、初始化调色板: 函数:Lcd_Palette8Bit_Init()
6、清屏 ClearScr(0x0)
7、向帧内存中写图像。
下面的代码参考了百问网韦东山老师提供的源码。
1、配置引脚用于LCD,函数:Lcd_Port_Init()
-
/*
-
* 初始化用于LCD的引脚
-
*/
-
void Lcd_Port_Init(void)
-
{
-
GPCUP =
0xffffffff;
// 禁止内部上拉
-
GPCCON =
0xaaaaaaaa;
// GPIO管脚用于VD[7:0],LCDVF[2:0],VM,VFRAME,VLINE,VCLK,LEND
-
GPDUP =
0xffffffff;
// 禁止内部上拉
-
GPDCON =
0xaaaaaaaa;
// GPIO管脚用于VD[23:8]
-
GPBCON &= ~(GPB0_MSK);
// Power enable pin
-
GPBCON |= GPB0_out;
-
GPBDAT &= ~(
1<<
0);
// Power off
-
printf(
"Initializing GPIO ports..........\n");
-
}
2、根据显示模式,配置LCD控制寄存器组,以MODE_TFT_8BIT_240320为例。
函数:void Tft_Lcd_Init(int type):
-
/*
-
* 初始化LCD控制器
-
* 输入参数:
-
* type: 显示模式
-
* MODE_TFT_8BIT_240320 : 240*320 8bpp的TFT LCD
-
* MODE_TFT_16BIT_240320 : 240*320 16bpp的TFT LCD
-
* MODE_TFT_8BIT_640480 : 640*480 8bpp的TFT LCD
-
* MODE_TFT_16BIT_640480 : 640*480 16bpp的TFT LCD
-
*/
-
void Tft_Lcd_Init(int type)
-
{
-
switch(type)
-
{
-
case MODE_TFT_8BIT_240320:
-
/*
-
* 设置LCD控制器的控制寄存器LCDCON1~5
-
* 1. LCDCON1:
-
* 设置VCLK的频率:VCLK(Hz) = HCLK/[(CLKVAL+1)x2]
-
* 选择LCD类型: TFT LCD
-
* 设置显示模式: 8BPP
-
* 先禁止LCD信号输出
-
* 2. LCDCON2/3/4:
-
* 设置控制信号的时间参数
-
* 设置分辨率,即行数及列数
-
* 现在,可以根据公式计算出显示器的频率:
-
* 当HCLK=100MHz时,
-
* Frame Rate = 1/[{(VSPW+1)+(VBPD+1)+(LIINEVAL+1)+(VFPD+1)}x
-
* {(HSPW+1)+(HBPD+1)+(HFPD+1)+(HOZVAL+1)}x
-
* {2x(CLKVAL+1)/(HCLK)}]
-
* = 60Hz
-
* 3. LCDCON5:
-
* 设置显示模式为8BPP时,调色板中的数据格式: 5:6:5
-
* 设置HSYNC、VSYNC脉冲的极性(这需要参考具体LCD的接口信号): 反转
-
* 字节交换使能
-
*/
-
LCDCON1 = (CLKVAL_TFT_240320<<
8) | (LCDTYPE_TFT<<
5) | \
-
(BPPMODE_8BPP<<
1) | (ENVID_DISABLE<<
0);
-
LCDCON2 = (VBPD_240320<<
24) | (LINEVAL_TFT_240320<<
14) | \
-
(VFPD_240320<<
6) | (VSPW_240320);
-
LCDCON3 = (HBPD_240320<<
19) | (HOZVAL_TFT_240320<<
8) | (HFPD_240320);
-
LCDCON4 = HSPW_240320;
-
LCDCON5 = (FORMAT8BPP_565<<
11) | (HSYNC_INV<<
9) | (VSYNC_INV<<
8) | \
-
(BSWP<<
1);
-
-
/*
-
* 设置LCD控制器的地址寄存器LCDSADDR1~3
-
* 帧内存与视口(view point)完全吻合,
-
* 图像数据格式如下(8BPP时,帧缓冲区中的数据为调色板中的索引值):
-
* |----PAGEWIDTH----|
-
* y/x 0 1 2 239
-
* 0 idx idx idx ... idx
-
* 1 idx idx idx ... idx
-
* 1. LCDSADDR1:
-
* 设置LCDBANK、LCDBASEU
-
* 2. LCDSADDR2:
-
* 设置LCDBASEL: 帧缓冲区的结束地址A[21:1]
-
* 3. LCDSADDR3:
-
* OFFSIZE等于0,PAGEWIDTH等于(240/2)
-
*/
-
LCDSADDR1 = ((LCDFRAMEBUFFER>>
22)<<
21) | LOWER21BITS(LCDFRAMEBUFFER>>
1);
-
LCDSADDR2 = LOWER21BITS((LCDFRAMEBUFFER+ \
-
(LINEVAL_TFT_240320+
1)*(HOZVAL_TFT_240320+
1)*
1)>>
1);
-
LCDSADDR3 = (
0<<
11) | (LCD_XSIZE_TFT_240320/
2);
-
-
/* 禁止临时调色板寄存器 */
-
TPAL =
0;
-
-
fb_base_addr = LCDFRAMEBUFFER;
-
bpp =
8;
-
xsize =
240;
-
ysize =
320;
-
-
break;
-
-
......
-
}
3、打开LCD电源。
函数:Lcd_PowerEnable(0, 1); // 设置LCD_PWREN有效,它用于打开LCD的电源
-
/*
-
* 设置是否输出LCD电源开关信号LCD_PWREN
-
* 输入参数:
-
* invpwren: 0 - LCD_PWREN有效时为正常极性
-
* 1 - LCD_PWREN有效时为反转极性
-
* pwren: 0 - LCD_PWREN输出有效
-
* 1 - LCD_PWREN输出无效
-
*/
-
void Lcd_PowerEnable(int invpwren, int pwren)
-
{
-
GPGCON = (GPGCON & (~(
3<<
8))) | (
3<<
8);
// GPG4用作LCD_PWREN
-
GPGUP = (GPGUP & (~(
1<<
4))) | (
1<<
4);
// 禁止内部上拉
-
-
LCDCON5 = (LCDCON5 & (~(
1<<
5))) | (invpwren<<
5);
// 设置LCD_PWREN的极性: 正常/反转
-
LCDCON5 = (LCDCON5 & (~(
1<<
3))) | (pwren<<
3);
// 设置是否输出LCD_PWREN
-
}
4、使能LCD控制器输出信号
函数:Lcd_EnvidOnOff(1); // 使能LCD控制器输出信号
-
/*
-
* 设置LCD控制器是否输出信号
-
* 输入参数:
-
* onoff:
-
* 0 : 关闭
-
* 1 : 打开
-
*/
-
void Lcd_EnvidOnOff(int onoff)
-
{
-
if (onoff ==
1)
-
{
-
LCDCON1 |=
1;
// ENVID ON
-
GPBDAT |= (
1<<
0);
// Power on
-
}
-
else
-
{
-
LCDCON1 &=
0x3fffe;
// ENVID Off
-
GPBDAT &= ~(
1<<
0);
// Power off
-
}
-
}
5、初始化调色板:
函数:Lcd_Palette8Bit_Init(); // 初始化调色板
-
/*
-
* 设置调色板
-
*/
-
void Lcd_Palette8Bit_Init(void)
-
{
-
int i;
-
volatile
unsigned
int *palette;
-
-
LCDCON1 &= ~
0x01;
// stop lcd controller
-
-
LCDCON5 |= (FORMAT8BPP_565<<
11);
// 设置调色板中数据格式为5:6:5
-
-
palette = (
volatile
unsigned
int *)PALETTE;
-
for (i =
0; i <
256; i++)
-
*palette++ = DEMO256pal[i];
-
-
LCDCON1 |=
0x01;
// re-enable lcd controller
-
}、
6、清屏
-
ClearScr(
0x0)
-
{
-
//往帧内存中写0,或者用临时调色板方法。
-
}
7、向帧内存中写图像。先在屏幕指定位置画一个点,再画一条线。
函数:void PutPixel(UINT32 x, UINT32 y, UINT32 color)
void DrawLine(int x1,int y1,int x2,int y2,int color)
指定位置画一点:
-
/*
-
* 画点
-
* 输入参数:
-
* x、y : 象素坐标
-
* color: 颜色值
-
* 对于16BPP: color的格式为0xAARRGGBB (AA = 透明度),
-
* 需要转换为5:6:5格式
-
* 对于8BPP: color为调色板中的索引值,
-
* 其颜色取决于调色板中的数值
-
*/
-
void PutPixel(UINT32 x, UINT32 y, UINT32 color)
-
{
-
UINT8 red,green,blue;
-
-
switch (bpp){
-
case
16:
-
{
-
UINT16 *addr = (UINT16 *)fb_base_addr + (y * xsize + x);
-
red = (color >>
19) &
0x1f;
// 5 BIT
-
green = (color >>
10) &
0x3f;
// 6 bit
-
blue = (color >>
3) &
0x1f;
// 5 bit
-
color = (red <<
11) | (green <<
5) | blue;
// 格式5:6:5
-
*addr = (UINT16) color;
-
break;
-
}
-
-
case
8:
-
{
-
UINT8 *addr = (UINT8 *)fb_base_addr + (y * xsize + x);
-
*addr = (UINT8) color;
-
break;
-
}
-
-
default:
-
break;
-
}
-
}
需要注意的是PutPixel()传入的颜色值是32位的,格式是AARRGGBB,这里转为565格式,要取出R、G、B所在位的高5位,高6位,高5位,然后合并为565格式。
画任意一条线段:
-
/*
-
* 画线
-
* 输入参数:
-
* x1、y1 : 起点坐标
-
* x2、y2 : 终点坐标
-
* color : 颜色值
-
* 对于16BPP: color的格式为0xAARRGGBB (AA = 透明度),
-
* 需要转换为5:6:5格式
-
* 对于8BPP: color为调色板中的索引值,
-
* 其颜色取决于调色板中的数值
-
*/
-
void DrawLine(int x1,int y1,int x2,int y2,int color)
-
{
-
-
}
实现方法大家可以参考东山老师源码,也可以参考bresenham算法,无非就是一条线段,取距离这条线段最近的离散的整数点,每取一个整数点PutPixel.
</div>