16.ARM裸板实现LCD显示

 

目录

1.LCD原理 

2.LCD时序

3.BPP的概念

4.初步编程

4.1.建立重要结构体

4.2.创建结构体变量,并赋值

4.3.初始化LCD使用到的引脚

 4.4.设置LCD控制器

4.5.使能和禁用LCD控制器函数

4.6.从结构体变量获取LCD重要参数

4.7.写测试函数

4.8.修改Makefile

4.9.编译下载

5.画点函数实现

6.画圆画线函数

7.LCD输出字符串

源代码


 

1.LCD原理 

1.LCD原理:

16.ARM裸板实现LCD显示_第1张图片

问题1:

想象有一个能发出各种颜色的电子枪,屏幕上的颜色都是他构造出来的,从而引申出一些问题。

  • 电子枪是如何移动的? 

答:有一条CLK时钟线与LCD相连,每发出一次CLK(高低电平),电子枪就移动一个像素。

  • 颜色如何确定? 

答:由连接LCD的三组线:R(Red)、G(Green)、B(Blue)确定。

  • 电子枪如何得知应跳到下一行? 

答:有一条HSYNC信号线与LCD相连,每发出一次脉冲(高低电平),电子枪就跳到下一行。

  • 电子枪如何得知应回到原点? 

答:有一条VSYNC信号线与LCD相连,每发出一次脉冲(高低电平),电子枪就跳到原点。

  • RGB线上的数据从何而来? 

答:内存里面划分一块显存(FrameBuffer),里面存放了要显示的数据,LCD控制器从里面将数据读出来,通过RGB三组线传给电子枪,电子枪再依次打到显示屏上。

  • 前面的信号由谁发给LCD? 

答:有S3C2440里面的LCD控制器来控制发出信号。

备注:

在S3C2440芯片上集成了LCD控制器,只需去控制LCD控制器相关的寄存器就能够控制LCD

  • 以下是对应LCD控制的原理图接口:

16.ARM裸板实现LCD显示_第2张图片

  • 所使用屏幕的型号是:AT043TN24,大小为4.3寸,对应数据手册引脚的描述如下:

16.ARM裸板实现LCD显示_第3张图片

16.ARM裸板实现LCD显示_第4张图片

注意:RGB数据线上的数据从何而来?

内存里面手动划分一块显存(FrameBuffer),里面存放了要显示的数据,LCD控制器从里面将数据读出来,通过RGB三组线传给电子枪,电子枪再依次打到显示屏上。


2.LCD时序

对应AT043TN24型号的LCD的时序图如下,其标出一些重要参数:

16.ARM裸板实现LCD显示_第5张图片

附录图:

16.ARM裸板实现LCD显示_第6张图片

备注:

在这里使用的芯片是S3C2440,如果想要这款芯片能够控制这块LCD,那么S3C2440的LCD控制器所发出的时序必须满足上面的时序才可以。


3.BPP的概念

  • 每个像素占用多少位:称为bbp(bit per piexl)

在上面的LCD芯片手册中可以得知,像素数据线总共有24根,RGB各占用8根、

硬件上LCD中的BBP是确定的,是24根,但是我不一定需要把所有的数据线全部都用,通常使用的是RGB565,也就是说R占据5条,G占据6条,B占据5条

16.ARM裸板实现LCD显示_第7张图片

 

其中原理图,提供了18根线,但是也只用到了16根:

16.ARM裸板实现LCD显示_第8张图片

所以一个像素占据16位,即两个字节。

 


4.初步编程

有了上面的知识之后就可以开始编程了。

4.1.建立重要结构体

一般设置LCD控制器,只需要直接写相应的寄存器就可以了,但是为了简单明了,简化程序,结构体在这里必不可少,这个结构体包含了设置LCD的重要参数:

1.引脚极性:可能HSYNC(行同步信号)在外接的LCD是高电平有效,但是S3C2440的控制器的VSYNC是电平有效,这时就需要把HSYNC的引脚极性设置为反转,以匹配外部控制的需要。

2.时序:我们需要把外界LCD的时序告诉LCD控制器,通过参数,设置LCD控制器的时序。

3.分辨率,以及bpp(每个像素占多少位,以及在framBuffer存放的格式)。

4.frambuffer的地址,在内存可以划分出一块内存区域制定为frambuffer,当需要网LCD写数据时,只需往这块内存区域写数据,LCD控制器就会自动把颜色数据发送到LCD,然后显示在外接的LCD上。

 

  • 结构体如下:
typedef struct lcd_params {
	/*LCD名称*/
        char *name;
	
	/* 引脚极性 */
	pins_polarity pins_pol;
	
	/* 时序 */
	time_sequence time_seq;
	
	/* 分辨率, bpp */
	int xres;
	int yres;
	int bpp;
	
	/* framebuffer的地址 */
	unsigned int fb_base;
}lcd_params, *p_lcd_params;
  •  因为有多个引脚极性可能是需要反转的,因此单独为引脚极性创建一个结构体,如下:
