嵌入式Linux下彩色LCD驱动的设计与实现

转载

摘要:本文介绍了如何在嵌入在开发彩色LCD显示驱动的方法,并对Linux中的显示驱动程序结构和框架作一介绍。

长期以来,在常见的掌上电脑(PDA)等小型手持式设备上,由于硬件条件等的限制,我们看到的显示器件通常是单色LCD,用户界面也非常简单,几乎看不到 PC机上美观整齐的图形界面(GUI)支持。由于早期嵌入式处理器的速度有限,在处理图形和多媒体数据方面也显得力不从心。

随着高性能嵌入式处理器的普及和硬件成本的不断降低,尤其是Arm系列处理器的推出,嵌入式系统的功能也越来越强。在多媒体应用的推动下,彩色LCD也越来越多地应用到了嵌入式系统中,如新一代掌上电脑(PDA)多采用TFT显示器件,支持彩色图形界面,图片显示和视频媒体播放。掌上电脑(PDA)的操作系统有微软Window CE, PalmOS等。而Linux做为开放源代码的操作系统也在市场中占据了一席之地。由于Linux成本低廉,任何人都可以得到其源代码并在其基础上进行开发,成为各家厂商极力发展的操作系统,加上其核心小,潜力可观。

在应用需求的推动下,Linux下也出现了许多图形界面软件包,如 MiniGUI、Trolletech公司的Embedded QT等,其图形界面及开发工具与Windows CE不相上下。在图形软件包的开发和移植工作中都牵扯到底层LCD的驱动问题。笔者参与了一个基于ARM9的PDA系统的开发,用的是摩托罗拉公司龙珠系列的MC928MX1。软件采用Linux 2.4.18平台,编译器为gcc的ARM交叉编译器。

一、硬件平台

MC928MX1(以下简称MX1)是摩托罗拉公司基于ARM核心的第一款MCU,主要面向高端嵌入式应用。内部采用ARM920T内核,并集成了SDRAM/Flash,LCD,USB,蓝牙(bluetooth),多媒体闪存卡(MMC),CMOS摄像头等控制器。

LCD 控制器的功能是产生显示驱动信号,驱动LCD显示器。用户只需要通过读写一系列的寄存器,完成配制和显示控制。MX1中的LCD控制器可支持单色/彩色 LCD显示器。支持彩色TFT时,可提供4/8/12/16位颜色模式,其中16位颜色模式下可以显示65536种颜色。配置LCD控制器重要的一步是指定显示缓冲区,显示的内容就是从缓冲区中读出的,其大小由屏幕分辨率和显示颜色数决定。在本例中,笔者采用的是夏普LQ035Q2DD54 TFT 显示模块,在240x320分辨率下可提供16位彩色显示。

二、Linux下的设备驱动

Linux 将设备分为最基本的两大类,字符设备和块设备。字符设备是以单个字节为单位进行顺序读写操作,通常不使用缓冲技术,如鼠标等,驱动程序实现比较简单;而块设备则是以固定大小的数据块进行存储和读写的,如硬盘,软盘等。为提高效率,系统对于块设备的读写提供了缓存机制,由于涉及缓冲区管理,调度,同步等问题,实现起来比字符设备复杂的多。

Linux的设备管理是和文件系统解密结合的,各种设备名称都以文件的形式存放在/dev目录下,称为设备文件。应用程序可以打开,关闭,读写这些设备文件,完成对设备的操作,就象操作普通的数据文件一样。为了管理这些设备,系统为设备编了号,每个设备号又分为主设备号和次设备号。主设备号用来区分不同种类的设备,而次设备号用来区分同一类型的多个设备。对于常用设备,Linux有约定俗成的编号,如硬盘主设备号是3。在Linux的/dev/目录下使用ls -l命令可察看个设备文件的设备号。例如,/dev/hda为块设备,主设备号3,次设备号0,是系统的第一块硬盘。/dev/hd1主设备号3,次设备号1,为系统的第二块硬盘。我们将要介绍的显示设备也是一个设备文件/dev/fb,主设备号29。在编写设备驱动程序的时候,也要指明所操作设备的主设备号和次设备号。

