1.原理概述

1.1 显示原理

   如图 1-1所示,LCD驱动会申请一块FB内存,应用程序向FB内存中搬运显示的数据,屏幕上就会有对应的显示。

SylixOS的LCD驱动移植_SylixOS

 1-1显示原理

1.2 时序Timing

   每一个LCD屏幕都会有自定义的时序,这些数据一般都会在屏幕的数据手册中提供,LCD控制器需要根据这些时序产生对应的行场,屏幕在获得正确的行场后才能正常显示。

   LCD显示的时序如图 1-2所示,其中屏幕可见区域大小为xres * yres。

SylixOS的LCD驱动移植_LCD_02

 1-2 LCD显示时序

    显示时序图中有相关概念,如表 1-1所示。

 1-1时序使用的相关概念

名词对应缩写说明
left_marginHFP(Horizontal Front Porch)每行开始时需要插入的像素时钟周期数
right_marginHBP(Horizontal Back Porch)每行结束时需要插入的像素时钟周期数
upper_marginVFP(Vertical Front Porch)垂直同步周期之后帧开头时的无效行数
lower_marginVBP(Vertical Back Porch)本帧结束到下一帧垂直同步周期开始之
前的无效行数
hsync_lenHPW (HSYNC plus width)表示水平同步信号的宽度(单位:像素时钟周期)
vsync_lenVPW (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 Areathd                 800                 DCLK
DCLK frequencyfclk-3050MHz
One Horizontal Lineth8899281143DCLK
HS pulse widththpw148255DCLK
HS Back Porch(Blanking)thb                 88                 DCLK
HS Front Porchthfp140255DCLK
DE mode Blankingth-thd85128512DCLK


 1-3 Vertical timing

Parameter

Symbol

Min. Spec.

Typ. Spec.

Max. Spec.

Unit

Vertical Display Area

tvd                 480                 TH
VS period timetv513525768TH
VS pulse widthtvpw33255TH

VS Back Porch(Blanking)

tvb                 32                 TH
VS Front Porchtvfp113255TH
DE mode Blankingtv-tvd445255

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中的四个函数即可。

SylixOS的LCD驱动移植_SylixOS_03

 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)                                      
};