1. 原理概述
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 | HBP(Horizontal Back Porch) | 每行开始时需要插入的像素时钟周期数 |
right_margin | HFP(Horizontal Front Porch) | 每行结束时需要插入的像素时钟周期数 |
upper_margin | VBP(Vertical Back Porch) | 垂直同步周期之后帧开头时的无效行数 |
lower_margin | VFP(Vertical Front 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的具体实现
返回了屏幕的实际高度、灰度等数值,具体实现如代码清单 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的具体实现
代码清单 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配置
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;
代码清单 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)
};