欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

s5pv210——LCD的原理和实战

程序员文章站 2022-07-14 10:36:27
...

以下内容源于朱友鹏《物联网大讲堂》课程的学习,以及博客http://www.cnblogs.com/biaohc/p/6286946.html的学习,如有侵权,请告知删除。



s5pv210——LCD的原理和实战

一、S5PV210的LCD控制器

1、FIMD结构框图(P1162)

s5pv210——LCD的原理和实战

  • 210的LCD控制器叫FIMD;
  • FIMD是210内部和图像处理相关的一些部件,在摄像头等和图像处理有关的部分都可以有关联。
  • FIMD在内部与AHB总线等相连接,在外部提供RGB接口、I80接口、YUV接口与外部相连接,我们实际使用的是RGB接口。

2、虚拟屏幕叠加(数据手册P1194)

(1)虚拟屏幕

  • 屏幕显示的场景,实际是很多个屏幕显示叠加在一起的效果(譬如新闻图像、电视台台标、下方飘动的字幕新闻);
  • S5PV210的LCD控制器中有5个虚拟屏幕Window0到Window4,虚拟屏幕不存在于真实而存在于内存中。
  • LCD显示时,显示对应的内存中的显存区域的数值。
  • 虚拟屏幕其实就是一个内存中的显存区域,有几个显存区域就有几个虚拟屏幕。
  • 这些虚拟屏幕都被映射到一个真实的显示屏上面,实际效果是这几个虚拟屏幕的显示内容的叠加。
  • 叠加时,上面一层会覆盖下面一层,因此要注意谁在前谁在后,通过相关寄存器进行设置。
(2)使用虚拟屏幕,而非整个LCD只使用一个显存

  • 第一,可以保证不污染源图像,方便程序处理;
  • 第二,可以减少屏幕刷新,提高显示效率,减少CPU工作量。

3、虚拟显示(数据手册P1206)

s5pv210——LCD的原理和实战

(1)解决如何实现在小分辨率的屏幕上(真实地,即不允许修改分辨路)显示大分辨率的图像?

(2)按照以前的思想,当需要在屏幕上显示不同图像时,需要对整个显存区域进行刷新。

  • 即使是显示同一幅图像的不同区域(比如只需要屏幕显示移动一点点),整个屏幕对应的显存空间也需要整个重新刷新,工作量和完全重新显示一幅图像是一样的。
  • 这使得CPU刷新屏幕的工作量很大,效率很低。

(3)如何能够在显示一个大图片的不同区域时,让CPU刷新屏幕工作量减少?

  • 方法是,虚拟显示。
  • 在内存中建立显示缓存时,建立一个很大的区域,直接将大图像全部一次性加载入显示缓存区(这样就可以只加载一次);
  • LCD选择其中的一部分区域作为有效的显示区域;
  • 通过移动有效显示区域就可以显示大图像的不同区域了。

4、主要寄存器简介

(1)DISPAY_CONTROL寄存器:设置为10或11,即RGB模式可行。

s5pv210——LCD的原理和实战

(2)VIDCON0寄存器(Video Main Control 0 Register)

s5pv210——LCD的原理和实战

s5pv210——LCD的原理和实战

s5pv210——LCD的原理和实战

  • bit0,bit1:为使能控制信号都使能;
  • bit2:选择时钟源,选HCLK,连的是HCLC_DSYS 为166MHz;
  • bit4:开启分频;
  • bit13-6:设置时钟大小,时钟频率要小于控制器的最大时钟,也要小于LCD驱动器的最大时钟。
  • bit18:设置RGB数据传输为并行还是串行,因为有24根数据线所以为并行;
  • bit28-26:选择为RGB模式。
(3)VIDCON1寄存器(Video Main Control 1 Register)

s5pv210——LCD的原理和实战

  • bit5,bit6:设置HSYNC和VSYNC的极性,如果LCD的高低电平脉冲是相同的话,则Normal,如果极性相反则Invert。
(3)VIDTCONn(n=0,1,2,3)寄存器:设置时序,根据LCD数据手册中的时序来设置。

s5pv210——LCD的原理和实战

s5pv210——LCD的原理和实战

s5pv210——LCD的原理和实战

(4)WINCONn(n=0~4)寄存器

