嵌入式Linux下彩色LCD驅動的設計與實現 --zt

原地址:

http://www.felixwoo.com/article.asp?id=75

摘 要:本文介紹瞭如何在嵌入在開發彩色LCD顯示驅動的方法,並對Linux中的顯示驅動程序結構和框架作一介紹。

關鍵字:ARM,幀緩衝(Framebuffer),MC928MX1。

長期以來,在常見的掌上電腦(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》

作者簡介

許慶豐 Libo小組發起人。現任職於摩托羅拉半導體部,主要研究方向是嵌入式操作系統和應用。
  

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章