來源::電子產品世界
長期以來,常見的掌上電腦(PDA)等小型掌上型設備上,由於硬體條件的限制,我們看到的顯示器件通常是單色LCD,用戶介面也非常簡單,幾乎看不到PC機上美觀整齊的圖形介面(GUI)支援。因為早期嵌入式處理器的速度有限,在處理圖形和多媒體資料方面顯得力不從心。
隨著高性能嵌入式處理器的普及和硬體成本的不斷降低,尤其是ARM系列處理器的推出,嵌入式系統的功能也越來越強。在多媒體應用的推動下,彩色LCD也越來越多地應用到了嵌入式系統中,如新一代掌上電腦(PDA)多採用TFT顯示器件,支援彩色圖形介面,圖片顯示和視頻媒體播放。掌上電腦(PDA)的作業系統有微軟Window CE,PalmOS等。而Linux做為開放源代碼的作業系統也在市場中佔據了一席之地。由於Linux成本低廉,任何人都可以得到其源代碼並在其基礎上進行開發,成為各家廠商極力發展的作業系統,加上其核心小,潛力可觀。
在應用需求的推動下,Linux下也出現了許多圖形介面套裝軟體,如MiniGUI、Trolletech公司的Embedded QT等,其圖形介面及開發工具與Windows CE不相上下。在圖形套裝軟體的開發和移植工作中都牽涉到底層LCD的驅動問題。筆者參與了一個很有代表性的專案,是基於ARM9的PDA系統的開發,用的是Motorola公司龍珠系列的MC9328MX1。軟體採用Linux
硬體平臺
MC9328MX1(以下簡稱MX1)是Motorola公司基於ARM核心的第一款MCU,主要面向高端嵌入式應用。內部採用ARM920T內核,並集成了SDRAM/Flash,LCD,USB,藍牙,多媒體快閃記憶體卡(MMC/SD, Memory Stick),CMOS攝像頭等控制器。
LCD控制器的功能是產生顯示驅動信號,驅動LCD顯示器。用戶只需要通過讀寫一系列的寄存器,完成配製和顯示控制。MX1中的LCD控制器可支援單色/彩色LCD顯示器。支援彩色TFT時,可提供
Linux下的設備驅動
Linux將設備分為最基本的兩大類,字元設備和塊設備。字元設備是以單個位元組為單位進行順序讀寫操作,通常不使用緩衝技術,如滑鼠等,驅動程式實現比較簡單;而塊設備則是以固定大小的資料塊進行存儲和讀寫的,如硬碟,軟碟等。為提高效率,系統對於塊設備的讀寫提供了緩存機制,由於涉及緩衝區管理,調度,同步等問題,實現起來比字元設備複雜得多。
Linux的設備管理是和檔系統解密結合的,各種設備都以檔的形式存放在/dev目錄下,稱為設備檔。應用程式可以打開,關閉,讀寫這些設備檔,完成對設備的操作,就象操作普通的資料檔案一樣。為了管理這些設備,系統為設備編了號,每個設備號又分為主設備號和次設備號。主設備號用來區分不同種類的設備,而次設備號用來區分同一類型的多個設備。對於常用設備,Linux有約定俗成的編號,如硬碟主設備號是3。
Unix / 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
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下提供了多種顯示卡的幀緩衝設備驅動程式程式,用戶自己的驅動程式可參考成熟的代碼編寫或直接修改得到。
';$(".articleExtAd").append(notVIP);setTimeout(function() {$('.top-toolbar').data('top-toolbar').setAD({title: "\u5d4c\u5165\u5f0fLinux\u4e0b\u5f69\u8272LCD\u9a45\u52d5\u7684\u8a2d\u8a08\u8207\u5be6\u73fe",label_id: 163,label_name: "\u96fb\u8166\u786c\u9ad4"});}, 2000);
離婚證人