s3c2410 LCD驅(qū)動(dòng)學(xué)習(xí)心得
掃描二維碼
隨時(shí)隨地手機(jī)看文章
一 實(shí)驗(yàn)內(nèi)容簡要描述
1.實(shí)驗(yàn)?zāi)康?/p>
學(xué)會(huì)驅(qū)動(dòng)程序的編寫方法,配置S3C2410的LCD驅(qū)動(dòng),以及在LCD屏上顯示包括bmp和jpeg兩種格式的圖片
2.實(shí)驗(yàn)內(nèi)容
(1)分析S3c2410實(shí)驗(yàn)箱LCD以及LCD控制器的硬件原理,據(jù)此找出相應(yīng)的硬件設(shè)置參數(shù),參考xcale實(shí)驗(yàn)箱關(guān)于lcd的設(shè)置,完成s3c2410實(shí)驗(yàn)箱LCD的設(shè)置
(2)在LCD上顯示一張BMP圖片或JPEG圖片
3.實(shí)驗(yàn)條件(軟硬件環(huán)境)
PC機(jī)、S3C2410開發(fā)板、PXA255開發(fā)板
二 實(shí)驗(yàn)原理
1. S3C2410內(nèi)置LCD控制器分析
1.1 S3C2410 LCD控制器
一 塊LCD屏顯示圖像,不但需要LCD驅(qū)動(dòng)器,還需要有相應(yīng)的LCD控制器。通常LCD驅(qū)動(dòng)器會(huì)以COF/COG的形式與LCD 玻璃基板制作在一起,而LCD控制器則由外部電路來實(shí)現(xiàn)。而S3C2410內(nèi)部已經(jīng)集成了LCD控制器,因此可以很方便地去控制各種類型的LCD屏,例如:STN和TFT屏。S3C2410 LCD控制器的特性如下:
(1)STN屏
支持3種掃描方式:4bit單掃、4位雙掃和8位單掃
支持單色、4級(jí)灰度和16級(jí)灰度屏
支持256色和4096色彩色STN屏(CSTN)
支持分辯率為640*480、320*240、160*160以及其它規(guī)格的多種LCD
(2)TFT屏
支持單色、4級(jí)灰度、256色的調(diào)色板顯示模式
支持64K和16M色非調(diào)色板顯示模式
支持分辯率為640*480,320*240及其它多種規(guī)格的LCD
對(duì)于控制TFT屏來說,除了要給它送視頻資料(VD[23:0])以外,還有以下一些信號(hào)是必不可少的,分別是:
VSYNC(VFRAME) :幀同步信號(hào)
HSYNC(VLINE) :行同步信號(hào)
VCLK :像數(shù)時(shí)鐘信號(hào)
VDEN(VM) :數(shù)據(jù)有效標(biāo)志信號(hào)
由于本項(xiàng)目所用的S3C2410上的LCD是TFT屏,并且TFT屏將是今后應(yīng)用的主流,因此接下來,重點(diǎn)圍繞TFT屏的控制來進(jìn)行。
圖1.1是S3C2410內(nèi)部的LCD控制器的邏輯示意圖:
圖1.1
REGBANK 是LCD控制器的寄存器組,用來對(duì)LCD控制器的各項(xiàng)參數(shù)進(jìn)行設(shè)置。而 LCDCDMA 則是LCD控制器專用的DMA信道,負(fù)責(zé)將視頻資料從系統(tǒng)總線(System Bus)上取來,通過 VIDPRCS 從VD[23:0]發(fā)送給LCD屏。同時(shí) TIMEGEN 和 LPC3600 負(fù)責(zé)產(chǎn)生 LCD屏所需要的控制時(shí)序,例如VSYNC、HSYNC、VCLK、VDEN,然后從 VIDEO MUX 送給LCD屏。
1.2 TFT屏?xí)r序分析
圖 1.2是TFT屏的典型時(shí)序。其中VSYNC是幀同步信號(hào),VSYNC每發(fā)出1個(gè)脈沖,都意味著新的1屏視頻資料開始發(fā)送。而HSYNC為行同步信號(hào),每個(gè)HSYNC脈沖都表明新的1行視頻資料開始發(fā)送。而VDEN則用來標(biāo)明視頻資料的有效,VCLK是用來鎖存視頻資料的像數(shù)時(shí)鐘。
并且在幀同步以及行同步的頭尾都必須留有回掃時(shí)間,例如對(duì)于VSYNC來說前回掃時(shí)間就是(VSPW+1)+(VBPD+1),后回掃時(shí)間就是(VFPD +1);HSYNC亦類同。這樣的時(shí)序要求是當(dāng)初CRT顯示器由于電子槍偏轉(zhuǎn)需要時(shí)間,但后來成了實(shí)際上的工業(yè)標(biāo)準(zhǔn),乃至于后來出現(xiàn)的TFT屏為了在時(shí)序上于CRT兼容,也采用了這樣的控制時(shí)序。
圖1.2
S3C2410實(shí)驗(yàn)箱上的LCD是一款3.5寸TFT真彩LCD屏,分辯率為240*320,下圖為該屏的時(shí)序要求。
圖1.3
通過對(duì)比圖1.2和圖1.3,我們不難看出:
VSPW+1=2 -> VSPW=1
VBPD+1=2 -> VBPD=1
LINVAL+1=320-> LINVAL=319
VFPD+1=3 -> VFPD=2
HSPW+1=4 -> HSPW=3
HBPD+1=7 -> HBPW=6
HOZVAL+1=240-> HOZVAL=239
HFPD+1=31 -> HFPD=30
以上各參數(shù),除了LINVAL和HOZVAL直接和屏的分辯率有關(guān),其它的參數(shù)在實(shí)際操作過程中應(yīng)以上面的為參考,不應(yīng)偏差太多。
1.3 LCD控制器主要寄存器功能詳解
圖1.4
LINECNT :當(dāng)前行掃描計(jì)數(shù)器值,標(biāo)明當(dāng)前掃描到了多少行。
CLKVAL :決定VCLK的分頻比。LCD控制器輸出的VCLK是直接由系統(tǒng)總線(AHB)的工作頻率HCLK直接分頻得到的。做為240*320的TFT屏,應(yīng)保證得出的VCLK在5~10MHz之間。
MMODE :VM信號(hào)的觸發(fā)模式(僅對(duì)STN屏有效,對(duì)TFT屏無意義)。
PNRMODE :選擇當(dāng)前的顯示模式,對(duì)于TFT屏而言,應(yīng)選擇[11],即TFT LCD panel。
BPPMODE :選擇色彩模式,對(duì)于真彩顯示而言,選擇16bpp(64K色)即可滿足要求。
ENVID :使能LCD信號(hào)輸出。
圖1.5
VBPD , LINEVAL , VFPD , VSPW 的各項(xiàng)含義已經(jīng)在前面的時(shí)序圖中得到體現(xiàn)。
圖1.6
HBPD , HOZVAL , HFPD 的各項(xiàng)含義已經(jīng)在前面的時(shí)序圖中得到體現(xiàn)。
圖1.7
HSPW 的含義已經(jīng)在前面的時(shí)序圖中得到體現(xiàn)。
MVAL 只對(duì) STN屏有效,對(duì)TFT屏無意義。
HSPW 的含義已經(jīng)在前面的時(shí)序圖中得到體現(xiàn),這里不再贅述。
MVAL 只對(duì) STN屏有效,對(duì)TFT屏無意義。
圖1.8
VSTATUS :當(dāng)前VSYNC信號(hào)掃描狀態(tài),指明當(dāng)前VSYNC同步信號(hào)處于何種掃描階段。
HSTATUS :當(dāng)前HSYNC信號(hào)掃描狀態(tài),指明當(dāng)前HSYNC同步信號(hào)處于何種掃描階段。
BPP24BL :設(shè)定24bpp顯示模式時(shí),視頻資料在顯示緩沖區(qū)中的排列順序(即低位有效還是高位有效)。對(duì)于16bpp的64K色顯示模式,該設(shè)置位無意義。
FRM565 :對(duì)于16bpp顯示模式,有2中形式,一種是RGB=5:5:5:1,另一種是5:6:5。后一種模式最為常用,它的含義是表示64K種色彩的16bit RGB資料中,紅色(R)占了5bit,綠色(G)占了6bit,蘭色(B)占了5bit
INVVCLK , INVLINE , INVFRAME , INVVD :通過前面的時(shí)序圖,我們知道,CPU的LCD控制器輸出的時(shí)序默認(rèn)是正脈沖,而LCD需要VSYNC(VFRAME)、VLINE(HSYNC)均為負(fù)脈沖,因此 INVLINE 和 INVFRAME 必須設(shè)為“1 ”,即選擇反相輸出。
INVVDEN , INVPWREN , INVLEND 的功能同前面的類似。
PWREN 為LCD電源使能控制。在CPU LCD控制器的輸出信號(hào)中,有一個(gè)電源使能管腳LCD_PWREN,用來做為LCD屏電源的開關(guān)信號(hào)。
ENLEND 對(duì)普通的TFT屏無效,可以不考慮。
BSWP 和 HWSWP 為字節(jié)(Byte)或半字(Half-Word)交換使能。由于不同的GUI對(duì)FrameBuffer(顯示緩沖區(qū))的管理不同,必要時(shí)需要通過調(diào)整 BSWP 和 HWSWP 來適應(yīng)GUI。
2. Linux 驅(qū)動(dòng)
2.1 FrameBuffer
Linux 是工作在保護(hù)模式下,所以用戶態(tài)進(jìn)程是無法像DOS那樣使用顯卡BIOS里提供的中斷調(diào)用來實(shí)現(xiàn)直接寫屏,Lin仿顯卡的功能,將顯ux抽象出 FrameBuffer這個(gè)設(shè)備來供用戶態(tài)進(jìn)程實(shí)現(xiàn)直接寫屏。Framebuffer機(jī)制模卡硬件結(jié)構(gòu)抽象掉,可以通過Framebuffer的讀寫直接對(duì)顯存進(jìn)行操作。用戶可以將Framebuffer看成是顯示內(nèi)存的一個(gè)映像,將其映射到進(jìn)程地址空間之后,就可以直接進(jìn)行讀寫操作,而寫操作可以立即反應(yīng)在屏幕上。這種操作是抽象的,統(tǒng)一的。用戶不必關(guān)心物理顯存的位置、換頁機(jī)制等等具體細(xì)節(jié)。這些都是由Framebuffer設(shè)備驅(qū)動(dòng)來完成的。
在 Linux系統(tǒng)下,F(xiàn)rameBuffer的主要的結(jié)構(gòu)如圖所示。Linux為了開發(fā)FrameBuffer程序的方便,使用了分層結(jié)構(gòu)。fbmem.c 處于Framebuffer設(shè)備驅(qū)動(dòng)技術(shù)的中心位置。它為上層應(yīng)用程序提供系統(tǒng)調(diào)用,也為下一層的特定硬件驅(qū)動(dòng)提供接口;那些底層硬件驅(qū)動(dòng)需要用到這兒的接口來向系統(tǒng)內(nèi)核注冊它們自己。
fbmem.c 為所有支持FrameBuffer的設(shè)備驅(qū)動(dòng)提供了通用的接口,避免重復(fù)工作。下將介紹fbmem.c主要的一些數(shù)據(jù)結(jié)構(gòu)。
2.2 數(shù)據(jù)結(jié)構(gòu)
2.2.1 Linux FrameBuffer的數(shù)據(jù)結(jié)構(gòu)
在FrameBuffer中,fb_info可以說是最重要的一個(gè)結(jié)構(gòu)體,它是Linux為幀緩沖設(shè)備定義的驅(qū)動(dòng)層接口。它不僅包含了底層函數(shù),而且還有記錄設(shè)備狀態(tài)的數(shù)據(jù)。每個(gè)幀緩沖設(shè)備都與一個(gè)fb_info結(jié)構(gòu)相對(duì)應(yīng)。fb_info的主要成員如下
struct fb_info {
int node;
struct fb_var_screeninfo var;/* Current var */
struct fb_fix_screeninfo fix;/* Current fix */
struct fb_videomode *mode;/* current mode */
struct fb_ops *fbops;
struct device *device;/* This is the parent */
struct device *dev;/* This is this fb device */
char __iomem *screen_base;/* Virtual address */
unsigned long screen_size;/* Amount of ioremapped VRAM or 0 */
…………
};
其中node成員域標(biāo)示了特定的FrameBuffer,實(shí)際上也就是一個(gè)FrameBuffer設(shè)備的次設(shè)備號(hào)。fb_var_screeninfo結(jié)構(gòu)體成員記錄用戶可修改的顯示控制器參數(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é)表示。其他域見以下代碼注釋。
struct fb_var_screeninfo {
__u32 xres;/* visible resolution */
__u32 yres;
__u32 xoffset;/* offset from virtual to visible */
__u32 yoffset;/* resolution */
__u32 bits_per_pixel;/* bits/pixel */
__u32 pixclock;/* pixel clock in ps (pico seconds) */
__u32 left_margin;/* time from sync to picture*/
__u32 right_margin;/* time from picture to sync*/
__u32 hsync_len;/* length of horizontal sync*/
__u32 vsync_len;/* length of vertical sync*/
…………
};
在fb_info結(jié)構(gòu)體中,fb_fix_screeninfo中記錄用戶不能修改的顯示控制器的參數(shù),如屏幕緩沖區(qū)的物理地址,長度。當(dāng)對(duì)幀緩沖設(shè)備進(jìn)行映射操作的時(shí)候,就是從fb_fix_screeninfo中取得緩沖區(qū)物理地址的。
struct fb_fix_screeninfo {
char id[16]; /* identification string eg "TT Builtin" */
unsigned long smem_start; /* Start of frame buffer mem (physical address) */
__u32 smem_len; /* Length of frame buffer mem */
unsigned long mmio_start; /* Start of Mem Mapped I/O(physical address) */
__u32 mmio_len; /* Length of Memory Mapped I/O */
…………
};
fb_info 還有一個(gè)很重要的域就是fb_ops。它是提供給底層設(shè)備驅(qū)動(dòng)的一個(gè)接口。通常我們編寫字符驅(qū)動(dòng)的時(shí)候,要填寫一個(gè)file_operations結(jié)構(gòu)體,并使用register_chrdev()注冊之,以告訴Linux如何操控驅(qū)動(dòng)。當(dāng)我們編寫一個(gè)FrameBuffer的時(shí)候,就要依照Linux FrameBuffer編程的套路,填寫fb_ops結(jié)構(gòu)體。這個(gè)fb_ops也就相當(dāng)于通常的file_operations結(jié)構(gòu)體。
struct fb_ops {
int (*fb_open)(struct fb_info *info, int user);
int (*fb_release)(struct fb_info *info, int user);
ssize_t (*fb_read)(struct file *file, char __user *buf, size_t count, loff_t *ppos);
ssize_t (*fb_write)(struct file *file, const char __user *buf, size_t count,
loff_t *ppos);
int (*fb_set_par)(struct fb_info *info);
int (*fb_setcolreg)(unsigned regno, unsigned red, unsigned green,
unsigned blue, unsigned transp, struct fb_info *info);
int (*fb_setcmap)(struct fb_cmap *cmap, struct fb_info *info)
int (*fb_mmap)(struct fb_info *info, struct vm_area_struct *vma);
……………
}
上面的結(jié)構(gòu)體,根據(jù)函數(shù)的名字就可以看出它的作用,這里不在一一說明。下圖給出了Linux FrameBuffer的總體結(jié)構(gòu),作為這一部分的總結(jié)。
圖2.2
2.2.2 S3C2410中LCD的數(shù)據(jù)結(jié)構(gòu)
在S3C2410的LCD設(shè)備驅(qū)動(dòng)中,定義了s3c2410fb_info來標(biāo)識(shí)一個(gè)LCD設(shè)備,結(jié)構(gòu)體如下:
struct s3c2410fb_info {
struct fb_info*fb;
struct device*dev;
struct s3c2410fb_mach_info *mach_info;
struct s3c2410fb_hwregs;/* LCD Hardware Regs */
dma_addr_tmap_dma;/* physical */
u_char *map_cpu;/* virtual */
u_intmap_size;
/* addresses of pieces placed in raw buffer */
u_char *screen_cpu;/* virtual address of buffer */
dma_addr_tscreen_dma;/* physical address of buffer */
…………
};
成 員變量fb指向我們上面所說明的fb_info結(jié)構(gòu)體,代表了一個(gè)FrameBuffer。dev則表示了這個(gè)LCD設(shè)備。 map_dma,map_cpu,map_size這三個(gè)域向了開辟給LCD DMA使用的內(nèi)存地址。screen_cpu,screen_dma指向了LCD控制器映射的內(nèi)存地址。另外regs標(biāo)識(shí)了LCD控制器的寄存器。
struct s3c2410fb_hw {
unsigned longlcdcon1;
unsigned longlcdcon2;
unsigned longlcdcon3;
unsigned longlcdcon4;
unsigned longlcdcon5;
};
這個(gè)寄存器和硬件的寄存器一一對(duì)應(yīng),主要作為實(shí)際寄存器的映像,以便程序使用。
這個(gè)s3c2410fb_info中還有一個(gè)s3c2410fb_mach_info成員域。它存放了和體系結(jié)構(gòu)相關(guān)的一些信息,如時(shí)鐘、LCD設(shè)備的GPIO口等等。這個(gè)結(jié)構(gòu)體定義為
struct s3c2410fb_mach_info {
unsigned charfixed_syncs;/* do not update sync/border */
inttype; /* LCD types */
intwidth; /* Screen size */
intheight;
struct s3c2410fb_val xres; /* Screen info */
struct s3c2410fb_val yres;
struct s3c2410fb_val bpp;
struct s3c2410fb_hw regs; /* lcd configuration registers */
/* GPIOs */
unsigned longgpcup;
unsigned longgpcup_mask;
unsigned longgpccon;
unsigned longgpccon_mask;
…………
};
圖2.3
上圖表示了S3C2410驅(qū)動(dòng)的整體結(jié)構(gòu),反映了結(jié)構(gòu)體之間的相互關(guān)系
2.3 主要代碼結(jié)構(gòu)以及關(guān)鍵代碼分析
2.3.1 FrameBuffer驅(qū)動(dòng)的統(tǒng)一管理
fbmem.c 實(shí)現(xiàn)了Linux FrameBuffer的中間層,任何一個(gè)FrameBuffer驅(qū)動(dòng),在系統(tǒng)初始化時(shí),必須向fbmem.c注冊,即需要調(diào)用 register_framebuffer()函數(shù),在這個(gè)過程中,設(shè)備驅(qū)動(dòng)的信息將會(huì)存放入名稱為registered_fb數(shù)組中,這個(gè)數(shù)組定義為
struct fb_info *registered_fb[FB_MAX];
int num_registered_fb;
它是類型為fb_info的數(shù)組,另外num_register_fb則存放了注冊過的設(shè)備數(shù)量。
我們分析一下register_framebuffer的代碼。
int register_framebuffer(struct fb_info *fb_info)
{
int i;
struct fb_event event;
struct fb_videomode mode;
if (num_registered_fb == FB_MAX)return -ENXIO;/* 超過最大數(shù)量 */
num_registered_fb++;
for (i = 0 ; i < FB_MAX; i++)
if (!registered_fb[i])break;/* 找到空余的數(shù)組空間 */
fb_info->node = i;
fb_info->dev = device_create(fb_class, fb_info->device,
MKDEV(FB_MAJOR, i), "fb%d", i);/* 為設(shè)備建立設(shè)備節(jié)點(diǎn) */
if (IS_ERR(fb_info->dev)) {
…………
} else{
fb_init_device(fb_info);/* 初始化改設(shè)備 */
}
…………
return 0;
}
從上面的代碼可知,當(dāng)FrameBuffer驅(qū)動(dòng)進(jìn)行注冊的時(shí)候,它將驅(qū)動(dòng)的fb_info結(jié)構(gòu)體記錄到全局?jǐn)?shù)組registered_fb中,并動(dòng)態(tài)建立設(shè)備節(jié)點(diǎn),進(jìn)行設(shè)備的初始化。注意,這里建立的設(shè)備節(jié)點(diǎn)的次設(shè)備號(hào)就是該驅(qū)動(dòng)信息在registered_fb存放的位置,即數(shù)組下標(biāo)i 。在完成注冊之后,fbmem.c就記錄了驅(qū)動(dòng)的fb_info。這樣我們就有可能實(shí)現(xiàn)fbmem.c對(duì)全部FrameBuffer驅(qū)動(dòng)的統(tǒng)一處理。
2.3.2 實(shí)現(xiàn)消息的分派
fbmem.c實(shí)現(xiàn)了對(duì)系統(tǒng)全部FrameBuffer設(shè)備的統(tǒng)一管理。當(dāng)用戶嘗試使用一個(gè)特定的FrameBuffer時(shí),fbmem.c怎么知道該調(diào)用那個(gè)特定的設(shè)備驅(qū)動(dòng)呢?
我們知道,Linux是通過主設(shè)備號(hào)和次設(shè)備號(hào),對(duì)設(shè)備進(jìn)行唯一標(biāo)識(shí)。不同的FrameBuffer設(shè)備向fbmem.c注冊時(shí),程序分配給它們的主設(shè)備號(hào)是一樣的,而次設(shè)備號(hào)是不一樣的。于是我們就可以通過用戶指明的次設(shè)備號(hào),來覺得具體該調(diào)用哪一個(gè)FrameBuffer驅(qū)動(dòng)。下面通過分析 fbmem.c的fb_open()函數(shù)來說明。(注:一般我們寫FrameBuffer驅(qū)動(dòng)不需要實(shí)現(xiàn)open函數(shù),這里只是說明函數(shù)流程。)
static int fb_open(struct inode *inode, struct file *file){
int fbidx = iminor(inode);
struct fb_info *info;
int res;
/* 得到真正驅(qū)動(dòng)的函數(shù)指針 */
if (!(info = registered_fb[fbidx])) return -ENODEV;
if (info->fbops->fb_open) {
res = info->fbops->fb_open(info,1);//調(diào)用驅(qū)動(dòng)的open()
if (res) module_put(info->fbops->owner);
}
return res;
}
當(dāng)用戶打開一個(gè)FrameBuffer設(shè)備的時(shí),將調(diào)用這里的fb_open()函數(shù)。傳進(jìn)來的inode就是欲打開設(shè)備的設(shè)備號(hào),包括主設(shè)備和次設(shè)備號(hào)。 fb_open函數(shù)首先通過iminor()函數(shù)取得次設(shè)備號(hào),然后查全局?jǐn)?shù)組registered_fb得到設(shè)備的fb_info信息,而這里面存放了設(shè)備的操作函數(shù)集fb_ops。這樣,我們就可以調(diào)用具體驅(qū)動(dòng)的fb_open() 函數(shù),實(shí)現(xiàn)open的操作。下面給出了一個(gè)LCD驅(qū)動(dòng)的open() 函數(shù)的調(diào)用流程圖,用以說明上面的步驟。
圖2.4
2.3.3 開發(fā)板S3C2410 LCD驅(qū)動(dòng)的流程
(1)在mach-smdk2410.c中,定義了初始的LCD參數(shù)。注意這是個(gè)全局變量。
static struct s3c2410fb_mach_info smdk2410_lcd_cfg = {
.regs= {
.lcdcon1 = S3C2410_LCDCON1_TFT16BPP |
S3C2410_LCDCON1_TFT|
S3C2410_LCDCON1_CLKVAL(7),
......
},
.width = 240, .height = 320,
.xres= {.min = 240,.max= 240,.defval = 240},
.bpp = {.min = 16, .max= 16, .defval = 16},
......
};
(2)內(nèi)核初始化時(shí)候調(diào)用s3c2410fb_probe函數(shù)。下面分析這個(gè)函數(shù)的做的工作。首先先動(dòng)態(tài)分配s3c2410fb_info空間。
fbinfo = framebuffer_alloc(sizeof(struct s3c2410fb_info),&pdev->dev);
把域mach_info指向mach-smdk2410.c中的smdk2410_lcd_cfg 。
info->mach_info = pdev->dev.platform_data;
設(shè)置fb_info域的fix,var,fops字段。
fbinfo->fix.type = FB_TYPE_PACKED_PIXELS;
fbinfo->fix.type_aux = 0;
fbinfo->fix.xpanstep = 0;
fbinfo->var.nonstd = 0;
fbinfo->var.activate = FB_ACTIVATE_NOW;
fbinfo->var.height = mach_info->height;
fbinfo->var.width = mach_info->width;
fbinfo->fbops = &s3c2410fb_ops;
……
該函數(shù)調(diào)用s3c2410fb_map_video_memory()申請(qǐng)DMA內(nèi)存,即顯存。
fbi->map_size = PAGE_ALIGN(fbi->fb->fix.smem_len + PAGE_SIZE);
fbi->map_cpu = dma_alloc_writecombine(fbi->dev, fbi->map_size,
&fbi->map_dma, GFP_KERNEL);
fbi->map_size = fbi->fb->fix.smem_len;
…….
設(shè)置控制寄存器,設(shè)置硬件寄存器。
memcpy(&info->regs, &mach_info->regs,sizeof(info->regs));
info->regs.lcdcon1 &= ~S3C2410_LCDCON1_ENVID;
……….
調(diào)用函數(shù)s3c2410fb_init_registers(),把初始值寫入寄存器。
writel(fbi->regs.lcdcon1, S3C2410_LCDCON1);
writel(fbi->regs.lcdcon2, S3C2410_LCDCON2);
(3)當(dāng)用戶調(diào)用mmap()映射內(nèi)存的時(shí)候,F(xiàn)bmem.c把剛才設(shè)置好的顯存區(qū)域映射給用戶。
start = info->fix.smem_start;
len = PAGE_ALIGN( (start & ~PAGE_MASK) + info->fix.smem_len);
io_remap_pfn_range(vma, vma->vm_start, off >> PAGE_SHIFT,
vma->vm_end - vma->vm_start,vma->vm_page_prot);
……
這樣就完成了驅(qū)動(dòng)初始化到用戶調(diào)用的整個(gè)過程。
3. BMP和JPEG圖形顯示程序
3.1 在LCD上顯示BMP或JPEG圖片的主流程圖
首先,在程序開始前。要在nfs/dev目錄下創(chuàng)建LCD的設(shè)備結(jié)點(diǎn),設(shè)備名fb0,設(shè)備類型為字符設(shè)備,主設(shè)備號(hào)為29,次設(shè)備號(hào)為0。命令如下:
mknod fb0 c 29 0
在 LCD上顯示圖象的主流程圖如圖3.1所示。程序一開始要調(diào)用open函數(shù)打開設(shè)備,然后調(diào)用ioctl獲取設(shè)備相關(guān)信息,接下來就是讀取圖形文件數(shù)據(jù),把圖象的RGB值映射到顯存中,這部分是圖象顯示的核心。對(duì)于JPEG格式的圖片,要先經(jīng)過JPEG解碼才能得到RGB數(shù)據(jù),本項(xiàng)目中直接才用現(xiàn)成的 JPEG庫進(jìn)行解碼。對(duì)于bmp格式的圖片,則可以直接從文件里面提取其RGB數(shù)據(jù)。要從一個(gè)bmp文件里面把圖片數(shù)據(jù)陣列提取出來,首先必須知道bmp 文件的格式。下面來詳細(xì)介紹bmp文件的格式。
圖3.1
3.2 bmp位圖格式分析
位圖文件可看成由四個(gè)部分組成:位圖文件頭、位圖信息頭、彩色表和定義位圖的字節(jié)陣列。如圖3.2所示。
圖3.2
文件頭中各個(gè)段的地址及其內(nèi)容如圖3.3。
圖3.3
位圖文件頭數(shù)據(jù)結(jié)構(gòu)包含BMP圖象文件的類型,顯示內(nèi)容等信息。它的數(shù)據(jù)結(jié)構(gòu)如下定義:
Typedef struct
{
int bfType;//表明位圖文件的類型,必須為BM
long bfSize;//表明位圖文件的大小,以字節(jié)為單位
int bfReserved1;//屬于保留字,必須為本0
int bfReserved2;//也是保留字,必須為本0
long bfOffBits;//位圖陣列的起始位置,以字節(jié)為單位
} BITMAPFILEHEADER;
圖3.4 位圖文件頭的數(shù)據(jù)結(jié)構(gòu)
(2)信息頭中各個(gè)段的地址及其內(nèi)容如圖3.5所示。
圖3.5
位圖信息頭的數(shù)據(jù)結(jié)構(gòu)包含了有關(guān)BMP圖象的寬,高,壓縮方法等信息,它的C語言數(shù)據(jù)結(jié)構(gòu)如圖3.6所示。
Typedef struct {
long biSize; //指出本數(shù)據(jù)結(jié)構(gòu)所需要的字節(jié)數(shù)
long biWidth;//以象素為單位,給出BMP圖象的寬度
long biHeight;//以象素為單位,給出BMP圖象的高度
int biPlanes;//輸出設(shè)備的位平面數(shù),必須置為1
int biBitCount;//給出每個(gè)象素的位數(shù)
long biCompress;//給出位圖的壓縮類型
long biSizeImage;//給出圖象字節(jié)數(shù)的多少
long biXPelsPerMeter;//圖像的水平分辨率
long biYPelsPerMeter;//圖象的垂直分辨率
long biClrUsed;//調(diào)色板中圖象實(shí)際使用的顏色素?cái)?shù)
long biClrImportant;//給出重要顏色的索引值
} BITMAPINFOHEADER;
圖3.6 BITMAPINFOHEADER數(shù)據(jù)結(jié)構(gòu)
(3)對(duì)于象素小于或等于16位的圖片,都有一個(gè)顏色表用來給圖象數(shù)據(jù)陣列提供顏色索引,其中的每塊數(shù)據(jù)都以B、G、R的順序排列,還有一個(gè)是reserved保留位。而在圖形數(shù)據(jù)區(qū)域存放的是各個(gè)象素點(diǎn)的索引值。它的C語言結(jié)構(gòu)如圖3.7所示。
圖3.7 顏色表數(shù)據(jù)結(jié)構(gòu)
(4)對(duì)于24位和32位的圖片,沒有彩色表,他在圖象數(shù)據(jù)區(qū)里直接存放圖片的RGB數(shù)據(jù),其中的每個(gè)象素點(diǎn)的數(shù)據(jù)都以B、G、R的順序排列。每個(gè)象素點(diǎn)的數(shù)據(jù)結(jié)構(gòu)如圖3.8所示。
圖3.8 圖象數(shù)據(jù)陣列的數(shù)據(jù)結(jié)構(gòu)
(5)由于圖象數(shù)據(jù)陣列中的數(shù)據(jù)是從圖片的最后一行開始往上存放的,因此在顯示圖象時(shí),是從圖象的左下角開始逐行掃描圖象,即從左到右,從下到上。
(6)對(duì)S3C2410或PXA255開發(fā)板上的LCD來說,他們每個(gè)象素點(diǎn)所占的位數(shù)為16位,這16位按B:G:R=5:6:5的方式分,其中B在最高位,R在最低位。而從bmp圖象得到的R、G、B數(shù)據(jù)則每個(gè)數(shù)據(jù)占8位,合起來一共24位,因此需要對(duì)該R、G、B數(shù)據(jù)進(jìn)行移位組合成一個(gè)16位的數(shù)據(jù)。移位方法如下:
b >>= 3; g >>= 2; r >>= 3;
RGBValue = ( r<<11 | g << 5 | b);
基于以上分析,提取各種類型的bmp圖象的流程如圖3.9所示
圖 3.9
3.3 實(shí)現(xiàn)顯示任意大小的圖片
開發(fā)板上的LCD屏的大小是固定的,S3C2410上的LCD為:240*320,PXA255上的為:640*480。比屏幕小的圖片在屏上顯示當(dāng)然沒問題,但是如果圖片比屏幕大呢?這就要求我們通過某種算法對(duì)圖片進(jìn)行縮放。
縮放的基本思想是將圖片分成若干個(gè)方塊,對(duì)每個(gè)方塊中的R、G、B數(shù)據(jù)進(jìn)行取平均,得到一個(gè)新的R、G、B值,這個(gè)值就作為該方塊在LCD屏幕上的映射。
縮放的算法描述如下:
(1)、計(jì)算圖片大小與LCD屏大小的比例,以及方塊的大小。為了適應(yīng)各種屏幕大小,這里并不直接給lcd_width和lcd_height賦值為240和320。而是調(diào)用標(biāo)準(zhǔn)的接口來獲取有關(guān)屏幕的參數(shù)。具體如下:
// Get variable screen information
if (ioctl(fbfd, FBIOGET_VSCREENINFO, &vinfo)) {
printf("Error reading variable information. ");
exit(3);
}
unsigned int lcd_width=vinfo.xres;
unsigned int lcd_height=vinfo.yres;
計(jì)算比例:
widthScale=bmpi->width/lcd_width;
heightScale=bmpi->height/lcd_height;
本程序中方塊的大小以如下的方式確定:
unsigned int paneWidth=
unsigned int paneHeight= ;
符號(hào) 代表向上取整。
(2)、 從圖片的左上角開始,以(i* widthScale,j* heightScale)位起始點(diǎn),以寬paneWidth 高paneHeight為一個(gè)小方塊,對(duì)該方塊的R、G、B數(shù)值分別取平均,得到映射點(diǎn)的R、G、B值,把該點(diǎn)作為要在LCD上顯示的第(i , j)點(diǎn)存儲(chǔ)起來。
這部分的程序如下:
//-------------取平均--------
for( i=0;i
{
for(j=0;j
{
color_sum_r=0;
color_sum_g=0;
color_sum_b=0;
for(m=i*heightScale;m
{
for(n=j*widthScale;n
{
color_sum_r+=pointvalue[m][n].r;
color_sum_g+=pointvalue[m][n].g;
color_sum_b+=pointvalue[m][n].b;
}
}
RGBvalue_256->r=div_round(color_sum_r,paneHeight*paneWidth);
RGBvalue_256->g=div_round(color_sum_g,paneHeight*paneWidth);
RGBvalue_256->b=div_round(color_sum_b,paneHeight*paneWidth);
}
}
3.4 圖片數(shù)據(jù)提取及顯示的總流程
通過以上的分析,整個(gè)圖片數(shù)據(jù)提取及顯示的總流程如圖3.10 所示。
圖 3.10
三 實(shí)驗(yàn)過程與結(jié)果
1. Linux 源代碼的修改
首先修改arch/arm/mach-smdk2410.c文件,加入以下代碼。
static struct s3c2410fb_mach_info smdk2440_lcd_cfg __initdata = {
.regs= {
.lcdcon1= S3C2410_LCDCON1_TFT16BPP |
S3C2410_LCDCON1_TFT |
S3C2410_LCDCON1_CLKVAL(7),
.lcdcon2= S3C2410_LCDCON2_VBPD(4) |
S3C2410_LCDCON2_LINEVAL(319) |
S3C2410_LCDCON2_VFPD(1) |
S3C2410_LCDCON2_VSPW(1),
.lcdcon3= S3C2410_LCDCON3_HBPD(26) |
S3C2410_LCDCON3_HOZVAL(239) |
S3C2410_LCDCON3_HFPD(30),
.lcdcon4= S3C2410_LCDCON4_HSPW(13) |
S3C2410_LCDCON4_MVAL(13),
.lcdcon5= S3C2410_LCDCON5_FRM565 |
S3C2410_LCDCON5_INVVLINE |
S3C2410_LCDCON5_INVVFRAME |
S3C2410_LCDCON5_PWREN |
S3C2410_LCDCON5_HWSWP,
},
.lpcsel= ((0xCE6) & ~7) | 1<<4,
.width= 240,
.height= 320,
.xres= {
.min= 240,
.max= 240,
.defval= 240,
},
.yres= {
.min= 320,
.max= 320,
.defval = 320,
},
.bpp= {
.min= 16,
.max= 16,
.defval = 16,
},
};
在函數(shù)smdk2410_machine_init()函數(shù)中加入LCD的初始化代碼,見下
static void __init smdk2410_machine_init(void){
s3c24xx_fb_set_platdata(&smdk2440_lcd_cfg);
smdk_machine_init();
}
2.編譯內(nèi)核,產(chǎn)生zImage文件,放入tftp目錄下。
3.在nfs的dev目錄下建立FrameBuffer的設(shè)備節(jié)點(diǎn),使用命令:
mknod fb0 c 29 0
4.啟動(dòng)開發(fā)板,加載內(nèi)核和文件系統(tǒng)。
5.編寫LCD的應(yīng)用程序,程序見附錄。
6.采用arm-linux-gcc 編譯應(yīng)用程序,產(chǎn)生可執(zhí)行文件,放入nfs目錄中。
7.在開發(fā)板上運(yùn)行編譯好的可執(zhí)行文件,便可。
8.下圖是BMP位圖顯示程序,在S3C2410上的運(yùn)行結(jié)果。
四 實(shí)驗(yàn)心得體會(huì)
1.LCD驅(qū)動(dòng)的主要問題是沒有LCD屏的文檔,我們找不到它的那些參數(shù)值,后來只能參照Linux源碼里面的其他LCD屏的參數(shù)進(jìn)行實(shí)驗(yàn)。
2.在驅(qū)動(dòng)差錯(cuò)的過程中,我們采用跟蹤打印的方法進(jìn)行調(diào)試。剛開始的時(shí)候,內(nèi)核打印出一行找不到LCD設(shè)備。我們定位到輸出這行提示的代碼處,進(jìn)行反向跟蹤。發(fā)現(xiàn)傳給函數(shù)的設(shè)備指針為空,于是往上排查,終于發(fā)現(xiàn)源代碼中沒有定義LCD的設(shè)備信息。于是驅(qū)動(dòng)問題也就順利解決了。
3.原來一直以為,只要LCD驅(qū)動(dòng)工作正常了,內(nèi)核起來的時(shí)候,液晶屏?xí)@示出Logo。當(dāng)時(shí)搞了很久一直沒有,還以為是驅(qū)動(dòng)的問題。后來隨便寫了一個(gè)LCD應(yīng)用程序,竟然能用。
4.在調(diào)試過程應(yīng)用程序中發(fā)現(xiàn),在讀取文件頭的時(shí)候,如果直接定義一個(gè)bitmapfileheader為它動(dòng)態(tài)分配內(nèi)存:
*bmph=(bitmapfileheader*)malloc(sizeof(bitmapfileheader));
然后用fread((char*)bmph,sizeof(bitmapfileheader),1,f)把文件頭一次性讀出來,讀出來的文件頭是錯(cuò)誤的,經(jīng)過調(diào)試發(fā)現(xiàn)原因是bitmapfileheader這個(gè)結(jié)構(gòu)體中的type屬性原本應(yīng)該占2字節(jié),但是被編譯器在分配內(nèi)存的時(shí)候進(jìn)行了內(nèi)存對(duì)齊的優(yōu)化,給他分配了4個(gè)字節(jié)的空間,造成讀文件的錯(cuò)誤。因此在編程中要特別注意內(nèi)存對(duì)齊的影響。
typedef struct
{
WORD type;(被優(yōu)化)
DWORD bfsize;
DWORD reserved;
DWORD offbits;
} bitmapfileheader;
5.在嵌入式應(yīng)用程序的移植過程中,我們原來認(rèn)為ARM和PC機(jī)大小尾順序是不同的,因此在應(yīng)用程序中,也對(duì)這個(gè)差別進(jìn)行了處理。當(dāng)時(shí),在調(diào)試過程中發(fā)現(xiàn),PC 機(jī)程序可以直接移植到ARM上,不需要任何改動(dòng)。但是我們的程序,的確存在會(huì)產(chǎn)生大小尾問題代碼(在使用fread()讀入時(shí))。這究竟是為什么?有人說,ARM是可以設(shè)置大小尾順序的。后來這個(gè)問題也沒有深究下去。
五 參考文獻(xiàn)
(1)嵌入式系統(tǒng)設(shè)計(jì)與應(yīng)用開發(fā):鄭靈翔. 北京:北京航天航空大學(xué)出版社 2006