蜜蜂vp加速器七天試用杭州優(yōu)化公司在線留言
前言:
在 Linux 系統(tǒng)中通過 Framebuffer 驅動程序來控制 LCD。Frame 是幀的意 思,buffer 是緩沖的意思,這意味著 Framebuffer 就是一塊內存,里面保存著 一幀圖像。Framebuffer 中保存著一幀圖像的每一個像素顏色值,假設 LCD 的 分辨率是 1024x768,每一個像素的顏色用 32 位來表示,那么 Framebuffer 的 大小就是:1024x768x32/8=3145728 字節(jié)。
目錄
一、LCD 操作原理
二、涉及的 API 函數(shù)
?1.open 函數(shù)
2.ioctl 函數(shù)
?3.mmap 函數(shù)
三、Framebuffer 程序分析?
1.Framebuffer源碼如下:
2. 打開設備:
3.獲取 LCD 參數(shù)
4.映射 Framebuffer
5.描點函數(shù)
6.隨便畫幾個點?
四、上機實驗測試
一、LCD 操作原理
1.驅動程序設置好 LCD 控制器:
???????? 根據(jù) LCD 的參數(shù)設置 LCD 控制器的時序、信號極性;
? ? ? ? ?根據(jù) LCD 分辨率、BPP 分配 Framebuffer。
2.APP 使用 ioctl 獲得 LCD 分辨率、BPP
3.APP 通過 mmap 映射 Framebuffer,在 Framebuffer 中寫入數(shù)據(jù)
????????假設需要設置 LCD 中坐標(x,y)處像素的顏色,首要要找到這個像素對應的 內存,然后根據(jù)它的 BPP 值設置顏色。假設 fb_base 是 APP 執(zhí)行 mmap 后得到 的 Framebuffer 地址,如圖
可以用以下公式算出(x,y)坐標處像素對應的 Framebuffer 地址:
(x,y)像素起始地址=fb_base+(xres*bpp/8)*y + x*bpp/8
????????最后一個要解決的問題就是像素的顏色怎么表示?它是用 RGB 三原色(紅、綠、 藍)來表示的,在不同的 BPP 格式中,用不同的位來分別表示 R、G、B
? ? ? ? ?對于 32BPP,一般只設置其中的低 24 位,高 8 位表示透明度,一般的 LCD 都不支持。
???????? 對于 24BPP,硬件上為了方便處理,在 Framebuffer 中也是用 32 位來表 示,效果跟 32BPP 是一樣的。
????????對于 16BPP,常用的是 RGB565;很少的場合會用到 RGB555,這可以通過 ioctl 讀取驅動程序中的 RGB 位偏移來確定使用哪一種格式。
二、涉及的 API 函數(shù)
目的是:打開 LCD 設備節(jié)點,獲取分辨率等參數(shù),映射 Framebuffer,最后實現(xiàn)描點函數(shù)。
?1.open 函數(shù)
在 Ubuntu 中執(zhí)行“man 2 open”,可以看到 open 函數(shù)的說明:
頭文件:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
函數(shù)原型:
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
函數(shù)說明:
1.pathname 表示打開文件的路徑;
2.Flags 表示打開文件的方式,常用的有以下 6 種,
????????? O_RDWR 表示可讀可寫方式打開;
????????? O_RDONLY 表示只讀方式打開;
????????? O_WRONLY 表示只寫方式打開;
????????? O_APPEND 表示如果這個文件中本來是有內容的,則新寫入的內容會接續(xù)到原來內容的后面;
????????? O_TRUNC 表示如果這個文件中本來是有內容的,則原來的內容會被丟棄,截斷; ????????? O_CREAT 表示當前打開文件不存在,我們創(chuàng)建它并打開它,通常與 O_EXCL 結合使用,當沒有文件時創(chuàng)建文件,有這個文件時會報錯提醒我們;
3.Mode 表示創(chuàng)建文件的權限,只有在 flags 中使用了 O_CREAT 時才有效, 否則忽略。
4.返回值:打開成功返回文件描述符,失敗將返回-1。
2.ioctl 函數(shù)
在 Ubuntu 中執(zhí)行“man ioctl”,可以看到 ioctl 函數(shù)的說明:
頭文件:?
#include <sys/ioctl.h>
函數(shù)原型:
int ioctl(int fd, unsigned long request, ...);
1. fd 表示文件描述符;
2. request 表示與驅動程序交互的命令,用不同的命令控制驅動程序輸出我們 需要的數(shù)據(jù); 3. … 表示可變參數(shù) arg,根據(jù) request 命令,設備驅動程序返回輸出的數(shù)據(jù)。
4.返回值:打開成功返回文件描述符,失敗將返回-1。
?????ioctl 的作用非常強大、靈活。不同的驅動程序內部會實現(xiàn)不同的 ioctl, APP 可以使用各種 ioctl 跟驅動程序交互:可以傳數(shù)據(jù)給驅動程序,也可以從驅動程序中讀出數(shù)據(jù)。
?3.mmap 函數(shù)
在 Ubuntu 中執(zhí)行“man mmap”,可以看到 mmap 函數(shù)的說明:
頭文件:
#include <sys/mman.h>
函數(shù)原型:
void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset);
1.addr 表示指定映射的內存起始地址,通常設為 NULL 表示讓系統(tǒng)自動選定 地址,并在成功映射后返回該地址;
2.length 表示將文件中多大的內容映射到內存中;
3.prot 表示映射區(qū)域的保護方式,可以為以下 4 種方式的組合
????????? PROT_EXEC 映射區(qū)域可被執(zhí)行
????????? PROT_READ 映射區(qū)域可被讀出
????????? PROT_WRITE 映射區(qū)域可被寫入
????????? PROT_NONE 映射區(qū)域不能存取 Flags 表示影響映射區(qū)域的不同特性,常用的有以下兩種
????????? MAP_SHARED 表示對映射區(qū)域寫入的數(shù)據(jù)會復制回文件內,原來的文件會改變。 ????????? MAP_PRIVATE 表示對映射區(qū)域的操作會產生一個映射文件的復制,對此區(qū)域的任何修改都不會寫回原來的文件內容中。
5.返回值:若成功映射,將返回指向映射的區(qū)域的指針,失敗將返回-1。
三、Framebuffer 程序分析?
1.Framebuffer源碼如下:
/*********************************************************************** 函數(shù)名稱: lcd_put_pixel* 功能描述: 在LCD指定位置上輸出指定顏色(描點)* 輸入?yún)?shù): x坐標,y坐標,顏色* 輸出參數(shù): 無* 返 回 值: 會***********************************************************************/
void lcd_put_pixel(int x, int y, unsigned int color)
{unsigned char *pen_8 = fb_base+y*line_width+x*pixel_width;unsigned short *pen_16;unsigned int *pen_32;unsigned int red, green, blue;pen_16 = (unsigned short *)pen_8;pen_32 = (unsigned int *)pen_8;switch (var.bits_per_pixel){case 8:{*pen_8 = color;break;}case 16:{/* 565 */red = (color >> 16) & 0xff;green = (color >> 8) & 0xff;blue = (color >> 0) & 0xff;color = ((red >> 3) << 11) | ((green >> 2) << 5) | (blue >> 3);*pen_16 = color;break;}case 32:{*pen_32 = color;break;}default:{printf("can't surport %dbpp\n", var.bits_per_pixel);break;}}
}int main(int argc, char **argv)
{int i;fd_fb = open("/dev/fb0", O_RDWR);if (fd_fb < 0){printf("can't open /dev/fb0\n");return -1;}if (ioctl(fd_fb, FBIOGET_VSCREENINFO, &var)){printf("can't get var\n");return -1;}line_width = var.xres * var.bits_per_pixel / 8;pixel_width = var.bits_per_pixel / 8;screen_size = var.xres * var.yres * var.bits_per_pixel / 8;fb_base = (unsigned char *)mmap(NULL , screen_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd_fb, 0);if (fb_base == (unsigned char *)-1){printf("can't mmap\n");return -1;}/* 清屏: 全部設為白色 */memset(fb_base, 0xff, screen_size);/* 隨便設置出100個為紅色 */for (i = 0; i < 100; i++)lcd_put_pixel(var.xres/2+i, var.yres/2, 0xFF0000);munmap(fb_base , screen_size);close(fd_fb);return 0;
}
2. 打開設備:
首先打開設備節(jié)點:
fd_fb = open("/dev/fb0", O_RDWR);if (fd_fb < 0){printf("can't open /dev/fb0\n");return -1;}
3.獲取 LCD 參數(shù)
LCD 驅動程序給 APP 提供 2 類參數(shù):
????????可變的參數(shù) fb_var_screeninfo
????????固 定的參數(shù) fb_fix_screeninfo
編寫應用程序時主要關心可變參數(shù),它的結構 體定義如下(#include <Linux/fd.h>):
可以使用以下代碼獲取 fb_var_screeninfo:
static struct fb_var_screeninfo var; /* Current var */......if (ioctl(fd_fb, FBIOGET_VSCREENINFO, &var))
{printf("can't get var\n");return -1;
}
????????注意到 ioctl 里用的參數(shù)是:FBIOGET_VSCREENINFO,它表示 get var screen info,獲得屏幕的可變信息;當然也可以使用 FBIOPUT_VSCREENINFO 來調整這 些參數(shù),但是很少用到。
對于固定的參數(shù) fb_fix_screeninfo,在應用編程中很少用到。它的結構 體定義如下:
?可以使用 ioctl FBIOGET_FSCREENINFO 來讀出這些信息,但是很少用到。
4.映射 Framebuffer
????????要映射一塊內存,需要知道它的地址──這由驅動程序來設置,需要知道它 的大小──這由應用程序決定。代碼如下:
line_width = var.xres * var.bits_per_pixel / 8;
pixel_width = var.bits_per_pixel / 8;
screen_size = var.xres * var.yres * var.bits_per_pixel / 8;
fb_base = (unsigned char *)mmap(NULL , screen_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd_fb, 0);
if (fb_base == (unsigned char *)-1)
{printf("can't mmap\n");return -1;
}
screen_size 是整個 Framebuffer 的大小;PROT_READ | PROT_WRITE 表示該區(qū)域可讀、可寫;MAP_SHARED 表示該區(qū)域是共享的,APP 寫 入數(shù)據(jù)時,會直達驅動程序。
5.描點函數(shù)
能夠在 LCD 上描繪指定像素后,就可以寫字、畫圖,描點函數(shù)是基礎。代碼如下:
void lcd_put_pixel(int x, int y, unsigned int color)
{unsigned char *pen_8 = fb_base+y*line_width+x*pixel_width;unsigned short *pen_16;unsigned int *pen_32;unsigned int red, green, blue;pen_16 = (unsigned short *)pen_8;pen_32 = (unsigned int *)pen_8;switch (var.bits_per_pixel){case 8:{*pen_8 = color;break;}case 16:{/* 565 */red = (color >> 16) & 0xff;green = (color >> 8) & 0xff;blue = (color >> 0) & 0xff;color = ((red >> 3) << 11) | ((green >> 2) << 5) | (blue >> 3);*pen_16 = color;break;}case 32:{*pen_32 = color;break;}default:{printf("can't surport %dbpp\n", var.bits_per_pixel);break;}}
}
1.void lcd_put_pixel(int x, int y, unsigned int color)
傳入的 color 表示顏色,它的格式永遠是 0x00RRGGBB,即 RGB888。 當 LCD 是16bpp 時,要把 color 變量中的 R、G、B 抽出來再合并成 RGB565 格式。
2.unsigned char *pen_8 = fb_base+y*line_width+x*pixel_width;
計算(x,y)坐標上像素對應的 Framebuffer 地址。
3.*pen_8 = color;
對于 8bpp,color 就不再表示 RBG 三原色了,這涉及調色板的概念,color 是調色板的值。 第 49~51 行,先從 color 變量中把 R、G、B 抽出來。
4.? red = (color >> 16) & 0xff;
? ? ?green = (color >> 8) & 0xff;
? ? ?blue = (color >> 0) & 0xff;
把 red、green、blue 這三種 8 位顏色值,根據(jù) RGB565 的格式, 只保留 red 中的高 5 位、green 中的高 6 位、blue 中的高 5 位,組合成一個新 的 16 位顏色值。
5.color = ((red >> 3) > 2) > 3);
把新的 16 位顏色值寫入 Framebuffer。
6.*pen_32 = color;
對于 32bpp,顏色格式跟 color 參數(shù)一致,可以直接寫入 Framebuffer。
6.隨便畫幾個點?
本程序的 main 函數(shù),在最后只是簡單地畫了幾個點:
/* 清屏: 全部設為白色 */memset(fb_base, 0xff, screen_size);/* 隨便設置出100個為紅色 */for (i = 0; i < 100; i++)lcd_put_pixel(var.xres/2+i, var.yres/2, 0xFF0000);
四、上機實驗測試
在 Ubuntu 中編譯程序,先設置交叉編譯工具鏈,再執(zhí)行以下命令:
book@100ask:~/source/07_framebuffer$ arm-buildroot-linux-gnueabihf-gcc -o show_pixel show_pixel.c
book@100ask:~/source/07_framebuffer$ cp show_pixel ~/nfs_rootfs/
在開發(fā)板上掛載網(wǎng)絡文件系統(tǒng)
??????
?運行程序
[root@100ask:~]# mount -t nfs -o nolock,vers=3 192.168.5.11:/home/book/nfs_rootfs /mnt
[root@100ask:~]# /mnt/show_pixel
?運行效果: