1. 程式人生 > >framebuffer實驗:編寫應用程式測試lcd驅動

framebuffer實驗:編寫應用程式測試lcd驅動

一、Linux的幀緩衝裝置原理

幀緩衝(framebuffer)是 Linux 為顯示裝置提供的一個介面,把視訊記憶體抽象後的一種裝置,他允許上層應用程式在圖形模式下直接對顯示緩衝區進行讀寫操作。這種操作是抽象的,統一的。使用者不必關心物理視訊記憶體的位置、換頁機制等等具體細節。這些都是由Framebuffer 裝置驅動來完成的。幀緩衝驅動的應用廣泛,在 linux 的桌面系統中,Xwindow 伺服器就是利用幀緩衝進行視窗的繪製。尤其是通過幀緩衝可顯示漢字點陣,成為 Linux漢化的唯一可行方案。

幀緩衝裝置對應的裝置檔案為/dev/fb*,如果系統有多個顯示卡,Linux 下還可支援多個幀緩衝裝置,最多可達 32 個,分別為/dev/fb0 到/dev/fb31,而/dev/fb 則為當前預設的幀緩衝裝置,通常指向/dev/fb0。當然在嵌入式系統中支援一個顯示裝置就夠了。幀緩衝裝置為標準字元裝置,主裝置號為29,次裝置號則從0到31。分別對應/dev/fb0-/dev/fb31。

通過/dev/fb,應用程式的操作主要有這幾種:

1.讀/寫(read/write)/dev/fb:相當於讀/寫螢幕緩衝區。例如用  cp /dev/fb0 tmp 命令可將當前螢幕的內容拷貝到一個檔案中,而命令 cp tmp > /dev/fb0 則將圖形檔案tmp顯示在螢幕上。

2.對映(map)操作:由於 Linux 工作在保護模式,每個應用程式都有自己的虛擬地址空間,在應用程式中是不能直接訪問物理緩衝區地址的。為此,Linux 在檔案操作 file_operations 結構中提供了mmap 函式,可將檔案的內容對映到使用者空間。對於幀緩衝裝置,則可通過對映操作,可將螢幕緩衝區的實體地址對映到使用者空間的一段虛擬地址中,之後使用者就可以通過讀寫這段虛擬地址訪問螢幕緩衝區,在螢幕上繪圖了。

3.I/O控制:對於幀緩衝裝置,對裝置檔案的 ioctl操作可讀取/設定顯示裝置及螢幕的引數,如解析度,顯示顏色數,螢幕大小等等。ioctl 的操作是由底層的驅動程式來完成的。

在應用程式中,操作/dev/fb的一般步驟如下:

1.開啟/dev/fb裝置檔案。

2.用 ioctrl 操作取得當前顯示螢幕的引數,如螢幕解析度,每個畫素點的位元數。根據螢幕引數可計算螢幕緩衝區的大小。

3.將螢幕緩衝區對映到使用者空間(mmep)。

4.對映後就可以直接讀寫螢幕緩衝區,進行繪圖和圖片顯示了。

 典型程式段如下:

#include <linux/fb.h>

int main()

{  

 int fbfd = 0;

 struct fb_var_screeninfo vinfo;

 struct fb_fix_screeninfo finfo;

 long int screensize = 0;

/*開啟裝置檔案*/

 fbfd = open("/dev/fb0", O_RDWR);

/*取得螢幕相關引數*/

 ioctl(fbfd, FBIOGET_FSCREENINFO, &finfo);  

 ioctl(fbfd, FBIOGET_VSCREENINFO, &vinfo);

/*計算螢幕緩衝區大小*/

 screensize = vinfo.xres * vinfo.yres * vinfo.bits_per_pixel / 8;

/*對映螢幕緩衝區到使用者地址空間*/

fbp=(char*)mmap(0,screensize,PROT_READ|PROT_WRITE,MAP_SHARED, fbfd, 0);

/*下面可通過 fbp指標讀寫緩衝區*/

……

/*釋放緩衝區,關閉裝置*/

munmap(fbp, screensize);

close(fbfd);

}

二、ioctl操作

ioctl(fbfd, FBIOGET_FSCREENINFO, &finfo)

獲取fb_var_screeninfo結構的資訊,在linux/include/linux/fb.h定義。

ioctl(fbfd, FBIOGET_VSCREENINFO, &vinfo)

獲取fb_fix_screeninfon結構的資訊。在linux/include/linux/fb.h定義。

fbfd為裝置檔案號。

三.mmap函式

功能描述:

mmap函式是unix/linux下的系統呼叫

