s5pv210——LCD的原理和实战
程序员文章站
2022-07-14 10:36:27
...
以下内容源于朱友鹏《物联网大讲堂》课程的学习,以及博客http://www.cnblogs.com/biaohc/p/6286946.html的学习,如有侵权,请告知删除。
一、S5PV210的LCD控制器
1、FIMD结构框图(P1162)
- 210的LCD控制器叫FIMD;
- FIMD是210内部和图像处理相关的一些部件,在摄像头等和图像处理有关的部分都可以有关联。
- FIMD在内部与AHB总线等相连接,在外部提供RGB接口、I80接口、YUV接口与外部相连接,我们实际使用的是RGB接口。
2、虚拟屏幕叠加(数据手册P1194)
(1)虚拟屏幕
- 屏幕显示的场景,实际是很多个屏幕显示叠加在一起的效果(譬如新闻图像、电视台台标、下方飘动的字幕新闻);
- S5PV210的LCD控制器中有5个虚拟屏幕Window0到Window4,虚拟屏幕不存在于真实而存在于内存中。
- LCD显示时,显示对应的内存中的显存区域的数值。
- 虚拟屏幕其实就是一个内存中的显存区域,有几个显存区域就有几个虚拟屏幕。
- 这些虚拟屏幕都被映射到一个真实的显示屏上面,实际效果是这几个虚拟屏幕的显示内容的叠加。
- 叠加时,上面一层会覆盖下面一层,因此要注意谁在前谁在后,通过相关寄存器进行设置。
- 第一,可以保证不污染源图像,方便程序处理;
- 第二,可以减少屏幕刷新,提高显示效率,减少CPU工作量。
3、虚拟显示(数据手册P1206)
(2)按照以前的思想,当需要在屏幕上显示不同图像时,需要对整个显存区域进行刷新。
- 即使是显示同一幅图像的不同区域(比如只需要屏幕显示移动一点点),整个屏幕对应的显存空间也需要整个重新刷新,工作量和完全重新显示一幅图像是一样的。
- 这使得CPU刷新屏幕的工作量很大,效率很低。
(3)如何能够在显示一个大图片的不同区域时,让CPU刷新屏幕工作量减少?
- 方法是,虚拟显示。
- 在内存中建立显示缓存时,建立一个很大的区域,直接将大图像全部一次性加载入显示缓存区(这样就可以只加载一次);
- LCD选择其中的一部分区域作为有效的显示区域;
- 通过移动有效显示区域就可以显示大图像的不同区域了。
4、主要寄存器简介
(1)DISPAY_CONTROL寄存器:设置为10或11,即RGB模式可行。
(2)VIDCON0寄存器(Video Main Control 0 Register)
- bit0,bit1:为使能控制信号都使能;
- bit2:选择时钟源,选HCLK,连的是HCLC_DSYS 为166MHz;
- bit4:开启分频;
- bit13-6:设置时钟大小,时钟频率要小于控制器的最大时钟,也要小于LCD驱动器的最大时钟。
- bit18:设置RGB数据传输为并行还是串行,因为有24根数据线所以为并行;
- bit28-26:选择为RGB模式。
- bit5,bit6:设置HSYNC和VSYNC的极性,如果LCD的高低电平脉冲是相同的话,则Normal,如果极性相反则Invert。
(4)WINCONn(n=0~4)寄存器
- bit0:使能window0 ;
- bit5-2:选择RGB888模式;
- bit15:设置输出顺序为 red green blue还是 blue green red,设置为1时BGR,设置为0时RGB。
- 用来设置内存中window0的大小;
- 比如设置为LCD屏幕的尺寸,即左上坐标为(0,0),右下坐标为(1023,767)。
(6)VIDOSD0A寄存器
- 也是设置内存中window0的大小,比如设置为LCD屏幕的尺寸=1024*768。
- VIDW0xADD0Bx寄存器,设置内存中window0的起始地址;
- VIDW0xADD1Bx寄存器,设置内存中window0的结束地址。
5、底板、核心板解读
二、代码实战
#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里的烧录工具。
#!/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软件?
上一篇: s5pv210——LCD基础理论