/*创建一个结构体用于定义LCD的极性*/
typedef struct pins_polarity {
	int de;    /* normal: 高电平时可以传输数据 */
	int pwren; /* normal: 高电平有效 */
	int vclk;  /* normal: 在下降沿获取数据 */
	int rgb;   /* normal: 高电平表示1 */
	int hsync; /* normal: 高脉冲 */
	int vsync; /* normal: 高脉冲 */
}pins_polarity, *p_pins_polarity;

为了简单易看,再创建一个枚举类型,来表示正常极性和反转极性,如下:

/*建立一个枚举类型,用于极性的判断*/
enum {
	NORMAL = 0,
	INVERT = 1,
};

/* NORMAL : 正常极性:通过原理图才可知正常极性
 * INVERT : 反转极性
 */

4.2.创建结构体变量,并赋值

在4.1中只是创建了一个包含了lcd各种参数的结构体,并没有创建结构体变量,现在需要创建一个lcd_params类型的结构体变量,创建的变量名为lcd_4_3_params,这个变量包含了共5项,需要对这些成员变量赋值,以便后面设置LCD控制器时,直接读取结构体变量lcd_4_3_params的值就OK了。

第一项:name,此成员变量起识别作用,可随意取值

第二项:pins_pol这个成员变量是pins_polarity类型的,它是一个结构体,保存了有关极性引脚。

之所以需要设置极性的引脚,是因为,S3C2440的LCD控制器,它是可编程的,通过设置可以符合很多不同外接的LCD屏幕,但是为了确保LCD控制器能够控制外接的LCD,那就需要确保我所发出的有效电平(比如低电平),和外接LCD所接的有效电平(必须也为低电平)是一致的。因此S3C2440专门有这样一个寄存器来设置这些默认极性是否需要反转,此寄存器为LCDCON5.

LCDCON5寄存器描述如下:

16.ARM裸板实现LCD显示_第9张图片

 16.ARM裸板实现LCD显示_第10张图片

例子:

通过查看S3C2440的LCD控制器的时序图,下边所标的电平就是LCD控制器所认为的normal 极性

16.ARM裸板实现LCD显示_第11张图片

接着看看外接的LCD屏幕的时序图,看看它需要的是什么时序:

 

16.ARM裸板实现LCD显示_第12张图片

可以看出外接LCD的HSYNCVSYNC他们的有效电平是低电平,所以说LCD控制器的默认normal电平并不能满足外接时序的需要,所以需要设置LCD控制器的HSYNC和VSYNC的极性为反转极性,设置为反转极性,原本高电平为有效电平,现在变成低电平了。

其他引脚极性都是符合外接LCD的,所以直接采用默认的值就可以了。


第三项:time_seq这个成员变量是time_sequence结构体类型的变量,其保存了有关于时序的变量。

外接的LCD时序图如下:

其中有几个重要的参数是:

/* 垂直方向 */
tvp;    /* vysnc脉冲宽度 */
tvb;    /* 上边黑框, Vertical Back porch */
tvf;     /* 下边黑框, Vertical Front porch */

/* 水平方向 */
thp;   /* hsync脉冲宽度 */
thb;   /* 左边黑框, Horizontal Back porch */
thf;    /* 右边黑框, Horizontal Front porch */

/*像素时钟频率*/

vclk;

16.ARM裸板实现LCD显示_第13张图片

 

外接LCD芯片给出了这些时序的时间值,肯定就能找到一张表,它会给出一些这些时间段的最大/最小值,有些会给出建议设置的值,直接按照给出的值进行设置就OK了。

16.ARM裸板实现LCD显示_第14张图片

根据这个表的值,给这个成员变量赋值就可以了,后面会使用这些值,对寄存器进行设置。


第四项:分辨率和bpp

这个LCD型号是AT043TN24,查找相关参数得知分辨率大小为:480*272

16.ARM裸板实现LCD显示_第15张图片

bpp的含义是:每一个像素所占的位数,这款LCD控制器支持三种格式:8bpp,16bpp,24bpp,4bpp,2bpp

而使用不同的bpp,肯定是会对颜色的数据大小产生影响,16bpp比8bpp所占的内存空间大,那么这些颜色在frambuffer存放的内存就需要多一些,而且设置不同的bpp,在frambuffer存放格式是可以自己来设置的。

例子:

16.ARM裸板实现LCD显示_第16张图片

第五项:是frambuffer的起始地址

16.ARM裸板实现LCD显示_第17张图片

在这里指定frambuffer的地址,当设置完毕,LCD控制器会配合时序通过LCDDMA直接从frambuffer取颜色数据(数据的bpp和数据存放格式需要字节设置),发送到VD数据线上(其实这VD数据线就像是能移动并且发出各种颜色的电子枪),最后从屏幕中显示出来。

当我们需要去改变颜色时,只需要改变frambuffer的数据,在相应区域存入不同的颜色数据,LCDDMA自动从frambuffer取数据,显示在LCD屏幕上就OK了。还是比较方便的、

这里有个问题需要注意的就是,我们选定的这款frambuffer区域应该是未实用的,否则,当我们一写好数据颜色,马上就会被改变了。就达不到我们想要显示的效果了。

备注:这款frambuffer的区域的大小是可以计算的,但是前提是需要先知道bpp和数据存放格式,以及分辨率