mmap將一個檔案或者其它物件對映進記憶體。檔案被對映到多個頁上,如果檔案的大小不是所有頁的大小之和,最後一個頁不被使用的空間將會清零。munmap執行相反的操作,刪除特定地址區域的物件對映。

基於檔案的對映,在mmap和munmap執行過程的任何時刻,被對映檔案的st_atime可能被更新。如果st_atime欄位在前述的情況下沒有得到更新,首次對對映區的第一個頁索引時會更新該欄位的值。用PROT_WRITE 和 MAP_SHARED標誌建立起來的檔案對映,其st_ctime 和 st_mtime

在對對映區寫入之後,但在msync()通過MS_SYNC 和 MS_ASYNC兩個標誌呼叫之前會被更新。

用法:

#include <sys/mman.h>

void *mmap(void *start, size_t length, int prot, int flags,

int fd, off_t offset);

int munmap(void *start, size_t length);

引數:

start:對映區的開始地址。

length:對映區的長度。

prot:期望的記憶體保護標誌,不能與檔案的開啟模式衝突。是以下的某個值,可以通過or運算合理地組合在一起

PROT_EXEC //頁內容可以被執行

PROT_READ //頁內容可以被讀取

PROT_WRITE //頁可以被寫入

PROT_NONE //頁不可訪問

flags:指定對映物件的型別,對映選項和對映頁是否可以共享。它的值可以是一個或者多個以下位的組合體

MAP_FIXED //使用指定的對映起始地址,如果由start和len引數指定的記憶體區重疊於現存的對映空間,重疊部分將會被丟棄。如果指定的起始地址不可用,操作將會失敗。並且起始地址必須落在頁的邊界上。

MAP_SHARED //與其它所有對映這個物件的程序共享對映空間。對共享區的寫入,相當於輸出到檔案。直到msync()或者munmap()被呼叫,檔案實際上不會被更新。

MAP_PRIVATE //建立一個寫入時拷貝的私有對映。記憶體區域的寫入不會影響到原檔案。這個標誌和以上標誌是互斥的,只能使用其中一個。

MAP_DENYWRITE //這個標誌被忽略。

MAP_EXECUTABLE //同上

MAP_NORESERVE //不要為這個對映保留交換空間。當交換空間被保留,對對映區修改的可能會得到保證。當交換空間不被保留,同時記憶體不足,對對映區的修改會引起段違例訊號。

MAP_LOCKED //鎖定對映區的頁面,從而防止頁面被交換出記憶體。

MAP_GROWSDOWN //用於堆疊,告訴核心VM系統,對映區可以向下擴充套件。

MAP_ANONYMOUS //匿名對映,對映區不與任何檔案關聯。

MAP_ANON //MAP_ANONYMOUS的別稱,不再被使用。

MAP_FILE //相容標誌,被忽略。

MAP_32BIT //將對映區放在程序地址空間的低2GB,MAP_FIXED指定時會被忽略。當前這個標誌只在x86-64平臺上得到支援。

MAP_POPULATE //為檔案對映通過預讀的方式準備好頁表。隨後對對映區的訪問不會被頁違例阻塞。

MAP_NONBLOCK //僅和MAP_POPULATE一起使用時才有意義。不執行預讀,只為已存在於記憶體中的頁面建立頁表入口。

fd:有效的檔案描述詞。如果MAP_ANONYMOUS被設定,為了相容問題,其值應為-1。

offset:被對映物件內容的起點。

返回說明:

成功執行時,mmap()返回被對映區的指標,munmap()返回0。失敗時,mmap()返回MAP_FAILED[其值為(void *)-1],munmap返回-1。errno被設為以下的某個值

EACCES:訪問出錯

EAGAIN:檔案已被鎖定,或者太多的記憶體已被鎖定

EBADF:fd不是有效的檔案描述詞

EINVAL:一個或者多個引數無效

ENFILE:已達到系統對開啟檔案的限制

ENODEV:指定檔案所在的檔案系統不支援記憶體對映

ENOMEM:記憶體不足,或者程序已超出最大記憶體對映數量

EPERM:權能不足,操作不允許

ETXTBSY:已寫的方式開啟檔案,同時指定MAP_DENYWRITE標誌

SIGSEGV:試著向只讀區寫入

SIGBUS:試著訪問不屬於程序的記憶體區

