基于AT91RM9200的LCD驅動程序設計
1 引言
嵌入式系統(tǒng)應用于工控領域越來越普及,對于傳統(tǒng)工控產品升級換代發(fā)揮重要作用,隨著由此帶來的工控產品性能的大幅提高,與之對應的較高檔次、友好的人機界面需求也不斷增大。為此,Linux也出現(xiàn)了許多圖形界面軟件包,在其開發(fā)和移植過程種都涉及到底層LCD的驅動。本文針對一款基于AT91RM9200芯片的工業(yè)級嵌入式系統(tǒng)開發(fā)板,加上可擴展外圍控制器SLD13506,在Linux2.4.19操作系統(tǒng)下,通過編寫其驅動程序,再用arm-linux-gcc進行編譯,使ARM9開發(fā)板添加12.1英寸TFT彩色LCD顯示功能。
2 硬件介紹
AT91RM9200是一款基于ARM920T內核的高性價比、低功耗、32位的ARM 芯片,擁有獨立的16K指令和16K數(shù)據(jù)cache,寫緩存,全功能的MMU虛擬內存管理單元,內部的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的顯示控制芯片,其體系結構應低成本、低功耗的嵌入式市場的需求而設計,多用于移動通訊工具,手提電腦和辦公自動化。它可支持4/8位單色或4/8/16位彩色的單板單顯示接口,直接支持9/12位TFT/D-TFD彩色顯示,在18位TFT/D-TFD下可顯示65536種顏色,最大分辨率可為18bpp800×600。通過編寫SLD13506的設備驅動程序,讀寫一系列的寄存器來產生驅動信號,就可以驅動LCD的顯示。
3設備驅動程序
Linux是Unix操作系統(tǒng)的一種變種,類似于大部分Unix系統(tǒng),Linux應用程序獨立于底層硬件運行,用戶無需關心硬件問題,但需要為每一款硬件編寫驅動程序【²】,從而構成完整的運行系統(tǒng)。模塊化驅動程序后,用戶操作只需要通過一組標準化的調用來完成。把這些調用映射到設備特定的操作上,則是設備驅動程序的任務【2】。
系統(tǒng)調用是操作系統(tǒng)內核和應用程序之間的接口,設備驅動程序是操作系統(tǒng)內核和機器硬件之間的接口。設備驅動程序為應用程序屏蔽了硬件的細節(jié),這樣在應用程序看來,硬件設備只是一個設備文件,應用程序可以象操作普通文件一樣對硬件設備進行操作。這種機制可稱為“文件層-驅動層”接口方式。
應用程序是通過設備文件操作硬件,實際上是通過如 open,read,write,close系統(tǒng)調用來實現(xiàn)的。而file_operations這一關鍵的數(shù)據(jù)結構就把系統(tǒng)調用和驅動程序關聯(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 *);
}
這個結構的每一個成員的名字都對應著一個系統(tǒng)調用,應用程序利用系統(tǒng)調用在對設備文件進行諸如read/write操作時,系統(tǒng)調用通過設備文件的主設備號找到相應的設備驅動程序,然后讀取這個數(shù)據(jù)結構相應的函數(shù)指針,接著把控制權交給該函數(shù),這是linux的設備驅動程序工作的基本原理。所以編寫LCD驅動程序的主要工作就是編寫子函數(shù)來填充file_operations的各個域。[!--empirenews.page--]
4 linux下的幀緩沖區(qū)
Linux操作系統(tǒng)為LCD等顯示設備提供了幀緩沖區(qū),它是一種驅動程序接口【3】。幀緩沖區(qū)為圖像硬件設備提供了一種抽象化處理,那么應用軟件無需關心硬件設備的細節(jié),就可以通過定義明確的界面來訪問圖像硬件設備。所以為LCD硬件設備編寫驅動程序,實際上就是為幀緩沖區(qū)編寫驅動程序, 它們的關系如下圖1-1所示。
500)this.style.width=500;" border="0" />
把硬件設備抽象化為幀緩沖區(qū)設備,首先要指定LCD的幀緩沖區(qū),在fb.h文件中,其結構體fb_info為幀緩沖設備定義了驅動層接口,它不僅包含了底層函數(shù),而且還可以記錄設備狀態(tài)的數(shù)據(jù)。每個幀緩沖設備都與一個fb_info結構相對應。其中成員變量modename為設備名稱,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:定義了顯示輸出設備自身的屬性,如屏幕緩沖區(qū)的物理地址和長度。
??2)Struct fb_var_screeninfo:記錄了幀緩沖設備和指定顯示模式的可修改信息。它包括顯示屏幕的分辨率、每個像素的比特數(shù)和一些時序變量。其中變量xres定義了屏幕一行所占的像素數(shù),yres定義了屏幕一列所占的像素數(shù),bits_per_pixel定義了每個像素用多少個位來表示。
幀緩沖設備也屬于字符設備(文件設備的一種,還有塊設備),要實現(xiàn)“文件層-驅動層”的接口方式來對LCD進行驅動就必須定義一個類似于File_operationes可實現(xiàn)文件設備操作數(shù)據(jù)結構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); …};
這個結構中的每一個字段都必須指向驅動程序中實現(xiàn)特定操作的函數(shù),對于不支持的操作,對應的字段可以被置為NULL,或留到后續(xù)開發(fā)時載添加。
5 幀緩沖區(qū)驅動程序的編寫
為LCD顯示設備指定了幀緩沖區(qū)后,要實現(xiàn)LCD驅動實際上就是編寫幀緩沖區(qū)的驅動程序。在編寫幀緩沖區(qū)的驅動程序時,首先要對指出與fb_info中fb_ops結構相對應的成員函數(shù),然后分別實現(xiàn)它們,再對LCD的顯示進行初始化。只有實現(xiàn)了這些成員子函數(shù)才能在“文件層-驅動層”實現(xiàn)系統(tǒng)調用【4】,從而使應用程序可直接操作顯示硬件?,F(xiàn)分述如下:
5.1 編寫結構體fb_info中fb_ops及其對應的成員函數(shù)
本系統(tǒ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ù)都是用來設置和獲取驅動層接口fb_info結構體中的成員變量的,在第4小節(jié)中已提到,當應用程序對設備文件進行操作或讀取設備文件狀態(tài)時會調用這些函數(shù)。如fb_get_fix和fb_get_var函數(shù)得到的是fb_info中變量fix和var,fb_set_var則是對var變量進行設置。這些函數(shù)都要根據(jù)實際的操作來進行實現(xiàn),下面以s1d13xxxfb_set_var函數(shù)為例來說明這些子函數(shù)都是如何實現(xiàn)的。它的作用是設置fb_info里的結構體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硬件有關的參數(shù)}
5.2 LCD初始化
LCD控制器是通過產生顯示驅動信號來驅動LCD的。在驅動程序里,用戶只需要通過讀寫一系列的寄存器,就可以完成配置和顯示控制。而Linux下驅動程序總是先調用module_init()函數(shù),括號里的參數(shù)是所要初始化的文件設備的初始化函數(shù)。因此在本系統(tǒng)中,通過調用module_init(s1d13xxxfb_init)初始化函數(shù)來實現(xiàn)對一系列寄存器的設置。s1d13xxxfb_init初始化函數(shù)部分代碼如下:
int __init s1d13xxxfb_init(char *dummy){
S1D_INDEX s1dReg; //定義寄存器數(shù)組
S1D_VALUE s1dValue; //設置所對應寄存器的值
plateform_init_video(); //LCD顯示電壓寄存器的初始化
for (i = 0; i < sizeof(aS1DRegs)/sizeof(aS1DRegs[0]); i++) {
s1dReg = aS1DRegs[i].Index; //把設定的值寫入寄存器
s1dValue = aS1DRegs[i].Value;… }
local_s1d13xxxfb_open(); //打開sld13506控制器
strcpy(fb_info.modename, "s1d13xxx"); //復制modename
fb_info.node = -1; //指向底層函數(shù)指針賦初值為-1
fb_info.fbops = &s1d13xxxfb_ops; //對結構體fb_info.fbops初始化
fbgen_get_var(&disp.var, -1, &fb_info.gen.info); //獲取當前的顯示參數(shù)
fbgen_do_set_var(&disp.var, 1, &fb_info.gen); //設置顯示控制器參數(shù)
fbgen_install_cmap(0, &fb_info.gen); //根據(jù)LCD硬件參數(shù)開辟顯存空間
if (register_framebuffer(&fb_info.gen.info) < 0) {//注冊顯示驅動程序,不成功則報錯
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,但通過前面對結構Struct fb_var_screeninfo的賦值并不能真正設定其分辨率,因為結構Struct fb_var_screeninfo的值只是作為一個顯示記錄來用,必須通過設定寄存器的值,才能達到需要的分辨率。其中最主要的幾個寄存器及其代表意義如圖1-2所示:
500)this.style.width=500;" border="0" />
本系統(tǒng)通過一個數(shù)組對寄存器進行賦值,在初始化函數(shù)中利用s1dReg和s1dValue這兩個實參寫入:
static S1D_REGS aS1DRegs[] = {
…//前一個值為寄存器標識,后一個為寫入寄存器的值[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的驅動程序框架基本上已經完成了,通過5.1小節(jié)中已實現(xiàn)的成員函數(shù)調用就可以對幀緩沖區(qū)的顯示內容進行控制調試,步驟是把編寫好驅動程序用arm-linux-gcc進行交叉編譯,然后通過串口在目標板上運行insmod/remod命令動態(tài)加載/卸載來調試。若程序已調試好就可以把它編譯到Linux內核,燒錄進目標板的flash就可以完成LCD的顯示了。
6 結束語
文中介紹了Linux操作系統(tǒng)下的LCD驅動程序基本原理和框架,以及幀緩沖設備的作用。以基于AT91RM9200芯片的開發(fā)板和SLD13506控制器為例,編寫了一個典型的幀緩沖設備驅動程序。LCD顯示器的型號雖然很多,但其驅動的編寫基本上是類似的,可以通過本文介紹的步驟對其它彩色LCD進行編寫。
本文作者創(chuàng)新點:本文是介紹了基于ARM9技術的芯片的LCD顯示屏驅動控制程序,給出實際項目中已實現(xiàn)了的操作,對同類芯片中的LCD驅動或其他硬件驅動有很好的參考價值。