1.1 显示原理
如图 1-1所示,LCD驱动会申请一块FB内存,应用程序向FB内存中搬运显示的数据,屏幕上就会有对应的显示。
图 1-1显示原理
1.2 时序Timing
每一个LCD屏幕都会有自定义的时序,这些数据一般都会在屏幕的数据手册中提供,LCD控制器需要根据这些时序产生对应的行场,屏幕在获得正确的行场后才能正常显示。
LCD显示的时序如图 1-2所示,其中屏幕可见区域大小为xres * yres。
图 1-2 LCD显示时序
显示时序图中有相关概念,如表 1-1所示。
表 1-1时序使用的相关概念
名词 | 对应缩写 | 说明 |
left_margin | HFP(Horizontal Front Porch) | 每行开始时需要插入的像素时钟周期数 |
right_margin | HBP(Horizontal Back Porch) | 每行结束时需要插入的像素时钟周期数 |
upper_margin | VFP(Vertical Front Porch) | 垂直同步周期之后帧开头时的无效行数 |
lower_margin | VBP(Vertical Back Porch) | 本帧结束到下一帧垂直同步周期开始之 前的无效行数 |
hsync_len | HPW (HSYNC plus width) | 表示水平同步信号的宽度(单位:像素时钟周期) |
vsync_len | VPW (VSYNC width) | 表示垂直同步脉冲的宽度(单位:显示一行的时间) |
如表 1-2和表 1-3所示,E50A2V1屏幕的数据手册中有水平timing和竖直timing的定义,根据数据手册中的参数设置对应的LCD控制器寄存器即可。
此外需要配置LCD控制器总时钟DCLK,E50A2V1为30MHz。
表 1-2 Horizontal timing
Parameter | Symbol | Min. Spec. | Typ. Spec. | Max. Spec. | Unit |
Horizontal Display Area | thd | 800 | DCLK | ||
DCLK frequency | fclk | - | 30 | 50 | MHz |
One Horizontal Line | th | 889 | 928 | 1143 | DCLK |
HS pulse width | thpw | 1 | 48 | 255 | DCLK |
HS Back Porch(Blanking) | thb | 88 | DCLK | ||
HS Front Porch | thfp | 1 | 40 | 255 | DCLK |
DE mode Blanking | th-thd | 85 | 128 | 512 | DCLK |
表 1-3 Vertical timing
Parameter | Symbol | Min. Spec. | Typ. Spec. | Max. Spec. | Unit |
Vertical Display Area | tvd | 480 | TH | ||
VS period time | tv | 513 | 525 | 768 | TH |
VS pulse width | tvpw | 3 | 3 | 255 | TH |
VS Back Porch(Blanking) | tvb | 32 | TH | ||
VS Front Porch | tvfp | 1 | 13 | 255 | TH |
DE mode Blanking | tv-tvd | 4 | 45 | 255 | TH |
1.3 RGB和灰度
1.3.1 RGB888和RGB565
对于彩×××像,每个像素通常用三个分量表示,即R(Red)、G(Green)、B(Blue)三个分量,每个分量用一个字节表示,因此每个分量的取值范围从0到255。
RGB888每个像素由3个字节组成,R、G、B各占8bit;
RGB565 每个像素由2个字节组成,R占5bit,G占6bit,B占5bit。
1.3.2 灰度
灰度表示一个像素最多能显示的颜色数量。
对于RGB888来说,一共可以显示256 * 256 * 256 = 16777216种色彩,那么灰度就是16777216。
对于RGB565来说,一共可以显示32 * 64 * 32 = 65536种色彩,那么灰度就是65536。
2.技术实现2.1 LCD驱动框架
lcd驱动实现基本功能,只要实现如图 2-1中的四个函数即可。
图 2-1 LCD驱动需要关注的四个函数
2.1.1 函数__lcdOpen
这个函数需要向系统申请一块用于FB的内存,并且根据timing初始化行场信号,最后开启背光。
2.1.2 函数__lcdClose
这个函数是lcdOpen的逆向过程,主要操作就是关闭背光。
2.1.3 函数__lcdGetVarInfo
这个函数用于获取和屏幕相关的一些参数,比如屏幕的高度和宽度、RGB格式和灰度等。
2.1.4 函数__lcdGetScrInfo
这个函数用于获取和FB相关的一些参数,比如FB的大小。
2.2 调试过程
调试的过程首先要确认屏幕的背光是否开启,如果背光不亮屏幕上是不会有任何显示的。在确认背光开启之后再进行后面几步的测试。
2.2.1 通过示波器确认行场信号
1. 确认DCLK
通过示波器测量DCLK引脚,正常显示的时候应该会产生30MHz的波形。
2. 确认HSYNC
通过示波器测量HSYNC引脚,正常显示的时候应该会产生波形。
3. 确认VSYNC
通过示波器测量VSYNC引脚,正常显示的时候应该会产生波形。
2.2.2 编写测试程序
可以编写一个绘制BMP图形的测试用例,另外利用PhotoShop或其他制图工具,参考实际的分辨率大小制作一张对应的BMP图片。
例如:E50A2V1的分辨率为800 x 480,RGB格式为RGB565,制作这样的一张图片。接着如代码清单 1所示,在代码中打开BMP图片后跳过头部信息,将实际内容拷贝到FB中,那么在屏幕上就应该有对应的图形显示了。
代码清单 1测试程序基本流程
/* * 打开位图 */ pFp = fopen(pBmpfile, "rb"); …… /* * 读取位图文件头 */ iRc = fread(&FileHead, sizeof(BITMAPFILEHEADER), 1, pFp); …… /* * 读取文件信息头 */ iRc = fread((char*)&InfoHead, sizeof(BITMAPINFOHEADER), 1, pFp); …… /* * 跳过文件头 */ fseek(pFp, (INT)charToLong(FileHead.cfoffBits, 4), SEEK_SET); …… /* * 逐行、逐像素绘制图片 */ while (!feof(pFp)){ iRc = fread((CHAR *)&pix, 1, sizeof(UINT16), pFp); if (iRc != sizeof(UINT16)){ break; } lLocation = iLineX * iBitsPerPixel /8+(iLineY)* iXres * iBitsPerPixel /8; usTmp = pix.blue <<0| pix.green <<5| pix.red <<11; *((UINT16 *)(__GpcFb + lLocation))= usTmp; iLineX++; if(iLineX == iWidth){ iLineX =0; iLineY++; if(iLineY == iHeight +1){ break; } } }
3. 代码实现
3.1 驱动代码
3.1.1 __lcdOpen的具体实现
__lcdOpen调用了API_VmmPhyAllocAlign向系统申请了一块FB的内存,具体实现如代码清单 2所示。
代码清单 2 __lcdOpen的具体实现
static INT __lcdOpen (PLW_GM_DEVICE pgmdev, INT iFlag, INT iMode) { if (GpvFbMemBase == LW_NULL) { GpvFbMemBase = API_VmmPhyAllocAlign(800 * 480 * 3, /* 最大800 * 400分辨率 */ 4 * 1024 * 1024, /* 4M字节对齐 */ LW_ZONE_ATTR_DMA); if (GpvFbMemBase == LW_NULL) { __lcdDisable(); printk(KERN_ERR "__lcdOpen() low vmm memory!\n"); return (PX_ERROR); } __lcdInit(); } __lcdEnable(); return (ERROR_NONE); }
3.1.2 __lcdGetVarInfo的具体实现
__lcdGetVarInfo返回了屏幕的实际高度、灰度等数值,具体实现如代码清单 3所示。
代码清单 3 __lcdGetVarInfo的实现
static INT __lcdGetVarInfo (PLW_GM_DEVICE pgmdev, PLW_GM_VARINFO pgmvi) { if (pgmvi) { pgmvi->GMVI_ulXRes = curDisplayDev.uiDevWidth; /* 屏幕实际宽度 */ pgmvi->GMVI_ulYRes = curDisplayDev.uiDevHeight; /* 屏幕实际高度 */ pgmvi->GMVI_ulXResVirtual = curDisplayDev.uiDevWidth; /* 屏幕虚拟区域宽度 */ pgmvi->GMVI_ulYResVirtual = curDisplayDev.uiDevHeight; /* 屏幕虚拟区域高度 */ pgmvi->GMVI_ulXOffset = 0; /* 屏幕x坐标偏移 */ pgmvi->GMVI_ulYOffset = 0; /* 屏幕y坐标偏移 */ /* * BPP和掩码设置 */ switch (curDisplayDev.uiStreamColorFormat) { …… case FORMAT_RGB565: pgmvi->GMVI_ulBitsPerPixel = 16; pgmvi->GMVI_ulBytesPerPixel = 2; pgmvi->GMVI_ulRedMask = 0xF800; pgmvi->GMVI_ulGreenMask = 0x07E0; pgmvi->GMVI_ulBlueMask = 0x001F; break; …… } /* * 灰度设置 */ switch (curDisplayDev.uiColorsType) { case COLORTYPE_4K: pgmvi->GMVI_ulGrayscale = 4096; break; …… } pgmvi->GMVI_ulTransMask = 0; pgmvi->GMVI_bHardwareAccelerate = LW_FALSE; pgmvi->GMVI_ulMode = LW_GM_SET_MODE; pgmvi->GMVI_ulStatus = 0; } return (ERROR_NONE); }
3.1.3 __lcdGetScrInfo的具体实现
__lcdGetScrInfo返回FB相关的FB名称、FB大小等参数,具体实现如代码清单 4所示。
代码清单 4 __lcdGetScrInfo的具体实现
static INT __lcdGetScrInfo (PLW_GM_DEVICE pgmdev, PLW_GM_SCRINFO pgmsi) { UINT32 uiDevWidth = curDisplayDev.uiDevWidth; UINT32 uiDevHeight = curDisplayDev.uiDevHeight; if (pgmsi) { pgmsi->GMSI_pcName = "/dev/fb0"; pgmsi->GMSI_ulId = 0; /* * 内存区域大小设置 */ switch (curDisplayDev.uiStreamColorFormat) { …… case FORMAT_RGB565: pgmsi->GMSI_stMemSize = uiDevWidth * uiDevHeight * 2; pgmsi->GMSI_stMemSizePerLine = uiDevWidth * 2; break; …… } pgmsi->GMSI_pcMem = (caddr_t)GpvFbMemBase; } return (ERROR_NONE); }
3.2 timing配置
为了方便timing的配置,我们定义了__LCD_DEV_PANEL结构体,如图 32所示。这样如果以后更换了其他分辨率的屏幕,理论上只需要根据这个结构体填充对应的timing数据即可。
代码清单 5屏参相关的结构体
struct dev_panel_t { CHAR cPanelName[32]; UINT32 uiPanelId; #define COLORTYPE_4K (0) #define COLORTYPE_64K (1) #define COLORTYPE_256K (2) #define COLORTYPE_16M (3) UINT32 uiColorsType; #define FORMAT_YUV422 (0) #define FORMAT_YCBCR422 (1) #define FORMAT_RGB888 (2) #define FORMAT_RGB666 (3) #define FORMAT_RGB565 (4) #define FORMAT_RGB444_L (5) #define FORMAT_RGB332 (6) #define FORMAT_RGB444_H (7) UINT32 uiStreamColorFormat; UINT32 uiDevWidth; UINT32 uiDevHeight; UINT32 uiPixelClock; UINT32 uiHFrontPorch; UINT32 uiHBackPorch; UINT32 uiHSyncWidth; UINT32 uiVFrontPorch; UINT32 uiVBackPorch; UINT32 uiVSyncWidth; #define DATAWIDTH8or9 (0) #define DATAWIDTH16or18 (1) UINT32 uiDataBusWidth; #define MAPNORMAL (0) #define MAPJEIDA (1) UINT32 uiMappingMode; UINT32 uiDataInterlaced; UINT32 uiSyncInterlaced; UINT32 uiVerticalPol; UINT32 uiHorizontalPol; UINT32 uiDEPol; #define DEVICE_SYNC_YUV422 (0) #define DEVICE_SYNC_YUV444 (1) #define DEVICE_SYNC_UNIPAC (4) #define DEVICE_SYNC_EPSON (5) #define DEVICE_SYNC_HIGHCOLOR (6) #define DEVICE_MPU (7) UINT32 uiDevType; UINT32 uiGpioBlkEn; }; typedef struct dev_panel_t __LCD_DEV_PANEL; typedef struct dev_panel_t* __PLCD_DEV_PANEL;
根据E50A2V1的timing数据填充的结构体如代码清单 6所示。
代码清单 6 E50A2V1的屏参
#define E50A2V1 (0) static __LCD_DEV_PANEL DEF_LCD_E50A2V1_800x480 = { "E50A2V1", E50A2V1, COLORTYPE_16M, FORMAT_RGB565, 800, 480, 3000000, 88, 40, 48, 32, 13, 3, DATAWIDTH16or18, MAPNORMAL, 0, 0, 0, 0, 0, DEVICE_SYNC_HIGHCOLOR, NUC970_GPIO_NUMR(__BANK_G, 3) };