• LCD驱动程序

    2019-06-27 15:29:15
    学习目标:熟悉TFT LCD的概念,分层驱动工作原理和程序编写。 一、LCD 概念 1. 显示器数据组织格式 1)一幅图像成为一帧,每帧由多行组成,每行由多个像素组成。每个像素的颜色由若干位表示,对于256色LCD,每个...

    学习目标:熟悉TFT LCD的概念,分层驱动工作原理和程序编写。

    一、LCD 概念

    1.  显示器数据组织格式

    1)一幅图像成为一帧,每帧由多行组成,每行由多个像素组成。每个像素的颜色由若干位表示,对于256色LCD,每个像素由8位表示,称为8BPP。

    2)显示器呈Z字行的路线进行扫描显示,使用HSYNC、VSYNC控制扫描和跳转的路径;

    2、操作过程

    1)设置LCD的HSYNC、VSYNC\VCLK等信号的参数,并将帧内存的地址告诉LCD控制器,塔克自动的发起DMA传输,从帧内存中得到图像数据,出现在数据总线VD[23:0]上。我们只需要将显示的图像数据写入帧内存中即可。

    2)图像数据的存储:

    例如:由三原色组建的256色(8BPP)显示模式,使用8位数据表示一个像素的颜色。但特殊的是,这8位数据用于表示在调色板中的索引值。这里的调色板使用256*16的内存,即使用16BPP的显示格式来表示对应各个索引值的颜色。因此,最终在LCD显示的仍为16BPP的数据。

    内存数据和像素对应的关系为:

    其中,P1、P2...为一个个的像素。

    像素在调色板中的数据存放模式16BPP分为两种格式:5:6:5和5:5:5:1.即:

    二、LCD驱动

    1、帧缓冲设备

         frambuffer设备层是对显示设备的一种抽象。其中,帧缓冲是Linux为显示设备提供的一个接口,它把一些显示设备描述成一个缓冲区,允许应用程序通过FrameBuffer定义好的接口访问这些图形设备,从而不用去关心具体的硬件细节。对于帧缓冲设备而言,只要在显示缓冲区与显示点对应的区域写入颜色值,对应的颜色就会自动的在屏幕上显示。

    2、LCD作为一种帧缓冲设备,也是一种标准的字符型设备,对应于文件系统下/dev/fb%d设备文件。

    3、驱动结构

    首先分析一下driver/video/fbmem.c

    1)进入__init fbmem_init(入口函数),主要创建了字符设备“fb”和类,利用cat 命令查看(cat /proc/devices),可看到该目录下的fb,主设备号为29。由于还没有注册LCD驱动,所以没有设备节点,

     1 static int __init fbmem_init(void)
     2 {
     3     create_proc_read_entry("fb", 0, NULL, fbmem_read_proc, NULL);
     4     //创建字符设备"fb"
     5     if (register_chrdev(FB_MAJOR,"fb",&fb_fops))
     6         printk("unable to get major %d for fb devs\n", FB_MAJOR);
     7 
     8     fb_class = class_create(THIS_MODULE, "graphics"); //创建类graphics"
     9     if (IS_ERR(fb_class)) {
    10         printk(KERN_WARNING "Unable to create fb class; errno = %ld\n", PTR_ERR(fb_class));
    11         fb_class = NULL;
    12     }
    13     return 0;
    14 }

    2)fb_fops结构体及open函数

     1 static const struct file_operations fb_fops = {
     2     .owner =    THIS_MODULE,
     3     .read  =    fb_read,
     4     .write =    fb_write,
     5     .ioctl =    fb_ioctl,
     6 #ifdef CONFIG_COMPAT
     7     .compat_ioctl = fb_compat_ioctl,
     8 #endif
     9     .mmap =       fb_mmap,
    10     .open =       fb_open,
    11     .release =    fb_release,
    12 #ifdef HAVE_ARCH_FB_UNMAPPED_AREA
    13     .get_unmapped_area = get_fb_unmapped_area,
    14 #endif
    15 #ifdef CONFIG_FB_DEFERRED_IO
    16     .fsync =    fb_deferred_io_fsync,
    17 #endif
    18 };

    -->fb_open函数:

     1 static int fb_open(struct inode *inode, struct file *file)
     2 {
     3     int fbidx = iminor(inode); //取出设备次设备号
     4     struct fb_info *info;    //定义一个fb_info结构体
     5     int res = 0;
     6 
     7     if (fbidx >= FB_MAX)
     8         return -ENODEV;
     9 #ifdef CONFIG_KMOD
    10     if (!(info = registered_fb[fbidx])) // 在次设备里面得到fb_info结构信息(lcd的驱动信息)赋值给info
    11 try_to_load(fbidx); 12 #endif /* CONFIG_KMOD */ 13 if (!(info = registered_fb[fbidx])) 14 return -ENODEV; 15 if (!try_module_get(info->fbops->owner)) 16 return -ENODEV; 17 file->private_data = info; 18 if (info->fbops->fb_open) { //如果该设备info结构体有open函数,就执行registered_fb[fbidx]->fbops->fb_open
    19 res = info->fbops->fb_open(info,1); 20 if (res) 21 module_put(info->fbops->owner); 22 } 23 return res; 24 }

    由于fb设备(帧缓冲设备)主设备号固定,不同设备以次设备号进行区分,执行该设备open函数时,最终指向的是对应设备的open函数。

    接下来看一下fb_read函数:

     1 static ssize_t
     2 fb_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
     3 {
     4     unsigned long p = *ppos;
     5     struct inode *inode = file->f_path.dentry->d_inode;
     6     int fbidx = iminor(inode);               //取出设备次设备号
     7     struct fb_info *info = registered_fb[fbidx];  //定义并获取设备的fb_info结构体
     8     u32 *buffer, *dst;
     9     u32 __iomem *src;
    10     int c, i, cnt = 0, err = 0;
    11     unsigned long total_size;
    12 
    13     if (!info || ! info->screen_base)
    14         return -ENODEV;
    15 
    16     if (info->state != FBINFO_STATE_RUNNING)
    17         return -EPERM;
    18 
    19     if (info->fbops->fb_read)
    20         return info->fbops->fb_read(info, buf, count, ppos); //如果该设备fb_info结构体有read函数,就执行registered_fb[fbidx]->fbops->fb_read
    21     
    22     total_size = info->screen_size;
    23 
    24     if (total_size == 0)
    25         total_size = info->fix.smem_len;
    26 
    27     if (p >= total_size)
    28         return 0;
    29 
    30     if (count >= total_size)
    31         count = total_size;
    32 
    33     if (count + p > total_size)
    34         count = total_size - p;
    35 
    36     buffer = kmalloc((count > PAGE_SIZE) ? PAGE_SIZE : count,
    37              GFP_KERNEL);
    38     if (!buffer)
    39         return -ENOMEM;
    40 
    41     src = (u32 __iomem *) (info->screen_base + p);
    42 
    43     if (info->fbops->fb_sync)
    44         info->fbops->fb_sync(info);
    45 
    46     while (count) {
    47         c  = (count > PAGE_SIZE) ? PAGE_SIZE : count;
    48         dst = buffer;
    49         for (i = c >> 2; i--; )
    50             *dst++ = fb_readl(src++);
    51         if (c & 3) {
    52             u8 *dst8 = (u8 *) dst;
    53             u8 __iomem *src8 = (u8 __iomem *) src;
    54 
    55             for (i = c & 3; i--;)
    56                 *dst8++ = fb_readb(src8++);
    57 
    58             src = (u32 __iomem *) src8;
    59         }
    60 
    61         if (copy_to_user(buf, buffer, c)) {
    62             err = -EFAULT;
    63             break;
    64         }
    65         *ppos += c;
    66         buf += c;
    67         cnt += c;
    68         count -= c;
    69     }
    71     kfree(buffer);
    73     return (err) ? err : cnt;
    74 }

    由以上程序可知,read的调用和open类似。都依赖于对应设备的fb_info结构体info,在程序中是由registered_fb[fbidx]数组获取的。
    3)最后,看一下registered_fb[fbidx]数组的定义,位于register_framebuffer函数中。

     1 int register_framebuffer(struct fb_info *fb_info)
     2 {
     3     int i;
     4     struct fb_event event;
     5     struct fb_videomode mode;
     6 
     7     if (num_registered_fb == FB_MAX)
     8         return -ENXIO;
     9     num_registered_fb++;
    10     for (i = 0 ; i < FB_MAX; i++)
    11         if (!registered_fb[i])
    12             break;
    13     fb_info->node = i;
    14 
    15     fb_info->dev = device_create(fb_class, fb_info->device,MKDEV(FB_MAJOR, i), "fb%d", i);//创建设备节点,名称为fbi,主设备号为FB_MAJOR 29,次设备号为i
    17     if (IS_ERR(fb_info->dev)) {
    18         /* Not fatal */
    19         printk(KERN_WARNING "Unable to create device for framebuffer %d; errno = %ld\n", i, PTR_ERR(fb_info->dev));
    20         fb_info->dev = NULL;
    21     } else
    22         fb_init_device(fb_info);
    23 
    24     if (fb_info->pixmap.addr == NULL) {
    25         fb_info->pixmap.addr = kmalloc(FBPIXMAPSIZE, GFP_KERNEL);
    26         if (fb_info->pixmap.addr) {
    27             fb_info->pixmap.size = FBPIXMAPSIZE;
    28             fb_info->pixmap.buf_align = 1;
    29             fb_info->pixmap.scan_align = 1;
    30             fb_info->pixmap.access_align = 32;
    31             fb_info->pixmap.flags = FB_PIXMAP_DEFAULT;
    32         }
    33     }    
    34     fb_info->pixmap.offset = 0;
    35 
    36     if (!fb_info->pixmap.blit_x)
    37         fb_info->pixmap.blit_x = ~(u32)0;
    38 
    39     if (!fb_info->pixmap.blit_y)
    40         fb_info->pixmap.blit_y = ~(u32)0;
    41 
    42     if (!fb_info->modelist.prev || !fb_info->modelist.next)
    43         INIT_LIST_HEAD(&fb_info->modelist);
    44 
    45     fb_var_to_videomode(&mode, &fb_info->var);
    46     fb_add_videomode(&mode, &fb_info->modelist);
    47     registered_fb[i] = fb_info; //赋值到registered_fb[i]数组中
    48 
    49     event.info = fb_info;
    50     fb_notifier_call_chain(FB_EVENT_FB_REGISTERED, &event);
    51     return 0;
    52 }

    接下来看一下fb硬件驱动程序,以/drivers/video/s3c2410fb.c为例。

    1)驱动入口

     1 static struct platform_driver s3c2410fb_driver = {
     2     .probe        = s3c2410fb_probe,
     3     .remove        = s3c2410fb_remove,
     4     .suspend    = s3c2410fb_suspend,
     5     .resume        = s3c2410fb_resume,
     6     .driver        = {
     7         .name    = "s3c2410-lcd",
     8         .owner    = THIS_MODULE,
     9     },
    10 };
    11 
    12 int __devinit s3c2410fb_init(void)
    13 {
    14     return platform_driver_register(&s3c2410fb_driver);
    15 }
    16 
    17 static void __exit s3c2410fb_cleanup(void)
    18 {
    19     platform_driver_unregister(&s3c2410fb_driver);
    20 }

    2)当平台设备的驱动和设备匹配后,会直接调用prob函数。

      1 static int __init s3c2410fb_probe(struct platform_device *pdev)
      2 {
      3     struct s3c2410fb_info *info;
      4     struct fb_info       *fbinfo;
      5     struct s3c2410fb_hw *mregs;
      6     int ret;
      7     int irq;
      8     int i;
      9     u32 lcdcon1;
     10 
     11     mach_info = pdev->dev.platform_data;  //获取lcd设备信息
     12     if (mach_info == NULL) {
     13         dev_err(&pdev->dev,"no platform data for lcd, cannot attach\n");
     14         return -EINVAL;
     15     }
     25     fbinfo = framebuffer_alloc(sizeof(struct s3c2410fb_info), &pdev->dev); //分配fb_info结构体
     26     if (!fbinfo) {
     27         return -ENOMEM;
     28     }
         //设置fb_info结构体
    31 info = fbinfo->par; 32 info->fb = fbinfo; 33 info->dev = &pdev->dev; 34 35 platform_set_drvdata(pdev, fbinfo); 37 dprintk("devinit\n"); 39 strcpy(fbinfo->fix.id, driver_name); 41 memcpy(&info->regs, &mach_info->regs, sizeof(info->regs)); 43 /* Stop the video and unset ENVID if set */ 44 info->regs.lcdcon1 &= ~S3C2410_LCDCON1_ENVID; 45 lcdcon1 = readl(S3C2410_LCDCON1); 46 writel(lcdcon1 & ~S3C2410_LCDCON1_ENVID, S3C2410_LCDCON1); 47 48 info->mach_info = pdev->dev.platform_data; 49 50 fbinfo->fix.type = FB_TYPE_PACKED_PIXELS; 51 fbinfo->fix.type_aux = 0; 52 fbinfo->fix.xpanstep = 0; 53 fbinfo->fix.ypanstep = 0; 54 fbinfo->fix.ywrapstep = 0; 55 fbinfo->fix.accel = FB_ACCEL_NONE;
    64 fbinfo->fbops = &s3c2410fb_ops; 65 fbinfo->flags = FBINFO_FLAG_DEFAULT; 66 fbinfo->pseudo_palette = &info->pseudo_pal; 67 73 74 fbinfo->var.upper_margin = S3C2410_LCDCON2_GET_VBPD(mregs->lcdcon2) + 1; 75 fbinfo->var.lower_margin = S3C2410_LCDCON2_GET_VFPD(mregs->lcdcon2) + 1; 76 fbinfo->var.vsync_len = S3C2410_LCDCON2_GET_VSPW(mregs->lcdcon2) + 1; 77 78 fbinfo->var.left_margin = S3C2410_LCDCON3_GET_HFPD(mregs->lcdcon3) + 1; 79 fbinfo->var.right_margin = S3C2410_LCDCON3_GET_HBPD(mregs->lcdcon3) + 1; 80 fbinfo->var.hsync_len = S3C2410_LCDCON4_GET_HSPW(mregs->lcdcon4) + 1; 81 90 fbinfo->fix.smem_len = mach_info->xres.max * 91 mach_info->yres.max * 92 mach_info->bpp.max / 8; 93 94 for (i = 0; i < 256; i++) 95 info->palette_buffer[i] = PALETTE_BUFF_CLEAR; 96 97 if (!request_mem_region((unsigned long)S3C24XX_VA_LCD, SZ_1M, "s3c2410-lcd")) { 98 ret = -EBUSY; 99 goto dealloc_fb; 100 } 103 dprintk("got LCD region\n"); 104 //硬件相关的操作,中断、时钟.... 105 ret = request_irq(irq, s3c2410fb_irq, IRQF_DISABLED, pdev->name, info); 106 if (ret) { 107 dev_err(&pdev->dev, "cannot get irq %d - err %d\n", irq, ret); 108 ret = -EBUSY; 109 goto release_mem; 110 } 111 112 info->clk = clk_get(NULL, "lcd"); 113 if (!info->clk || IS_ERR(info->clk)) { 114 printk(KERN_ERR "failed to get lcd clock source\n"); 115 ret = -ENOENT; 116 goto release_irq; 117 } 118 119 clk_enable(info->clk); 120 dprintk("got and enabled clock\n"); 121 122 msleep(1); 123 124 /* Initialize video memory */ 125 ret = s3c2410fb_map_video_memory(info); 126 if (ret) { 127 printk( KERN_ERR "Failed to allocate video RAM: %d\n", ret); 128 ret = -ENOMEM; 129 goto release_clock; 130 } 137 ret = register_framebuffer(fbinfo);//注册fb_info结构体 138 if (ret < 0) { 139 printk(KERN_ERR "Failed to register framebuffer device: %d\n", ret); 140 goto free_video_memory; 141 } 142 143 /* create device files */ 144 device_create_file(&pdev->dev, &dev_attr_debug); 145 146 printk(KERN_INFO "fb%d: %s frame buffer device\n", 147 fbinfo->node, fbinfo->fix.id); 148 149 return 0; 150 151 free_video_memory: 152 s3c2410fb_unmap_video_memory(info); 153 release_clock: 154 clk_disable(info->clk); 155 clk_put(info->clk); 156 release_irq: 157 free_irq(irq,info); 158 release_mem: 159 release_mem_region((unsigned long)S3C24XX_VA_LCD, S3C24XX_SZ_LCD); 160 dealloc_fb: 161 framebuffer_release(fbinfo); 162 return ret; 163 }

    小结:

    根据驱动结构和程序源码分析可知,lcd驱动程序需要完成以下几部分:

    1)分配一个fb_info结构体:由函数framebuffer_alloc() 完成 ;

    2)设置fb_info结构体;

    3)注册fb_inforegister_framebuffer();

    4)硬件相关的操作 

    转载于:https://www.cnblogs.com/lxl-lennie/p/10248889.html

    展开全文
  • LCD驱动开发思路

    2014-05-22 14:55:48
    今天看了下LCD驱动开发的代码,发现思路

    今天看了下LCD驱动开发的代码,发现思路是这样的。

    如以下任务,需要用主控芯片Cortext M3架构的MCU,驱动字符液晶模组tc1602。另外,配以串并转换芯片74HC595。给你以上这样3个器件,你要怎么让LCD动态显示一串字符和时间?

    1.首先,为了节省IO口资源,MCU一般会使用SSI(同步串行传输接口)传指令和数据。而SSI的协议有多种,其中最著名的就是SPI接口。所以,你必须得懂得SSI的驱动怎么写。

    2.因为字符液晶模组一般是并口的,可以是4位或者8位或者更多,所以MCU和LCD之间的数据传输,要有串并转换芯片来实现,如74HC595。

    3.串口芯片有什么特征?首先,串口输入部分是用移位寄存器,而输出部分是并口存储器,很明显,输出比较慢,要等待串行输入结束才可以输出。所以输出和输入是两个时钟,在SSI编程时,会用两个不同的延时MCU端口来发时钟给串并转换芯片,以同步输入和输出端的数据(如下图的SCK和RCK)。

    4.那么MCU发送数据给LCD的流程是怎样的呢?如下:

    (1)首先,MCU把数据串行地发给串并转换芯片。这时,要根据串并转换芯片的要求,允许一些数据操作端口。

    (2)其次,根据串并转换芯片的输出端的时钟,允许串并转换芯片的数据从并口输出。但在这个操作之前,MCU要在相应的端口提醒LCD接收数据

    5.这有点需要注意的地方,那就是MCU既有控制LCD读取的端口,也有控制串并转换芯片读写的端口。如下图的R/W   RS  OE

     

    总结:可见,给LCD发数据,主要是给串并转换芯片发数据,在写函数的时候,其实是环环嵌套的,打个比方:

    函数嵌套如下:

    LCD字符串send->调用LCD字节SEND->调用SSI_SEND->调用纯字节send

    SSI_SEND和纯字节SEND函数有什么区别呢?区别就是纯send,只是把数据写入寄存器,而协议SSI_SEND需要把相应的位OE拉高或者拉低,还要判断芯片是否busy等。LCD_SEND也是这样,需要在SSI_SEND的基础上给控制端口拉高或拉低电平。

    可见,驱动开发=写命令和数据寄存器+写控制端口+准确设计延时。而上面提到的嵌套,就是因为普通的写数据寄存器都依赖写控制端口+写命令寄存器+时延函数,所以会出现很多调用。但如上所述,原理其实很简单。


    以上说的参考代码如下(《CortexM3内核微控制器 快速入门与应用》):

    //*********************************************************************************
     //博圆周立功单片机&嵌入式系统培训[博圆单片机初学之路]
     //Email: bymcupx@126.com 
     //文件名: TC1602_lcd.h
     //功能: TC1602液晶驱动程序
     //开发者:刘同法
     //日期: 2008年9月15日
    //---------------------------------------------------------------------------------
    #include "hw_memmap.h"
    #include "hw_types.h"
    #include "ssi.h"
    #include "gpio.h"
    #include "sysctl.h"
    #include "systick.h"
    
    
    #define uchar unsigned char  //映射uchar 为无符号字符
    #define uint  unsigned int   //映射uint  为无符号整数 
    
    #define RS GPIO_PIN_0  //PA0 用于命令与数据选择RS=0选择发送命令,RS=1选择发送数据
    #define RW GPIO_PIN_1  //PA1 读/写数据选择脚 RW=0选择命令或数据写,Rw=1选择命令或数据读
    #define E  GPIO_PIN_6  //PB6 使能线
    
    #define GCS    GPIO_PIN_3  //SPI    片选              PA3
    #define GDIO   GPIO_PIN_5  //SPIDAT[MOSI] 数据         PA5
    #define GSCLK  GPIO_PIN_2  //SPICLK 时钟                PA2
    uchar uchBuff;
    
    void GSEND_DATA8(uchar chSDAT);
    void GSEND_COM8(uchar chSDAT);
    /****************************************************************************
    * 名称:Delay50uS
    * 功能:50uS软件延时
    * 说明:用户根据自已的系统相应更改
    ****************************************************************************/
    void Delay_uS(uint nNum2)
    { 
      unsigned long i;
      i=SysCtlClockGet()/1000000;  //获取系统1us的时间*1得出1个us
       i=i*nNum2;
       while(i--); 
    }
    //-------------------------------------------------------------------------------
    //延时函数
    //说明:每执行一次大约100us
    //------------------------------------------------------------------------------
    void DelayDS(uint nNum)
    { 
      unsigned long i;
      i=SysCtlClockGet()/1000000*100;  //获取系统1us的时间*100得出100个us
       i=i*nNum;
       while(i--);   
    }
    //------------------------------------------------------------------------------
    // 函数名称  GPio_Initi_PA
    // 函数功能  启动外设GPIO输入输出
    // 输入参数  无。
    // 输出参数  无。
    //-----------------------------------------------------------------------------
    void GPio_Initi_PA(void)
    {
        // 使能GPIO B口外设。用于指示灯
       SysCtlPeripheralEnable( SYSCTL_PERIPH_GPIOA | SYSCTL_PERIPH_GPIOB );   
       
       GPIODirModeSet(GPIO_PORTA_BASE, RS | RW | E,GPIO_DIR_MODE_OUT);
        
       GPIODirModeSet(GPIO_PORTB_BASE, E,GPIO_DIR_MODE_OUT);
       
        // 设定 GPIO A 2~5 引脚为使用外设功能 GPIO_DIR_MODE_HW由硬件进行控制
       GPIODirModeSet(GPIO_PORTA_BASE, (GPIO_PIN_2 | GPIO_PIN_3 | 
                                  GPIO_PIN_5), GPIO_DIR_MODE_OUT); //HW[]  
       
    }
    //-----------------------------------------------------------------------------
    //函数名称: Write_Command()
    //函数功能: 向TC1602写入命令[将ACC寄存器命令内容发送到P1口]
    //入口参数:chComm[送递要发送的命令]
    //出口参数:无
    //编程序:  刘同法
    //编程序日期:2008年9月15日
    //------------------------------------------------------------------------------
    void Write_Command(uchar chComm)
    {
          //RS=0;    //CLR RS 命令与数据选择脚 RS=0选择发送命令,RS=1选择发送数据
         GPIOPinWrite( GPIO_PORTA_BASE, RS,~RS );
          //RW=0;  //CLR RW 读/写数据选择脚 RW=0选择命令或数据写,Rw=1选择命令或数据读
         GPIOPinWrite( GPIO_PORTA_BASE, RW,~RW );
        // E=1;           //SETB E 开启数据锁存脚[数据锁存允许]
         GPIOPinWrite( GPIO_PORTB_BASE, E,E );
     
         GSEND_COM8(chComm);
         
         //E=0;    //CLR E  关闭数据锁存脚并向对方锁存数据[告诉对方数据已发送请接收]
         GPIOPinWrite( GPIO_PORTB_BASE, E,~E );
         DelayDS(10);  //延时1ms 100*10=1000us=1ms
     
         
    }
    //--------------------------------------------------------------
    //函数名称: Write_Data()
    //函数功能: 向TC1602写入数据[将ACC寄存器数据内容发送到P1口]
    //入口参数:chData[送递要发送的数据]
    //出口参数:无
    //编程序:  刘同法
    //编程序日期:2008年9月15日
    //----------------------------------------------------------------
    void Write_Data(uchar chData)
    {
          //RS=1;    //SETB RS 命令与数据选择脚 RS=0选择发送命令,RS=1选择发送数据
         GPIOPinWrite( GPIO_PORTA_BASE, RS,RS );
          //RW=0;    //CLR RW  读/写数据选择脚 RW=0选择命令或数据写,Rw=1选择命令或数据读
         GPIOPinWrite( GPIO_PORTA_BASE, RW,~RW );
          //E=1;     //SETB E 开启数据锁存脚[数据锁存允许]
         GPIOPinWrite( GPIO_PORTB_BASE, E,E );
     
         GSEND_COM8(chData);
         //E=0;    //CLR E 关闭数据锁存脚并向对方锁存数据[告诉对方数据已发送请接收]
         GPIOPinWrite( GPIO_PORTB_BASE, E,~E );
         
         DelayDS(5);  //延时0.5ms 5*100=500us=0.5ms
          
    }     
    //--------------------------------------
    //函数名称:Busy_tc1602()
    //函数功能:判忙子程序[用于判断LCD是否在忙于写入,如LCD在忙于别的事情,那就等LCD忙完后才操作]
    //入出参数:无
    //编程序:  刘同法
    //编程序日期:2008年9月15日
    //-----------------------------------------
    void Busy_tc1602()
    {
         uchar ACC; 
        // RS=0;  //CLR RS
         GPIOPinWrite( GPIO_PORTA_BASE, RS,~RS );
         //RW=1;  //SETB RW  设为从TC1602中读取数据
         GPIOPinWrite( GPIO_PORTA_BASE, RW,RW );
         do
         { 
           //E=1;   //SETB E
           GPIOPinWrite( GPIO_PORTB_BASE, E,E );
           //ACC=P1; //MOV A,P1 读取P1口数据
           //ACC=Lm101_Ssi_Rcv();
           //E=0;   //CLR E   锁存数据
           GPIOPinWrite( GPIO_PORTB_BASE, E,~E );
           ACC=ACC&0x80; //ANL A,#80H
           
          }while(ACC);    //JNZ TT0
     
         //POP   ACC
     
    }  
    //-------------------------------------------------------------
    //函数名称: Delay_1602()
    //函数功能: 用于毫秒级延时
    //入口参数:nDTime[用于传递延时间单位为ms,如果nDTime=1即为1ms]
    //出口参数: 无
    //编程序:  刘同法
    //编程序日期:2008年9月15日
    //---------------------------------------------------------------
    void Delay_1602(uint nDTime)
    {
       uint a;
       for(a=0;a<nDTime;a++)
          DelayDS(10);  //取每循环一次为1ms
       
    }   
    //------------------------------------------------------------
    //下面是TC1602外用程序
    //-------------------------------------------------------------
    //函数名称: Init_TC1602()(外部调用)
    //函数功能: 初始化TC1602液晶显示屏[TC1602必须要初使化才能使用]
    //入出参数:无
    //编程序:  刘同法
    //编程序日期:2008年9月15日
    //-----------------------------------------------------------
    void Init_TC1602()
    {
          //初始化SPI 
          GPio_Initi_PA();  
          //共延时15ms
          Delay_1602(15);
    
          //发送命令
          Write_Command(0x38);
          Delay_1602(5);  //LCALL TME0    ;延时5ms
          
          //重发一次
          Write_Command(0x38);
          Delay_1602(5);  //LCALL TME0    ;延时5ms
          
          Write_Command(0x38);  //设置为8总线16*2 5*7点阵
          Write_Command(0x01);  //发送清屏命令
          Write_Command(0x06);  //设读写字符时地址加1,且整屏显示不移动
          Write_Command(0x0F);  //开显示,开光标显示,光标和光标所在的字符闪烁
          Delay_1602(5);  //LCALL TME0    ;延时5ms
          
    }      
    //------------------------------------------------------
    //函数名称: Cls()
    //函数功能: 用于清屏
    //入出参数:元
    //编程序:  刘同法
    //编程序日期:2008年9月15日
    //--------------------------------------------------------
    void Cls()
    {
      Write_Command(0x01);  //发送清屏命令  
    }
    //--------------------------------------------------------
    //下面是应用部分
    //--------------------------------------------------------
    //函数名称: Send_String_1602()
    //函数功能: 用于向TC1602发送字符串
    //入口参数:chCom[传送命令行列] lpDat[传送数据串不要超个16个字符] nCount[传送发送数据的个数]
    //出口参数: 无
    //编程序:  刘同法
    //编程序日期:2008年9月15日
    //---------------------------------------------------------
    void Send_String_1602(uchar chCom,uchar *lpDat,uint nCount)
    {
       uint i=0;
       
       Write_Command(chCom);  //发送起始行列号
       Delay_1602(10);
       for(i=0;i<nCount;i++)
       {
         Write_Data(*lpDat);  //发送数据
         lpDat++;             //让指针向前进1[加1]读取下一个字符
       }
      Delay_1602(20);  
    }
    //--------------------------------------------------------
    //函数名称: Send_Data_1602()
    //函数功能: 用于向TC1602发送整型数
    //入口参数:chCom[传送命令行列] nDat[传送整型数据] nCount[传送发送数据的个数]
    //出口参数: 无
    //编程序:  刘同法
    //编程序日期:2008年9月15日
    //---------------------------------------------------------
    void Send_Data_1602(uchar chCom,uint nData,uint nCount)
    {
       uint nInt,nInt1,nInt2;  //用来存放数据
       uchar chC[5];
       if(nCount>4)return ;   //判断是否大于4个,如果大于4个就反回
                              //控制5个不准显示
       if(nCount==1)
       { chC[0]=nData%10;
         chC[0]|=0x30;      //使用逻辑或加入显示字符因为tc1602使用的是ASCII码作显示
         Write_Command(chCom);  //发送起始行列号
         Write_Data(chC[0]);  //发送数据
        }
        else if(nCount==2)
        {
            nInt=nData%100;
            chC[0]=nInt/10;
            chC[0]|=0x30;      //使用逻辑或加入显示字符因为tc1602使用的是ASCII码作显示
            chC[1]=nInt%10;
            chC[1]|=0x30;      //同chC[1]=chC[1]|0x30;  逻辑或运算,但是千万不能用加法运算,否则得出来的数是乱码
            Write_Command(chCom);  //发送起始行列号
            Write_Data(chC[0]);  //发送数据
            Write_Data(chC[1]);  //发送数据
          }
          else if(nCount==3)
          {
            nInt=nData%1000;
            chC[0]=nInt/100;
            chC[0]|=0x30;          //逻辑或运算变为ASCII美国国家标准信息码用于显示
            nInt=nInt%100;
            chC[1]=nInt/10;
            chC[1]|=0x30;
            chC[2]=nInt%10;
            chC[2]|=0x30;
            Write_Command(chCom);  //发送起始行列号
            Write_Data(chC[0]);  //发送数据
            Write_Data(chC[1]);  //发送数据
            Write_Data(chC[2]);  //发送数据
           }
           else if(nCount==4)      
           {
            nInt=nData%10000;
            nInt1=nInt/100;   //取商
            nInt2=nInt%100;   //取余
            chC[0]=nInt1/10;
            chC[0]|=0x30;
            chC[1]=nInt1%10;
            chC[1]|=0x30;
            chC[2]=nInt2/10;
            chC[2]|=0x30;
            chC[3]=nInt2%10;
            chC[3]|=0x30; 
            Write_Command(chCom);  //发送起始行列号
            Write_Data(chC[0]);  //发送数据
            Write_Data(chC[1]);  //发送数据
            Write_Data(chC[2]);  //发送数据
            Write_Data(chC[3]);  //发送数据
            }else;           
          
       return ;   
    }
    //------------------------------------------------------------------------------------
    //为自编写字模用WRCGRAM子程序写入1602LCD夜晶显示器CGRAM存储器
    uchar ZhiMou[]={0x08,0x0F,0x12,0x0F,0x0A,0x1F,0x02,0x02, //年
    	                 0x0F,0x09,0x0F,0x09,0x0F,0x09,0x11,0x00, // ;月
    	                 0x0F,0x09,0x09,0x0F,0x09,0x09,0x0F,0x00}; //;日
    	                 
    uchar  chTB1[6]={0x42,0x59,0x50,0x58,0x42};  //   ;BYPXB
    uchar  chTB2[14]={0x6C,0x74,0x66,0x32,0x30,0x30,0x35,0x00,0x31,0x30,0x01,0x31,0x02};
                        //  l   t     f   2    0    0     5   年   1    0    月   1    日	
    uchar  chTB4[8]={0x62,0x79,0x6D,0x63,0x75,0x70,0x78}; //bymcupx
    uchar  chTB5[8]={0x32,0x30,0x30,0x35,0x31,0x30,0x39}; //2005109
    uchar  chTB6[10]={'L','i','u','T','o','n','g','F','a'}; 
                  	                 
    //-------------------------------------------------------------------------------
    //写入用户汉字字模数据子程序	
    //------------------------------------------------------------------------------
    //函数名称:Write_WRCGRAM()
    //功能:[创建用户字模地址从00~07共8个,且只能创建8个]把要建立的汉字字模数据写入用户字模存储器[CGRAM]
    //入出参数:无
    //编程序:  刘同法
    //编程序日期:2008年9月15日
    //------------------------------------------------------------------------------
    void Write_WRCGRAM()
    {
         uint i=0;
         
         Write_Command(0x40);  //发送命令从00H地址开始存放字模
         
         for(i=0;i<24;i++)    //8*8*8=24
           Write_Data(ZhiMou[i]);  //发送字模数据
    
    }
    //----------------------------------------------------------------------------
    //下面是74HC595驱动程序
    //用于向74HC595发送数据
    //-----------------------------------------------------------------------------
    //程序名称:GSEND_DATA8()
    //程序功能:用于发送8位数据[单字节发送子程序]
    //入口参数: chSDAT[传送要发送的数据]
    //出口参数:无
    //编程:刘同法
    //时间:2008年6月10日
    //说明:ZLG7289使用的是SPI同步串行通信,本子程序已是串行通信程序,
    //     再加上CS片选动作即可实施SPI同步串行通信
    //     数据发送高位在前
    //---------------------------------------------------------------------------------
    void GSEND_DATA8(uchar chSDAT)
    {
            
            GPIODirModeSet(GPIO_PORTA_BASE, GPIO_PIN_5 , GPIO_DIR_MODE_OUT);
    
            uint  JSQ1=8;   //准备发送8次
            Delay_uS(50);    //调用50us延时子程序
            uchBuff=chSDAT;
            
         SD:
            //先将高7位发送出去
            if(uchBuff&0x80)
              GPIOPinWrite( GPIO_PORTA_BASE, GDIO, GDIO );  //如果是1就发1
             else 
               GPIOPinWrite( GPIO_PORTA_BASE, GDIO, 0x00 ); // 如果是0就发0
           //将高6位移到高7位准备下一次发送
            uchBuff=uchBuff<<1; //然后向左移一位准备下一次的发送
           
            Delay_uS(2);
            
            GPIOPinWrite( GPIO_PORTA_BASE, GSCLK, GSCLK ); //准备数据锁存 =1
            Delay_uS(8);    //12微秒延时
            GPIOPinWrite( GPIO_PORTA_BASE, GSCLK, 0x00 );  //在脉冲的下沿锁存数据 
            Delay_uS(8);    //12微秒延时锁存数据
             
            if(--JSQ1)goto SD;
    
            GPIOPinWrite( GPIO_PORTA_BASE, GDIO, 0x00 );//清零数据线 
                  
           return;
    }
    //----------------------------------------------------  
    //程序名称:GSEND_COM8()
    //程序功能:单字节命令发送子程序[发送纯指令的子程序]
    //入口参数: chSDAT[传送要发送的数据]
    //出口参数:无
    //编程:刘同法
    //时间:2008年6月10日
    //----------------------------------------------------        
    void GSEND_COM8(uchar chSDAT)
    {
          
          GPIOPinWrite( GPIO_PORTA_BASE, GCS, 0x00 );//GCS=0;
        
          GPIOPinWrite( GPIO_PORTA_BASE, GSCLK, 0x00 ); // GSCLK=0;
          
          GSEND_DATA8(chSDAT);
          
          GPIOPinWrite( GPIO_PORTA_BASE, GCS, GCS ); // GCS=1;
        
    }             
    //-----------------------------------------------------------------------------
    //******************************************************************************
    



    展开全文
  • 在上一篇博客我们可以看到,作为开发者,我们需要调用register_framebuffer向核心层注册一个fb_info,还在博客中指出了,核心层的module_init模块入口函数中创建了主设备号,同时也创建了一个class,但是没有在这个...

    在上一篇博客我们可以看到,作为开发者,我们需要调用register_framebuffer向核心层注册一个fb_info,还在博客中指出了,核心层的module_init模块入口函数中创建了主设备号,同时也创建了一个class,但是没有在这个class基础上创建device(设备节点),而是在register_framebuffer中创建device,所以我们可以从这里对主设备号、file_operation和次设备号之间的关系有进一步认识。注册fb_info之前我们需要对fb_info进行初始化,当然这不是全部的工作。
    本篇博客LCD驱动开发的环境;内核版本基于2.6.22,soc基于s3c2440,LCD基于HITACHI的TX09D70VM1CBA
    整理一下思路
    因为需要向核心层注册fb_info,所以首先我们需要申请一个fb_info,用的函数接口是framebuffer_alloc

    struct fb_info *s3c_lcd
    static __init int lcd_init(void)
    {
    	/*申请一个fb_info*/
    	s3c_lcd = framebuffer_alloc(0, NULL);
    }
    

    申请fb_info成功后,我们需要对其进行初始化

    struct fb_info {
    	int node;
    	int flags;
    	struct fb_var_screeninfo var;	/* Current var */
    	struct fb_fix_screeninfo fix;	/* Current fix */
    	struct fb_monspecs monspecs;	/* Current Monitor specs */
    	struct work_struct queue;	/* Framebuffer event queue */
    	struct fb_pixmap pixmap;	/* Image hardware mapper */
    	struct fb_pixmap sprite;	/* Cursor hardware mapper */
    	struct fb_cmap cmap;		/* Current cmap */
    	struct list_head modelist;      /* mode list */
    	struct fb_videomode *mode;	/* current mode */
    
    
    	struct fb_ops *fbops;
    	struct device *device;		/* This is the parent */
    	struct device *dev;		/* This is this fb device */
    	int class_flag;                    /* private sysfs flags */
    
    	char __iomem *screen_base;	/* Virtual address */
    	unsigned long screen_size;	/* Amount of ioremapped VRAM or 0 */ 
    	void *pseudo_palette;		/* Fake palette of 16 colors */ 
    #define FBINFO_STATE_RUNNING	0
    #define FBINFO_STATE_SUSPENDED	1
    	u32 state;			/* Hardware state i.e suspend */
    	void *fbcon_par;                /* fbcon use-only private area */
    	/* From here on everything is device dependent */
    	void *par;	
    };
    

    首先我们要设置固定参数,看下固定参数涉及到哪些内容

    struct fb_fix_screeninfo {
    	char id[16];			/* identification string eg "TT Builtin" */
    	unsigned long smem_start;	/* Start of frame buffer mem */
    					/* (physical address) */
    	__u32 smem_len;			/* Length of frame buffer mem */
    	__u32 type;			/* see FB_TYPE_*		*/
    	__u32 type_aux;			/* Interleave for interleaved Planes 这里不需要设置 */
    	__u32 visual;			/* see FB_VISUAL_*		*/ 
    	__u16 xpanstep;			/* zero if no hardware panning 设置为0 */
    	__u16 ypanstep;			/* zero if no hardware panning 设置为0 */
    	__u16 ywrapstep;		/* zero if no hardware ywrap   设置为0 */
    	__u32 line_length;		/* length of a line in bytes    */
    	unsigned long mmio_start;	/* Start of Memory Mapped I/O   */
    					/* (physical address) */
    	__u32 mmio_len;			/* Length of Memory Mapped I/O  */
    	__u32 accel;			/* Indicate to driver which	*/
    					/*  specific chip/card we have	*/
    	__u16 reserved[3];		/* Reserved for future compatibility */
    };
    

    因为这些参数都有注释,还是比较容易理解的。
    固定参数设置

    	strcpy(s3c_lcd->fix.id, "mylcd");
    	s3c_lcd->fix.smem_len = 240*320*16/8;
    	s3c_lcd->fix.type     = FB_TYPE_PACKED_PIXELS;
    	s3c_lcd->fix.visual   = FB_VISUAL_TRUECOLOR; /* TFT */
    	s3c_lcd->fix.line_length = 240*2;
    

    设置可变参数fb_var_screeninfo

    struct fb_var_screeninfo {
    	__u32 xres;			/* visible resolution	分辨率	*/
    	__u32 yres;
    	__u32 xres_virtual;		/* virtual resolution	虚拟分辨率,就是我们可以在硬件的分辨率基础上自定义降低分辨率	*/
    	__u32 yres_virtual;
    	__u32 xoffset;			/* offset from virtual to visible 虚拟分辨率和物理分辨率的offset,我们这里为了简化问题,将虚拟分辨率设置和物			理分辨率相同*/
    	__u32 yoffset;			/* resolution			*/
    
    	__u32 bits_per_pixel;		/* guess what			*/
    	__u32 grayscale;		/* != 0 Graylevels instead of colors 灰度值*/
    
    	struct fb_bitfield red;		/* bitfield in fb mem if true color, */
    	struct fb_bitfield green;	/* else only length is significant */
    	struct fb_bitfield blue;	//这里实际上是对bbp模式来设置,我们采用的是16位,布局565
    	struct fb_bitfield transp;	/* transparency		透明色	*/	
    
    	__u32 nonstd;			/* != 0 Non standard pixel format */
    
    	__u32 activate;			/* see FB_ACTIVATE_*		*/
    
    	__u32 height;			/* height of picture in mm    */
    	__u32 width;			/* width of picture in mm     */
    
    	__u32 accel_flags;		/* (OBSOLETE) see fb_info.flags */
    	//由于我们将会在配置lcd controller时配置时序,所以我把和时序相关的参数删掉
    };
    

    虚拟参数设置为

    	/* 2.2 设置可变的参数 */
    	s3c_lcd->var.xres           = 240;
    	s3c_lcd->var.yres           = 320;
    	s3c_lcd->var.xres_virtual   = 240;
    	s3c_lcd->var.yres_virtual   = 320;
    	s3c_lcd->var.bits_per_pixel = 16;
    
    	/* RGB:565 */
    	s3c_lcd->var.red.offset     = 11;
    	s3c_lcd->var.red.length     = 5;
    	
    	s3c_lcd->var.green.offset   = 5;
    	s3c_lcd->var.green.length   = 6;
    
    	s3c_lcd->var.blue.offset    = 0;
    	s3c_lcd->var.blue.length    = 5;
    
    	s3c_lcd->var.activate       = FB_ACTIVATE_NOW;
    

    还需要设置一个file_operation,fops的实现稍后处理

    s3c_lcd->fbops   = &s3c_lcdfb_ops;
    

    现在是核心部分,硬件的配置
    先来看下LCD显示的原理 我本来想自己总结一下,但是发现有个前辈写得太好了,链接如下
    https://blog.csdn.net/JerryGou/article/details/79952882#commentBox

    所以我们要做什么呢?
    首先我们需要根据根据LCD的时序图和soc的lcd controller时序图来对两者进行适配
    然后,在之前我们把固定参数中的framebuffer虚拟地址没有进行设置,这是因为我们直接把这个信息配置到LCD控制器
    1、配置GPIO为LCD所用
    在这里插入图片描述
    在这里插入图片描述
    可以看到,需要配置是GPC0~GPC15 和GPD0-15

    static volatile unsigned long *gpbcon;
    static volatile unsigned long *gpbdat;
    static volatile unsigned long *gpccon;
    static volatile unsigned long *gpdcon;
    static volatile unsigned long *gpgcon;
    static volatile struct lcd_regs* lcd_regs;
    
    	/* 对gpio进行逐个ioremap*/
    	gpbcon = ioremap(0x56000010, 8);
    	gpbdat = gpbcon+1;
    	gpccon = ioremap(0x56000020, 4);
    	gpdcon = ioremap(0x56000030, 4);
    	gpgcon = ioremap(0x56000060, 4);
    	
    	//配置GPC和GPD 1010对应着16进制的a
    	gpccon = 0xaaaaaaaa;
    	gpdcon = 0xaaaaaaaa;
    	
    	//从背光电路上可以看到,要想让背光点电路,需要配置keyboard为输出引脚,keyboard对应的是gpb0
    	gpbcon &= ~(0x3 << 0); //因为我们需要同时写入0和1,如果在写入之前我们不知道对应位的内容,而写入0和1的方式是不一样的,所以先清0
    	gpbcon |= 0x01;	
    	//当把gpio配置为输入、输出引脚时,需要配置相应dat寄存器来确认是输入(输出)高电平还是低电平
    	gpbdat &= ~0x1;
    	
    	//配置电源,从电源电路图可以看到对应着gpg4
    	gpgcon |= (0x3 << 4*2);
    

    配置GPIO的过程包括配置配置各个管脚对应的gpio为LCD所用,还要配置背光电路和电源
    现在来配置lcd controller的各个寄存器
    配置之前我们同样需要对将要使用的物理地址空间ioremmap,由于我们将要配置的寄存器在内存上是位于几乎连续的地址空间,所以可以直接把这个整块的连续地址空间进行映射处理,中间如果有不连续的,可以用保留位来填充。

    /*可以看到BLUELUT到下一个寄存器之间有一段不连续的空间,按照10机制来算有36个字节大小的空洞,所以我们需要用9位unsigned long 保留位来占据*/
    /*LCD Controller*/
    #define	    LCDCON1  	             __REG(0X4D000000)  //LCD control 1                          
    #define	    LCDCON2  	             __REG(0X4D000004)  //LCD control 2                          
    #define	    LCDCON3  	             __REG(0X4D000008)  //LCD control 3                          
    #define	    LCDCON4  	             __REG(0X4D00000C)  //LCD control 4                          
    #define	    LCDCON5  	             __REG(0X4D000010)  //LCD control 5                          
    #define	    LCDSADDR1	             __REG(0X4D000014)  //STN/TFT: frame buffer start address 1  
    #define	    LCDSADDR2	             __REG(0X4D000018)  //STN/TFT: frame buffer start address 2  
    #define	    LCDSADDR3	             __REG(0X4D00001C)  //STN/TFT: virtual screen address set    
    #define	    REDLUT   	             __REG(0X4D000020)  //STN: red lookup table                  
    #define	    GREENLUT 	             __REG(0X4D000024)  //STN: green lookup table                
    #define	    BLUELUT  	             __REG(0X4D000028)  //STN: blue lookup table                 
    #define	    DITHMODE 	             __REG(0X4D00004C)  //STN: dithering mode                    
    #define	    TPAL     	             __REG(0X4D000050)  //TFT: temporary palette                 
    #define	    LCDINTPND	             __REG(0X4D000054)  //LCD interrupt pending                  
    #define	    LCDSRCPND	             __REG(0X4D000058)  //LCD interrupt source                   
    #define	    LCDINTMSK	             __REG(0X4D00005C)  //LCD interrupt mask                     
    #define	    TCONSEL  	             __REG(0X4D000060)  //TCON(LPC3600/LCC3600) control    
    /*全局结构体*/
    struct lcd_regs {
    	unsigned long	lcdcon1;
    	unsigned long	lcdcon2;
    	unsigned long	lcdcon3;
    	unsigned long	lcdcon4;
    	unsigned long	lcdcon5;
        unsigned long	lcdsaddr1;
        unsigned long	lcdsaddr2;
        unsigned long	lcdsaddr3;
        unsigned long	redlut;
        unsigned long	greenlut;
        unsigned long	bluelut;
        unsigned long	reserved[9];
        unsigned long	dithmode;
        unsigned long	tpal;
        unsigned long	lcdintpnd;
        unsigned long	lcdsrcpnd;
        unsigned long	lcdintmsk;
        unsigned long	lpcsel;
    };
    
    /*init函数中地址映射*/
    lcd_regs = ioremap(0X4D000000, sizeof(struct lcd_regs));
    

    对于时序匹配问题,分为两个层次,先分析one frame,后分析one line
    在这里插入图片描述
    在这里插入图片描述
    上图是LCD屏驱动器的时序,下图是LCD controller的时序图,经过对比可以得出如下结论
    VSPM + 1代表切换每一帧要多长时间
    VSPM + 1 = T1 = 1

    VBPD + 1代表当VSYNC发出信号start以后才可以正式传输framebuffer的数据
    VBPD + 1 = T0 - T2 = 5

    LINEVAL + 1 表示整个传输framebuffer的数据多长时间
    LINEVAL + 1 = T5 = 320

    VFPD + 1代表着当framebuffer的数据传输结束到VSYNC的end信号之间的间隔时间
    VFPD + 1 = T5 -T2 = 2
    在这里插入图片描述
    在这里插入图片描述
    可以推断出,单位ns
    HSPW + 1 = T7 = 5
    HBPD + 1 = T6 - T7 - T8 = 17
    HOZVAL + 1 = T11 = 240
    HFPD + 1 = T8 - T11 = 11
    现在来看下相关的寄存器
    LCDCON1
    CLKVAL FT: VCLK = HCLK / [(CLKVAL+1) x 2] 查看LCD手册里表格得知最小时钟周期为100ns,所以我们就把频率设置为10MHZ
    HCLK我们按照100MHZ,得到CLKVAL = 4;
    设置LCD controller的性质

    	/* bit[17:8]: VCLK = HCLK / [(CLKVAL+1) x 2], LCD手册P14
    	 *            10MHz(100ns) = 100MHz / [(CLKVAL+1) x 2]
    	 *            CLKVAL = 4
    	 * bit[6:5]: 0b11, TFT LCD
    	 * bit[4:1]: 0b1100, 16 bpp for TFT
    	 * bit[0]  : 0 = Disable the video output and the LCD control signal.
    	 */
    	lcd_regs->lcdcon1  = (4<<8) | (3<<5) | (0x0c<<1);
    

    设置时序

    	/* 垂直方向的时间参数
    	 * bit[31:24]: VBPD, VSYNC之后再过多长时间才能发出第1行数据
    	 *             LCD手册 T0-T2-T1=4
    	 *             VBPD=3
    	 * bit[23:14]: 多少行, 320, 所以LINEVAL=320-1=319
    	 * bit[13:6] : VFPD, 发出最后一行数据之后,再过多长时间才发出VSYNC
    	 *             LCD手册T2-T5=322-320=2, 所以VFPD=2-1=1
    	 * bit[5:0]  : VSPW, VSYNC信号的脉冲宽度, LCD手册T1=1, 所以VSPW=1-1=0
    	 */
    	lcd_regs->lcdcon2  = (3<<24) | (319<<14) | (1<<6) | (0<<0);
    
    
    	/* 水平方向的时间参数
    	 * bit[25:19]: HBPD, VSYNC之后再过多长时间才能发出第1行数据
    	 *             LCD手册 T6-T7-T8=17
    	 *             HBPD=16
    	 * bit[18:8]: 多少列, 240, 所以HOZVAL=240-1=239
    	 * bit[7:0] : HFPD, 发出最后一行里最后一个象素数据之后,再过多长时间才发出HSYNC
    	 *             LCD手册T8-T11=251-240=11, 所以HFPD=11-1=10
    	 */
    	lcd_regs->lcdcon3 = (16<<19) | (239<<8) | (10<<0);
    
    	/* 水平方向的同步信号
    	 * bit[7:0]	: HSPW, HSYNC信号的脉冲宽度, LCD手册T7=5, 所以HSPW=5-1=4
    	 */	
    	lcd_regs->lcdcon4 = 4;
    
    

    设置信号的极性,如果LCD controller低电平有效而对应LCD驱动器高电平有效,则说明需要极性反转(signal polarity inverted)

    	/*
    	 * bit[11]: 1=565 format
    	 * bit[10]: 0 = The video data is fetched at VCLK falling edge
    	 * bit[9] : 1 = HSYNC信号要反转,即低电平有效 
    	 * bit[8] : 1 = VSYNC信号要反转,即低电平有效 
    	 * bit[6] : 0 = VDEN不用反转
    	 * bit[3] : 0 = PWREN输出0
    	 * bit[1] : 0 = BSWP
    	 * bit[0] : 1 = HWSWP 2440手册P413
    	 */
    	lcd_regs->lcdcon5 = (1<<11) | (0<<10) | (1<<9) | (1<<8) | (1<<0);
    

    设置显存,首先需要申请一段显存

    	s3c_lcd->screen_base = dma_alloc_writecombine(NULL, s3c_lcd->screen_size, &s3c_lcd->fix.smem_start, GFP_KERNEL);
    	lcd_regs->lcdsaddr1 =  (s3c_lcd->fix.smem_start >> 1) & ~(3 << 30);
    	lcd_regs->lcdsaddr2 = ((s3c_lcd->fix.smem_start + s3c_lcd->fix.smem_len) >> 1) & 0x1fffff;
    	lcd_regs->lcdsaddr3  = (240*16/16);  /* 一行的长度(单位: 2字节) */	
    

    在设置LCDCON1等寄存器时,我们没有使能LCD 控制器、LCD 背光的GPIO的数据寄存器和LCD本身。为了降低能耗,我们把这些工作放在其他准备工作完成后再来处理

    /* 启动LCD */
    	lcd_regs->lcdcon1 |= (1<<0); /* 使能LCD控制器 */
    	lcd_regs->lcdcon5 |= (1<<3); /* 使能LCD本身 */
    	*gpbdat |= 1;     /* 输出高电平, 使能背光 */
    	/* 4. 注册 */
    	register_framebuffer(s3c_lcd);
    

    在前面有提过fb_info对应的fop还没有处理,现在我们可以借鉴一下内核中已有的代码是如何来处理的
    fb_ops中的函数指针非常的多,其中对于I/O函数,在上一篇博客我们分析open read write函数时可以知道,如果我们在这里设置了read write函数,那么内核会优先使用我们自定义的I/O函数,如果我们自己没有定义,内核会使用核心层的自带的I/O函数
    我们在这里不去定义核心层已经有的函数,只设置如下四个函数

    struct fb_ops {
    	...
    	/* set color register */
    	int (*fb_setcolreg)(unsigned regno, unsigned red, unsigned green,
    			    unsigned blue, unsigned transp, struct fb_info *info);
    	/* Draws a rectangle */
    	void (*fb_fillrect) (struct fb_info *info, const struct fb_fillrect *rect);
    	/* Copy data from area to another */
    	void (*fb_copyarea) (struct fb_info *info, const struct fb_copyarea *region);		
    	/* Draws a image to the display */
    	void (*fb_imageblit) (struct fb_info *info, const struct fb_image *image);			    
    

    后面的三个函数是通用函数,我们只需要写第一个函数来设置虚拟调色板

    static int s3c_lcdfb_setcolreg(unsigned int regno, unsigned int red,
    			     unsigned int green, unsigned int blue,
    			     unsigned int transp, struct fb_info *info)
    {
    	unsigned int val;
    	
    	if (regno > 16)
    		return 1;
    
    	/* 用red,green,blue三原色构造出val */
    	val  = chan_to_field(red,	&info->var.red);
    	val |= chan_to_field(green, &info->var.green);
    	val |= chan_to_field(blue,	&info->var.blue);
    	
    	//((u32 *)(info->pseudo_palette))[regno] = val;
    	pseudo_palette[regno] = val;
    	return 0;
    }
    

    驱动程序基本上说完了,来说一下如何测试程序

    展开全文
  • LCD 驱动开发

    2009-08-20 08:04:00
    http://blog.csdn.net/Harrison_zhu/archive/2008/07/24/2702606.aspx
    展开全文
  • Linux lcd驱动开发学习

    2019-04-27 14:48:52
    做Linux lcd驱动移植时,先找到lcd驱动的位置:grep "s702" -nR 例如:我的nanopc t3 plus可以看到: 可以看到,上面有相应的lcds.c,因为我们的开发板是nanopi t3 plus,即进入到nanopi3,看看里面的lcds.c的类型...

    做Linux lcd驱动移植时,先找到lcd驱动的位置:grep "s702" -nR
    例如:我的nanopc t3 plus可以看到:
     

    可以看到,上面有相应的lcds.c,因为我们的开发板是nanopi t3 plus,即进入到nanopi3,看看里面的lcds.c的类型的s702的结构体即可,既是如下

    static struct nxp_lcd wvga_s702 = {
            .width = 800,
            .height = 480,
            .p_width = 155,
            .p_height = 93,
            .bpp = 24,
            .freq = 61,
    
            .timing = {
                    .h_fp = 44,
                    .h_bp = 26,
                    .h_sw = 20,
                    .v_fp = 22,
                    .v_fpe = 1,
                    .v_bp = 15,
                    .v_bpe = 1,
                    .v_sw = 8,
            },
            .polarity = {
                    .rise_vclk = 1,
                    .inv_hsync = 1,
                    .inv_vsync = 1,
                    .inv_vden = 0,
            },
            .gpio_init = s702_gpio_init,
    };

    上面的结构体是uboot的结构体,Linux内核中也有一样的lcd结构体,同样参考数据手册改成一样的参数即可。

    s702是开发板的lcd屏幕的规格类型,找到之后找lcd屏幕厂家找到相应的datasheet,既是对应相应的规格书,对照着规格书改写里面的参数即可,记一下我在网上找到的规格书参数:

                 参照上面表中的参数修改lcd结构体里面的相应的参数即可。

    展开全文
  • android lcd驱动简析

    2015-09-07 10:04:38
    对于lcd驱动的分析主要分为三部分: 底层硬件结构浅析 framebuffer浅析 MIPI浅析 底层硬件结构浅析 1、 要使一块LCD正常的显示文字或图像,不仅需要LCD驱动器,而且还需要相应的LCD控制器。在通常情况下,生产厂商...
  • 嵌入式系统中使用的linux OS的话,lcd开发主要内容就是LCD设备驱动的移植,使用芯片SDK中现有的LCD框架做修改,在linux版本3.10之前没有使用dts和这之后使用的dts,还是有些区别的。主要修改调试的地方如:平台...
  • 平台信息: 内核:linux3.4.39 系统:android4.4  平台:S5P4418(cortex a9) 作者:瘋耔(欢迎转载,请注明作者) ...欢迎指正错误,共同学习、共同进步!...以下是Samsung Exynos4412搭配TTL转LVDS芯片SN75LVDS83B...
  • 作为人机交互的display界面,lcd屏幕一直扮演着很重要的角色。试想一下,如果你的电子设备没有显示屏,那么人机交互将变成什么样的?比如说手机~ 一般情况,LCD屏需要驱动器和控制器。拿51单片机入门的lcd1602来讲...
  • 关键词:android LCD TFTSN75LVDS83B TTL-LVDS LCD电压背光电压平台信息:内核:linux2.6/linux3.0系统:android/android4.0 平台:samsung exynos 4210、exynos 4412 、exynos 5250作者:xubin341719(欢迎...
  • Linux LCD设备驱动详解

    2019-04-21 14:07:13
    本文是基于mini2440开发板Linux版本号是linux-2.6.32.2的学习笔记 一. LCD device硬件信息 ...2.lcd device的名称:s3c2410-lcd struct platform_device s3c_device_lcd = { .name = "s3c2410-lcd", .id = ...
  • ELAN 6730 高通820 8996平台...创建Touch驱动文件夹 cd android/kernel/msm-3.18/drivers/input/touchscreen/ mkdir Damon_elan cd Damon_elan 移入Touch驱动.c和.h档案,编写Makefile和Kconfig vim Kconfig vi...
  • LCD驱动流程

    2017-09-07 15:58:28
    基于MTK LCD驱动流程 preloader按照mtk的说法是MTK in-house developed loader,也就说是mtk内部开发的一个loader,那么单独编译preloader也是可以的,使用命令./mk project_name n pl。 1. 启动流程 ...
  • 目前手机芯片厂家提供的源码里包含整个LCD驱动框架,一般厂家会定义一个xxx_fb.c的源文件,注册一个平台设备和平台驱动,在驱动的probe函数中来调用register_framebuffer(),从而生成/dev/fbx的设备节点。...
  • 本文主要讲述了在拿到一套硬件和相关的内核源代码时,怎样找到特定驱动的源代码相关的文件。 首先,linux的驱动可以通过make ...以下讲述一下我在没有其它资料的前提下怎么查找s3c6410芯片LCD相关驱动代码的位置的过
  • STM32驱动LCD原理

    2020-06-30 14:00:37
    01、使用FSMC驱动LCD 02、标准8080接口 03、使用FSMC驱动8080接口 04、颜色模式 TFTLCD即薄膜晶体管液晶显示器。它与无源TN-LCD、STN-LCD的简单矩阵不同,它在液晶显示屏的每一个像素上都设置有一个薄膜晶体管...
  • LCD驱动其实对TinyCLR并无必要,特别是在EM-STM3210E开发板上,因为该开发板上的内存太小了,片内64K,片外扩展了128K,加起来也不过172K,而我们知道针对320*240的显示大小,16bit的位图所占的大小就是150K,很显然...
  • Linux的LCD驱动

    2019-01-13 13:13:35
     Linux的源码中本身已经抽象出了LCD驱动的公共部分代码——drivers/video/fbmem.c,对于驱动开发人员来讲,只需要理解这部分的代码并会调用其提供的接口即可。驱动开发人员需要做的就是针对具体的SOC和LCD,设置...
  • 嵌入式Linux LCD驱动

    2020-02-19 11:32:22
    Framebuffer表示帧缓冲,简称fb,fb是一种机制,将系统中所有有关软硬件集合起来,虚拟出一个fb设备,当编写好LCD驱动之后会生成一个 /dev/fbX(X=0~n)的设备,应用层通过操作这个文件来访问LCD。NXP的官方linux内核...
  • 液晶LCD驱动详解

    2018-04-17 17:35:27
    本文是作者根据宋宝华的驱动详解和LCD项目开发经验得来。一、硬件1、LCD控制器 显示控制器主要负责从... 通过LCD控制器给LCD驱动器发所需的控制信号来控制STN/tFT屏2、LCD芯片CH463驱动芯片,内置时钟震荡电路,...
1 2 3 4 5 ... 20
收藏数 13,623
精华内容 5,449
关键字:

lcd驱动开发