13.2 TFT LCD显示实例
13.2.1 程序设计
本实例的目的是从串口输出一个菜单,从中选择各种方法进行测试,比如画线、
画圆、显示单色、使用调色板等。
13.2.2 代码详解
本实例源码在/work/hardware/lcd目录下,与LCD相关的代码有3个文件:lcddrv.c、
framebuffer.c和lcdlib.c(及相应的头文件)。
(1)lcddrv.c封装了对LCD控制器、调色板的访问函数,可以设置LCD的显示模式、
开启/关闭LCD、设置调色板等。
(2)framebuffer.c直接操作帧缓冲区,实现画点、画线、画同心圆、清屏等函数。
(3)lcdlib.c调用前两个文件提供的函数在LCD上进行各种操作。
程序的结构如图13.8所示。
1.main.c
main.c的代码很简单,其主体如下:
1
c = getc();
2
printf("%c\n\r", c);
3
switch(c)
4
{
5
case '1':
6
{
7
Test_Lcd_Tft_8Bit_240320();
8
break;
9
}
10
11
case '2':
12
{
13
Test_Lcd_Tft_16Bit_240320();
14
break;
15
}
16
17
case '3':
18
{
19
Test_Lcd_Tft_8Bit_640480();
20
break;
21
}
22
23
case '4':
24
{
25
Test_Lcd_Tft_16Bit_640480();
26
break;
27
}
28
}
它根据串口的输入选择是以哪种显示模式操作LCD,所调用的4个函数都在lcdlib.c中实现。
2.lcdlib.c
8BPP模式将用到调色板,其操作比16BPP模式稍复杂,但大部分仍相似。下面以
Test_Lcd_Tft_8Bit_240320为例进行说明。
1
行号
2
11行/*
3
12行 *以240x320、8BPP的显示模式测试TFT LCD
4
13行 */
5
14行void Test_Lcd_Tft_8Bit_240320(void)
6
15行{
7
16行 Lcd_Port_Init(); //设置LCD引脚
8
17行 Tft_Lcd_Init(MODE_TFT_8BIT_240320); //初始化LCD控制器
9
18行 Lcd_PowerEnable(0, 1); //设置LCD_PWREN有效,它用于打开LCD的电源
10
19行 Lcd_EnvidOnOff(1); //使能LCD控制器输出信号
第16行设置所涉及的GPIO引脚用于LCD功能。
第17行调用Tft_Lcd_Init函数初始化LCD控制器,即设置各个控制信号的时间特性、
LCD显示模式、帧缓冲区的地址等,它是lcddrv.c中最复杂的函数,在后面会详细分析这个
函数。
进行第16、17行的初始化之后,只要打开lcd,帧缓冲区中的数据就会被LCD控制器
自动地发送到LCD上去显示。打开操作由18、19行完成。
第18行发出LCD_PWREN信号。对于有电源开关控制引脚的LCD,可以使用其打开过关
闭LCD。LCD_PWREN信号的极性可以设置。
第19行使能LCD控制器输出信号。这时,帧缓冲区中数据就开始在LCD上显示出来了。
接下来就是按照设定的流程进行各类操作了,比如画线、清屏等,代码如下:
1
Lcd_Palette8Bit_Init(); //初始化调色板
2
ClearScr(0x0); //清屏
3
printf("[TFT 64K COLOR(16bpp) LCD TEST]\n");
4
5
printf("1. Press any key to draw line\n");
6
get();
7
DrawLine(0 , 0 , 239, 0 , 0); //颜色为DEMO256pal[0]
8
DrawLine(0 , 0 , 0 , 319, 1); //颜色为DEM0256pal[1]
9
DrawLine(239, 0 , 239, 319, 2); //...
10
DrawLine(0 , 319, 239, 319, 4);
11
DrawLine(0 , 0 , 239, 319, 8);
12
DrawLine(239, 0 , 0 , 319, 16);
13
DrawLine(120, 0 , 120, 319, 32);
14
DrawLine(0 , 160, 239, 160, 64);
15
16
printf("2. Press any key to draw circles\n");
17
getc();
18
Mire();
19
20
printf("3. Press any key to fill the screem with one color\n");
21
getc();
22
ClearScr(128); //输出单色图像,颜色值等于DEMO256pal[128]
23
24
printf("4. Press any key to fill the screem by temporary palette\n");
25
getc();
26
ClearScrWithTmpPlt(0x0000ff); //输出单色图像,颜色为蓝色
27
28
printf("5. Press any key to fill the screem by palette\n");
29
getc();
30
DisableTmpPlt(); //关闭临时调色板寄存器
31
ChangePalette(0xffff00); //改变整个调色板为黄色,输出单色图像
32
33
printf("6. Press any key to stop the testing\n");
34
getc();
35
Lcd_EnvidOnOff(0);
36
}
将上面的函数分成3类:
(1)清屏函数ClearScr、画线函数DrawLine,都是通过framebuffer.c中的PutPixel函数
来设置帧缓冲区的数据,以像素为单位修改颜色来实现的。
(2)Lcd_Palette8Bit_Init函数:设置调色板,ChangePalette函数:通过设置调色板来
实现
清屏功能,不涉及帧缓冲区,它在lcddrv.c中实现。
(3)ClearScrWithTmpPlt函数:通过临时调色板寄存器来快速地输出单色的图像,也
不涉及帧缓冲区,它在lcddrv.c中实现
lcddrv.c、framebuffer.c文件中各个函数才是本实例的关键。可以认为lcddrv.c是对操作
各寄存器的封装,framebuffer.c则是对操作图像数据的封装。先看lcddrv.c文件
3.lcddrv.c
这个文件中函数的重点在于Tft_Lcd_Init、Lcd_Palette8Bit_Init。
(1)Lcd_Port_Init函数。
设置所涉及的GPIO引脚用于LCD功能。
(2)Tft_Lcd_Init函数。
初始化LCD控制器,即设置各个控制信号的时间特性、LCD的显示模式、帧缓冲区的地址等。
首先是对5个控制寄存器LCDCON1~5的设置,代码如下:
1
/*
2
*初始化LCD控制器
3
*输入参数:
4
*type:显示模式
5
* MODE_TFT_8BIT_240320:240*320 8bpp的TFT LCD
6
* MODE_TFT_16BIT_240320:240*320 16bpp的TFT LCD
7
* MODE_TFT_8BIT_640480:640*480 8bpp的TFT LCD
8
* MODE_TFT_16BIT_640480:640*480 16bpp的TFT LCD
9
*/
10
void Tft_Lcd_Init(int type)
11
{
12
switch(type)
13
{
14
case MODE_TFT_8BIT_240320:
15
/*
16
*设置LCD控制器的控制寄存器LCDCON1~5
17
*1.LCDCON1
18
* 设置VCLK的频率:VCLK(Hz) = HCLK/[(CLKVAL+1) x 2]
19
* 选择LCD类型:TFT LCD
20
* 设置显示模式:8BPP
21
* 先禁止LCD信号输出
22
*2.LCDCON2/3/4
23
* 设置控制信号的时间参数
24
* 设置分辨率,即行数和列数
25
*现在,可以根据公式算出显示器的分辨率
26
*当HCLK = 100MHz时,
27
*Frame Rate = 1/[{(VSPW+1) + (VBPD+1) + (LIINEVAL+1) + (VFPD+1)} x
28
* {(HSPW+1) + (HBPD+1) + (HFPD+1) + (HOZVAL+1)} x
29
* {(2x(CLKVAL+1)/(HCLK))}]
30
* = 60Hz
31
*3.LCDCON5
32
* 设置显示模式为8BPP时,调色板中的数据格式为5:6:5
33
* 设置HSYNC、VSYNC脉冲的极性(这需要参考具体的LCD的接口信号):反转字节交换使能
34
*/
35
LCDCON1 = (CLKVAL_TFT_240320 << 8) | (LCDTYPE_TFT << 5) | \
36
(BPPMODE_8BPP << 1) | (ENVID_DISABLE << 0);
37
LCDCON2 = (VBPD_240320 << 24) | (LINEVAL_TFT_240320 << 14) | \
38
(VFPD_240320 << 6) | (VSPW_240320);
39
LCDCON3 = (HBPD_240320 << 19) | (HOZVAL_TFT_240320 << 8) | (HFPD_240320);
40
LCDCON4 = HSPW_240320;
41
LCDCON5 = (FORMAT8BPP_565 << 11) | (HSYNC_INV << 9) | (VSYNC_INV << 8) | \
42
(BSWP << 1);
时间参数VSPW、VBPD、VFPD、HSPW、HBPD、HFPD、CLKVAL的设置
可以
从LCD数据手册了解到,或使用经验值,或自行调整,并根据上面的公式确认显示频
率在60Hz左右或之上。
接下来是地址寄存器LCDSADDR1~3的设置,请参考图13.7帧内存与视图的位置关
系。在本程序中,帧内存与视图吻合,即图中的OFFSIZE为0,LCDBANK、LCDBASEU
指向同一个地址(它们是同一个地址的不同位)。
需要注意的是,8BPP的显示模式要用到调色板,帧缓冲区中的数据不是颜色值,而
是调色板中的索引值,真正的颜色值在调色板中。
代码如下:
1
行号
2
78行 /*
3
79行 *设置LCD控制器的地址寄存器:LCDSADDR1~3
4
80行 *帧内存与视口(view point)完全吻合
5
81行 *图像数据格式如下(8BPP时,帧缓冲区中的数据为调色板中的索引值):
6
82行 * |--------- PAGEWIDTH ----------|
7
83行 * y/x 0 1 2 239
8
84行 * 0 idx idx idx ... idx
9
85行 * 1 idx idx idx ... idx
10
86行 *1.LCDSADDR1
11
87行 * 设置LCDBANK、LCDBASEU
12
88行 *2.LCDSADDR2
13
89行 * 设置LCDBASEL:帧缓冲区的结束地址A[21:1]
14
90行 *3.LCDSADDR3
15
91行 * OFFSIZE等于0,PAGEWIDTH等于(240/2)
16
92行 */
17
93行 LCDSADDR1 = ((LCDFRAMEBUFFER >> 22) << 21) | LOWER21BITS (LCDFRAMEBUFFER >> 1);
18
94行 LCDSADDR2 = LOWER21BITS((LCDFRAMEBUFFER+ \
19
95行 (LINEVAL_TFT_240320 + 1) x (HOZVAL_TFT_240320 + 1) x 1) >> 1);
20
96行 LCDSADDR3 = (0 << 11) | (LCD_XSIZE_TFT_240320/2);
21
97行
第93行将帧缓冲区的开始地址写入LCDSADDR1寄存器。
第94行先计算帧缓冲区的结束地址,再取其位[21:1]存入LCDSADDR2中。这个地址值
在本实例中即是“LCDFRAMEBUFFER+320x240x1”,其中的“x1”表示在8BPP中一个像素
使用1个字节表示(对于16BPP,就是“x2”)。
在设置寄存器的最后,禁止临时调色板寄存器,现在还没用到它。
1
行号
2
98行 /*禁止临时调色板寄存器*/
3
99行 TPAL = 0;
4
100行
最后,将显示模式的主要参数记录下来,在framebuffer.c中需要用到。
1
101行 fb_base_addr = LCDFRAMEBUFFER;
2
102行 bpp = 8;
3
103行 xsize = 240;
4
104行 ysize = 320;
5
105行
其他显示模式的寄存器设置非常相似,不再赘述。
需要说明的是,显示模式为8BPP时,LCDCON5中BSWAP位设为1,表示“字节交换
使能”,这时帧缓冲区中的数据与屏幕上的像素位置关系如图13.6所示;
显示模式为16BPP时,LCDCON5中HWSWAP位设为1,表示“半字交换使能”,这时
帧缓冲区中的数据与屏幕上的像素位置关系如图13.5所示。它们都是“低地址的数据”对
应“位置靠前”的像素。
(3)Lcd_Palette8Bit_Init函数。
设置调色板上的数据:调色板大小为256x16,而8BPP模式中每个像素的索引值占据8
位,刚好有256个索引值。代码如下:
1
行号
2
296行 /*
3
297行 *设置调色板
4
298行 */
5
299行 void Lcd_Palette8Bit_Init(void)
6
300行 {
7
301行 int i;
8
302行 volatile unsigned int *palette;
9
303行
10
304行 LCDCON5 |= (FORMAT8BPP_565 << 11); //设置调色板中数据格式为:5:6:5
11
305行
12
306行 palette = (volatile unsigned int *)PALETTE;
13
307行 for(i = 0; i < 256; i++)
14
308行 *palette++ = DEMO256pal[i];
15
309行 }
16
310行
调色板中用16BPP的格式表示颜色。
第307、308行将数组DEMO256pal中数据写入调色板。这个数组中的数据没有
什么特别之处,读者可以自行构造。
(4)ChangePalette函数。
以给定的颜色值填充整个调色板,代码如下:
1
行号
2
311行 /*
3
312行 *改变调色板为一种颜色
4
313行 *输入参数:
5
314行 * color:颜色值,格式为0xRRGGBB
6
315行 */
7
316行 void ChangePalette(UINT32 color)
8
317行 {
9
318行 int i;
10
319行 unsigned char red, green, blue;
11
320行 UINT32 *palette;
12
321行
13
322行 palette = (UINT32 *)PALETTE;
14
323行 for(i = 0; i < 256; i++)
15
324行 {
16
325行 red = (color >> 19) & 0xff;
17
326行 green = (color >> 10) & 0xff;
18
327行 blue = (color >> 3) & 0xff;
19
328行 color = (red << 11) | (green << 5) | blue; //格式:5:6:5
20
329行
21
330行 while((LCDCON5 >> 16) == 2); //等待直到VSTATUS不为“有效”
22
331行 *palette++ = color;
23
332行 }
24
333行 }
25
334行
第330行检测当前VSYNC信号的状态,如果它处于有效的状态,则等待。前面说过,
读写调色板时,VSTATUS、HSTATUS不能处于有效状态。这里当VSTATUS不是“有效”
状态时,HSTATUS也不可能是“有效”状态。
(5)Lcd_PowerEnable函数。
用于控制是否发出LCD_PWREN信号。对于有电源开关控制引脚的LCD,可以使用
LCD_PWREN来打开或关闭LCD。LCD_PWREN信号的极性可以设置。代码如下:
1
/*
2
*设置是否输出LCD电源开关信号LCD_PWREN
3
*输入参数:
4
* invpwren:0表示LCD_PWREN有效时为正常极性
5
* 1表示................反转极性
6
* pwren :0表示LCD_PWREN输出有效
7
* 1表示LCD_PWREN输出无效
8
*/
9
void Lcd_PowerEnable(int invpwren, int pwren)
10
{
11
GPGCON = (GPGCON & (~(3 << 8))) | (3 << 8); //GPG4用于LCD_PWREN
12
GPGUP = (GPGUP & (~(1 << 4))) | (1 << 4); //禁止内部上拉
13
14
LCDCON5 = (LCDCON5 & (~(1 << 5))) | (invpwren << 5); //设置LCD_PWREN的极性:正常/反转
15
LCDCON5 = (LCDCON5 & (~(1 << 3))) | (pwren << 3); //设置是否输出LCD_PWREN
16
}
(6)Lcd_EnvidOnOff函数
用于控制是否使能LCD控制器输出各个LCD信号,当设置如控制寄存器、地址寄存器
之后,即可调用此函数输出各个LCD信号,这样,帧缓冲区中的数据即发送给LCD。代码如下:
1
/*
2
*设置LCD控制器是否输出信号
3
*输入参数:
4
*onoff:
5
* 0:关闭
6
* 1:打开
7
*/
8
void Lcd_EnvidOnOff(int onoff)
9
{
10
if(onoff == 1)
11
LCDCON1 |= 1; //ENVID ON
12
else
13
LCDCON1 &= 0x3fffe; //ENVID OFF
14
}
(7)ClearScrWithTmpPlt、DisableTmpPlt函数。
参考13.13TPAL寄存器格式,ClearScrWithTmpPlt函数设置颜色值并使能TPAL寄
存器,这使得LCD上显示单一颜色图像。DisableTmpPlt函数停止TPAL寄存器的功能,
继续输出帧缓冲区的图像。它们的代码如下:
1
/*
2
*使用临时调色板寄存器输出单色图像
3
*输入参数:
4
* color:颜色值,格式为0xRRGGBB
5
*/
6
void ClearScrWithTmpPlt(UINT32 color)
7
{
8
TPAL = (1 << 24) | ((color & 0xffffff) << 0);
9
}
10
11
/*
12
*停止使用临时调色板寄存器
13
*/
14
void DisableTmpPlt(void)
15
{
16
TPAL = 0;
17
}
4.framebuffer.c
此文件有4个函数:画点PutPixel、画线DrawLine、绘制同心圆Mire、清屏ClearScr,
后3个函数都是基于PutPixel函数实现的。画点函数时framebuffer.c文件的核心,它在
帧缓冲区中找到给定坐标的像素的内存,然后修改它的值,代码如下:
1
行号
2
8行 extern unsigned int fb_base_addr;
3
9行 extern unsigned int bpp;
4
10行 extern unsigned int xsize;
5
11行 extern unsigned int ysize;
6
12行
7
13行 /*
8
14行 *画点
9
15行 *输入参数:
10
16行 * x、y:像素坐标
11
17行 * color:颜色值
12
18行 * 对于16BPP:color的格式为0xAARRGGBB(AA = 透明度),
13
19行 * 需要转换为5:6:5格式
14
20行 * 对于8BPP:color为调色板中索引值,
15
21行 * 其颜色取决于调色板中的数值
16
22行 */
17
23行 void PutPixel(UINT32 x, UINT32 y, UINT32 color)
18
24行 {
19
25行 UINT8 red, green, blue;
20
26行
21
27行 switch(bpp){
22
28行 case 16:
23
29行 {
24
30行 UINT16 *addr = (UINT16 *)fb_base_addr + (y * xsize + x);
25
31行 red = (color >> 19) & 0xff;
26
32行 green = (color >> 10) & 0xff;
27
33行 blue = (color >> 3) & 0xff;
28
34行 color = (red << 11) | (green << 5) | blue; //格式:5:6:5
29
35行 *addr = (UINT16)color;
30
36行 break;
31
37行 }
32
38行
33
39行 case 8:
34
40行 {
35
41行 UINT8 *addr = (UINT8 *)fb_base_addr + (y * xsize + x);
36
42行 *addr = (UINT8)color;
37
43行 break;
38
44行 }
39
45行
40
46行 default:
41
47行 break;
42
48行 }
43
49行 }
44
50行
第8~11行的4个变量在lcddrv.c中的Tft_Lcd_Init函数中设置,PutPixel函数根据它们
来确定给定坐标的像素在帧缓冲区中的地址。
对于16BPP模式,每个像素占2字节;对于8BPP模式,每个像素占1字节。
对于16BPP模式,第31~34行从0xAARRGGBB格式的color变量中,提取8位红色值
的高5位、8位绿色值的高6位、8位蓝色值的高5位组成5:6:5格式的16BPP颜色值。
最后,第35、42行将颜色值(对于8BPP模式,为调色板的索引值)写入帧缓冲区中,
这样,下一次显示时,新颜色即可显示出来。
13.2.3 实例测试
本程序在main函数中通过串口输出一个菜单,用于选择LCD的显示模式进行测试。
实验步骤如下:
(1)使用串口连接开发板和PC,打开PC上串口工具并设置为115200、8N1.
(2)在LCD目录下执行make命令生成lcd可执行程序,烧入NAND Flash后运行。
(3)在PC串口工具上,可以看到如下菜单:
#### Test TFT LCD ####
[1] TFT240320 8Bit
[2] TFT240320 16Bit
[3] TFT640480 8Bit
[4] TFT640480 16Bit
Enter your selection:
(4)可以输入1、2、3或4,然后按照提示输入任意键可一步一步地观察到LCD中图像
的变化。
(5)最后又会出现第(3)步骤的菜单,可以再次选择。
附:代码:
链接: https://pan.baidu.com/s/1kV24a9L 密码: tfab