s5pv210——LCD的原理和实战s5pv210——LCD的原理和实战s5pv210——LCD的原理和实战s5pv210——LCD的原理和实战s5pv210——LCD的原理和实战

  • bit0:使能window0 ;
  • bit5-2:选择RGB888模式;
  • bit15:设置输出顺序为 red green blue还是 blue green red,设置为1时BGR,设置为0时RGB。
(5)VIDOSD0A、VIDOSD0B两个寄存器

s5pv210——LCD的原理和实战s5pv210——LCD的原理和实战

  • 用来设置内存中window0的大小;
  • 比如设置为LCD屏幕的尺寸,即左上坐标为(0,0),右下坐标为(1023,767)。

(6)VIDOSD0A寄存器

s5pv210——LCD的原理和实战

  • 也是设置内存中window0的大小,比如设置为LCD屏幕的尺寸=1024*768。
(7)VIDW0xADD0Bx寄存器、VIDW0xADD1Bx寄存器

s5pv210——LCD的原理和实战s5pv210——LCD的原理和实战

  • VIDW0xADD0Bx寄存器,设置内存中window0的起始地址;
  • VIDW0xADD1Bx寄存器,设置内存中window0的结束地址。
(8)SHODOWCON寄存器:设置虚拟windows显示,以下位可以分别设置哪个windows显示

s5pv210——LCD的原理和实战

5、底板、核心板解读

s5pv210——LCD的原理和实战

s5pv210——LCD的原理和实战


二、代码实战

#define GPF0CON			(*(volatile unsigned long *)0xE0200120)
#define GPF1CON			(*(volatile unsigned long *)0xE0200140)
#define GPF2CON			(*(volatile unsigned long *)0xE0200160)
#define GPF3CON			(*(volatile unsigned long *)0xE0200180)

#define GPD0CON			(*(volatile unsigned long *)0xE02000A0)
#define GPD0DAT			(*(volatile unsigned long *)0xE02000A4)

#define CLK_SRC1		(*(volatile unsigned long *)0xe0100204)
#define CLK_DIV1		(*(volatile unsigned long *)0xe0100304)
#define DISPLAY_CONTROL	(*(volatile unsigned long *)0xe0107008)

#define VIDCON0			(*(volatile unsigned long *)0xF8000000)
#define VIDCON1			(*(volatile unsigned long *)0xF8000004)
#define VIDTCON2		(*(volatile unsigned long *)0xF8000018)
#define WINCON0 		(*(volatile unsigned long *)0xF8000020)
#define WINCON2 		(*(volatile unsigned long *)0xF8000028)
#define SHADOWCON 		(*(volatile unsigned long *)0xF8000034)
#define VIDOSD0A 		(*(volatile unsigned long *)0xF8000040)
#define VIDOSD0B 		(*(volatile unsigned long *)0xF8000044)
#define VIDOSD0C 		(*(volatile unsigned long *)0xF8000048)

#define VIDW00ADD0B0 	(*(volatile unsigned long *)0xF80000A0)
#define VIDW00ADD1B0 	(*(volatile unsigned long *)0xF80000D0)

#define VIDTCON0 		(*(volatile unsigned long *)0xF8000010)
#define VIDTCON1 		(*(volatile unsigned long *)0xF8000014)

#define HSPW 			(0)
#define HBPD			(40 - 1)
#define HFPD 			(5 - 1)
#define VSPW			(0)
#define VBPD 			(8 - 1)
#define VFPD 			(8 - 1)

// FB地址
#define FB_ADDR			(0x23000000)   //这个是任意的,只要对齐就好
#define ROW				(480)
#define COL				(800)
#define HOZVAL			(COL-1)  
#define LINEVAL			(ROW-1)

