基于AT91RM9200的LCD驅(qū)動程序設(shè)計
1 引言
嵌入式系統(tǒng)應(yīng)用于工控領(lǐng)域越來越普及,對于傳統(tǒng)工控產(chǎn)品升級換代發(fā)揮重要作用,隨著由此帶來的工控產(chǎn)品性能的大幅提高,與之對應(yīng)的較高檔次、友好的人機界面需求也不斷增大。為此,Linux也出現(xiàn)了許多圖形界面軟件包,在其開發(fā)和移植過程種都涉及到底層LCD的驅(qū)動。本文針對一款基于AT91RM9200芯片的工業(yè)級嵌入式系統(tǒng)開發(fā)板,加上可擴展外圍控制器SLD13506,在Linux2.4.19操作系統(tǒng)下,通過編寫其驅(qū)動程序,再用arm-linux-gcc進行編譯,使ARM9開發(fā)板添加12.1英寸TFT彩色LCD顯示功能。
2 硬件介紹
AT91RM9200是一款基于ARM920T內(nèi)核的高性價比、低功耗、32位的ARM 芯片,擁有獨立的16K指令和16K數(shù)據(jù)cache,寫緩存,全功能的MMU虛擬內(nèi)存管理單元,內(nèi)部的16KB SDRAM和128KB ROM,在180MHz工作頻率下運行速度為200MIPS。AT91RM9200集成了EBI, PMC,I/O,Ethernet,USB,MCI,SSC,UASRT, SPI,RTC,TWI等接口及其控制器。卻沒有針對LCD顯示的控制器,所以本系統(tǒng)添加了SLD13506作為顯示控制器,來實現(xiàn)LCD的顯示。
SLD13506是EPSON公司一款用于LCD/CRT/TV的顯示控制芯片,其體系結(jié)構(gòu)應(yīng)低成本、低功耗的嵌入式市場的需求而設(shè)計,多用于移動通訊工具,手提電腦和辦公自動化。它可支持4/8位單色或4/8/16位彩色的單板單顯示接口,直接支持9/12位TFT/D-TFD彩色顯示,在18位TFT/D-TFD下可顯示65536種顏色,最大分辨率可為18bpp800×600。通過編寫SLD13506的設(shè)備驅(qū)動程序,讀寫一系列的寄存器來產(chǎn)生驅(qū)動信號,就可以驅(qū)動LCD的顯示。
3設(shè)備驅(qū)動程序
Linux是Unix操作系統(tǒng)的一種變種,類似于大部分Unix系統(tǒng),Linux應(yīng)用程序獨立于底層硬件運行,用戶無需關(guān)心硬件問題,但需要為每一款硬件編寫驅(qū)動程序【²】,從而構(gòu)成完整的運行系統(tǒng)。模塊化驅(qū)動程序后,用戶操作只需要通過一組標(biāo)準(zhǔn)化的調(diào)用來完成。把這些調(diào)用映射到設(shè)備特定的操作上,則是設(shè)備驅(qū)動程序的任務(wù)【2】。
系統(tǒng)調(diào)用是操作系統(tǒng)內(nèi)核和應(yīng)用程序之間的接口,設(shè)備驅(qū)動程序是操作系統(tǒng)內(nèi)核和機器硬件之間的接口。設(shè)備驅(qū)動程序為應(yīng)用程序屏蔽了硬件的細節(jié),這樣在應(yīng)用程序看來,硬件設(shè)備只是一個設(shè)備文件,應(yīng)用程序可以象操作普通文件一樣對硬件設(shè)備進行操作。這種機制可稱為“文件層-驅(qū)動層”接口方式。
應(yīng)用程序是通過設(shè)備文件操作硬件,實際上是通過如 open,read,write,close系統(tǒng)調(diào)用來實現(xiàn)的。而file_operations這一關(guān)鍵的數(shù)據(jù)結(jié)構(gòu)就把系統(tǒng)調(diào)用和驅(qū)動程序關(guān)聯(lián)起來,它的形式如下:
struct file_operations {
struct module *owner;
int (*read) (struct inode * ,struct file *, char ,int);
int (*write) (struct inode * ,struct file *, off_t ,int);
int (*ioctl) (struct inode * ,struct file *, unsined int ,unsigned long);
int (*mmap) (struct inode * ,struct file *, struct vm_area_struct *);
}
這個結(jié)構(gòu)的每一個成員的名字都對應(yīng)著一個系統(tǒng)調(diào)用,應(yīng)用程序利用系統(tǒng)調(diào)用在對設(shè)備文件進行諸如read/write操作時,系統(tǒng)調(diào)用通過設(shè)備文件的主設(shè)備號找到相應(yīng)的設(shè)備驅(qū)動程序,然后讀取這個數(shù)據(jù)結(jié)構(gòu)相應(yīng)的函數(shù)指針,接著把控制權(quán)交給該函數(shù),這是linux的設(shè)備驅(qū)動程序工作的基本原理。所以編寫LCD驅(qū)動程序的主要工作就是編寫子函數(shù)來填充file_operations的各個域。[!--empirenews.page--]
4 linux下的幀緩沖區(qū)
Linux操作系統(tǒng)為LCD等顯示設(shè)備提供了幀緩沖區(qū),它是一種驅(qū)動程序接口【3】。幀緩沖區(qū)為圖像硬件設(shè)備提供了一種抽象化處理,那么應(yīng)用軟件無需關(guān)心硬件設(shè)備的細節(jié),就可以通過定義明確的界面來訪問圖像硬件設(shè)備。所以為LCD硬件設(shè)備編寫驅(qū)動程序,實際上就是為幀緩沖區(qū)編寫驅(qū)動程序, 它們的關(guān)系如下圖1-1所示。
500)this.style.width=500;" border="0" />
把硬件設(shè)備抽象化為幀緩沖區(qū)設(shè)備,首先要指定LCD的幀緩沖區(qū),在fb.h文件中,其結(jié)構(gòu)體fb_info為幀緩沖設(shè)備定義了驅(qū)動層接口,它不僅包含了底層函數(shù),而且還可以記錄設(shè)備狀態(tài)的數(shù)據(jù)。每個幀緩沖設(shè)備都與一個fb_info結(jié)構(gòu)相對應(yīng)。其中成員變量modename為設(shè)備名稱,fontname為顯示字體,node為指向底層操作的函數(shù)的指針:
struct fb_info {
char modename[40];
kdev_t node; int open;
struct fb_var_screeninfo var;
struct fb_fix_screeninfo fix;
struct fb_cmap cmap;
struct fb_ops *fbops; …};
??1)Struct fb_fix_screeninfo:定義了顯示輸出設(shè)備自身的屬性,如屏幕緩沖區(qū)的物理地址和長度。
??2)Struct fb_var_screeninfo:記錄了幀緩沖設(shè)備和指定顯示模式的可修改信息。它包括顯示屏幕的分辨率、每個像素的比特數(shù)和一些時序變量。其中變量xres定義了屏幕一行所占的像素數(shù),yres定義了屏幕一列所占的像素數(shù),bits_per_pixel定義了每個像素用多少個位來表示。
幀緩沖設(shè)備也屬于字符設(shè)備(文件設(shè)備的一種,還有塊設(shè)備),要實現(xiàn)“文件層-驅(qū)動層”的接口方式來對LCD進行驅(qū)動就必須定義一個類似于File_operationes可實現(xiàn)文件設(shè)備操作數(shù)據(jù)結(jié)構(gòu)fb_ops,然后編寫子函數(shù)對fb_ops的各個域進行填充:
struct fb_ops {
struct module *owner;
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);
int (*fb_get_cmap)(struct fb_cmap *cmap, int kspc, int con, struct fb_info *info);
int (*fb_set_cmap)(struct fb_cmap *cmap, int kspc, int con, struct fb_info *info);
int (*fb_mmap)(struct fb_info *info, struct file *file, struct vm_area_struct *vma); …};
這個結(jié)構(gòu)中的每一個字段都必須指向驅(qū)動程序中實現(xiàn)特定操作的函數(shù),對于不支持的操作,對應(yīng)的字段可以被置為NULL,或留到后續(xù)開發(fā)時載添加。
5 幀緩沖區(qū)驅(qū)動程序的編寫
為LCD顯示設(shè)備指定了幀緩沖區(qū)后,要實現(xiàn)LCD驅(qū)動實際上就是編寫幀緩沖區(qū)的驅(qū)動程序。在編寫幀緩沖區(qū)的驅(qū)動程序時,首先要對指出與fb_info中fb_ops結(jié)構(gòu)相對應(yīng)的成員函數(shù),然后分別實現(xiàn)它們,再對LCD的顯示進行初始化。只有實現(xiàn)了這些成員子函數(shù)才能在“文件層-驅(qū)動層”實現(xiàn)系統(tǒng)調(diào)用【4】,從而使應(yīng)用程序可直接操作顯示硬件?,F(xiàn)分述如下:
5.1 編寫結(jié)構(gòu)體fb_info中fb_ops及其對應(yīng)的成員函數(shù)
本系統(tǒng)中定義了特定操作所對應(yīng)的成員函數(shù),代碼如下:
static struct fb_ops s1d13xxxfb_ops = {
owner: THIS_MODULE,
fb_get_fix: s1d13xxxfb _get_fix,
fb_get_var: s1d13xxxfb _get_var,
fb_set_var: s1d13xxxfb _set_var,
fb_get_cmap: s1d13xxxfb _get_cmap,
fb_set_cmap: s1d13xxxfb _set_cmap,
fb_mmap: s1d13xxx_mmap, …};[!--empirenews.page--]
這些函數(shù)都是用來設(shè)置和獲取驅(qū)動層接口fb_info結(jié)構(gòu)體中的成員變量的,在第4小節(jié)中已提到,當(dāng)應(yīng)用程序?qū)υO(shè)備文件進行操作或讀取設(shè)備文件狀態(tài)時會調(diào)用這些函數(shù)。如fb_get_fix和fb_get_var函數(shù)得到的是fb_info中變量fix和var,fb_set_var則是對var變量進行設(shè)置。這些函數(shù)都要根據(jù)實際的操作來進行實現(xiàn),下面以s1d13xxxfb_set_var函數(shù)為例來說明這些子函數(shù)都是如何實現(xiàn)的。它的作用是設(shè)置fb_info里的結(jié)構(gòu)體fb_var_screeninfo變量var的值:
static int s1d13xxxfb_set_var(struct fb_var_screeninfo *var,){
memset(var, 0, sizeof(struct fb_var_screeninfo));
var->xres = 800; //顯示800×600分辨率
var->yres = 600;
var->bits_per_pixel = 16; //定義16位顏色數(shù)
… //其他與LCD硬件有關(guān)的參數(shù)}
5.2 LCD初始化
LCD控制器是通過產(chǎn)生顯示驅(qū)動信號來驅(qū)動LCD的。在驅(qū)動程序里,用戶只需要通過讀寫一系列的寄存器,就可以完成配置和顯示控制。而Linux下驅(qū)動程序總是先調(diào)用module_init()函數(shù),括號里的參數(shù)是所要初始化的文件設(shè)備的初始化函數(shù)。因此在本系統(tǒng)中,通過調(diào)用module_init(s1d13xxxfb_init)初始化函數(shù)來實現(xiàn)對一系列寄存器的設(shè)置。s1d13xxxfb_init初始化函數(shù)部分代碼如下:
int __init s1d13xxxfb_init(char *dummy){
S1D_INDEX s1dReg; //定義寄存器數(shù)組
S1D_VALUE s1dValue; //設(shè)置所對應(yīng)寄存器的值
plateform_init_video(); //LCD顯示電壓寄存器的初始化
for (i = 0; i < sizeof(aS1DRegs)/sizeof(aS1DRegs[0]); i++) {
s1dReg = aS1DRegs[i].Index; //把設(shè)定的值寫入寄存器
s1dValue = aS1DRegs[i].Value;… }
local_s1d13xxxfb_open(); //打開sld13506控制器
strcpy(fb_info.modename, "s1d13xxx"); //復(fù)制modename
fb_info.node = -1; //指向底層函數(shù)指針賦初值為-1
fb_info.fbops = &s1d13xxxfb_ops; //對結(jié)構(gòu)體fb_info.fbops初始化
fbgen_get_var(&disp.var, -1, &fb_info.gen.info); //獲取當(dāng)前的顯示參數(shù)
fbgen_do_set_var(&disp.var, 1, &fb_info.gen); //設(shè)置顯示控制器參數(shù)
fbgen_install_cmap(0, &fb_info.gen); //根據(jù)LCD硬件參數(shù)開辟顯存空間
if (register_framebuffer(&fb_info.gen.info) < 0) {//注冊顯示驅(qū)動程序,不成功則報錯
return -EINVAL; }
printk("Installed sld31506 frame buffer n”); };
首先對LCD的背光燈進行點亮,LCD顯示是一種被動的顯示模式,它不能發(fā)光,只能依靠控制透射或反射周圍環(huán)境的光來達到顯示的目的,因此,必須通過寫電壓寄存器,用高電平對LCD顯示器加3.2伏電壓來實現(xiàn)背光燈的點亮。其函數(shù)的部分代碼如下:[!--empirenews.page--]
void plateform_init_video(void) {
AT91_SYS->PIOC_PER |= 0x00004000; //對LCD加3.2伏的背光電壓[5]
AT91_SYS->PIOC_OER |= 0x00004000;
AT91_SYS->PIOC_SODR |= 0x00004000; …}
本文系統(tǒng)采用的12.1寸TFT彩色LCD最佳分辨率是800×600,但通過前面對結(jié)構(gòu)Struct fb_var_screeninfo的賦值并不能真正設(shè)定其分辨率,因為結(jié)構(gòu)Struct fb_var_screeninfo的值只是作為一個顯示記錄來用,必須通過設(shè)定寄存器的值,才能達到需要的分辨率。其中最主要的幾個寄存器及其代表意義如圖1-2所示:
500)this.style.width=500;" border="0" />
本系統(tǒng)通過一個數(shù)組對寄存器進行賦值,在初始化函數(shù)中利用s1dReg和s1dValue這兩個實參寫入:
static S1D_REGS aS1DRegs[] = {
…//前一個值為寄存器標(biāo)識,后一個為寫入寄存器的值[6]
{0x0032,0x63}, // 分辨率水平象素 =) ((032h)bit6-0)+ 1) × 8為800
{0x0038,0x57}, // 分辨率垂直象素0 = (038h)bit7-0 +分辨率垂直象素1為600
{0x0039,0x02}, // 分辨率垂直象素1 = ((039h)bit1-0) + 1
{0x0042,0x00}, // (042h)bit7-0幀緩沖區(qū)開始地址
{0x0043,0x00}, // (043h)bit7-0
{0x0044,0x00}, // (044h)bit3-0
{0x0046,0x20}, // (046h)bit7-0幀緩沖區(qū)寬度偏移量
{0x0047,0x03}, // (047h)bit2-0
{0x0048,0x00}, // lcd顯示圖像的起始位置地址0x00
}
至此,彩色LCD的驅(qū)動程序框架基本上已經(jīng)完成了,通過5.1小節(jié)中已實現(xiàn)的成員函數(shù)調(diào)用就可以對幀緩沖區(qū)的顯示內(nèi)容進行控制調(diào)試,步驟是把編寫好驅(qū)動程序用arm-linux-gcc進行交叉編譯,然后通過串口在目標(biāo)板上運行insmod/remod命令動態(tài)加載/卸載來調(diào)試。若程序已調(diào)試好就可以把它編譯到Linux內(nèi)核,燒錄進目標(biāo)板的flash就可以完成LCD的顯示了。
6 結(jié)束語
文中介紹了Linux操作系統(tǒng)下的LCD驅(qū)動程序基本原理和框架,以及幀緩沖設(shè)備的作用。以基于AT91RM9200芯片的開發(fā)板和SLD13506控制器為例,編寫了一個典型的幀緩沖設(shè)備驅(qū)動程序。LCD顯示器的型號雖然很多,但其驅(qū)動的編寫基本上是類似的,可以通過本文介紹的步驟對其它彩色LCD進行編寫。
本文作者創(chuàng)新點:本文是介紹了基于ARM9技術(shù)的芯片的LCD顯示屏驅(qū)動控制程序,給出實際項目中已實現(xiàn)了的操作,對同類芯片中的LCD驅(qū)動或其他硬件驅(qū)動有很好的參考價值。