嵌入式Linux下彩色LCD驅(qū)動(dòng)的設(shè)計(jì)與實(shí)現(xiàn)
嵌入式Linux下彩色LCD驅(qū)動(dòng)的設(shè)計(jì)與實(shí)現(xiàn) PMT 許慶豐 2002年12月 摘 要:本文介紹了如何在嵌入在開發(fā)彩色LCD顯示驅(qū)動(dòng)的方法,并對(duì)Linux中的顯示驅(qū)動(dòng)程序結(jié)構(gòu)和框架作一介紹。 關(guān)鍵字:ARM,幀緩沖(Framebuffer),MC928MX1。 長(zhǎng)期以來(lái),在常見的掌上電腦(PDA)等小型手持式設(shè)備上,由于硬件條件等的限制,我們看到的顯示器件通常是單色LCD,用戶界面也非常簡(jiǎn)單,幾乎看不到PC機(jī)上美觀整齊的圖形界面(GUI)支持。由于早期嵌入式處理器的速度有限,在處理圖形和多媒體數(shù)據(jù)方面也顯得力不從心。 隨著高性能嵌入式處理器的普及和硬件成本的不斷降低,尤其是Arm系列處理器的推出,嵌入式系統(tǒng)的功能也越來(lái)越強(qiáng)。在多媒體應(yīng)用的推動(dòng)下,彩色LCD也越來(lái)越多地應(yīng)用到了嵌入式系統(tǒng)中,如新一代掌上電腦(PDA)多采用TFT顯示器件,支持彩色圖形界面,圖片顯示和視頻媒體播放。掌上電腦(PDA)的操作系統(tǒng)有微軟Window CE,PalmOS等。而Linux做為開放源代碼的操作系統(tǒng)也在市場(chǎng)中占據(jù)了一席之地。由于Linux成本低廉,任何人都可以得到其源代碼并在其基礎(chǔ)上進(jìn)行開發(fā),成為各家廠商極力發(fā)展的操作系統(tǒng),加上其核心小,潛力可觀。 在應(yīng)用需求的推動(dòng)下,Linux下也出現(xiàn)了許多圖形界面軟件包,如MiniGUI、Trolletech公司的Embedded QT等,其圖形界面及開發(fā)工具與Windows CE不相上下。在圖形軟件包的開發(fā)和移植工作中都牽扯到底層LCD的驅(qū)動(dòng)問題。筆者參與了一個(gè)基于ARM9的PDA系統(tǒng)的開發(fā),用的是摩托羅拉公司龍珠系列的MC928MX1。軟件采用Linux 2.4.18平臺(tái),編譯器為gcc的ARM交叉編譯器。 一. 硬件平臺(tái) MC928MX1(以下簡(jiǎn)稱MX1)是摩托羅拉公司基于ARM核心的第一款MCU,主要面向高端嵌入式應(yīng)用。內(nèi)部采用ARM920T內(nèi)核,并集成了SDRAM/Flash,LCD,USB,藍(lán)牙(bluetooth),多媒體閃存卡(MMC),CMOS攝像頭等控制器。 LCD控制器的功能是產(chǎn)生顯示驅(qū)動(dòng)信號(hào),驅(qū)動(dòng)LCD顯示器。用戶只需要通過讀寫一系列的寄存器,完成配制和顯示控制。MX1中的LCD控制器可支持單色/彩色LCD顯示器。支持彩色TFT時(shí),可提供4/8/12/16位顏色模式,其中16位顏色模式下可以顯示65536種顏色。配置LCD控制器重要的一步是指定顯示緩沖區(qū),顯示的內(nèi)容就是從緩沖區(qū)中讀出的,其大小由屏幕分辨率和顯示顏色數(shù)決定。在本例中,筆者采用的是夏普LQ035Q2DD54 TFT 顯示模塊,在240x320分辨率下可提供16位彩色顯示。 PMT 版權(quán)所有 2002 http://www.pmtsolution.net
Page 1
嵌入式 Linux 下彩色 LCD 驅(qū)動(dòng)的設(shè)計(jì)與實(shí)現(xiàn)
二. Linux下的設(shè)備驅(qū)動(dòng) Linux將設(shè)備分為最基本的兩大類,字符設(shè)備和塊設(shè)備。字符設(shè)備是以單個(gè)字節(jié)為單位進(jìn)行順序讀寫操作,通常不使用緩沖技術(shù),如鼠標(biāo)等,驅(qū)動(dòng)程序?qū)崿F(xiàn)比較簡(jiǎn)單;而塊設(shè)備則是以固定大小的數(shù)據(jù)塊進(jìn)行存儲(chǔ)和讀寫的,如硬盤,軟盤等。為提高效率,系統(tǒng)對(duì)于塊設(shè)備的讀寫提供了緩存機(jī)制,由于涉及緩沖區(qū)管理,調(diào)度,同步等問題,實(shí)現(xiàn)起來(lái)比字符設(shè)備復(fù)雜的多。 Linux的設(shè)備管理是和文件系統(tǒng)解密結(jié)合的,各種設(shè)備名稱都以文件的形式存放在/dev目錄下,稱為設(shè)備文件。應(yīng)用程序可以打開,關(guān)閉,讀寫這些設(shè)備文件,完成對(duì)設(shè)備的操作,就象操作普通的數(shù)據(jù)文件一樣。為了管理這些設(shè)備,系統(tǒng)為設(shè)備編了號(hào),每個(gè)設(shè)備號(hào)又分為主設(shè)備號(hào)和次設(shè)備號(hào)。主設(shè)備號(hào)用來(lái)區(qū)分不同種類的設(shè)備,而次設(shè)備號(hào)用來(lái)區(qū)分同一類型的多個(gè)設(shè)備。對(duì)于常用設(shè)備,Linux有約定俗成的編號(hào),如硬盤主設(shè)備號(hào)是3。在Linux的/dev/目錄下使用ls -l命令可察看個(gè)設(shè)備文件的設(shè)備號(hào)。例如,/dev/hda為塊設(shè)備,主設(shè)備號(hào)3,次設(shè)備號(hào)0,是系統(tǒng)的第一塊硬盤。/dev/hd1主設(shè)備號(hào)3,次設(shè)備號(hào)1,為系統(tǒng)的第二塊硬盤。我們將要介紹的顯示設(shè)備也是一個(gè)設(shè)備文件/dev/fb,主設(shè)備號(hào)29。在編寫設(shè)備驅(qū)動(dòng)程序的時(shí)候,也要指明所操作設(shè)備的主設(shè)備號(hào)和次設(shè)備號(hào)。 Linux的特點(diǎn)之一,是為所有的文件,包括設(shè)備文件,提供了統(tǒng)一的操作函數(shù)接口,定義如下: 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); }; 結(jié)構(gòu)體中的成員為一系列的接口函數(shù),如用于讀/寫的read/ write函數(shù),用于控制的ioctl等。打開一個(gè)文件就是調(diào)用這個(gè)文件file_operations中的open操作。不同類型的文件有不同的file_operations成員函數(shù)。如普通的磁盤數(shù)據(jù)文件,接口函數(shù)完成磁盤數(shù)據(jù)塊讀寫操作;而對(duì)于各種設(shè)備文件,則最終調(diào)用各自驅(qū)動(dòng)程序中的I/O函數(shù)進(jìn)行具體設(shè)備的操
PMT 版權(quán)所有 2002 http://www.pmtsolution.net
Page 2
嵌入式 Linux 下彩色 LCD 驅(qū)動(dòng)的設(shè)計(jì)與實(shí)現(xiàn)
作。這樣,應(yīng)用程序根本不用考慮操作的是設(shè)備還是普通文件,可一律當(dāng)作文件處理,具有非常清晰統(tǒng)一的I/O接口。所以file_operations是文件層次的I/O接口。 但是,由于外設(shè)的種類繁多,操作方式也各不相同。如聲音設(shè)備驅(qū)動(dòng)要使用DMA通道,顯示設(shè)備驅(qū)動(dòng)要提供對(duì)顯存的操作,硬盤驅(qū)動(dòng)要處理復(fù)雜的緩沖區(qū)結(jié)構(gòu),網(wǎng)絡(luò)設(shè)備驅(qū)動(dòng)和socket聯(lián)系緊密。如果file_operations中的函數(shù)都讓驅(qū)動(dòng)程序的開發(fā)人員來(lái)寫,則就要處理大量的細(xì)節(jié),幾乎是不可能的。為了解決設(shè)備多樣性的問題,Linux采用了特殊情況特殊處理的辦法,為不同設(shè)備定義好了文件層次file_operations結(jié)構(gòu)中的接口函數(shù),其中處理了大多數(shù)設(shè)備相關(guān)的操作,如各種緩沖區(qū)的申請(qǐng)和釋放等等,而具體操作底層硬件的一小部分則留給開發(fā)人員。所以Linux另外提供一個(gè)文件層到底層驅(qū)動(dòng)程序的接口,通常為一個(gè)結(jié)構(gòu)體,其中包含成員變量和函數(shù)指針。不同的設(shè)備驅(qū)動(dòng)有不同的結(jié)構(gòu)體。這樣,一方面保證了文件層I/O接口file_operations的一致性,另一方面驅(qū)動(dòng)程序的開發(fā)人員也不用了解太多細(xì)節(jié),只專著于硬件相關(guān)的I/O操作就可以了。例如,一個(gè)有代表性的特殊設(shè)備是聲音設(shè)備,其文件層的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等函數(shù)Linux都已提供,處理了與聲音設(shè)備相關(guān)的許多細(xì)節(jié),如DMA的申請(qǐng),釋放和操作等。而文件層到驅(qū)動(dòng)程序的接口為audio_driver結(jié)構(gòu),其中包含底層操作函數(shù)。文件層的sound_read,sound_write會(huì)在需要時(shí)調(diào)用audio_driver中的函數(shù)。開發(fā)人員只要編寫audio_driver中的函數(shù)就可以了,最大程度地減小了工作量。下面我們將看到,Linux為顯示設(shè)備提供的幀緩沖驅(qū)動(dòng)也是這種“文件層-驅(qū)動(dòng)層”的接口方式。 三. Linux的幀緩沖設(shè)備 幀緩沖(framebuffer)是Linux為顯示設(shè)備提供的一個(gè)接口,把顯存抽象后的一種設(shè)備,他允許上層應(yīng)用程序在圖形模式下直接對(duì)顯示緩沖區(qū)進(jìn)行讀寫操作。這種操作是抽象的,統(tǒng)一的。用戶不必關(guān)心物理顯存的位置、換頁(yè)機(jī)制等等具體細(xì)節(jié)。這些都是由Framebuffer設(shè)備驅(qū)動(dòng)來(lái)完成的。幀緩沖驅(qū)動(dòng)的應(yīng)用廣泛,在linux的桌面系統(tǒng)中,Xwindow服務(wù)器就是利用幀緩沖進(jìn)行窗口的繪制。尤其是通過幀緩沖可顯示漢字點(diǎn)陣,成為L(zhǎng)inux漢化的唯一可行方案。 幀緩沖設(shè)備對(duì)應(yīng)的設(shè)備文件為/dev/fb*,如果系統(tǒng)有多個(gè)顯示卡,Linux下還可支持多個(gè)幀緩沖設(shè)備,最多可達(dá)32個(gè),分別為/dev/fb0到/dev/fb31,而/dev/fb則為當(dāng)前缺省的幀緩沖設(shè)備,通常指向/dev/fb0。當(dāng)然在嵌入式系統(tǒng)中支持一個(gè)顯示設(shè)備就夠了。幀緩沖設(shè)備為標(biāo)準(zhǔn)字符設(shè)備,主設(shè)備號(hào)為29,次設(shè)備號(hào)則從0到31。分別對(duì)應(yīng)/dev/fb0-/dev/fb31。 PMT 版權(quán)所有 2002 http://www.pmtsolution.net
Page 3
嵌入式 Linux 下彩色 LCD 驅(qū)動(dòng)的設(shè)計(jì)與實(shí)現(xiàn)
通過/dev/fb,應(yīng)用程序的操作主要有這幾種: 1. 讀/寫(read/write)/dev/fb:相當(dāng)于讀/寫屏幕緩沖區(qū)。例如用 cp /dev/fb0 tmp命令可將當(dāng)前屏幕的內(nèi)容拷貝到一個(gè)文件中,而命令cp tmp > /dev/fb0 則將圖形文件tmp顯示在屏幕上。 2. 映射(map)操作:由于Linux工作在保護(hù)模式,每個(gè)應(yīng)用程序都有自己的虛擬地址空間,在應(yīng)用程序中是不能直接訪問物理緩沖區(qū)地址的。為此,Linux在文件操作 file_operations結(jié)構(gòu)中提供了mmap函數(shù),可將文件的內(nèi)容映射到用戶空間。對(duì)于幀緩沖設(shè)備,則可通過映射操作,可將屏幕緩沖區(qū)的物理地址映射到用戶空間的一段虛擬地址中,之后用戶就可以通過讀寫這段虛擬地址訪問屏幕緩沖區(qū),在屏幕上繪圖了。實(shí)際上,使用幀緩沖設(shè)備的應(yīng)用程序都是通過映射操作來(lái)顯示圖形的。由于映射操作都是由內(nèi)核來(lái)完成,下面我們將看到,幀緩沖驅(qū)動(dòng)留給開發(fā)人員的工作并不多。 3. I/O控制:對(duì)于幀緩沖設(shè)備,對(duì)設(shè)備文件的ioctl操作可讀取/設(shè)置顯示設(shè)備及屏幕的參數(shù),如分辨率,顯示顏色數(shù),屏幕大小等等。ioctl的操作是由底層的驅(qū)動(dòng)程序來(lái)完成的。 在應(yīng)用程序中,操作/dev/fb的一般步驟如下: 1. 打開/dev/fb設(shè)備文件。 2. 用ioctrl操作取得當(dāng)前顯示屏幕的參數(shù),如屏幕分辨率,每個(gè)像素點(diǎn)的比特?cái)?shù)。根據(jù)屏幕參數(shù)可計(jì)算屏幕緩沖區(qū)的大小。 3. 將屏幕緩沖區(qū)映射到用戶空間。 4. 映射后就可以直接讀寫屏幕緩沖區(qū),進(jìn)行繪圖和圖片顯示了。 典型程序段如下: #include linux/fb.h> int main() { int fbfd = 0; struct fb_var_screeninfo vinfo; struct fb_fix_screeninfo finfo; long int screensize = 0; /*打開設(shè)備文件*/ fbfd = open(/dev/fb0, O_RDWR); /*取得屏幕相關(guān)參數(shù)*/ ioctl(fbfd, FBIOGET_FSCREENINFO, finfo); ioctl(fbfd, FBIOGET_VSCREENINFO, vinfo); /*計(jì)算屏幕緩沖區(qū)大小*/ screensize = vinfo.xres * vinfo.yres * vinfo.bits_per_pixel / 8; /*映射屏幕緩沖區(qū)到用戶地址空間*/ fbp=(char*)mmap(0,screensize,PROT_READ|PROT_WRITE,MAP_SHARED, fbfd, 0); /*下面可通過fbp指針讀寫緩沖區(qū)*/ ……
PMT 版權(quán)所有 2002 http://www.pmtsolution.net
Page 4
}
嵌入式 Linux 下彩色 LCD 驅(qū)動(dòng)的設(shè)計(jì)與實(shí)現(xiàn)
四. 幀緩沖驅(qū)動(dòng)的編寫 幀緩沖設(shè)備屬于字符設(shè)備,與聲音設(shè)備一樣,也采用“文件層-驅(qū)動(dòng)層”的接口方式。在文件層次上,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, /* 關(guān)閉操作 */ }; 其中的成員函數(shù)都在文件linux/driver/video/fbmem.c中定義。 由于顯示設(shè)備的特殊性,在驅(qū)動(dòng)層的接口中不但要包含底層函數(shù),還要有一些紀(jì)錄設(shè)備狀態(tài)的數(shù)據(jù)。Linux為幀緩沖設(shè)備定義的驅(qū)動(dòng)層接口為struct fb_info結(jié)構(gòu),在include/linux/fb.h中定義。這個(gè)結(jié)構(gòu)比較長(zhǎng),限于篇幅,文章中就不全部列出了。幸運(yùn)的是,嵌入式系統(tǒng)要求的顯示操作比較簡(jiǎn)單,只涉及到結(jié)構(gòu)中少數(shù)幾個(gè)成員,下面只對(duì)編寫驅(qū)動(dòng)中要用到的幾個(gè)關(guān)鍵成員作一說(shuō)明。 fb_info中紀(jì)錄了幀緩沖設(shè)備的全部信息,包括設(shè)備的設(shè)置參數(shù),狀態(tài)以及操作函數(shù)指針。每一個(gè)幀緩沖設(shè)備都必須對(duì)應(yīng)一個(gè)fb_info結(jié)構(gòu)。其中成員變量Modename為設(shè)備名稱,fontname為顯示字體,fbops為指向底層操作的函數(shù)的指針,這些函數(shù)是需要驅(qū)動(dòng)程序開發(fā)人員編寫的。成員fb_var_screeninfo和 fb_fix_screeninfo也是結(jié)構(gòu)體。其中fb_var_screeninfo記錄用戶可修改的顯示控制器參數(shù),包括屏幕分辨率和每個(gè)像素點(diǎn)的比特?cái)?shù)。fb_var_screeninfo中的xres定義屏幕一行有多少個(gè)點(diǎn), yres定義屏幕一列有多少個(gè)點(diǎn), bits_per_pixel定義每個(gè)點(diǎn)用多少個(gè)字節(jié)表示。而fb_fix_screeninfo中記錄用戶不能修改的顯示控制器的參數(shù),如屏幕緩沖區(qū)的物理地址,長(zhǎng)度。當(dāng)對(duì)幀緩沖設(shè)備進(jìn)行映射操作的時(shí)候,就是從fb_fix_screeninfo中取得緩沖區(qū)物理地址的。上面所說(shuō)的數(shù)據(jù)成員都是需要在驅(qū)動(dòng)程序中設(shè)置的。 在了解了上面所述的概念后,編寫幀緩沖驅(qū)動(dòng)的實(shí)際工作并不復(fù)雜,需要做的工作是: 1. 編寫初始化函數(shù):初始化函數(shù)首先初始化LCD控制器,設(shè)置顯示模式和顯示顏色數(shù),然后分配LCD顯示緩沖區(qū)。在Linux可通過kmalloc函數(shù)分配一片連續(xù)的空間。筆者采用的LCD顯示方式為240x320,16位彩色。需要分配的顯示緩沖區(qū)為240x320x2 = 150k字節(jié),緩沖區(qū)通常分配在片外SDRAM中,起始地址保存在LCD控制器寄存器中。 最后是初始化一個(gè)fb_info結(jié)構(gòu),填充其中的成員變量,并調(diào)用register_framebuffer(fb_info)將fb_info登記入內(nèi)核。 2. 編寫結(jié)構(gòu)fb_info中函數(shù)指針fb_ops對(duì)應(yīng)的成員函數(shù):對(duì)于嵌入式系統(tǒng)的簡(jiǎn)單實(shí)現(xiàn),只需要下列三個(gè)函數(shù)就可以了: struct fb_ops {
PMT 版權(quán)所有 2002 http://www.pmtsolution.net
Page 5
嵌入式 Linux 下彩色 LCD 驅(qū)動(dòng)的設(shè)計(jì)與實(shí)現(xiàn)
…….. 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中定義。這些函數(shù)都是用來(lái)設(shè)置/獲取fb_info結(jié)構(gòu)中的成員變量的。當(dāng)應(yīng)用程序?qū)υO(shè)備文件進(jìn)行Ioctl操作時(shí)候會(huì)調(diào)用它們,讀者可參考前文中的應(yīng)用程序例子。例如,對(duì)于fb_get_fix(),應(yīng)用程序傳入的是fb_fix_screeninfo結(jié)構(gòu),在函數(shù)中對(duì)其成員變量賦值,主要是smem_start(緩沖區(qū)起始地址)和smem_len(緩沖區(qū)長(zhǎng)度),最終返回給應(yīng)用程序。而fb_set_var()函數(shù)的傳入?yún)?shù)是fb_var_screeninfo,函數(shù)中需要對(duì)xres,yres,和bits_per_pixel賦值。 驅(qū)動(dòng)程序編寫完成后,開發(fā)者可選擇將其編譯為動(dòng)態(tài)加載模塊,或靜態(tài)地編譯入內(nèi)核中。由于篇幅所限,有關(guān)這方面的內(nèi)容請(qǐng)讀者參考相關(guān)驅(qū)動(dòng)程序文檔。 五. 結(jié)束語(yǔ) 由于篇幅所限,本文中僅對(duì)幀緩沖設(shè)備驅(qū)動(dòng)的基本原理和框架做了簡(jiǎn)單介紹。幸運(yùn)的是,在Linux的發(fā)布版本中,包含了大量的設(shè)備驅(qū)動(dòng)程序源代碼,其中drvers/video下提供了多種顯示卡的幀緩沖設(shè)備驅(qū)動(dòng)程序程序,用戶自己的驅(qū)動(dòng)程序可參考成熟的代碼編寫或直接修改得到。 參考資料 《MC928MX1 User Manual》 《Linux frame buffer driver howto》 作者簡(jiǎn)介 許慶豐 Libo小組發(fā)起人?,F(xiàn)任職于摩托羅拉半導(dǎo)體部,主要研究方向是嵌入式操作系統(tǒng)和應(yīng)用。 Libo小組面向所有對(duì)嵌入式系統(tǒng)開發(fā)感興趣的人士,旨在促進(jìn)和推廣嵌入式系統(tǒng)的應(yīng)用、相關(guān)的開源技術(shù)及行業(yè)內(nèi)的交流。Libo在線討論組: http://www.smiling.com.cn/group/homepage.ecgi?group_id=36725 PMT 版權(quán)所有 2002 http://www.pmtsolution.net
評(píng)論