本实验CPU:s3c2440
LCD: 4.3寸 分辨率(Resolution) 480*272 TFT-LCD 型号AT043TN24 V.1
s3c2440 LCD控制器支持:
①1/2/4/8bpp调色板显示模式,16bpp/24bpp非调色板显示模式
当选定了LCD型号后,硬件工程师做出电路板后,LCD模块接线确定,显示模式就确定了,如本实验使用8bpp调色板显示模式和16bpp非调色板显示模式,两种模式均为565像素格式
NOTE:
①24bpp表示24bit per pixel 即每像素用24位表示,正好对应RRGGBB颜色值
24bpp内存数据格式为:
P1是位于内存地址000H的高位或地位的颜色值X,lcd控制器取像素值往数据线VD[0-23]发,其中RR->VD[16-23], GG->VD[8-15],BB->VD[0-7],然后可以第一个像素显示此颜色值X
②16bpp
显示器上像素由三部分红(R)/绿(G)/蓝(B)组成,称为三原色,每个颜色由8位表示,即每个颜色有256级,可以由此三原色构成其它的颜色,比如RGB(255,255,255)合成白色, color(AARRGGBB(AA表示透明度))为UINT32型,因此需要取出16位构成像素颜色值(如何取代码有介绍),然后将16位颜色值存到Framebuffer中
③8bpp调色板模式
其中P1表示索引值index,范围为00H-FFH(15-4表也可看出),
16bpp/8bpp程序 结构:
main.c:
int main()
{
char c;
uart0_init(); // 波特率115200,8N1(8个数据位,无校验位,1个停止位)
while (1)
{
printf("\r\n##### Test TFT LCD #####\r\n");
printf("[1] TFT240320 8Bit\n\r");
printf("[2] TFT240320 16Bit\n\r");
/*
* author:xyc 添加支持480272 8Bit/16Bit
*/
printf("[3] TFT480272 8Bit\n\r");
printf("[4] TFT480272 16Bit\n\r");
printf("Enter your selection: ");
c = getc();
printf("%c\n\r", c);
switch (c)
{
case '1':
{
Test_Lcd_Tft_8Bit_240320();
break;
}
case '2':
{
Test_Lcd_Tft_16Bit_240320();
break;
}
case '3':
{
Test_Lcd_Tft_8Bit_480272();
break;
}
case '4':
{
Test_Lcd_Tft_16Bit_480272();
break;
}
default:
break;
}
}
return 0;
}
lcdlib.c:
/*
* author:xyc 添加480272屏幕16bpp支持
* 以480x272,16bpp的显示模式测试TFT LCD
*/
void Test_Lcd_Tft_16Bit_480272(void)
{
Lcd_Port_Init(); // 设置LCD引脚
Tft_Lcd_Init(MODE_TFT_16BIT_480272); // 初始化LCD控制器
Lcd_PowerEnable(0, 1); // 设置LCD_PWREN有效,它用于打开LCD的电源
Lcd_EnvidOnOff(1); // 使能LCD控制器输出信号
ClearScr(0x0); // 清屏,黑色
printf("[TFT 64K COLOR(16bpp) LCD TEST]\n");
printf("1. Press any key to draw line\n");
getc();
DrawLine(0 , 0 , 479, 0 , 0xff0000); // 红色
DrawLine(0 , 0 , 0 , 271, 0x00ff00); // 绿色
DrawLine(479, 0 , 479, 271, 0x0000ff); // 蓝色
DrawLine(0 , 271, 479, 271, 0xffffff); // 白色
DrawLine(0 , 0 , 479, 271, 0xffff00); // 黄色
DrawLine(479, 0 , 0 , 271, 0x8000ff); // 紫色
DrawLine(240, 0 , 240, 319, 0xe6e8fa); // 银色
DrawLine(0 , 136, 479, 136, 0xcd7f32); // 金色
printf("2. Press any key to draw circles\n");
getc();
Mire();
printf("3. Press any key to fill the screem with one color\n");
getc();
ClearScr(0xff0000); // 红色
printf("4. Press any key to fill the screem by temporary palette\n");
getc();
ClearScrWithTmpPlt(0x0000ff); // 蓝色
printf("5. Press any key stop the testing\n");
getc();
Lcd_EnvidOnOff(0);
}
/*
* author:xyc 添加480272屏幕支持
* 以480x272,8bpp的显示模式测试TFT LCD
*/
void Test_Lcd_Tft_8Bit_480272(void)
{
Lcd_Port_Init(); // 设置LCD引脚
Tft_Lcd_Init(MODE_TFT_8BIT_480272); // 初始化LCD控制器
Lcd_PowerEnable(0, 1); // 设置LCD_PWREN有效,它用于打开LCD的电源
Lcd_EnvidOnOff(1); // 使能LCD控制器输出信号,背光打开
Lcd_Palette8Bit_Init(); // 初始化调色板
ClearScr(0x0); // 清屏
printf("[TFT 64K COLOR(16bpp) LCD TEST]\n");
printf("1. Press any key to draw line\n");
getc();
#if 0
DrawLine(0 , 0 , 239, 0 , 0); // 颜色为DEMO256pal[0]
DrawLine(0 , 0 , 0 , 319, 1); // 颜色为DEMO256pal[1]
DrawLine(239, 0 , 239, 319, 2); // ……
DrawLine(0 , 319, 239, 319, 4);
DrawLine(0 , 0 , 239, 319, 8);
DrawLine(239, 0 , 0 , 319, 16);
DrawLine(120, 0 , 120, 319, 32);
DrawLine(0 , 160, 239, 160, 64);
#endif
DrawLine(0 , 0 , 479, 0 , 0); // 颜色为DEMO256pal[0]
DrawLine(0 , 0 , 0 , 271, 1); // 颜色为DEMO256pal[1]
DrawLine(479, 0 , 479, 271, 2);
DrawLine(0 , 271, 479, 271, 4);
DrawLine(0 , 0 , 479, 271, 8);
DrawLine(479, 0 , 0 , 271, 16);
DrawLine(240, 0 , 240, 319, 32);
DrawLine(0 , 136, 479, 136, 64);
printf("2. Press any key to draw circles\n");
getc();
Mire();
printf("3. Press any key to fill the screem with one color\n");
getc();
ClearScr(128); // 输出单色图像,颜色为DEMO256pal[128]
printf("4. Press any key to fill the screem by temporary palette\n");
getc();
ClearScrWithTmpPlt(0x0000ff); // 输出单色图像,颜色为蓝色
printf("5. Press any key to fill the screem by palette\n");
getc();
DisableTmpPlt(); // 关闭临时调色板寄存器
ChangePalette(0xffff00); // 改变整个调色板为黄色,输出单色图像
printf("6. Press any key stop the testing\n");
getc();
Lcd_EnvidOnOff(0);
}
/*
* 初始化用于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]
/*author:xyc 背光关闭GPB0端口为LED背光口,而不是电源,所以注释有误*/
GPBCON &= ~(GPB0_MSK); // Power enable pin
GPBCON |= GPB0_out;
GPBDAT &= ~(1<<0); // Power off
printf("Initializing GPIO ports..........\n");
}
/*
* 初始化LCD控制器
* 输入参数:
* type: 显示模式
* MODE_TFT_8BIT_240320 : 240*320 8bpp的TFT LCD
* MODE_TFT_16BIT_240320 : 240*320 16bpp的TFT LCD
* MODE_TFT_8BIT_480272 : 480*272 8bpp的TFT LCD
* MODE_TFT_16BIT_480272 : 480*272 16bpp的TFT LCD
*/
void Tft_Lcd_Init(int type)
{
switch(type)
{
/*
* author: xyc 添加480x272屏幕8Bpp支持
*/
case MODE_TFT_8BIT_480272:
/*
* 设置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 = (4<<8) | (LCDTYPE_TFT<<5) | \
(BPPMODE_8BPP<<1) | (ENVID_DISABLE<<0);
LCDCON2 = (1<<24) | (271<<14) | \
(1<<6) | (1);
LCDCON3 = (1<<19) | (479<<8) | (1);
LCDCON4 = 40;
LCDCON5 = (FORMAT8BPP_565<<11) | (HSYNC_INV<<9) | (VSYNC_INV<<8) | \
(1<<1);
/*
* 设置LCD控制器的地址寄存器LCDSADDR1~3
* 帧内存与视口(view point)完全吻合,
* 图像数据格式如下(8BPP时,帧缓冲区中的数据为调色板中的索引值):
* |----PAGEWIDTH----|
* y/x 0 1 2 479
* 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+ \
272*480*1)>>1);
LCDSADDR3 = (0<<11) | (480/2);
/* 禁止临时调色板寄存器 */
TPAL = 0;
fb_base_addr = LCDFRAMEBUFFER;
bpp = 8;
xsize = 480;
ysize = 272;
break;
case MODE_TFT_16BIT_480272:
/*
* 设置LCD控制器的控制寄存器LCDCON1~5
* 1. LCDCON1:
* 设置VCLK的频率:VCLK(Hz) = HCLK/[(CLKVAL+1)x2]
* 选择LCD类型: TFT LCD
* 设置显示模式: 16BPP
* 先禁止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:
* 设置显示模式为16BPP时的数据格式: 5:6:5
* 设置HSYNC、VSYNC脉冲的极性(这需要参考具体LCD的接口信号): 反转
* 半字(2字节)交换使能
*/
#if 0
LCDCON1 = (CLKVAL_TFT_240320<<8) | (LCDTYPE_TFT<<5) | \
(BPPMODE_16BPP<<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) | \
(HWSWP<<1);/*这里有疑问*/
#endif
LCDCON1 = (4<<8) | (LCDTYPE_TFT<<5) | \
(BPPMODE_16BPP<<1) | (ENVID_DISABLE<<0);
LCDCON2 = (1<<24) | (271<<14) | \
(1<<6) | (1);
LCDCON3 = (1<<19) | (479<<8) | (1);
LCDCON4 = 40;
LCDCON5 = (FORMAT8BPP_565<<11) | (HSYNC_INV<<9) | (VSYNC_INV<<8) | \
(1<<0);
/*
* 设置LCD控制器的地址寄存器LCDSADDR1~3
* 帧内存与视口(view point)完全吻合,
* 图像数据格式如下:
* |----PAGEWIDTH----|
* y/x 0 1 2 479
* 0 rgb rgb rgb ... rgb
* 1 rgb rgb rgb ... rgb
* 1. LCDSADDR1:
* 设置LCDBANK、LCDBASEU
* 2. LCDSADDR2:
* 设置LCDBASEL: 帧缓冲区的结束地址A[21:1]
* 3. LCDSADDR3:
* OFFSIZE等于0,PAGEWIDTH等于(240*2/2)
*/
LCDSADDR1 = ((LCDFRAMEBUFFER>>22)<<21) | LOWER21BITS(LCDFRAMEBUFFER>>1);
LCDSADDR2 = LOWER21BITS((LCDFRAMEBUFFER+ \
272*480*2)>>1);
LCDSADDR3 = (0<<11) | (480*2/2);
/* 禁止临时调色板寄存器 */
TPAL = 0;
fb_base_addr = LCDFRAMEBUFFER;
bpp = 16;
xsize = 480;
ysize = 272;
break;
default:
break;
}
}
/*
* 设置是否输出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
}
/*
* 设置LCD控制器是否输出信号
* 输入参数:
* onoff:
* 0 : 关闭
* 1 : 打开
*/
void Lcd_EnvidOnOff(int onoff)
{
if (onoff == 1)
{
LCDCON1 |= 1; // ENVID ON
/*author:xyc 背光打开,注释有误*/
GPBDAT |= (1<<0); // Power on
}
else
{
LCDCON1 &= 0x3fffe; // ENVID Off
GPBDAT &= ~(1<<0); // Power off
}
}
对于8bpp显示模式需要设置调色板,步骤如下:
①先定义含256个元素的16bpp全局数组;
static const unsigned short DEMO256pal[255]={0x001f,....};
② 初始化调色板( *palette++ = DEMO256pal[i],由表格15-4得出),由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++)
/*
* author:xyc 这里 DEMO256pal[i]数据为2字节短整形,而palette指向整形
* 是这样吧((unsigned short *)palettle)++ = DEMO256pal[i];
* 我理解有误,详见韦东山完全开发手册P220表格13.3
*/
*palette++ = DEMO256pal[i];
LCDCON1 |= 0x01; // re-enable lcd controller
}
画线或清屏时:
DrawLine(0 , 0 , 239, 0 , 0); // 颜色为DEMO256pal[0]
ClearScr(128); // 输出单色图像,颜色为DEMO256pal[128] ,128为索引值
Framebuffer.c:
/*
* 将屏幕清成单色
* 输入参数:
* color: 颜色值
* 对于16BPP: color的格式为0xAARRGGBB (AA = 透明度),
* 需要转换为5:6:5格式
* 对于8BPP: color为调色板中的索引值,
* 其颜色取决于调色板中的数值
*/
void ClearScr(UINT32 color)
{
UINT32 x,y;
for (y = 0; y < ysize; y++)
for (x = 0; x < xsize; x++)
PutPixel(x, y, color);
}
对于8bpp,ClearScr()循环调用PutPixel()函数写480*272个索引到基址为fb_base_addr的framebuffer内存中:
对于16bpp,ClearScr()循环调用PutPixel()函数写480*272个16bit像素颜色值(5:6:5格式)到基址为fb_base_addr的framebuffer内存中:
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;
green = (color >> 10) & 0x3f;
blue = (color >> 3) & 0x1f;
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;
}
}
若case 8这个选项 ,这3行表示将索引写入FrameBuffer内存处,FrameBuffer基地址为 ((UINT8 *)fb_base_addr),LCD发VD[3-7]/VD[10-15]/VD[19-23]视频数据时根据索引index找到调色板内存对应像素颜色值(16位)发个数据线VD
调试时发现:单色清屏时,1帧数据拖影严重,可以打开Icache或者提高CPU频率(HCLK不变)