这些在重要参数结构体中肯定是可以知道的,加入设bpp等于16

分辨率为:480*272 = 131648 像素点

采用16bpp,每个像素点占用16bit,也就是两个字节。

那么这块frambuffer所占的字节数是:263296 字节。

假如设置frambuffer的起始地址是:0x33a00000 那么frambuffer的区域就是从:0x33a00000  -  33a40480

如果为24bpp的话占用的内存会增加。


代码如下:

/*LCD的FramBuffer的基地址*/
#define LCD_FB_BASE 0x33a00000

/*======================================================================================*/

/*定义一个lcd_params的结构体变量,并为其赋值,根据数据手册来定义*/
lcd_params lcd_4_3_params = {
	.name = "lcd_4.3",
	.pins_pol = {
		.de    = NORMAL,	/* normal: 高电平时可以传输数据 */
		.pwren = NORMAL,    /* normal: 高电平有效 */
		.vclk  = NORMAL,	/* normal: 在下降沿获取数据 */
		.rgb   = NORMAL,	/* normal: 高电平表示1 */
		.hsync = INVERT,    /* normal: 高脉冲 */
		.vsync = INVERT, 	/* normal: 高脉冲 */
	},
	.time_seq = {
		/* 垂直方向 */
		.tvp=	10, /* vysnc脉冲宽度 */
		.tvb=	2,  /* 上边黑框, Vertical Back porch */
		.tvf=	2,  /* 下边黑框, Vertical Front porch */

		/* 水平方向 */
		.thp=	41, /* hsync脉冲宽度 */
		.thb=	2,  /* 左边黑框, Horizontal Back porch */
		.thf=	2,  /* 右边黑框, Horizontal Front porch */

		.vclk=	9,  /* MHz */
	},
	.xres = 480,
	.yres = 272,
	.bpp  = 16,  /* 16bpp */
	.fb_base = LCD_FB_BASE,
};

4.3.初始化LCD使用到的引脚

1.背光使用到了GPB0,设置为输出,高电平为使能

2.使用到了数据线VD,VD3- VD7,VD10-VD15,VD19-VD23 ,共16条,应配置为LCD模式

16.ARM裸板实现LCD显示_第18张图片

从原理图看出连接到了,GPC11-GPC15,GPD2-GPD7,GPD11-GPD15

16.ARM裸板实现LCD显示_第19张图片

需要初始化这些引脚为LCD引脚:

16.ARM裸板实现LCD显示_第20张图片

16.ARM裸板实现LCD显示_第21张图片

16.ARM裸板实现LCD显示_第22张图片

 设置GPG4为pwren引脚

16.ARM裸板实现LCD显示_第23张图片

代码如下: 

void jz2440_lcd_pin_init(void)
{
   /*初始化背光引脚:GPB0*/
	GPBCON &= ~0x3;
	GPBCON |=  0x01;
	/*设置LCD引脚*/
	  //为了方便,全部设置为VD模式
	GPCCON = 0xaaaaaaaa;
	GPDCON = 0xaaaaaaaa;
	//pwren引脚
	GPGCON |= (3<<8);
}

 


 4.4.设置LCD控制器

为了程序看起来简单和扩展性,把LCD参数的设置打包成一个函数,这个函数有一个参数是lcd_params的指针类型,根据需要设置的具体LCD参数,使用对应的结构体变量对LCD控制器进行设置,函数的原型如下:

void s3c2440_lcd_controller_init(p_lcd_params plcdparams)

此处总共需要设置7个寄存器:LCDCON1 - LCDCON5 ,LCDSADDR1 - LCDSADDR2

寄存器描述及参数如下所示:

  • LCDCON1寄存器设置

16.ARM裸板实现LCD显示_第24张图片

备注:

1.此处HCLK的值为100M,在计算CLKVAL,必须在知道VCLK(像素时钟频率)的前提下才能设置,在4.2节中知道此处设置的典型值是9M,根据公式:VCLK = HCLK / [(CLKVAL+1) x 2]

得到 CLKVAL = 100 / 9 /2 - 1  约等于4.5,为了方便取值,此处不妨设置CLKVAL = 5M,通过代入计算 VCLK = 8.3 M,也是符合vclk限定范围的(5M - 12M)。

2.屏幕类型采用TFTLCD,因此设置 [6:5] = 0b11

3.而bppmode,表示你现在使用的bpp,这个数值并不是不变的,通过外部传入的参数变化,比如使用16bpp,那么需要设置[4:1] = 0b1100 = c,使用24bpp时设置[4:1] = 0b1101 = d.所以还需要通过lcd_4_3_params结构体变量中bpp成员变量的值来设置。

代码如下:

int clkval = (float)HCLK/plcdparams->time_seq.vclk/2-1+0.5;
	//int clkval = 5;
	int bppmode = plcdparams->bpp == 8  ? 0xb :\
				  plcdparams->bpp == 16 ? 0xc :\
				  0xd;  /* 0xd: 24,32bpp */
	LCDCON1 = (clkval<<8) | (3<<5) | (bppmode<<1) ;
  • LCDCON2,LCDCON3,LCDCON4寄存器设置