Linux的特点之一,是为所有的文件,包括设备文件,提供了统一的操作函数接口,定义如下:

struct file_operations {

struct module *owner;

loff_t (*llseek) (struct file *, loff_t, int);

ssize_t (*read) (struct file *, char *, size_t, loff_t *);

ssize_t (*write) (struct file *, const char *, size_t, loff_t *);

int (*readdir) (struct file *, void *, filldir_t);

unsigned int (*poll) (struct file *, struct poll_table_struct *);

int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);

int (*mmap) (struct file *, struct vm_area_struct *);

int (*open) (struct inode *, struct file *);

int (*flush) (struct file *);

int (*release) (struct inode *, struct file *);

int (*fsync) (struct file *, struct dentry *, int datasync);

int (*fasync) (int, struct file *, int);

int (*lock) (struct file *, int, struct file_lock *);

ssize_t (*readv) (struct file *, const struct iovec *, unsigned long, loff_t *);

ssize_t (*writev) (struct file *, const struct iovec *, unsigned long, loff_t *);

ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);

unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);

};

结构体中的成员为一系列的接口函数,如用于读/写的read/ write函数,用于控制的ioctl等。打开一个文件就是调用这个文件file_operations中的open操作。不同类型的文件有不同的 file_operations成员函数。如普通的磁盘数据文件,接口函数完成磁盘数据块读写操作;而对于各种设备文件,则最终调用各自驱动程序中的 I/O函数进行具体设备的操作。这样,应用程序根本不用考虑操作的是设备还是普通文件,可一律当作文件处理,具有非常清晰统一的I/O接口。所以 file_operations是文件层次的I/O接口。

但是,由于外设的种类繁多,操作方式也各不相同。如声音设备驱动要使用 DMA通道,显示设备驱动要提供对显存的操作,硬盘驱动要处理复杂的缓冲区结构,网络设备驱动和socket联系紧密。如果 file_operations中的函数都让驱动程序的开发人员来写,则就要处理大量的细节,几乎是不可能的。为了解决设备多样性的问题,Linux采用了特殊情况特殊处理的办法,为不同设备定义好了文件层次file_operations结构中的接口函数,其中处理了大多数设备相关的操作,如各种缓冲区的申请和释放等等,而具体操作底层硬件的一小部分则留给开发人员。所以Linux另外提供一个文件层到底层驱动程序的接口,通常为一个结构体,其中包含成员变量和函数指针。不同的设备驱动有不同的结构体。这样,一方面保证了文件层I/O接口file_operations的一致性,另一方面驱动程序的开发人员也不用了解太多细节,只专著于硬件相关的I/O操作就可以了。例如,一个有代表性的特殊设备是声音设备,其文件层的file_operations定义如下:

struct file_operations oss_sound_fops = {

owner: THIS_MODULE,

llseek: sound_lseek,

read: sound_read,

write: sound_write,

poll: sound_poll,

ioctl: sound_ioctl,

mmap: sound_mmap,

open: sound_open,

release: sound_release,

};

其中的sound_read,sound_write等函数Linux都已提供,处理了与声音设备相关的许多细节,如DMA的申请,释放和操作等。而文件层到驱动程序的接口为audio_driver结构,其中包含底层操作函数。文件层的sound_read,sound_write会在需要时调用 audio_driver中的函数。开发人员只要编写audio_driver中的函数就可以了,最大程度地减小了工作量。下面我们将看到,Linux为显示设备提供的帧缓冲驱动也是这种“文件层-驱动层”的接口方式。

三、Linux的帧缓冲设备