// 初始化LCD
void lcd_init(void)
{
	// 配置引脚用于LCD功能
	/*
	*LED、LCD、蜂鸣等,都是与gpio口有关的
	*因此,涉及这些器件的,都需要初始化gpio口
	*而初始化gpio,需要用到相关的配置寄存器,比如GPIOCON寄存器控制引脚的模式
	*而至于配置哪个GPIO,需要看原理图。
	*这里从原理图知LCD接到gpf0123,所以要到相应的配置寄存器中进行配置
	*/
	GPF0CON = 0x22222222;
	GPF1CON = 0x22222222;
	GPF2CON = 0x22222222;
	GPF3CON = 0x22222222;

	// 打开背光	GPD0_0(PWMTOUT0)
	/*
	*由原理图可知,当PWMTOUT0接低电平时,正5V电压加到背光上,也就打开了背光灯
	*而查阅PWMTOUT0可知,它就是GPD0_0这个引脚
	*把GPD0_0这个引脚配置为输出模式,通过GPD0CON寄存器配置
	*然后配置GPD0_0这个引脚输出值为0,通过GPD0DAT寄存器配置
	*/
	GPD0CON &= ~(0xf<<0);
	GPD0CON |= (1<<0);			// output mode
	GPD0DAT &= ~(1<<0);			// output 0 to enable backlight

	// 10: RGB=FIMD I80=FIMD ITU=FIMD
	//由上面寄存器的分析可知,这里可以设置为10
	DISPLAY_CONTROL = 2<<0;

	
	// bit[26~28]:使用RGB接口
	// bit[18]:RGB 并行
	// bit[2]:选择时钟源为HCLK_DSYS=166MHz
	VIDCON0 &= ~( (3<<26)|(1<<18)|(1<<2) );

	// bit[1]:使能lcd控制器
	// bit[0]:当前帧结束后使能lcd控制器
	VIDCON0 |= ( (1<<0)|(1<<1) );

	
	// bit[4]:选择需要分频
	// bit[6~13]:分频系数为15,即VCLK = 166M/(14+1) = 11M,那么bit[6~13]=14;
	VIDCON0 |= 14<<6 | 1<<4;

    //屏幕有的是高电平脉冲提示开始,有的是低脉冲提示开始
	// H43-HSD043I9W1.pdf(p13) 时序图:VSYNC和HSYNC都是低脉冲提示开始
	// s5pv210芯片手册(p1207) 时序图:VSYNC和HSYNC都是高脉冲提示开始,所以需要反转
	VIDCON1 |= 1<<5 | 1<<6;

	// 设置时序
	VIDTCON0 = VBPD<<16 | VFPD<<8 | VSPW<<0;
	VIDTCON1 = HBPD<<16 | HFPD<<8 | HSPW<<0;
	
	// 设置长宽(这里设置的是物理尺寸)
	VIDTCON2 = (LINEVAL << 11) | (HOZVAL << 0);

	// 设置windows0
	// bit[0]:使能
	// bit[2~5]:24bpp
	WINCON0 |= 1<<0;
	WINCON0 &= ~(0xf << 2);//先清零
	WINCON0 |= (0xB<<2) | (1<<15);//再设置

#define LeftTopX     0
#define LeftTopY     0
#define RightBotX   479
#define RightBotY   271

	// 设置windows1的上下左右
	//这里设置的是显存空间的大小,可以比屏幕大。
	//这里设置和物理屏幕一样大
	VIDOSD0A = (LeftTopX<<11) | (LeftTopY << 0);
	VIDOSD0B = (RightBotX<<11) | (RightBotY << 0);
	VIDOSD0C = (LINEVAL + 1) * (HOZVAL + 1);


	// 设置fb的地址
	VIDW00ADD0B0 = FB_ADDR;//这个可以任选的,但必须对齐(比如按照1M对齐)
	//每个像素点3个字节(24bit的bpp)就可以了的,但是为了对齐,用了4字节。
	VIDW00ADD1B0 = (((HOZVAL + 1)*4 + 0) * (LINEVAL + 1)) & (0xffffff);
	//上述两句表明了一个屏幕占用的空间大小

	// 使能channel 0传输数据
	SHADOWCON = 0x1;
}


// 描点
void lcd_draw_pixel(int row, int col, int color)
{
	unsigned long * pixel = (unsigned long  *)FB_ADDR;

	*(pixel + row * COL + col) = color;
	
	//假设像素点坐标为(row,col),则该像素点在内存中的位置计算方法如下
	//有一个基地址,pixel(这里是一个指针,注意它是指针,加1会加上4个字节)
	//偏移量=每行多少个像素*有多少行*每个像素占用多少个字节+col个像素*每个像素多少字节
    //             COL      * row    *    4                 +   col * 4
    //那么所处的位置应该是(int)pixel+COL*row*4 + col * 4
    //或者简单地写成 pixel + row * COL + col	

}

// 清屏
void lcd_clear_screen(int color)
{
	int i, j;

	for (i = 0; i < ROW; i++)
		for (j = 0; j < COL; j++)
			lcd_draw_pixel(i, j, color);

}