垂直方向时序:

16.ARM裸板实现LCD显示_第25张图片

水平方向时序

16.ARM裸板实现LCD显示_第26张图片

16.ARM裸板实现LCD显示_第27张图片

可以看出这三个寄存器是设置LCD控制器时序的。

接着查看LCD控制器的时序描述,肯定可以发现和外接LCD类似的时序,但是名字可能会不同,那没办法,我们需要设置的是LCD控制器去符合外接LCD,所以需要将重要时序一一匹配起来,如下:

外接LCD时序

16.ARM裸板实现LCD显示_第28张图片

查看S3C2440的数据手册,有关于LCD控制器时序框图对比如下:

16.ARM裸板实现LCD显示_第29张图片

代码如下:

/* [31:24] : VBPD    = tvb - 1
	 * [23:14] : LINEVAL = xres - 1
	 * [13:6]  : VFPD    = tvf - 1
	 * [5:0]   : VSPW    = tvp - 1
	 */
	LCDCON2 = 	((plcdparams->time_seq.tvb - 1)<<24) | \
	            ((plcdparams->yres - 1)<<14)         | \
				((plcdparams->time_seq.tvf - 1)<<6)  | \
				((plcdparams->time_seq.tvp - 1)<<0);

	/* [25:19] : HBPD	 = thb - 1
	 * [18:8]  : HOZVAL  = yres- 1
	 * [7:0]   : HFPD	 = thf - 1
	 */
	LCDCON3 =	((plcdparams->time_seq.thb - 1)<<19) | \
				((plcdparams->xres - 1)<<8)		      | \
				((plcdparams->time_seq.thf - 1)<<0);

	/* 
	 * [7:0]   : HSPW	 = thp - 1
	 */
	LCDCON4 =	((plcdparams->time_seq.thp - 1)<<0);
  • LCDCON5寄存器设置

内容:设置引脚极性,设置16bpp数据格式,设置内存中像素存放的格式

16.ARM裸板实现LCD显示_第30张图片

16.ARM裸板实现LCD显示_第31张图片

/* 用来设置引脚极性, 设置16bpp数据格式, 设置内存中象素存放的格式
     * [12] : BPP24BL
	 * [11] : FRM565, 1-565
	 * [10] : INVVCLK, 0 = The video data is fetched at VCLK falling edge
	 * [9]  : HSYNC是否反转
	 * [8]  : VSYNC是否反转
	 * [7]  : INVVD, rgb是否反转
	 * [6]  : INVVDEN
	 * [5]  : INVPWREN
	 * [4]  : INVLEND
	 * [3]  : PWREN, LCD_PWREN output signal enable/disable
	 * [2]  : ENLEND  
	 * [1]  : BSWP
	 * [0]  : HWSWP
	 */
	/*数据存放格式*/
	pixelformat = plcdparams->bpp == 32 ? (0) : \
	             plcdparams->bpp == 16 ? (1) : \
	             2;  /* 8bpp */
	
	LCDCON5 = (plcdparams->pins_pol.vclk<<10) |\
	          (plcdparams->pins_pol.rgb<<7)   |\
	          (plcdparams->pins_pol.hsync<<9) |\
	          (plcdparams->pins_pol.vsync<<8) |\
 			  (plcdparams->pins_pol.de<<6)    |\
			  (plcdparams->pins_pol.pwren<<5) |\
			  (1<<11) | pixelformat;

备注:其中pixelformat数据格式的含义如下:只支持三种数据格式

24bpp

16.ARM裸板实现LCD显示_第32张图片

16bpp 

16.ARM裸板实现LCD显示_第33张图片

8bpp

16.ARM裸板实现LCD显示_第34张图片

  • LCDSADDR1 和LCDSADDR2寄存器的设置 

这两个寄存器设置的是frambuffer的起始地址和结束地址。

LCDSADDR1 寄存器描述:

16.ARM裸板实现LCD显示_第35张图片

LCDBANK需呀填写的是frambuffer地址的 A[30:22],而LCDBASEU需要填写的是frambuffer起始起始地址的A[21:1]

总结起来,就是把frambuffer起始地址的 A[30:1] 直接写入到寄存器LCDSADDR1即可

LCDSADDR2 寄存器描述:

16.ARM裸板实现LCD显示_第36张图片

这个寄存器填写的是frambuffer结束地址的A[21:1]

根据4.2节第三项中,可以知道frambuffer的结束地址如何计算了。

需要知道屏幕分辨率,bpp

那么frambuffer的结束地址就是: frambuffer的起始地址+ x*y *(bpp/8)  

注意:bpp的单位是bit,除以8转换为byte

代码如下:

/* framebuffer地址 */
	/*
	 * [29:21] : LCDBANK, A[30:22] of fb
	 * [20:0]  : LCDBASEU, A[21:1] of fb
	 */
	addr = plcdparams->fb_base & ~(1<<31);
	LCDSADDR1 = (addr >> 1);

	/* 
	 * [20:0] : LCDBASEL, A[21:1] of end addr
	 */
	addr = plcdparams->fb_base + plcdparams->xres*plcdparams->yres*plcdparams->bpp/8;
	addr >>=1;
	addr &= 0x1fffff;
	LCDSADDR2 = addr;//	