四、測試程式:
功能:在螢幕中間一塊區域填充指定一種顏色: R=(0b)100,g= (0b)100 ,b =(0b)10

  1. #include <unistd.h>

  2. #include <stdio.h>

  3. #include <fcntl.h>

  4. #include <linux/fb.h>

  5. #include <sys/mman.h>

  6.  
  7. int main()

  8. {

  9. int fbfd = 0;

  10. struct fb_var_screeninfo vinfo;

  11. struct fb_fix_screeninfo finfo;

  12. struct fb_cmap cmapinfo;

  13. long int screensize = 0;

  14. char *fbp = 0;

  15. int x = 0, y = 0;

  16. long int location = 0;

  17. int b,g,r;

  18. // Open the file for reading and writing

  19. fbfd = open("/dev/fb0", O_RDWR); // 開啟Frame Buffer裝置

  20. if (fbfd < 0) {

  21. printf("Error: cannot open framebuffer device.%x\n",fbfd);

  22. exit(1);

  23. }

  24. printf("The framebuffer device was opened successfully.\n");

  25.  
  26. // Get fixed screen information

  27. if (ioctl(fbfd, FBIOGET_FSCREENINFO, &finfo)) { // 獲取裝置固有資訊

  28. printf("Error reading fixed information.\n");

  29. exit(2);

  30. }

  31. printf(finfo.id);

  32. printf("\ntype:0x%x\n", finfo.type ); // FrameBuffer 型別,如0為象素

  33. printf("visual:%d\n", finfo.visual ); // 視覺型別:如真彩2,偽彩3

  34. printf("line_length:%d\n", finfo.line_length ); // 每行長度

  35. printf("\nsmem_start:0x%x,smem_len:%d\n", finfo.smem_start, finfo.smem_len ); // 映象RAM的引數

  36. printf("mmio_start:0x%x ,mmio_len:%d\n", finfo.mmio_start, finfo.mmio_len );

  37.  
  38. // Get variable screen information

  39. if (ioctl(fbfd, FBIOGET_VSCREENINFO, &vinfo)) { // 獲取裝置可變資訊

  40. printf("Error reading variable information.\n");

  41. exit(3);

  42. }

  43. printf("%dx%d, %dbpp\n", vinfo.xres, vinfo.yres, vinfo.bits_per_pixel );

  44. // Figure out the size of the screen in bytes

  45. screensize = vinfo.xres * vinfo.yres * vinfo.bits_per_pixel / 8;

  46. // Map the device to memory 通過mmap系統呼叫將framebuffer記憶體對映到使用者空間,並返回對映後的起始地址

  47. fbp = (char *)mmap(0, screensize, PROT_READ | PROT_WRITE, MAP_SHARED,

  48. fbfd, 0);

  49. if ((int)fbp == -1) {

  50. printf("Error: failed to map framebuffer device to memory.\n");

  51. exit(4);

  52. }

  53. printf("The framebuffer device was mapped to memory successfully.\n");

  54.  
  55. vinfo.xoffset = (640-420)/2; // (計算螢幕影象在螢幕中間一塊區域顯示)Where we are going to put the pixel (x座標偏移量:111) 要分清一塊屏有寬和高,寬即用x座標表示,高用y表示,和直角座標系一樣

  56. vinfo.yoffset = (480-340)/2; // (y座標偏移量:70) 即該區域的左上角的畫素點座標為(x,y)=(110,70),右下角的座標為(x,y)= (110+420,70+340)

  57. b = 10; // 即blue : 0000 0010

  58. g = 100; // A little green 即green: 0000 0100

  59. r = 100; // A lot of red 即red : 0000 0100

  60. // Figure out where in memory to put the pixel

  61. for ( y = 0; y < 340; y++ ) // 行掃描

  62. for ( x = 0; x < 420; x++ ) { // 列掃描

  63.  
  64. location = (x+vinfo.xoffset) * (vinfo.bits_per_pixel/8) + // 定位到具體哪一行的第幾個畫素

  65. (y+vinfo.yoffset) * finfo.line_length; //定位到哪一行(即該行的第一個畫素的地址) 這兩句即是實現求某一個畫素的地址的功能

  66.  
  67. if ( vinfo.bits_per_pixel == 32 ) { //

  68. *(fbp + location) = b; // Some blue

  69. *(fbp + location + 1) = g; // A little green

  70. *(fbp + location + 2) = r; // A lot of red

  71. *(fbp + location + 3) = 0; // No transparency

  72. } else { //16bpp: r:g:b=5:6:5 //assume 16bpp

  73.  
  74. unsigned short int t = r<<11 | g << 5 | b;

  75. *((unsigned short int*)(fbp + location)) = t;

  76. }

  77.  
  78. }

  79. munmap(fbp, screensize);

  80. close(fbfd);

  81. return 0;

  82. }