// 划横线
void lcd_draw_hline(int row, int col1, int col2, int color)
{
	int j;

	// 描第row行,第j列
	for (j = col1; j <= col2; j++)
		lcd_draw_pixel(row, j, color);

}

// 划竖线
void lcd_draw_vline(int col, int row1, int row2, int color)
{
	int i;
	// 描第i行,第col列
	for (i = row1; i <= row2; i++)
		lcd_draw_pixel(i, col, color);

}

// 划十字
void lcd_draw_cross(int row, int col, int halflen, int color)
{
	lcd_draw_hline(row, col-halflen, col+halflen, color);
	lcd_draw_vline(col, row-halflen, row+halflen, color);
}


三、显示内容

1、首先必须清屏;

2、显示点、线;

3、显示英文字符;

(1)每个英文字符,占用16*8个像素,如果单色显示,则每个像素占1bit,则每个英文字符是16Byte。
(2)字模。要想得到显示,必须先取“模”(知道该字符的字模),然后填充即可。
(3)代码示例
// 写字
// 写字的左上角坐标(x, y),字的颜色是color,字的字模信息存储在data中
static void show_8_16(unsigned int x, unsigned int y, unsigned int color, unsigned char *data)  
{  
// count记录当前正在绘制的像素的次序
    int i, j, count = 0;  
	  
    for (j=y; j<(y+16); j++)  
    {  
        for (i=x; i<(x+8); i++)  
        {  
            if (i<XSIZE && j<YSIZE)  
            {  
			// 在坐标(i, j)这个像素处判断是0还是1,如果是1写color;如果是0直接跳过
            	if (data[count/8]/*表示一个字模中(16行,每行8个像素)的第几行(每行8bit)*/ & (1<<(count%8))/*表示该行的第几bit*/)   
					lcd_draw_pixel(i, j, color);
            }  
            count++;  
        }  
    }  
} 

// 写字符串
// 字符串起始坐标左上角为(x, y),字符串文字颜色是color,字符串内容为str
void draw_ascii_ok(unsigned int x, unsigned int y, unsigned int color, unsigned char *str)
{
	int i;  
	unsigned char *ch;
    for (i=0; str[i]!='\0'; i++)  
    {  
		ch = (unsigned char *)ascii_8_16[(unsigned char)str[i]-0x20];
        show_8_16(x, y, color, ch); 
		
        x += 8;
		if (x >= XSIZE)
		{
			x -= XSIZE;			// 回车
			y += 16;			// 换行
		}
    }  
}

4、显示中文字符;

5、显示图像

(1)之前都是单色的,每个像素点用一个bit(单色显示时),像素点不是0就是1;而图像是彩色的,每个像素点占用4字节(RGB888时),像素点情况很多。
(2)需要取模。使用软件image2LCD。知道RGB顺序的含义,知道生成的步骤即可(一般设置输出的和屏幕实际大小一样)。
(3)代码

// 画800×480的图,图像数据存储在pData所指向的数组中
void lcd_draw_picture(const unsigned char *pData)
{
	u32 x, y, color, p = 0;
	
	for (y=0; y<480; y++)
	{
		for (x=0; x<800; x++)
		{
			// 在这里将坐标点(x, y)的那个像素填充上相应的颜色值即可
			color = (pData[p+0] << 0) | (pData[p+1] << 8) | (pData[p+2] << 16);
			lcd_draw_pixel(x, y, color);
			p += 3;//注意这里的p不是指针,而是int数。由于RGB888,三个一组,所以下一个像素是p+3
		}
	}
}
(4)这样处理得到的bin大于16KB,所以需要分成BL1,BL2部分。
  • 引用chapter10的分割方法,详见chapter14\6.LCD目录下的内容。
  • 最后使用烧录脚本(./write2sd),不能使用windows里的烧录工具。
write2sd是一个脚本,内容如下:

#!/bin/sh
sudo dd iflag=dsync oflag=dsync if=./BL1/BL1.bin of=/dev/sdb seek=1
sudo dd iflag=dsync oflag=dsync if=./BL2/BL2.bin of=/dev/sdb seek=45

(5)补充
  • bmp格式是原始图像,没有经过压缩?因此可以直接使用image2LCD软件解释成数据。
  • jpg、png、gif等压缩的不可以直接用image2LCD解释。需要先用对应的解压缩库对其解压缩,才能使用image2LCD软件?