4.5.使能和禁用LCD控制器函数

代码如下:

/*使能LCD控制器*/
void s3c2440_lcd_controller_enalbe(void)
{
	/* 背光引脚 : GPB0 */
	GPBDAT |= (1<<0);
	
	/* pwren    : 给LCD提供AVDD  */
	LCDCON5 |= (1<<3);
	
	/* LCDCON1'BIT 0 : 设置LCD控制器是否输出信号 */
	LCDCON1 |= (1<<0);
}
/*======================================================================================*/

/*禁止LCD控制器*/
void s3c2440_lcd_controller_disable(void)
{
	/* 背光引脚 : GPB0 */
	GPBDAT &= ~(1<<0);

	/* pwren	: 给LCD提供AVDD  */
	LCDCON5 &= ~(1<<3);

	/* LCDCON1'BIT 0 : 设置LCD控制器是否输出信号 */
	LCDCON1 &= ~(1<<0);
}

/*======================================================================================*/

4.6.从结构体变量获取LCD重要参数

代码如下:

/*获得结构体LCD参数函数*/
void get_lcd_params(unsigned int *fb_base, int *xres, int *yres, int *bpp)
{
	*fb_base = lcd_4_3_params.fb_base;
	*xres = lcd_4_3_params.xres;
	*yres = lcd_4_3_params.yres;
	*bpp = lcd_4_3_params.bpp;
}

4.7.写测试函数

有了上面的准备工作,从现在开始就显示简单一点了,刚开始写测试代码,为了显得简单一点,让整个屏幕显示出同一种颜色、

如何做呢?

直接往frambuffer填入颜色数值就行了。且设置bpp = 16,那么一个颜色的数据就占用两个字节。

代码如下:

/*LCD测试函数*/
void lcd_test(void)
{
	unsigned int fb_base;
	int xres, yres, bpp;
	int x, y;
	unsigned short *p;

	/*初始化LCD*/
	lcd_init();

	/*获得参数*/
	  //这个函数获取得到的数值是在lcd_test里面使用的
	get_lcd_params(&fb_base,&xres,&yres,&bpp);
	/*判断bpp的数值*/
	if (bpp == 16)
	{
		/* 让LCD输出整屏的红色 */

		/* 565: 0xf800 */

		p = (unsigned short *)fb_base;
		for (x = 0; x < xres; x++)
			for (y = 0; y < yres; y++)
				*p++ = 0xf800;

		/* green */
		p = (unsigned short *)fb_base;
		for (x = 0; x < xres; x++)
			for (y = 0; y < yres; y++)
				*p++ = 0x7e0;

		/* blue */
		p = (unsigned short *)fb_base;
		for (x = 0; x < xres; x++)
			for (y = 0; y < yres; y++)
				*p++ = 0x1f;

		/* black */
		p = (unsigned short *)fb_base;
		for (x = 0; x < xres; x++)
			for (y = 0; y < yres; y++)
				*p++ = 0;
			
	}
	
	
}

接着在主函数中调用这个测试函数,如下:

int main()
{	
	
	while(1)
	{
		/*lcd测试函数*/
		lcd_test();
	}

	return 0;

}

4.8.修改Makefile

在这里增加了lcd.c和lcd.h需要把它添加到编译的项目,如下:

all: start.o sdram_init.o interrupt.o nand_flash.o main.o  led.o uart.o   execption.o  timer.o lib1funcs.o my_printf.o  string_utils.o lcd.o 
	arm-linux-ld -T lcd.lds  $^ libgcc.a -o lcd.elf
	arm-linux-objcopy -O binary -S lcd.elf lcd.bin	
	arm-linux-objdump -D lcd.elf > lcd.dis

%.o : %.c
	arm-linux-gcc -march=armv4 -c -o $@ $<

%.o : %.S
	arm-linux-gcc -march=armv4 -c -o $@ $<
clean:
	rm *.bin *.o *.elf *.dis

 


4.9.编译下载

把代码上传到Linux系统编译,下载lcd.bin文件到开发版,复位之后,屏幕依次红绿蓝刷屏。

备注:此部分代码命令为:LCD显示01


5.画点函数实现

5.1.新建两个函数,drawpoint.c和drawpoint.h

1).编写drawpoint.c函数

在这个文件中需要用到一些,参数比如,分辨率,bpp,以及frambuffer的地址,因此,写一个函数来获取,然后保存在这个文件的全局变量中,以便这个文件调用,如下:


/* 获得LCD参数,static表示对外界这些变量是不可见的*/
static unsigned int fb_base;
static int xres, yres, bpp;

void drawpoint_get_lcd_params(void)
{
	/*获得的参数保存在drawpoint.c的局部变量中,对外界不可见*/
	get_lcd_params(&fb_base, &xres, &yres, &bpp);
}

在这里需要注意一个问题,RGB颜色都是32位的

16.ARM裸板实现LCD显示_第37张图片

