s3c2410 LCD驅(qū)動學(xué)習(xí)心得
一 實驗內(nèi)容簡要描述
1.實驗?zāi)康?/p>
學(xué)會驅(qū)動程序的編寫方法,配置S3C2410的LCD驅(qū)動,以及在LCD屏上顯示包括bmp和jpeg兩種格式的圖片
2.實驗內(nèi)容
(1)分析S3c2410實驗箱LCD以及LCD控制器的硬件原理,據(jù)此找出相應(yīng)的硬件設(shè)置參數(shù),參考xcale實驗箱關(guān)于lcd的設(shè)置,完成s3c2410實驗箱LCD的設(shè)置
(2)在LCD上顯示一張BMP圖片或JPEG圖片
3.實驗條件(軟硬件環(huán)境)
PC機、S3C2410開發(fā)板、PXA255開發(fā)板
二 實驗原理
1. S3C2410內(nèi)置LCD控制器分析
1.1 S3C2410 LCD控制器
一 塊LCD屏顯示圖像,不但需要LCD驅(qū)動器,還需要有相應(yīng)的LCD控制器。通常LCD驅(qū)動器會以COF/COG的形式與LCD 玻璃基板制作在一起,而LCD控制器則由外部電路來實現(xiàn)。而S3C2410內(nèi)部已經(jīng)集成了LCD控制器,因此可以很方便地去控制各種類型的LCD屏,例如:STN和TFT屏。S3C2410 LCD控制器的特性如下:
(1)STN屏
支持3種掃描方式:4bit單掃、4位雙掃和8位單掃
支持單色、4級灰度和16級灰度屏
支持256色和4096色彩色STN屏(CSTN)
支持分辯率為640*480、320*240、160*160以及其它規(guī)格的多種LCD
(2)TFT屏
支持單色、4級灰度、256色的調(diào)色板顯示模式
支持64K和16M色非調(diào)色板顯示模式
支持分辯率為640*480,320*240及其它多種規(guī)格的LCD
對于控制TFT屏來說,除了要給它送視頻資料(VD[23:0])以外,還有以下一些信號是必不可少的,分別是:
VSYNC(VFRAME) :幀同步信號
HSYNC(VLINE) :行同步信號
VCLK :像數(shù)時鐘信號
VDEN(VM) :數(shù)據(jù)有效標(biāo)志信號
由于本項目所用的S3C2410上的LCD是TFT屏,并且TFT屏將是今后應(yīng)用的主流,因此接下來,重點圍繞TFT屏的控制來進行。
圖1.1是S3C2410內(nèi)部的LCD控制器的邏輯示意圖:
圖1.1
REGBANK 是LCD控制器的寄存器組,用來對LCD控制器的各項參數(shù)進行設(shè)置。而 LCDCDMA 則是LCD控制器專用的DMA信道,負責(zé)將視頻資料從系統(tǒng)總線(System Bus)上取來,通過 VIDPRCS 從VD[23:0]發(fā)送給LCD屏。同時 TIMEGEN 和 LPC3600 負責(zé)產(chǎn)生 LCD屏所需要的控制時序,例如VSYNC、HSYNC、VCLK、VDEN,然后從 VIDEO MUX 送給LCD屏。
1.2 TFT屏?xí)r序分析
圖 1.2是TFT屏的典型時序。其中VSYNC是幀同步信號,VSYNC每發(fā)出1個脈沖,都意味著新的1屏視頻資料開始發(fā)送。而HSYNC為行同步信號,每個HSYNC脈沖都表明新的1行視頻資料開始發(fā)送。而VDEN則用來標(biāo)明視頻資料的有效,VCLK是用來鎖存視頻資料的像數(shù)時鐘。
并且在幀同步以及行同步的頭尾都必須留有回掃時間,例如對于VSYNC來說前回掃時間就是(VSPW+1)+(VBPD+1),后回掃時間就是(VFPD +1);HSYNC亦類同。這樣的時序要求是當(dāng)初CRT顯示器由于電子槍偏轉(zhuǎn)需要時間,但后來成了實際上的工業(yè)標(biāo)準(zhǔn),乃至于后來出現(xiàn)的TFT屏為了在時序上于CRT兼容,也采用了這樣的控制時序。
圖1.2
S3C2410實驗箱上的LCD是一款3.5寸TFT真彩LCD屏,分辯率為240*320,下圖為該屏的時序要求。
圖1.3
通過對比圖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ù)在實際操作過程中應(yīng)以上面的為參考,不應(yīng)偏差太多。
1.3 LCD控制器主要寄存器功能詳解
圖1.4
LINECNT :當(dāng)前行掃描計數(shù)器值,標(biāo)明當(dāng)前掃描到了多少行。
CLKVAL :決定VCLK的分頻比。LCD控制器輸出的VCLK是直接由系統(tǒng)總線(AHB)的工作頻率HCLK直接分頻得到的。做為240*320的TFT屏,應(yīng)保證得出的VCLK在5~10MHz之間。
MMODE :VM信號的觸發(fā)模式(僅對STN屏有效,對TFT屏無意義)。
PNRMODE :選擇當(dāng)前的顯示模式,對于TFT屏而言,應(yīng)選擇[11],即TFT LCD panel。
BPPMODE :選擇色彩模式,對于真彩顯示而言,選擇16bpp(64K色)即可滿足要求。
ENVID :使能LCD信號輸出。
圖1.5
VBPD , LINEVAL , VFPD , VSPW 的各項含義已經(jīng)在前面的時序圖中得到體現(xiàn)。
圖1.6
HBPD , HOZVAL , HFPD 的各項含義已經(jīng)在前面的時序圖中得到體現(xiàn)。
圖1.7
HSPW 的含義已經(jīng)在前面的時序圖中得到體現(xiàn)。
MVAL 只對 STN屏有效,對TFT屏無意義。
HSPW 的含義已經(jīng)在前面的時序圖中得到體現(xiàn),這里不再贅述。
MVAL 只對 STN屏有效,對TFT屏無意義。
圖1.8
VSTATUS :當(dāng)前VSYNC信號掃描狀態(tài),指明當(dāng)前VSYNC同步信號處于何種掃描階段。
HSTATUS :當(dāng)前HSYNC信號掃描狀態(tài),指明當(dāng)前HSYNC同步信號處于何種掃描階段。
BPP24BL :設(shè)定24bpp顯示模式時,視頻資料在顯示緩沖區(qū)中的排列順序(即低位有效還是高位有效)。對于16bpp的64K色顯示模式,該設(shè)置位無意義。
FRM565 :對于16bpp顯示模式,有2中形式,一種是RGB=5:5:5:1,另一種是5:6:5。后一種模式最為常用,它的含義是表示64K種色彩的16bit RGB資料中,紅色(R)占了5bit,綠色(G)占了6bit,蘭色(B)占了5bit
INVVCLK , INVLINE , INVFRAME , INVVD :通過前面的時序圖,我們知道,CPU的LCD控制器輸出的時序默認是正脈沖,而LCD需要VSYNC(VFRAME)、VLINE(HSYNC)均為負脈沖,因此 INVLINE 和 INVFRAME 必須設(shè)為“1 ”,即選擇反相輸出。
INVVDEN , INVPWREN , INVLEND 的功能同前面的類似。
PWREN 為LCD電源使能控制。在CPU LCD控制器的輸出信號中,有一個電源使能管腳LCD_PWREN,用來做為LCD屏電源的開關(guān)信號。
ENLEND 對普通的TFT屏無效,可以不考慮。
BSWP 和 HWSWP 為字節(jié)(Byte)或半字(Half-Word)交換使能。由于不同的GUI對FrameBuffer(顯示緩沖區(qū))的管理不同,必要時需要通過調(diào)整 BSWP 和 HWSWP 來適應(yīng)GUI。
2. Linux 驅(qū)動
2.1 FrameBuffer
Linux 是工作在保護模式下,所以用戶態(tài)進程是無法像DOS那樣使用顯卡BIOS里提供的中斷調(diào)用來實現(xiàn)直接寫屏,Lin仿顯卡的功能,將顯ux抽象出 FrameBuffer這個設(shè)備來供用戶態(tài)進程實現(xiàn)直接寫屏。Framebuffer機制??ㄓ布Y(jié)構(gòu)抽象掉,可以通過Framebuffer的讀寫直接對顯存進行操作。用戶可以將Framebuffer看成是顯示內(nèi)存的一個映像,將其映射到進程地址空間之后,就可以直接進行讀寫操作,而寫操作可以立即反應(yīng)在屏幕上。這種操作是抽象的,統(tǒng)一的。用戶不必關(guān)心物理顯存的位置、換頁機制等等具體細節(jié)。這些都是由Framebuffer設(shè)備驅(qū)動來完成的。
在 Linux系統(tǒng)下,F(xiàn)rameBuffer的主要的結(jié)構(gòu)如圖所示。Linux為了開發(fā)FrameBuffer程序的方便,使用了分層結(jié)構(gòu)。fbmem.c 處于Framebuffer設(shè)備驅(qū)動技術(shù)的中心位置。它為上層應(yīng)用程序提供系統(tǒng)調(diào)用,也為下一層的特定硬件驅(qū)動提供接口;那些底層硬件驅(qū)動需要用到這兒的接口來向系統(tǒng)內(nèi)核注冊它們自己。
fbmem.c 為所有支持FrameBuffer的設(shè)備驅(qū)動提供了通用的接口,避免重復(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可以說是最重要的一個結(jié)構(gòu)體,它是Linux為幀緩沖設(shè)備定義的驅(qū)動層接口。它不僅包含了底層函數(shù),而且還有記錄設(shè)備狀態(tài)的數(shù)據(jù)。每個幀緩沖設(shè)備都與一個fb_info結(jié)構(gòu)相對應(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,實際上也就是一個FrameBuffer設(shè)備的次設(shè)備號。fb_var_screeninfo結(jié)構(gòu)體成員記錄用戶可修改的顯示控制器參數(shù),包括屏幕分辨率和每個像素點的比特數(shù)。fb_var_screeninfo中的xres定義屏幕一行有多少個點, yres定義屏幕一列有多少個點, bits_per_pixel定義每個點用多少個字節(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)對幀緩沖設(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 還有一個很重要的域就是fb_ops。它是提供給底層設(shè)備驅(qū)動的一個接口。通常我們編寫字符驅(qū)動的時候,要填寫一個file_operations結(jié)構(gòu)體,并使用register_chrdev()注冊之,以告訴Linux如何操控驅(qū)動。當(dāng)我們編寫一個FrameBuffer的時候,就要依照Linux FrameBuffer編程的套路,填寫fb_ops結(jié)構(gòu)體。這個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ū)動中,定義了s3c2410fb_info來標(biāo)識一個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)體,代表了一個FrameBuffer。dev則表示了這個LCD設(shè)備。 map_dma,map_cpu,map_size這三個域向了開辟給LCD DMA使用的內(nèi)存地址。screen_cpu,screen_dma指向了LCD控制器映射的內(nèi)存地址。另外regs標(biāo)識了LCD控制器的寄存器。
struct s3c2410fb_hw {
unsigned longlcdcon1;
unsigned longlcdcon2;
unsigned longlcdcon3;
unsigned longlcdcon4;
unsigned longlcdcon5;
};
這個寄存器和硬件的寄存器一一對應(yīng),主要作為實際寄存器的映像,以便程序使用。
這個s3c2410fb_info中還有一個s3c2410fb_mach_info成員域。它存放了和體系結(jié)構(gòu)相關(guān)的一些信息,如時鐘、LCD設(shè)備的GPIO口等等。這個結(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ū)動的整體結(jié)構(gòu),反映了結(jié)構(gòu)體之間的相互關(guān)系
2.3 主要代碼結(jié)構(gòu)以及關(guān)鍵代碼分析
2.3.1 FrameBuffer驅(qū)動的統(tǒng)一管理
fbmem.c 實現(xiàn)了Linux FrameBuffer的中間層,任何一個FrameBuffer驅(qū)動,在系統(tǒng)初始化時,必須向fbmem.c注冊,即需要調(diào)用 register_framebuffer()函數(shù),在這個過程中,設(shè)備驅(qū)動的信息將會存放入名稱為registered_fb數(shù)組中,這個數(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é)點 */
if (IS_ERR(fb_info->dev)) {
…………
} else{
fb_init_device(fb_info);/* 初始化改設(shè)備 */
}
…………
return 0;
}
從上面的代碼可知,當(dāng)FrameBuffer驅(qū)動進行注冊的時候,它將驅(qū)動的fb_info結(jié)構(gòu)體記錄到全局?jǐn)?shù)組registered_fb中,并動態(tài)建立設(shè)備節(jié)點,進行設(shè)備的初始化。注意,這里建立的設(shè)備節(jié)點的次設(shè)備號就是該驅(qū)動信息在registered_fb存放的位置,即數(shù)組下標(biāo)i 。在完成注冊之后,fbmem.c就記錄了驅(qū)動的fb_info。這樣我們就有可能實現(xiàn)fbmem.c對全部FrameBuffer驅(qū)動的統(tǒng)一處理。
2.3.2 實現(xiàn)消息的分派
fbmem.c實現(xiàn)了對系統(tǒng)全部FrameBuffer設(shè)備的統(tǒng)一管理。當(dāng)用戶嘗試使用一個特定的FrameBuffer時,fbmem.c怎么知道該調(diào)用那個特定的設(shè)備驅(qū)動呢?
我們知道,Linux是通過主設(shè)備號和次設(shè)備號,對設(shè)備進行唯一標(biāo)識。不同的FrameBuffer設(shè)備向fbmem.c注冊時,程序分配給它們的主設(shè)備號是一樣的,而次設(shè)備號是不一樣的。于是我們就可以通過用戶指明的次設(shè)備號,來覺得具體該調(diào)用哪一個FrameBuffer驅(qū)動。下面通過分析 fbmem.c的fb_open()函數(shù)來說明。(注:一般我們寫FrameBuffer驅(qū)動不需要實現(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ū)動的函數(shù)指針 */
if (!(info = registered_fb[fbidx])) return -ENODEV;
if (info->fbops->fb_open) {
res = info->fbops->fb_open(info,1);//調(diào)用驅(qū)動的open()
if (res) module_put(info->fbops->owner);
}
return res;
}
當(dāng)用戶打開一個FrameBuffer設(shè)備的時,將調(diào)用這里的fb_open()函數(shù)。傳進來的inode就是欲打開設(shè)備的設(shè)備號,包括主設(shè)備和次設(shè)備號。 fb_open函數(shù)首先通過iminor()函數(shù)取得次設(shè)備號,然后查全局?jǐn)?shù)組registered_fb得到設(shè)備的fb_info信息,而這里面存放了設(shè)備的操作函數(shù)集fb_ops。這樣,我們就可以調(diào)用具體驅(qū)動的fb_open() 函數(shù),實現(xiàn)open的操作。下面給出了一個LCD驅(qū)動的open() 函數(shù)的調(diào)用流程圖,用以說明上面的步驟。
圖2.4
2.3.3 開發(fā)板S3C2410 LCD驅(qū)動的流程
(1)在mach-smdk2410.c中,定義了初始的LCD參數(shù)。注意這是個全局變量。
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)核初始化時候調(diào)用s3c2410fb_probe函數(shù)。下面分析這個函數(shù)的做的工作。首先先動態(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()申請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)存的時候,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ū)動初始化到用戶調(diào)用的整個過程。
3. BMP和JPEG圖形顯示程序
3.1 在LCD上顯示BMP或JPEG圖片的主流程圖
首先,在程序開始前。要在nfs/dev目錄下創(chuàng)建LCD的設(shè)備結(jié)點,設(shè)備名fb0,設(shè)備類型為字符設(shè)備,主設(shè)備號為29,次設(shè)備號為0。命令如下:
mknod fb0 c 29 0
在 LCD上顯示圖象的主流程圖如圖3.1所示。程序一開始要調(diào)用open函數(shù)打開設(shè)備,然后調(diào)用ioctl獲取設(shè)備相關(guān)信息,接下來就是讀取圖形文件數(shù)據(jù),把圖象的RGB值映射到顯存中,這部分是圖象顯示的核心。對于JPEG格式的圖片,要先經(jīng)過JPEG解碼才能得到RGB數(shù)據(jù),本項目中直接才用現(xiàn)成的 JPEG庫進行解碼。對于bmp格式的圖片,則可以直接從文件里面提取其RGB數(shù)據(jù)。要從一個bmp文件里面把圖片數(shù)據(jù)陣列提取出來,首先必須知道bmp 文件的格式。下面來詳細介紹bmp文件的格式。
圖3.1
3.2 bmp位圖格式分析
位圖文件可看成由四個部分組成:位圖文件頭、位圖信息頭、彩色表和定義位圖的字節(jié)陣列。如圖3.2所示。
圖3.2
文件頭中各個段的地址及其內(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)信息頭中各個段的地址及其內(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;//給出每個象素的位數(shù)
long biCompress;//給出位圖的壓縮類型
long biSizeImage;//給出圖象字節(jié)數(shù)的多少
long biXPelsPerMeter;//圖像的水平分辨率
long biYPelsPerMeter;//圖象的垂直分辨率
long biClrUsed;//調(diào)色板中圖象實際使用的顏色素數(shù)
long biClrImportant;//給出重要顏色的索引值
} BITMAPINFOHEADER;
圖3.6 BITMAPINFOHEADER數(shù)據(jù)結(jié)構(gòu)
(3)對于象素小于或等于16位的圖片,都有一個顏色表用來給圖象數(shù)據(jù)陣列提供顏色索引,其中的每塊數(shù)據(jù)都以B、G、R的順序排列,還有一個是reserved保留位。而在圖形數(shù)據(jù)區(qū)域存放的是各個象素點的索引值。它的C語言結(jié)構(gòu)如圖3.7所示。
圖3.7 顏色表數(shù)據(jù)結(jié)構(gòu)
(4)對于24位和32位的圖片,沒有彩色表,他在圖象數(shù)據(jù)區(qū)里直接存放圖片的RGB數(shù)據(jù),其中的每個象素點的數(shù)據(jù)都以B、G、R的順序排列。每個象素點的數(shù)據(jù)結(jié)構(gòu)如圖3.8所示。
圖3.8 圖象數(shù)據(jù)陣列的數(shù)據(jù)結(jié)構(gòu)
(5)由于圖象數(shù)據(jù)陣列中的數(shù)據(jù)是從圖片的最后一行開始往上存放的,因此在顯示圖象時,是從圖象的左下角開始逐行掃描圖象,即從左到右,從下到上。
(6)對S3C2410或PXA255開發(fā)板上的LCD來說,他們每個象素點所占的位數(shù)為16位,這16位按B:G:R=5:6:5的方式分,其中B在最高位,R在最低位。而從bmp圖象得到的R、G、B數(shù)據(jù)則每個數(shù)據(jù)占8位,合起來一共24位,因此需要對該R、G、B數(shù)據(jù)進行移位組合成一個16位的數(shù)據(jù)。移位方法如下:
b >>= 3; g >>= 2; r >>= 3;
RGBValue = ( r<<11 | g << 5 | b);
基于以上分析,提取各種類型的bmp圖象的流程如圖3.9所示
圖 3.9
3.3 實現(xiàn)顯示任意大小的圖片
開發(fā)板上的LCD屏的大小是固定的,S3C2410上的LCD為:240*320,PXA255上的為:640*480。比屏幕小的圖片在屏上顯示當(dāng)然沒問題,但是如果圖片比屏幕大呢?這就要求我們通過某種算法對圖片進行縮放。
縮放的基本思想是將圖片分成若干個方塊,對每個方塊中的R、G、B數(shù)據(jù)進行取平均,得到一個新的R、G、B值,這個值就作為該方塊在LCD屏幕上的映射。
縮放的算法描述如下:
(1)、計算圖片大小與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;
計算比例:
widthScale=bmpi->width/lcd_width;
heightScale=bmpi->height/lcd_height;
本程序中方塊的大小以如下的方式確定:
unsigned int paneWidth=
unsigned int paneHeight= ;
符號 代表向上取整。
(2)、 從圖片的左上角開始,以(i* widthScale,j* heightScale)位起始點,以寬paneWidth 高paneHeight為一個小方塊,對該方塊的R、G、B數(shù)值分別取平均,得到映射點的R、G、B值,把該點作為要在LCD上顯示的第(i , j)點存儲起來。
這部分的程序如下:
//-------------取平均--------
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ù)提取及顯示的總流程
通過以上的分析,整個圖片數(shù)據(jù)提取及顯示的總流程如圖3.10 所示。
圖 3.10
三 實驗過程與結(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é)點,使用命令:
mknod fb0 c 29 0
4.啟動開發(fā)板,加載內(nèi)核和文件系統(tǒng)。
5.編寫LCD的應(yīng)用程序,程序見附錄。
6.采用arm-linux-gcc 編譯應(yīng)用程序,產(chǎn)生可執(zhí)行文件,放入nfs目錄中。
7.在開發(fā)板上運行編譯好的可執(zhí)行文件,便可。
8.下圖是BMP位圖顯示程序,在S3C2410上的運行結(jié)果。
四 實驗心得體會
1.LCD驅(qū)動的主要問題是沒有LCD屏的文檔,我們找不到它的那些參數(shù)值,后來只能參照Linux源碼里面的其他LCD屏的參數(shù)進行實驗。
2.在驅(qū)動差錯的過程中,我們采用跟蹤打印的方法進行調(diào)試。剛開始的時候,內(nèi)核打印出一行找不到LCD設(shè)備。我們定位到輸出這行提示的代碼處,進行反向跟蹤。發(fā)現(xiàn)傳給函數(shù)的設(shè)備指針為空,于是往上排查,終于發(fā)現(xiàn)源代碼中沒有定義LCD的設(shè)備信息。于是驅(qū)動問題也就順利解決了。
3.原來一直以為,只要LCD驅(qū)動工作正常了,內(nèi)核起來的時候,液晶屏?xí)@示出Logo。當(dāng)時搞了很久一直沒有,還以為是驅(qū)動的問題。后來隨便寫了一個LCD應(yīng)用程序,竟然能用。
4.在調(diào)試過程應(yīng)用程序中發(fā)現(xiàn),在讀取文件頭的時候,如果直接定義一個bitmapfileheader為它動態(tài)分配內(nèi)存:
*bmph=(bitmapfileheader*)malloc(sizeof(bitmapfileheader));
然后用fread((char*)bmph,sizeof(bitmapfileheader),1,f)把文件頭一次性讀出來,讀出來的文件頭是錯誤的,經(jīng)過調(diào)試發(fā)現(xiàn)原因是bitmapfileheader這個結(jié)構(gòu)體中的type屬性原本應(yīng)該占2字節(jié),但是被編譯器在分配內(nèi)存的時候進行了內(nèi)存對齊的優(yōu)化,給他分配了4個字節(jié)的空間,造成讀文件的錯誤。因此在編程中要特別注意內(nèi)存對齊的影響。
typedef struct
{
WORD type;(被優(yōu)化)
DWORD bfsize;
DWORD reserved;
DWORD offbits;
} bitmapfileheader;
5.在嵌入式應(yīng)用程序的移植過程中,我們原來認為ARM和PC機大小尾順序是不同的,因此在應(yīng)用程序中,也對這個差別進行了處理。當(dāng)時,在調(diào)試過程中發(fā)現(xiàn),PC 機程序可以直接移植到ARM上,不需要任何改動。但是我們的程序,的確存在會產(chǎn)生大小尾問題代碼(在使用fread()讀入時)。這究竟是為什么?有人說,ARM是可以設(shè)置大小尾順序的。后來這個問題也沒有深究下去。
五 參考文獻
(1)嵌入式系統(tǒng)設(shè)計與應(yīng)用開發(fā):鄭靈翔. 北京:北京航天航空大學(xué)出版社 2006