驱动程序,就是让硬件正常工作的一段程序.抛开操作系统而言,各种外设的驱动程序的编写都是大同小异,所不同的只是驱动程序在整个系统中的位置,以及与应用程序和内核之间的调用关系.笔者在深圳天喜投资有限公司参与VOIP项目时,曾经接触过多种LCD(包括点阵型和字符型),也写过一个小的GUI,只能显示汉字以及画简单的直线、圆等等.在这个GUI中,应用程序直接调用LCD驱动程序的API,无需通过操作系统内核(nucleus).以这种方式编写驱动固然简单,但是不能达到标准化、规范化的目的,只能凭一己之力做一些较为简单的应用.相反,如果能充分利用开放源码的优势,在已有的代码基础上开发出自己的图形界面,就可以大大缩短开发时问. 近几年,随着Linux技术的兴起,uClinux在嵌入式领域逐渐占有了重要的地位.uClinux驱动程序的结构和标准Linux驱动程序的结构类似,不同的只是uClinux不支持内存管理单元.因此,为了充分利用uClinux的优势,驱动程序必须按照uClinux的方式来写.Microwindows是一个著名的开放式源码的嵌入式GUI软件,目的是把图形视窗环境引入到运行uClinux的小型设备上.而实现这一切的基础即为Framebuffer.开发者只需实现LCD的Framebuffer驱动,即可完成像Windows中的桌面图形界面,Framebuffer使得一切都变得那么简单. 1 底层LCD驱动机制 1.1 硬件平台 S3C44B0X是三星公司基于ARM 核心的一款MCU,集成了多种外围设备,其中包括LCD控制器.LCD控制器的功能是产生显示驱动信号,驱动LCD显示器.用户只需要通过读写一系列的寄存器,完成配置和显示控制.LCD控制器通过DMA将显存中的数据传入显示板的Driver IC来显示图形.S3C44B0X中的LCD控制器可支持单色/彩色LCD显示.最高只能支持8 b的STN(Super Twisted Nematic)显示.配置LCD控制器重要的一步是指定显示缓冲区,显示的内容就是从缓冲区中读出的,其大小由屏幕分辨率和显示颜色数决定.在文中,笔者采用的是优龙G35I显示面板,在240×320分辨率下提供4级灰度显示.
1.2 Framebuffer的实现机制 在uCLinux中,Framebuffer是一种能够提取图形的硬件设备,是用户进入图形界面的很好接口.Framebuffer是显存抽象后的一种设备,它允许上层应用程序在图形模式下直接对显示缓冲区进行读写操作.这种操作是抽象的,统一的.用户不必关心物理显存的位置、换页机制等等具体细节.这些都是由Framebuffer设备驱动来完成的.有了Framebuffer,用户的应用程序不需要对底层的驱动深入了解就能够做出很好的图形. 对于用户而言,它和/dev下面的其他设备没有什么区别,用户可以把Framebuffer看成一块内存,既可以向这块内存中写人数据,也可以从这块内存中读取数据.显示器将根据相应指定内存块的数据来显示对应的图形界面.而这一切都由LCD控制器和相应的驱动程序来完成. Framebuffer的显示缓冲区位于uClinux中核心态地址空间,而在uClinux中,每个应用程序都有自己的虚拟地址空间,在应用程序中是不能直接访问物理缓冲区地址的.为此,uClinux在文件操作file_operations结构中提供了mmap函数,可将文件的内容映射到用户空间.对于帧缓冲设备,则可通过映射操作,将屏幕缓冲区的物理地址映射到用户空间的一段虚拟地址中,之后用户就可以通过读写这段虚拟地址访问屏幕缓冲区,在屏幕上绘图.Framebuffer中内存块分布如图1所示. 图1 内存块分布图 2 底层LCD驱动接口 2.1 Linux下设备驱动程序 在Linux中,大部分的设备驱动都写成模块的形式,但嵌入式系统是针对具体应用的,一般都不需要这一功能.因此,uClinux的驱动程序都是直接编译到内核中. Linux将设备分为最基本的两大类,字符设备和块设备.字符设备是以单个字节为单位进行顺序读写操作,通常不使用缓冲技术,如鼠标等,驱动程序实现比较简单;而块设备则是以固定大小的数据块进行存储和读写,如硬盘、软盘等.为提高效率,系统对于块设备的读写提供了缓存机制,由于涉及缓冲区管理,调度,同步等问题,实现起来比字符设备复杂得多. Linux的设备管理是和文件系统紧密结合的,各种设备都以文件的形式存放在/dev目录下,称为设备文件口].应用程序可以打开,关闭,读写这些设备文件,完成对设备的操作,就象操作普通的数据文件一样.为了管理这些设备,系统为设备编了号,每个设备号又分为主设备号和次设备号.主设备号用来区分不同种类的设备,而次设备号用来区分同一类型的多个设备.对于常用设备,Linux有约定俗成的编号. Linux的特点之一,是为所有的文件,包括设备文件,提供了统一的操作函数接口,struct file_operations. 结构体中的成员为一系列的接口函数,如用于读/写的read/write函数,用于控制的ioctl等.打开一个文件就是调用这个文件file_operations中的open操作.不同类型的文件有不同的file_operations成员函数.如普通的磁盘数据文件,接口函数完成磁盘数据块读写操作;而对于各种设备文件,则最终调用各自驱动程序中的I/O函数进行具体设备的操作.这样,应用程序根本不用考虑操作的是设备还是普通文件,可一律当作文件处理,具有非常清晰统一的I/O接口.
2.2 Framebuffer设备驱动 在uCLinux中,由于外设的种类繁多,操作方式也各不相同.对于LCD的驱动,uCLinux采用帧缓冲设备,帧缓冲设备对应的设备文件为/dev/fb*,如果系统有多个显示卡,uCI inux还支持多个帧缓冲设备,分别为/dev/fbO到/dev/fb31,而/dev/fb则为当前缺省的帧缓冲设备,通常指向/dev/fb0.当然在嵌入式系统中支持一个显示设备就够了.帧缓冲设备为标准字符设备,主设备号为29,次设备号则从0到31.分别对应/dev/fb0-/dev/fb31.Framebuffer设备在很大程度上依靠了下面的3个数据结构,在fb.h中声明. Struct fb_var_screeninfo Struct fb_fix_screeninfo Struct fb_info 第1个结构是用来描述图形卡的特性的.通常是被用户设置的.第2个结构定义了图形卡的硬件特性,是不能改变的,用户选定了LCD控制器和显示器后,那么它的硬件特性也就定下来了.第3个结构定义了当前图形卡Framebuffer设备的独立状态,一个图形卡可能有两个Framebuffer,在这种情况下,就需要两个fb_info结构.这个结构是惟一内核空间可见的.
2.3 Framebuffer驱动程序的实现 应用程序通过内核对Framebuffer的控制,主要有下面3种方式. 1)读/写/dev/fb 相当于读/写屏幕缓冲区. 2)映射(map)操作 通过映射操作,可将屏幕缓冲区的物理地址映射到用户空问的一段虚拟地址中,之后用户就可以通过读写这段虚拟地址访问屏幕缓冲区,在屏幕上绘图. 3)I/O控制 对于帧缓冲设备,设备文件的ioctl操作可读取/设置显示设备及屏幕的参数,如分辨率、显示颜色数、屏幕大小等等.ioctl的操作是由底层的驱动程序来完成的. 因此,Framebuffer驱动要完成的工作已经是很少了,只需分配显存的大小、初始化LCD控制寄存器、设置修改硬件设备相应的var信息和fix信息. 帧缓冲设备属于字符设备,采用“文件层一驱动层”的接口方式.在文件层次上,uClinux为其定义了 static struct file_operations fb_fops={ owner:THIS_MODULE, read:fb_read,/*读操作*/ write:fb_write,/*写操作*/ ioctl:fb_ioctl,/*控制操作*/ mmap:fb_mmap,/* 映射操作*/ open:fb_open,/*打开操作*/ release:fb_release,/*关闭操作*/ }; 在uClinux中,由于帧缓冲设备是字符设备,应用程序需按文件的方式打开一个帧缓冲设备,如果打开成功,则可对帧缓冲设备进行读、写等操作.在上文中已经介绍了帧缓冲设备的地址空间问题,对于操作系统来说,读、写帧缓冲设备就是对物理地址空间进行数据读写.当然,对于应用程序来说,物理地址空间是透明的.由此可见,读写帧缓冲设备最主要的任务就是获取帧缓冲设备在内存中的物理地址空间以及相应LCD的一些特性. 图2反映了应用程序如何写帧缓冲设备来显示图形的全过程. 图2 驱动程序实现框图 在了解了上面所述的概念后,编写帧缓冲驱动的实际工作并不复杂,需要做如下工作. 1)编写初始化函数 初始化函数首先初始化LCD控制器,通过写寄存器设置显示模式和显示颜色数,然后分配I CD显示缓冲区.在Linux可通过kmalloc函数分配一片连续的空间.笔者采用的LCD显示方式为240×320,4位灰度.需要分配的显示缓冲区为240×320×1/2=37.5k字节,缓冲区通常分配在大容量的片外SDRAM 中,起始地址保存在LCD控制器寄存器中.最后是初始化一个fb_info结构,填充其中的成员变量,并调用register_framebuffer(&fb_info),将fb_info登记人内核.下面是初始化帧缓冲设备的程序代码. int __init s3c44b0xfb_init(void) { …… struct known_lcd_panels *p_lcd: //LCD Panel feature /*Init LCD Controller*/ lcd_Init(p_lcd->bpp); lcd_DispON();//Open LCD display /* Init Framebuffer Parameter*/ fb_parameter_init(p_lcd); memset(&fb_info.gen,0,sizeof(fb_info.gen)); /* Print some information */ printk("S3C44B0X framebuffer driver:Init ready"); return 0; ) 2)编写结构fb_info中函数指针fb_ops对应的成员函数 对于嵌入式系统的简单实现,只需要下列3个函数就可以了. struct fb_ops{ …… int(*fb_get_fix)(struct fb_fix_screeninfo *fix,int con,struct fb_info *info); int(*fb_get_var)(struct fb_var_screeninfo *var,int con,struet fb_info *info); int(*fb_set_var)(struet fb_var_screeninfo *var,int con,struct fb_info *info); …… }; struct fb_ops在include/linux/fb.h中定义.这些函数都是用来设置/获取fb_info结构中的成员变量的.当应用程序对设备文件进行Ioctl操作时候会调用它们,例如,对于fb_get_fix(),应用程序传入的是fb_fix_screeninfo结构,在函数中对其成员变量赋值,主要是smem_start(缓冲区起始地址)和smem_len(缓冲区长度),最终返回给应用程序.而fb_set_var()函数的传入参数是fb_vat_screeninfo,函数中需要对xres,yres,和bits_per_pixel赋值. 3 在应用程序中使用Framebuffer设备 uClinux通过设备文件来提供应用程序和设备驱动的接口.MicroWinows就是利用Frame_buffer的典型应用. Microwindows从原理上采用分层设计的方法,每个层次都完成特定的功能,并且能够在不影响其他层次的基础上针对不同的应用进行改编或者重写.在最底层,显示屏、鼠标、触摸屏等的驱动程序提供了与交互相关的硬件设备的访问.在中间层,是一个精简的图形引擎,提供了划线、区域填充、多边形等多种基本的图形功能.最上层为图形应用程序提供了丰富的编程接口函数(API),通过这些接口函数可以定制桌面和窗口的外观.目前,Microwindows提供两套API接口,与Win32/WinCE基本兼容的API和采用X体系的Nano_XAPI. 因此,为了将Microwindows 移植入S3C44B0X,只需更改与显示屏相关的Framebuffer驱动程序. 4 结束语 笔者在自己开发的S3C44B0X板上实现了uClinux以及Microwindows,为各种嵌入式应用提供了一个很好的开发平台.由于uCLinux是完全免费开放式源代码,为嵌入式设备提供了更多的解决方案.熟悉图形应用程序的用户可以很快就在该系统上编写出自己的图形应用程序,在未来的嵌入式系统设计中,它的作用是无可限量的.
|