但是如果使用16bpp的话,颜色数据才两个字节,因此需要把32位的颜色数据转换为16位,且16bpp时数据组成格式是RGB565

把32bpp的颜色数据转换为16bpp 

函数如下:
 

/* rgb: 0x00RRGGBB */
unsigned short convert32bppto16bpp(unsigned int rgb)
{
	int r = (rgb >> 16)& 0xff;
	int g = (rgb >> 8) & 0xff;
	int b = rgb & 0xff;

	/* rgb565 */
	r = r >> 3;
	g = g >> 2;
	b = b >> 3;

	return ((r<<11) | (g<<5) | (b));
}

接着实现画点函数,这个函数有三个参数,x,y代表坐标,第三个参数,代表这个点的颜色color

代码如下:

/* color : 32bit, 0x00RRGGBB
 *
 */
void fb_drawpoint(int x, int y, unsigned int color)
{
	unsigned char  *pc;  /* 8bpp */
	unsigned short *pw;  /* 16bpp */
	unsigned int   *pdw; /* 32bpp */
	
	unsigned int pixel_base = fb_base + (xres * bpp / 8) * y + x * bpp / 8;

	switch (bpp)
	{
		case 8:
			pc = (unsigned char *) pixel_base;
			*pc = color;
			break;
		case 16:
			pw = (unsigned short *) pixel_base;
			*pw = convert32bppto16bpp(color);
			break;
		case 32:
			pdw = (unsigned int *) pixel_base;
			*pdw = color;
			break;
	}
}

在这里需要清楚画点的原理,从上面可知,画点,其实就是往frambuffer特定的地址,填入颜色数据而已。那如何去计算这个值呢?

有一个LCD的屏幕如下面简图所示:

16.ARM裸板实现LCD显示_第38张图片

这个点如何计算呢?

算出y轴的偏移,即:   先算出一行所占的字节数,然后乘于 y0行  ===》  (xres * bpp /8) *y0,此时的点就是(0,y0)点的颜色数据

加上x轴的偏移,即:(xres * bpp /8) *y0  + x0*bpp/8  ,此时的点就是(x0,y0)的颜色数据

最后加上基地址就是在内存frambuffer对应颜色数据的值了。

还有一点需要注意:决定了每个像素所占字节的大小。


6.画圆画线函数

画圆画线的基础都是画点,圆和线都是由一个个点组成的,只要有了画点的基础,实现这个功能就是算法的问题了。

代码如下:

void draw_circle(int x, int y, int r, int color)  
 {	
	 int a, b, num;  
	 a = 0;  
	 b = r;
	 /*画点之前先获得LCD参数*/
	 drawpoint_get_lcd_params();
	 while(22 * b * b >= r * r) 		 // 1/8圆即可  
	 {	
		 fb_drawpoint(x + a, y - b,color); // 0~1  
		 fb_drawpoint(x - a, y - b,color); // 0~7  
		 fb_drawpoint(x - a, y + b,color); // 4~5  
		 fb_drawpoint(x + a, y + b,color); // 4~3  
   
		 fb_drawpoint(x + b, y + a,color); // 2~3  
		 fb_drawpoint(x + b, y - a,color); // 2~1  
		 fb_drawpoint(x - b, y - a,color); // 6~7  
		 fb_drawpoint(x - b, y + a,color); // 6~5  
		   
		 a++;  
		 num = (a * a + b * b) - r*r;  
		 if(num > 0)  
		 {	
			 b--;  
			 a--;  
		 }	
	 }	
 }	
   
 //-----------画线。参数:起始坐标,终点坐标,颜色--------	
 void draw_line(int x1,int y1,int x2,int y2,int color)	
 {	
	 int dx,dy,e;
	 /*画点之前先获得LCD参数*/
	 drawpoint_get_lcd_params();
	 dx=x2-x1;	 
	 dy=y2-y1;	
	 if(dx>=0)	
	 {	
		 if(dy >= 0) // dy>=0  
		 {	
			 if(dx>=dy) // 1/8 octant  
			 {	
				 e=dy-dx/2;  
				 while(x1<=x2)	
				 {	
					 fb_drawpoint(x1,y1,color);  
					 if(e>0){y1+=1;e-=dx;}	   
					 x1+=1;  
					 e+=dy;  
				 }	
			 }	
			 else		 // 2/8 octant	
			 {	
				 e=dx-dy/2;  
				 while(y1<=y2)	
				 {	
					 fb_drawpoint(x1,y1,color);  
					 if(e>0){x1+=1;e-=dy;}	   
					 y1+=1;  
					 e+=dx;  
				 }	
			 }	
		 }	
		 else			// dy<0  
		 {	
			 dy=-dy;   // dy=abs(dy)  
			 if(dx>=dy) // 8/8 octant  
			 {	
				 e=dy-dx/2;  
				 while(x1<=x2)	
				 {	
					 fb_drawpoint(x1,y1,color);  
					 if(e>0){y1-=1;e-=dx;}	   
					 x1+=1;  
					 e+=dy;  
				 }	
			 }	
			 else		 // 7/8 octant	
			 {	
				 e=dx-dy/2;  
				 while(y1>=y2)	
				 {	
					 fb_drawpoint(x1,y1,color);  
					 if(e>0){x1+=1;e-=dy;}	   
					 y1-=1;  
					 e+=dx;  
				 }	
			 }	
		 }	   
	 }	
	 else //dx<0  
	 {	
		 dx=-dx;	 //dx=abs(dx)  
		 if(dy >= 0) // dy>=0  
		 {	
			 if(dx>=dy) // 4/8 octant  
			 {	
				 e=dy-dx/2;  
				 while(x1>=x2)	
				 {	
					 fb_drawpoint(x1,y1,color);  
					 if(e>0){y1+=1;e-=dx;}	   
					 x1-=1;  
					 e+=dy;  
				 }	
			 }	
			 else		 // 3/8 octant	
			 {	
				 e=dx-dy/2;  
				 while(y1<=y2)	
				 {	
					 fb_drawpoint(x1,y1,color);  
					 if(e>0){x1-=1;e-=dy;}	   
					 y1+=1;  
					 e+=dx;  
				 }	
			 }	
		 }	
		 else			// dy<0  
		 {	
			 dy=-dy;   // dy=abs(dy)  
			 if(dx>=dy) // 5/8 octant  
			 {	
				 e=dy-dx/2;  
				 while(x1>=x2)	
				 {	
					 fb_drawpoint(x1,y1,color);  
					 if(e>0){y1-=1;e-=dx;}	   
					 x1-=1;  
					 e+=dy;  
				 }	
			 }	
			 else		 // 6/8 octant	
			 {	
				 e=dx-dy/2;  
				 while(y1>=y2)	
				 {	
					 fb_drawpoint(x1,y1,color);  
					 if(e>0){x1-=1;e-=dy;}	   
					 y1-=1;  
					 e+=dx;  
				 }	
			 }	
		 }	   
	 }	
 }	

