李文睿,陈 新
(福州大学 物理与信息工程学院,福建 福州350108)
电子纸可以实现显示内容的重写,具有对比度高、重量轻、可以适当弯曲,且在断电的情况下,能保持原有的显示内容等优点。Linux是一种具有开放性、多用户、多任务、设备独立性、系统可靠安全、良好移植性等优点的操作系统[1],其内核可根据具体的运行平台进行适当的裁剪,这对于资源有限的嵌入式系统至关重要。因此,如何将电子纸显示屏移植应用到Linux操作系统的嵌入式平台下,引起了业界的广泛关注。
本文采用GD6210E作为电子纸显示屏的控制芯片,在S3C2440处理器上使用GPIO口对GD6210E进行扩展。编写、编译基于Linux中framebuffer的电子纸显示屏驱动程序。
本文搭建的嵌入式系统采用Samsung公司推出的S3C2440芯片为处理器。S3C2440采用ARM920T内核,拥有丰富的GPIO口,能够很好地对电子纸显示屏控制芯片实现扩展;外围设备有容量为128 MB的NAND Flash、64 MB的SDRAM、以太网以及扩展GD6210E电子纸显示屏控制芯片[2-3]等。嵌入式系统架构框图如图1所示。
GD6210E芯片是Giga Device Semiconductor公司开发的电子纸控制芯片。该控制芯片提供多种显示功能,如支持部分屏或整屏显示、全局或局部刷新、支持图像翻转等,从而能够减少CPU的运算时间,并支持CPU用命令访问内部寄存器和存储介质。
在接口设计上,本文使用S3C2440的GPD0~GPD15作为数据/命令的输入/输出口;GPC15作为探测GD6210E是否准备好下一次操作的引脚,若为1,说明在GD6210E上已完成一个操作,进入等待接收下一个操作命令状态;利用S3C2440的CLKOUT0引脚为GD6210E提供时钟源/S3C2440对GD6210E扩展接口如图2所示。
驱动程序是应用程序与硬件之间的一个中间软件层,没有main()函数,其工作过程是通过使用宏module_init(初始化函数名)将初始化函数加入内核全局初始化函数列表中,在内核初始化时执行驱动的初始化函数,从而完成驱动的初始化和注册,之后驱动便停止,等待被应用程序调用。应用程序通过调用设备驱动程序中实现的接口函数(如 read()、ioctl()等)实现对硬件的操作。
本文设计的电子纸驱动程序基于framebuffer,整体分为两大部分:(1)GD6210E的驱动程序,其任务是完成S3C2440 GPIO口的设置、GD6210E初始化并使其处在运行状态;(2)完成framebuffer的填写和内核对显示设备驱动的注册。
GD6210E是一个从设备,无法自主地工作,必须由外部的CPU对其发送命令,再把命令转换成能使电子纸显示屏做出相应动作的电平时序。GD6210E驱动程序分为以下部分。
(1)初始化GD6210E。首先分别利用Linux中的s3c-2410_gpio_cfgpin()和 s3c2410_gpio_setpin()两个函数设置复用GPIO口的引脚功能和引脚值。接口配置完后,再向GD6210E发出INIT_SYS_RUN命令,使芯片进入等待初始化状态;然后初始化GD6210E中的display engine,填写显示时序寄存器,配置显示时序。其中时序包括行数据输出时间、行同步时间、帧数据输出时间、帧同步时间;最后设置Image buffer和Updimage buffer在GD6210E中SDRAM的开始地址,至此初始化完成。GD6210E初始化流程如图3所示。
(2)实现GD6210E对显示数据的装载和更新显示屏。首先ARM9向GD6210E发送WAIT_DSPE_FREND命令,等待GD6210E当前工作完成。若Ready引脚为1,ARM9发送 LD_IMG命令,再判断 Ready是否为 1,若是,则开始向GD6210E内部的存储器件发送图像数据。数据发送完毕,ARM9发送LD_IMG_END命令,表示数据发送完成;最后发送UPD_FULL命令,开始电子纸更新。GD6210E更新电子纸显示内容过程如图4所示。
Linux操作系统为显示设备提供专用接口即帧缓冲(framebuffer),它将显示缓冲区进行抽象,允许上层应用程序在图形模式下直接对显示缓存区进行读写操作[4]。在Linux系统中,每个显示外设都与自己的一个fb_info结构体相对应。显示驱动的实现很大部分是在初始化fb_info各项。fb_info包含 fb_fix_screeninfo、fb_var_screeninfo、fb_cmap、fb_ops 4 个结构体[5]。
(1)fb_fix_screeninfo结构体主要设置:①显示设备的id、显示缓冲区的大小、显示屏每行在缓冲区字节数。②设置显示屏的行像素数、列像素数、像素深度、色彩模式等。这两个结构体的设置较为简单,根据实际情况对显示屏进行具体设置即可。
(2)fb_info最关键的是要填写 fb_ops。fb_ops要实现fb_mmap()和 fb_ioctl()两个关键的函数。
①fb_mmap():在Linux系统中,应用程序不可能直接访问设备驱动所在的内核空间。但是,可通过调用mmap()函数,使应用程序能直接访问设备所在的物理地址。mmap()将用户空间的一段内存与设备内存进行映射,当用户访问用户空间的这段地址范围时,实际上会转化为对设备的访问,但mmap()必须以 PAGE_SIZE为单位进行映射。所以在fb_mmap()函数中,首先为应用程序建立一个VMA(VMA描述的是一段连续的、具有相同访问属性的虚存空间)。建立VMA后只是说明进程可以访问这个虚存空间,但还没有为其分配相应的物理页面,所以在访问VMA时,就会产生一个缺页异常,系统将自动调用VMA中的fault()函数。所以在填写 fault()函数时,首先调用vmalloc_to_page(*addr)找到电子纸显示缓冲区所对应的物理页面;接着调用get_page(*struct page)函数,获得显示缓冲区所在物理页面的页面描述符,再根据页面描述符找到物理页面地址,将物理页面的地址填充到进程的页表中。这样就能正常访问VMA所描述的虚拟空间,从而完成电子纸的显示缓冲区和用户空间的映射。页面映射的流程如图5所示。
② fb_ioctl():fb_ioctl(fb_info*info、unsigned int cmd、unsigned long arg)函数最终实现用户对I/O控制命令的执行。其3个参数分别表示操作的fb_info对象、指令和指令所带的参数。内核中预定义了一些I/O控制命令,如framebuffer中的FBIOGET_VSCREENINFO、FBIOGET_FSCREENINFO分别为获取可变的屏幕参数和固定屏幕参数。这些预定义的命令被内核处理而不是被设备驱动处理。若命令为用户自定义的,则调用设备驱动中的fb_ioctl()。本文自定义显示更新命令为FBUPDATA,在fb_ioctl()中,若判断出命令为FBUPDATA,则调用GD6210E驱动中的更新函数。Framebuffer中fb_ioctl()调用流程如图6所示。
完整的电子纸驱动程序架构如图7所示。应用程序首先打开显示设备驱动,再调用framebuffer中fb_ops结构体的fb_mmap()函数,把驱动中的显示缓存区映射到用户空间,并填充显示数据。再向显示设备驱动发送cmd,fb_ioctl()接收 cmd,再根据 cmd调用 GD6210E驱动程序中相对应的操作函数。如刷新显示屏,则调用GD6210E驱动程序中的刷新函数。
测试是在Fedroa 13 Linux操作系统下进行,安装arm-linux-gcc 4.4.3版本的交叉编译工具。把电子纸驱动程序放在Linux 2.6.32.2内核/drivers/vedio目录下,并修改该目录下Makefile和Kconfig这两个文件,目的是为了在配置内核时能添加电子纸驱动。运行make menuconfig指令重新配置内核,把电子纸显示屏驱动配置进内核。最后运行make zImage指令,用交叉编译工具重新编译内核,完成Linux内核对电子纸驱动的添加。
驱动测试程序首先用open()打开电子纸驱动,再用fopen()打开一个8BBP的bmp图片文件,跳过头文件信息,提取每个像素点的灰阶值。由于测试中采用的电子纸显示屏是4BBP,所以要把8BBP的灰阶值提取高4 bit转成4BBP;接着调用mmap()把帧缓冲区间映射到用户空间,对缓冲区填充每个像素点的灰阶值;最后调用ioctl(),向显示驱动发送更新命令,更新电子纸显示屏。
目前,Linux图形用户界面程序编译工具大部分是基于 framebuffer(如Qt),而本文中的电子纸显示屏驱动也是基于framebuffer,所以图形用户界面程序的开发人员利用本文的方法就可以完全不用理会电子纸与其他显示屏在硬件上的差异问题。从而可以大大缩短采用电子纸为显示屏的电子产品的开发时间,更利于电子纸显示屏的普及应用。
[1]陈博,孙宏彬,於岳.Linux实用教程[M].北京:人民邮电出版社,2010.
[2]孙天泽,袁文菊.嵌入式设计及Linux驱动开发指南—基于ARM9处理器[M].二版.北京:电子工业出版社,2007.
[3]Christopher Hallinan.嵌入式 Linux基础教程[M].北京:人民邮电出版社,2009.
[4]宋宝华.Linux设备驱动开发详解[M].北京:人民邮电出版社,2008.
[5]商斌.Linux设备驱动开发入门与编程实践[M].北京:电子工业出版社,2009.