帧缓冲(framebuffer)是Linux为显示设备提供的一个接口,把显存抽象后的一种设备,他允许上层应用程序在图形模式下直接对显示缓冲区进行读写操作。这种操作是抽象的,统一的。用户不必关心物理显存的位置、换页机制等等具体细节。这些都是由Framebuffer设备驱动来完成的。帧缓冲驱动的应用广泛,在linux的桌面系统中,Xwindow服务器就是利用帧缓冲进行窗口的绘制。尤其是通过帧缓冲可显示汉字点阵,成为Linux汉化的唯一可行方案。

帧缓冲设备对应的设备文件为/dev/fb*,如果系统有多个显示卡,Linux下还可支持多个帧缓冲设备,最多可达32 个,分别为/dev/fb0到/dev/fb31,而/dev/fb则为当前缺省的帧缓冲设备,通常指向/dev/fb0。当然在嵌入式系统中支持一个显示设备就够了。帧缓冲设备为标准字符设备,主设备号为29,次设备号则从0到31。分别对应/dev/fb0-/dev/fb31。通过/dev/fb,应用程序的操作主要有这几种:

  1. 读/写(read/write)/dev/fb:相当于读/写屏幕缓冲区。例如用 cp /dev/fb0 tmp命令可将当前屏幕的内容拷贝到一个文件中,而命令cp tmp > /dev/fb0 则将图形文件tmp显示在屏幕上。
  2. 映射(map)操作:由于Linux工作在保护模式,每个应用程序都有自己的虚拟地址空间,在应用程序中是不能直接访问物理缓冲区地址的。为此, Linux在文件操作 file_operations结构中提供了mmap函数,可将文件的内容映射到用户空间。对于帧缓冲设备,则可通过映射操作,可将屏幕缓冲区的物理地址映射到用户空间的一段虚拟地址中,之后用户就可以通过读写这段虚拟地址访问屏幕缓冲区,在屏幕上绘图了。实际上,使用帧缓冲设备的应用程序都是通过映射操作来显示图形的。由于映射操作都是由内核来完成,下面我们将看到,帧缓冲驱动留给开发人员的工作并不多。
  3. I/O控制:对于帧缓冲设备,对设备文件的ioctl操作可读取/设置显示设备及屏幕的参数,如分辨率,显示颜色数,屏幕大小等等。ioctl的操作是由底层的驱动程序来完成的。

在应用程序中,操作/dev/fb的一般步骤如下:

  1. 打开/dev/fb设备文件。
  2. 用ioctrl操作取得当前显示屏幕的参数,如屏幕分辨率,每个像素点的比特数。根据屏幕参数可计算屏幕缓冲区的大小。
  3. 将屏幕缓冲区映射到用户空间。
  4. 映射后就可以直接读写屏幕缓冲区,进行绘图和图片显示了。

典型程序段如下:

#include <linux/fb.h>

int main()

{

int fbfd = 0;

struct fb_var_screeninfo vinfo;

struct fb_fix_screeninfo finfo;

long int screensize = 0;

/*打开设备文件*/

fbfd = open("/dev/fb0", O_RDWR);

/*取得屏幕相关参数*/

ioctl(fbfd, FBIOGET_FSCREENINFO, &finfo);

ioctl(fbfd, FBIOGET_VSCREENINFO, &vinfo);

/*计算屏幕缓冲区大小*/

screensize = vinfo.xres * vinfo.yres * vinfo.bits_per_pixel / 8;

/*映射屏幕缓冲区到用户地址空间*/

fbp=(char*)mmap(0,screensize,PROT_READ|PROT_WRITE,MAP_SHARED, fbfd, 0);

/*下面可通过fbp指针读写缓冲区*/

……

}

四、帧缓冲驱动的编写

帧缓冲设备属于字符设备,与声音设备一样,也采用“文件层-驱动层”的接口方式。在文件层次上,Linux为其定义了

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, /* 关闭操作 */

};

其中的成员函数都在文件linux/driver/video/fbmem.c中定义。