修改lcd_test函数,调用画点画圆函数,如下:

/*LCD测试函数*/
void lcd_test(void)
{
	unsigned int fb_base;
	int xres, yres, bpp;
	int x, y;
	unsigned short *p;

	/*初始化LCD*/
	lcd_init();

	/*获得参数*/
	  //这个函数获取得到的数值是在lcd_test里面使用的
	get_lcd_params(&fb_base,&xres,&yres,&bpp);
	  //这个函数中获取的值是在画点函数中用的,
	  //fb_get_lcd_params();
	/*获得字体需要用的LCD参数*/
	//font_init();
	/*判断bpp的数值*/
	if (bpp == 16)
	{
		/* 让LCD输出整屏的红色 */

		/* 565: 0xf800 */

		p = (unsigned short *)fb_base;
		for (x = 0; x < xres; x++)
			for (y = 0; y < yres; y++)
				*p++ = 0xf800;

		/* green */
		p = (unsigned short *)fb_base;
		for (x = 0; x < xres; x++)
			for (y = 0; y < yres; y++)
				*p++ = 0x7e0;

		/* blue */
		p = (unsigned short *)fb_base;
		for (x = 0; x < xres; x++)
			for (y = 0; y < yres; y++)
				*p++ = 0x1f;

		/* black */
		p = (unsigned short *)fb_base;
		for (x = 0; x < xres; x++)
			for (y = 0; y < yres; y++)
				*p++ = 0;
			
	}
	delay(1000000);
	/* 画线 */
	draw_line(0, 0, xres - 1, 0, 0xff0000);
	draw_line(xres - 1, 0, xres - 1, yres - 1, 0xffff00);
	draw_line(0, yres - 1, xres - 1, yres - 1, 0xff00aa);
	draw_line(0, 0, 0, yres - 1, 0xff00ef);
	draw_line(0, 0, xres - 1, yres - 1, 0xff4500);
	draw_line(xres - 1, 0, 0, yres - 1, 0xff0780);

	delay(1000000);

	/* 画圆 */
	draw_circle(xres/2, yres/2, yres/4, 0xff00);
	delay(1000000);
	

	
}

下载烧录,就可以看到画线和画圆的效果了。

备注:这个部分的代码命名为:LCD画线画圆


7.LCD输出字符串

字符串的显示的基础也是画点,字符都是由一个个点组成了,所以在输入字符之前,我们首先需要知道字体多大。

在这里使用 8 * 16 为例(宽为8,长为16,一个字符占用16个字节的数据),那么一个字符就需要占据128像素。

查看一下点阵文件中A字符的表示方法:

总共有16个字节数据,从左至右,从上至下,如果哪个字节的位为1,则画点,A字符由16字节组成的点阵字符

16.ARM裸板实现LCD显示_第39张图片

同理,B的点阵字符集如下:

16.ARM裸板实现LCD显示_第40张图片

由这样的一个一个字符的点阵集组成一个数组,就构成了LCD输出字符串的基础

16.ARM裸板实现LCD显示_第41张图片

备注:这些点阵字符在fontdata_8x16[ ] 数组中的排序方式是以ASCII码的方式。 

如果想取出字符‘A’  点阵字符集,直接使用  fontdata_8x16[ A * 16 ]  

字符A的ascii码是:65


 有了这个点阵数组,和这些基础知识就可以开始写程序了。

