1、FIMD结构框图
(1)Samsung的s5pv210的LCD控制器叫做FIMD(也叫显示控制器)。Display controller(显示控制器)包括用于将图像数据从相机接口控制器的本
地总线或位于系统存储器(例如:显存)中的视频缓冲器传送到外部LCD驱动器接口的逻辑。 LCD驱动接口支持三种接口,即RGB接口,I80接口和YUV
接口。显示控制器使用多达五个覆盖图像窗口(也就是虚拟窗口win0-win4),其支持各种颜色格式,如RGB、YUV。
FIMD在内部与AHB总线等相连接,在外部提供RGB接口、I80接口、YUV接口与外部相连接,我们实际使用的是RGB接口。这个接口就是我在上一篇博文中说的
LCD控制器的数据输出接口。RGB接口传输的是RGB编码的颜色数据,同理YUV接口传输的就是YUV编码的颜色数据,至于I80我不了解。本文讨论也是RGB接口。
(2)RGB接口信号
LCD_HSYNC :水平同步信号
LCD_VSYNC:垂直同步信号
LCD_VCLK:像素时钟,LCD工作时需要主板控制器给LCD模组一个工作时钟信号,就是VCLK
LCD_VDEN:数据有效标志,时序信号,和HSYNC、VSYNC结合使用。
LCD_VD[23:0]:24根数据线,用来传输图像信息。可见LCD是并行接口,速率才够快。
2、LCD显示一帧图像的过程
(1)LCD显示单位:帧(frame)
显示器上一整个画面的内容成为一个帧(frame),整个显示器工作时是一帧一帧的在显示。
帧内数据:一帧分为多行,一行分为多像素,因此一帧图像其实就是多个像素组成的矩阵。
帧外数据:整个视频由很多个帧构成,最终播放视频时逐个播放各个图像帧即可。
(2)显示一帧图像
首先把帧分为行,然后再把行分为像素,然后逐个像素去显示。(显示像素:其实就是LCD驱动器按照接收到的LCD控制器给的显示数据,驱动一个像素的液晶分子旋转,让
这个像素显示出相应的颜色值的过程)。
关键点:LCD控制器和驱动器之间一次只能传一个像素点的显示数据。所以一帧图像在屏幕上其实是串行的依次被显示上去的,不是同一时间显示出来的。
(3)为了向前兼容出现的六个时序参数
HSPW 水平同步信号脉宽
HBPD 水平同步信号前肩
HFPD 水平同步信号后肩
VSPW 垂直同步信号脉宽
VBPD 垂直同步信号前肩
VFPD 垂直同步信号后肩
(3.1)一行的通信过程是这样的:LCD控制器先发送一个HSYNC高电平脉冲(脉冲宽度是HSPW
),脉冲告诉驱动器下面的信息是一行信息。然后开始这一行信息,这一行信息包括3部
分:HBPD+有效行信息+HFPD。其中前肩和后肩都属于时序信息(和LCD屏幕具体有关),
有效行信息就是横向分辨率。所以你可以认为一行总共包含4部分:HSPW+HBPD+有效行信
息+HFPD。
(3.2)一帧图像其实就是一列,一列图像由多个行组成,每行都是上面讲的这个时序。
(3.3)一帧图像的通信过程是这样的:整个帧图像信号分为4部分:VSPW+VBPD+帧有效信号
+VFPD。VSPW是帧同步信号宽度,用来告诉驱动器一帧图像要开始了;VBPD和VFPD分别是
垂直同步信号前后肩。
(3.4)必须说明:这6个参数对于LCD显示器其实本来是没用的,这些信号其实是老式的CRT
显示器才需要的,LCD本身不需要,但是出于历史兼容性要求,LCD选择了兼容CRT显示器
的这些时序要求,所以理解LCD显示器时序和编程时,用CRT的方式来理解不会错。
(3.5)要注意,这几个时序参数本身是LCD屏幕本身的参数,与LCD控制器无关。所以同一个
主板如果接的屏幕不一样则时序参数设置也会不同。这些参数的来源一般是:第一,厂
家会直接给出,一般以实例代码的形式给出;第二,来自于LCD的数据手册。
2、虚拟屏幕叠加
(1)虚拟屏幕的意思是,我们平时看到的屏幕上显示出来的场景实际是很多个屏幕显示叠加在一起的效果(譬如新闻图像、电视台台标、下方飘动的字幕新闻)。
(2)像S5PV210的LCD控制器中有5个虚拟屏幕Window0到Window4,虚拟屏幕不存在于真实而存在于内存中。(之前讲过,LCd显示时实际是显示的是对应的内存
中的显存区域的数值)虚拟屏幕其实就是一个内存中的显存区域,有几个显存区域就有几个虚拟屏幕,但是这些虚拟屏幕都被映射到一个真实的显示屏上面,所以将
来真实的现实效果实际是这几个虚拟屏幕的显示内容的叠加。(叠加时要注意上面一层会覆盖下面一层,所以要注意谁在前谁在后,设置寄存器时有这个选项)。
(3)使用虚拟屏幕而不是整个LCD使用一个显存是有一定好处的:第一,可以保证不污染源图像,方便程序处理;第二,可以减少屏幕刷新,提高显示效率,减少CPU工作量。
3、虚拟显示
(1)如何实现在小分辨率的屏幕上(真实)显示大分辨率的图像?
细节上,我们需要屏幕上看到不同图像时,需要对显存区域进行刷新。即使我们只需要屏幕显示移动一点点,整个屏幕对应的显存空间也需要整个重新刷新,工作量和完全
重新显示一幅图像是一样的。这个显然不好,这样CPU刷新屏幕的工作量太大了,效率很低。
如何能够在显示一个大图片的不同区域时让CPU刷新屏幕工作量减少?
有,方法就是虚拟显示。具体做法就是在内存中建立显示缓存的时候实际建立一个很大的区域,然后让LCD去对应其中的一部分区域作为有效的显示区域。将来要显示大图像
时,直接将大图像全部一次性加载入显示缓存区,然后通过移动有效显示区域就可以显示大图像的不同区域了。
4、LCD控制器的主要寄存器
LCD相关寄存器寄存器介绍:因为这里面的寄存器实在太多了,我们的一个原则就是能够
让LCD工作就行了,所以很多寄存器我们都没有涉及到,同一个寄存器中有些位也是没有
涉及到的,所以需要用到的位我们就设置,不需要我们暂且不管,如果以后做方面相关
的时候再去好好研究。
(1)VIDCON0:
bit[0]: 当前帧结束后是否使能视频输出控制器,也就是一帧画面显示结束后是否使能
LCD控制器,1使能 0禁止
bit[1]: 使能视频输出控制器(指的是当前) 1使能 0禁止
bit[2]: 时钟源选择 0=HCLK_DSYS 1=SCLK_FIMD
bit[13:6]: 时钟源分频器分频系数设置 本LCd控制器时钟不超过100MHZ,最终的时钟频
率是需要考虑LCD控制器和LCD驱动器两个方面,要低于两个中的最小值。
bit[4]: 时钟控制,使用分频器设置后的时钟还是源时钟,0是源时钟 1是分频器设置
后 的时钟。
bit[18]: 选择显示的模式,0是RGB并行模式 1是RGB串行模式
bit[28:26]: 选择视频输出格式,000=RGB接口 010=I80接口 011=I80接口...
(2)VIDCON1:
bit[4]: VSYNC信号反转控制,要跟LCd驱动器的电平要一致
bit[5]: HSYNC信号反转控制,要跟LCD驱动器的电平要一致
(3)VIDTCON0:
bit[7:0]: VSPW设置,意思在上面已经说过
bit[15:8]: VFPD设置
bit[23:16]: VBPD设置
(4)VIDTCON1:
bit[7:0]: HSPW设置
bit[15:8]: HFPD设置
bit[23:16]: HBPD设置
(5)VIDTCON2:
bit[10:0]: 显示水平大小设置,水平分辨率
bit[21:11]: 显示垂直大小设置,垂直分辨率
(6)WINCON0,WINCON1,WINCON2,WINCON3,WINCON4:
分别设置5个虚拟显示,对应分配的5个显存
bit[0]: 使能位,0禁止当前显存 1使能当前显存
bit[5:2]: 选择像素深度(bpp)
0000=1bpp 0001=2bpp 0010=4bpp.....1011=24bpp(R:8 G:8 B:8)
bit[15]: 像素交换使能控制位,像素交换就是 24位数据怎么分布的问题,是RGB还是
BGR
0禁止 1使能交换
(7)VIDOSD0A,VIDOSD1A,VIDOSD2A,VIDOSD3A....
用来设置虚拟显存的位置相关的
bit[10:0]: 指定左上像素的垂直坐标
bit[21:11]: 指定左上像素的水平坐标
(8)VIDOSD0B................
bit[10:0]: 指定右下角像素的垂直坐标
bit[21:11]: 指定右下角像素的水平坐标
(9)VIDOSD0C....... 设置显存的大小、注意显存的大小可以大于LCD实际的一帧数据的大
小的,这是需要理解的。
bit[23:0]: 注意这里设置其实是以像素为单位的,也就是说这里设置的是我们的显存
的
(10)VIDW00ADD0B0(分为5组)分别对应我们的5个虚拟显存
bit[31:0]: 显存的内存地址设置 , 将地址值写入这个寄存器
VIDW00ADD1B0(分为5组)分别对应我们的5个虚拟显存
bit[31:0]: 显存的大小,以字节为单位
SHADOWCON:用来设置我们的5个虚拟显存使能控制位和叠加方式寄存器,如果我们显示
的图片是需要叠加的话是需要好好看看这里的。
bit[0]: win0显存使能控制开关 0禁止 1使能 所以由此可知我们的每一个虚拟显存
的使能开关有两个一个就是在各自的寄存器上,另一个就是在这里集中的控制
,当然我们还有总的视频控制输出开关,之前说过了。
....
/*************************************************************************************/
附上裸机下操作LCD控制器的代码:(基于:S5PV210平台)
1 #define HSPW (40) // 1~40 DCLK 2 #define HBPD (10 - 1) // 46 3 #define HFPD (240 - 1) // 16 210 354 4 #define VSPW (20) // 1~20 DCLK 5 #define VBPD (10 - 1) // 23 6 #define VFPD (30 - 1) // 7 22 147 7 8 #define FB_ADDR (0x23000000) 9 #define ROW (600) 10 #define COL (1024) 11 #define HOZVAL (COL-1) 12 #define LINEVAL (ROW-1) 13 14 #define XSIZE COL 15 #define YSIZE ROW 16 17 #define LeftTopX 0 18 #define LeftTopY 0 19 #define RightBotX 1023 20 #define RightBotY 599 21 22 #define GPF0CON (*(volatile unsigned int *)0xE0200120) 23 #define GPF1CON (*(volatile unsigned int *)0xE0200140) 24 #define GPF2CON (*(volatile unsigned int *)0xE0200160) 25 #define GPF3CON (*(volatile unsigned int *)0xE0200180) 26 27 #define GPD0CON (*(volatile unsigned int *)0xE02000A0) 28 #define GPD0DAT (*(volatile unsigned int *)0xE02000A4) 29 30 #define DISPLAY_CONTROL (*(volatile unsigned int *)0xE0107008) 31 32 #define VIDCON0 (*(volatile unsigned int *)0xF8000000) 33 #define VIDCON1 (*(volatile unsigned int *)0xF8000004) 34 35 #define VIDTCON0 (*(volatile unsigned int *)0xF8000010) 36 #define VIDTCON1 (*(volatile unsigned int *)0xF8000014) 37 #define VIDTCON2 (*(volatile unsigned int *)0xF8000018) 38 #define VIDOSD0A (*(volatile unsigned long *)0xF8000040) 39 #define VIDOSD0B (*(volatile unsigned long *)0xF8000044) 40 #define VIDOSD0C (*(volatile unsigned long *)0xF8000048) 41 #define VIDW00ADD0B0 (*(volatile unsigned long *)0xF80000A0) 42 #define VIDW00ADD1B0 (*(volatile unsigned long *)0xF80000D0) 43 #define WINCON0 (*(volatile unsigned long *)0xF8000020) 44 #define SHADOWCON (*(volatile unsigned long *)0xF8000034) 45 46 47 typedef unsigned int u32; 48 typedef unsigned short u16; 49 50 51 /*填充像素点*/ 52 static inline void lcd_draw_pixel(u32 x, u32 y, u32 color) 53 { 54 *(u32 *)(FB_ADDR + (COL*x + y)*4) = color; 55 } 56 57 /*填充LCD背景*/ 58 void lcd_draw_background(const u32 color) 59 { 60 u32 i = 0; 61 u32 j = 0; 62 63 for (i = 1; i <= ROW; ++i) 64 { 65 for (j = 0; j <= COL; ++j) 66 lcd_draw_pixel(i, j, color); 67 } 68 } 69 70 /*在LCd上绘制水平线*/ 71 void lcd_draw_lline(const u32 x, const u32 y, const u32 length, 72 const u32 width, const u32 color) 73 { 74 volatile u32 i = 0; 75 volatile u32 j = 0; 76 77 for (i = x; i < width+x; i++) 78 { 79 for (j = y; j < length+y; j++) 80 { 81 lcd_draw_pixel(i, j, color); 82 } 83 } 84 } 85 86 /*在LCd上绘制垂直线*/ 87 void lcd_draw_vline(const u32 x, const u32 y, const u32 length, 88 const u32 width, const u32 color) 89 { 90 volatile u32 i = 0; 91 volatile u32 j = 0; 92 93 for (i = x; i < length+x; i++) 94 { 95 for (j = y; j < width+y; j++) 96 { 97 lcd_draw_pixel(i, j, color); 98 } 99 } 100 } 101 102 // glib库中的画线函数,可以画斜线,线两端分别是(x1, y1)和(x2, y2) 103 void glib_line(unsigned int x1, unsigned int y1, 104 unsigned int x2, unsigned int y2, unsigned int color) 105 { 106 int dx,dy,e; 107 dx=x2-x1; 108 dy=y2-y1; 109 110 if(dx>=0) 111 { 112 if(dy >= 0) // dy>=0 113 { 114 if(dx>=dy) // 1/8 octant 115 { 116 e=dy-dx/2; 117 while(x1<=x2) 118 { 119 lcd_draw_pixel(x1,y1,color); 120 if(e>0){y1+=1;e-=dx;} 121 x1+=1; 122 e+=dy; 123 } 124 } 125 else // 2/8 octant 126 { 127 e=dx-dy/2; 128 while(y1<=y2) 129 { 130 lcd_draw_pixel(x1,y1,color); 131 if(e>0){x1+=1;e-=dy;} 132 y1+=1; 133 e+=dx; 134 } 135 } 136 } 137 else // dy<0 138 { 139 dy=-dy; // dy=abs(dy) 140 141 if(dx>=dy) // 8/8 octant 142 { 143 e=dy-dx/2; 144 while(x1<=x2) 145 { 146 lcd_draw_pixel(x1,y1,color); 147 if(e>0){y1-=1;e-=dx;} 148 x1+=1; 149 e+=dy; 150 } 151 } 152 else // 7/8 octant 153 { 154 e=dx-dy/2; 155 while(y1>=y2) 156 { 157 lcd_draw_pixel(x1,y1,color); 158 if(e>0){x1+=1;e-=dy;} 159 y1-=1; 160 e+=dx; 161 } 162 } 163 } 164 } 165 else //dx<0 166 { 167 dx=-dx; //dx=abs(dx) 168 if(dy >= 0) // dy>=0 169 { 170 if(dx>=dy) // 4/8 octant 171 { 172 e=dy-dx/2; 173 while(x1>=x2) 174 { 175 lcd_draw_pixel(x1,y1,color); 176 if(e>0){y1+=1;e-=dx;} 177 x1-=1; 178 e+=dy; 179 } 180 } 181 else // 3/8 octant 182 { 183 e=dx-dy/2; 184 while(y1<=y2) 185 { 186 lcd_draw_pixel(x1,y1,color); 187 if(e>0){x1-=1;e-=dy;} 188 y1+=1; 189 e+=dx; 190 } 191 } 192 } 193 else // dy<0 194 { 195 dy=-dy; // dy=abs(dy) 196 197 if(dx>=dy) // 5/8 octant 198 { 199 e=dy-dx/2; 200 while(x1>=x2) 201 { 202 lcd_draw_pixel(x1,y1,color); 203 if(e>0){y1-=1;e-=dx;} 204 x1-=1; 205 e+=dy; 206 } 207 } 208 else // 6/8 octant 209 { 210 e=dx-dy/2; 211 while(y1>=y2) 212 { 213 lcd_draw_pixel(x1,y1,color); 214 if(e>0){x1-=1;e-=dy;} 215 y1-=1; 216 e+=dx; 217 } 218 } 219 } 220 } 221 } 222 223 //画圆函数,圆心坐标是(centerX, centerY),半径是radius,圆的颜色是color 224 void draw_circular(unsigned int centerX, unsigned int centerY, 225 unsigned int radius, unsigned int color) 226 { 227 int x,y ; 228 int tempX,tempY;; 229 int SquareOfR = radius*radius; 230 231 for(y=0; y<XSIZE; y++) 232 { 233 for(x=0; x<YSIZE; x++) 234 { 235 if(y<=centerY && x<=centerX) 236 { 237 tempY=centerY-y; 238 tempX=centerX-x; 239 } 240 else if(y<=centerY&& x>=centerX) 241 { 242 tempY=centerY-y; 243 tempX=x-centerX; 244 } 245 else if(y>=centerY&& x<=centerX) 246 { 247 tempY=y-centerY; 248 tempX=centerX-x; 249 } 250 else 251 { 252 tempY = y-centerY; 253 tempX = x-centerX; 254 } 255 if ((tempY*tempY+tempX*tempX)<=SquareOfR) 256 lcd_draw_pixel(x, y, color); 257 } 258 } 259 } 260 261 262 void lcd_draw_Chinese(const u32 x, const u32 y, 263 const u32 color, const char *Chinese) 264 { 265 u32 j = 0; 266 u32 count = 0; 267 u32 g = 0; 268 u32 y1 = y; 269 const unsigned char *ptr = (unsigned char *)0; 270 271 if (*Chinese == 'd') 272 ptr = deng; 273 else 274 ptr = tao; 275 276 for (g = x; g < x+97; g++) 277 { 278 u32 i = 0; 279 u32 k = 0; 280 281 for (i = count; i < count +12; i++) 282 { 283 for (j = 0, k = y1; j <= 7; j++, k++) 284 { 285 if (((1 << j) & ptr[count]) == 1) 286 lcd_draw_pixel(x, k, color); 287 } 288 289 y1 = k; //更新y值 290 } 291 292 y1 = y; 293 294 count = i; //更新count计数 295 } 296 } 297 298 299 void lcd_init(void) 300 { 301 /*GPIO初始化*/ 302 GPF0CON = 0x22222222; 303 GPF1CON = 0x22222222; 304 GPF2CON = 0x22222222; 305 GPF3CON = 0x332222; 306 307 /*打开LCD背光*/ 308 GPD0CON &= ~(0xf << 0); 309 GPD0CON |= (1 << 0); //配置为输出模式 310 GPD0DAT &= ~(1 << 0); //输出为低电平 311 312 /*设置RGB格式由FIMD输出 其实就是指定GRB格式由FIMD输出*/ // Display path selection 313 DISPLAY_CONTROL = 2 << 0; // RGB=FIMD I80=FIMD ITU=FIMD 314 315 /*CON0寄存器配置*/ 316 VIDCON0 &= ~((1 << 18) | (7 << 26)); //选择显示模式和输出格式 317 VIDCON0 &= ~(1 << 2); //选择时钟源HCLK 318 VIDCON0 &= ~(0xff << 6); 319 VIDCON0 |= ((4 << 6) | (1 << 4)); //设置分频器系数和使能分频器 320 VIDCON0 |= ((1 << 0)|(1 << 1)) ; //使能视频输出和使能帧结束视频输出 321 322 /*CON1寄存器配置*/ 323 VIDCON1 |= ((1<<5) | (1<<6)); //VSYNC和HSYNC信号反转,是否反转与LCd显示器相关 324 325 /*关于时序的6个参数的设置*/ 326 VIDTCON0 &= ~(0xffffff << 0); 327 VIDTCON0 |= (VSPW << 0) | (VFPD << 8) | (VBPD << 16); 328 VIDTCON1 &= ~(0xffffff << 0); 329 VIDTCON1 |= (HSPW << 0) | (HFPD << 8) | (HBPD << 16); 330 331 /*设置LCD像素,对应实际的LCd像素*/ 332 VIDTCON2 &= ~(0x3fffff << 0); 333 VIDTCON2 |= (LINEVAL << 11) | (HOZVAL << 0); 334 335 /*设置window0的像素空间范围和空间大小*/ 336 VIDOSD0A &= ~(0x3fffff << 0); 337 VIDOSD0A |= (LeftTopX << 11) | (LeftTopY << 0); 338 VIDOSD0B &= ~(0x3fffff << 0); 339 VIDOSD0B |= (RightBotX << 11) | (RightBotY << 0); 340 VIDOSD0C &= ~(0xffffffff << 0); 341 VIDOSD0C |= (LINEVAL + 1) * (HOZVAL + 1); 342 343 /*设置window0显存的内存地址*/ 344 VIDW00ADD0B0 = FB_ADDR; //内存地址 345 VIDW00ADD1B0 = (HOZVAL + 1) * (LINEVAL + 1) * 4; //显存字节大小,这个空间可以做很大的 346 347 /*使能window0*/ 348 WINCON0 &= ~(0xf << 2); 349 WINCON0 |= (0xb << 2); //选择像素深度 = 24bpp(RGB888) 350 WINCON0 |= (1 << 0) | (1 << 15); //使能显存和使能交换 351 352 // 使能channel 0传输数据 对应window0 353 SHADOWCON = 0x1; 354 355 }
参考:《朱友鹏嵌入式Linux开发1.ARM裸机全集1.14.ARM裸机第十四部分-LCD显示器》
http://blog.chinaunix.net/uid-27411029-id-3302040.html
http://blog.csdn.net/xubin341719/article/details/9177085