由于显示设备的特殊性,在驱动层的接口中不但要包含底层函数,还要有一些纪录设备状态的数据。Linux为帧缓冲设备定义的驱动层接口为struct fb_info结构,在include/linux/fb.h中定义。这个结构比较长,限于篇幅,文章中就不全部列出了。幸运的是,嵌入式系统要求的显示操作比较简单,只涉及到结构中少数几个成员,下面只对编写驱动中要用到的几个关键成员作一说明。

fb_info中纪录了帧缓冲设备的全部信息,包括设备的设置参数,状态以及操作函数指针。每一个帧缓冲设备都必须对应一个fb_info结构。其中成员变量Modename为设备名称, fontname为显示字体,fbops为指向底层操作的函数的指针,这些函数是需要驱动程序开发人员编写的。成员fb_var_screeninfo和 fb_fix_screeninfo也是结构体。其中fb_var_screeninfo记录用户可修改的显示控制器参数,包括屏幕分辨率和每个像素点的比特数。fb_var_screeninfo中的xres定义屏幕一行有多少个点, yres定义屏幕一列有多少个点,

bits_per_pixel 定义每个点用多少个字节表示。而fb_fix_screeninfo中记录用户不能修改的显示控制器的参数,如屏幕缓冲区的物理地址,长度。当对帧缓冲设备进行映射操作的时候,就是从fb_fix_screeninfo中取得缓冲区物理地址的。上面所说的数据成员都是需要在驱动程序中设置的。

在了解了上面所述的概念后,编写帧缓冲驱动的实际工作并不复杂,需要做的工作是:

1.编写初始化函数:初始化函数首先初始化LCD控制器,设置显示模式和显示颜色数,然后分配LCD显示缓冲区。在Linux可通过kmalloc函数分配一片连续的空间。笔者采用的LCD显示方式为240x320,16位彩色。需要分配的显示缓冲区为240x320x2 = 150k字节,缓冲区通常分配在片外SDRAM中,起始地址保存在LCD控制器寄存器中。最后是初始化一个fb_info结构,填充其中的成员变量,并调用register_framebuffer(&fb_info)将fb_info登记入内核。

2. 编写结构fb_info中函数指针fb_ops对应的成员函数:对于嵌入式系统的简单实现,只需要下列三个函数就可以了:

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, struct fb_info *info);