先实现一个最简单的程序,在指定的坐标显示指定的单个字符。

1)、新建文件font.c和font.h文件

写font.c文件,实现输出字符函数

思路:

比如现在想打印出 'A'字符,那我们必须在刚才那个点阵数组中,把这个字符的16字节点阵数据取出来,然后读取这16字节数据的每一个点,根据数据来决定是否画点。

代码如下

/* 根据字母的点阵在LCD上描画文字 */

void fb_print_char(int x, int y, char c, unsigned int color)
{
	int i, j;
	
	/* 根据c的ascii码在fontdata_8x16中得到点阵数据的首地址 */
	unsigned char *dots = &fontdata_8x16[c * 16];

	unsigned char data;
	int bit;

	/* 根据点阵来设置对应象素的颜色 */
	for (j = y; j < y+16; j++)   //总共需要写16个字节,循环16次
	{
		//先取出首字节数据,然后取出下一字节数据,
		data = *dots++;
		//先写最高位
		bit = 7;
		for (i = x; i < x+8; i++)//先第一行数据,循环8次
		
			/* 点阵字节为1,则画点,否则不画点 */
			if (data & (1<
  • 输出字符串函数

字符串的输出的基础是单个字符的输出。

思路:把需要输出的字符串保存在一个数组中,然后依次读取出这个字符串数组的字符,如果不为空则打印出来,碰到 "\n\r"就换行并回到下一行的起始位置,继续打印数据。

代码如下:

void fb_print_string(int x, int y, char* str, unsigned int color)
{
	int i = 0, j;
	
	while (str[i])  //但str[i] 不为空
	{  
		if (str[i] == '\n')
			y = y+16;
		else if (str[i] == '\r')
			x = 0;

		else
		{
			fb_print_char(x, y, str[i], color);  //在指定的位置输出一个字符
			x = x+8;    //一个字符的宽度为8,没输出一个字符x的值  +8
			if (x >= xres) /* 当x轴方向输出的文字已经满了,到下一行的开始位置,继续输出,换行 */
			{
				x = 0;      /*x轴回到原点,y轴的坐标加上16,因为这个字体的大小是: 8*16 
							 * 例如第一个字体开始位置是 0,0 那么下一行的起始坐标是:0,16
				              1*/
				y = y+16;
			}
		}
		i++;  /*继续读取str数组下一个字符*/
	}
}

修改lcd_test函数,调用字符串输出函数,如下:

/*LCD测试函数*/
void lcd_test(void)
{
	unsigned int fb_base;
	int xres, yres, bpp;
	int x, y;
	unsigned short *p;

	/*初始化LCD*/
	lcd_init();

	/*获得参数*/
	  //这个函数获取得到的数值是在lcd_test里面使用的
	get_lcd_params(&fb_base,&xres,&yres,&bpp);
	  //这个函数中获取的值是在画点函数中用的,
	  //fb_get_lcd_params();
	/*获得字体需要用的LCD参数*/
	font_init();
	/*判断bpp的数值*/
	if (bpp == 16)
	{
		/* 让LCD输出整屏的红色 */

		/* 565: 0xf800 */

		p = (unsigned short *)fb_base;
		for (x = 0; x < xres; x++)
			for (y = 0; y < yres; y++)
				*p++ = 0xf800;

		/* green */
		p = (unsigned short *)fb_base;
		for (x = 0; x < xres; x++)
			for (y = 0; y < yres; y++)
				*p++ = 0x7e0;

		/* blue */
		p = (unsigned short *)fb_base;
		for (x = 0; x < xres; x++)
			for (y = 0; y < yres; y++)
				*p++ = 0x1f;

		/* black */
		p = (unsigned short *)fb_base;
		for (x = 0; x < xres; x++)
			for (y = 0; y < yres; y++)
				*p++ = 0;
			
	}
	delay(1000000);
	/* 画线 */
	draw_line(0, 0, xres - 1, 0, 0xff0000);
	draw_line(xres - 1, 0, xres - 1, yres - 1, 0xffff00);
	draw_line(0, yres - 1, xres - 1, yres - 1, 0xff00aa);
	draw_line(0, 0, 0, yres - 1, 0xff00ef);
	draw_line(0, 0, xres - 1, yres - 1, 0xff4500);
	draw_line(xres - 1, 0, 0, yres - 1, 0xff0780);

	delay(1000000);

	/* 画圆 */
	draw_circle(xres/2, yres/2, yres/4, 0xff00);
	delay(1000000);
	/* 输出文字 */
	fb_print_string(10, 10, "hello yuan\n\rHello world!", 0xff00);
	delay(1000000);



	
}

编译,下载,运行。

运行结果:屏幕在坐标 (10,10)的位置输出   :

hello yuan

Hello world!

 备注:此处代码命名为:LCD输出字符串


源代码

LCD显示01:https://download.csdn.net/download/qq_36243942/10961769

LCD画线画圆:https://download.csdn.net/download/qq_36243942/10961819

LCD输出字符串:https://download.csdn.net/download/qq_36243942/10963395

你可能感兴趣的:(Linux裸机开发学习笔记)