int (*fb_set_var)(struct 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_var_screeninfo,函数中需要对xres,yres,和 bits_per_pixel赋值。

驱动程序编写完成后,开发者可选择将其编译为动态加载模块,或静态地编译入内核中。由于篇幅所限,有关这方面的内容请读者参考相关驱动程序文档。

五、结束语

由于篇幅所限,本文中仅对帧缓冲设备驱动的基本原理和框架做了简单介绍。幸运的是,在Linux的发布版本中,包含了大量的设备驱动程序源代码,其中 drvers/video下提供了多种显示卡的帧缓冲设备驱动程序程序,用户自己的驱动程序可参考成熟的代码编写或直接修改得到。

参考资料

《MC928MX1 User Manual》

《Linux frame buffer driver howto》

嵌入式Linux下彩色LCD驱动的设计与实现相关推荐

  1. 嵌入式linux音频播放器设计,基于嵌入式Linux下Madplay音频播放器设计论文.docx

    基于嵌入式Linux下Madplay音频播放器设计论文 滁州职业技术学院计算机应用技术专业毕业论文PAGE I 滁州职业技术学院信息工程系--2015届计算机应用专业毕业论文 姓 名: 周杰 班 级: ...

  2. 嵌入式linux下网卡设备驱动与网络协议栈的设计实现.pdf,嵌入式系统的通用网络驱动模型设计与实现.pdf...

    维普资讯 第3O卷第4期 长春理工大学学报 (自然科学版 ) VO1.3O N0.4 2007年 12月 JournalofChangchunUniversityofScienceandTechnol ...

  3. 嵌入式linux下u盘升级的设计

    一.内核配置,配置使其支持u盘 make menu_config Device Drivers ---> [*]USB support --> <*>   USB Mass S ...

  4. 嵌入式linux下复位声卡驱动

    本文博客链接:http://blog.csdn.net/jdh99,作者:jdh,转载请注明. 最近在做一个对讲系统,平台是tiny6410搭载的linux.遇到的问题是对讲一段时间后声卡会挂掉,报出 ...

  5. linux -- 嵌入式linux下wifi无线网卡驱动

    插入模块驱动 [root@WEIYAN wifi]$ insmod zd1211rw.ko usb 1-1: reset full speed USB device using s3c2410-ohc ...

  6. Linux下的FrameBuffer驱动框架

    一.RGB LCD经典显示器件介绍: 1.LCD屏幕的重要属性参数: ① 分辨率:也就是屏幕上的像素点的个数: ② 像素格式:即单个像素点RGB三种颜色的表达方式,包括RGB888.ARGB8888和 ...

  7. 南京邮电大学嵌入式系统开发实验5:嵌入式Linux下LED报警灯驱动设计及编程

    实验5  嵌入式Linux下LED报警灯驱动设计及编程 一.实验目的 理解驱动本质,掌握嵌入式Linux系统下驱动开发相关知识,包括端口寄存器访问.接口函数编写.和文件系统挂接.注册及相关应用编程等知 ...

  8. 嵌入式linux查看usb设备驱动程序,嵌入式Linux下USB驱动程序的设计

    嵌入式Linux下USB驱动程序的设计 usb概念:  USB(Universal Serial Bus)即通用串行总线,是一种全新的双向同步传输的支持热插拔的数据传输总线,其目的是为了提供一种兼容不 ...

  9. Linux下的硬件驱动——USB设备(下)

    Linux下的硬件驱动--USB设备(下)(驱动开发部分) 文档选项 打印本页 将此页作为电子邮件发送 未显示需要 JavaScript 的文档选项 级别: 初级 赵明, 联想软件设计中心嵌入式研发处 ...

最新文章

  1. 首页列表显示全部问答,完成问答详情页布局。
  2. JSP学习笔记(四十九):抛弃POI,使用iText生成Word文档
  3. python class函数报错_Python 的函数是第一类 First-Class 对象
  4. Springboot 常用注解
  5. 保姆级计算机视觉学习路线
  6. Golang 实现求素数【 输入N,求N内素数个数 】
  7. 【算法】159题 Longest Substring with at Most Two Distinct Characters 最大的子串
  8. [Yarn] Yarn local-dirs are bad 导致节点处于不健康状态
  9. 加密模式 openssl sm4_OpenSSL/GmSSL 动态引擎
  10. 华为交换机SSH登录配置
  11. WebStorm配置Sass
  12. 值得收藏的几千T超多学习资源合集分享
  13. 关系代数表达式优化步骤
  14. 基于JSP网上拍卖平台系统
  15. 河南省第九届ACM程序设计大赛总结
  16. 《跑步人生》 作者:村上春树
  17. linux的应用界面设计,技术|Xperience UI 设计理念:优雅的 Linux 桌面设计欣赏
  18. spleeter分离伴奏和人声
  19. (Docker实战)在CentOS7上使用Docker;(超详细、附图、附代码)
  20. 如何使用logcat和getevent查看遥控器的ir key,linux key和android key值

热门文章

  1. 阿里巴巴--阿里健康 招聘博客
  2. 95码号办理需要具备的条件和办理流程
  3. 品牌形象设计的流程解析
  4. huntshowdown服务器维护吗,huntshowdown怎么玩?新手玩法指南
  5. JTAG(Joint Test Action Group)
  6. Qt:为什么QGraphicsView设置Antialiasing/SmoothPixmapTransform没生效?
  7. 2022-2028年中国整装卫浴行业市场专项调研及发展策略分析报告
  8. 时域卷积定理的证明 | 卷积的傅里叶变化等于傅里叶变换的乘积
  9. 【较详细】Ubuntu18.04 解决键盘布局乱码问题
  10. Android APK瘦身/减小包体