本文轉載自:http://blog.csdn.net/liuzijiang1123/article/details/46972723
如想想對lcd屏進行操作(例如在lcd屏幕上畫線,或者顯示視頻數據),我們就必須得了framebuffer(幀緩沖),網上各種百度,大多都說的很官方,至少很難找到那些讓人覺得很生動的描述,讓我們這些出入門的菜鳥能好好了解一下。 下面就是我結合老師的指點和論壇上的解釋再加上自己的理解寫的一些東西。
************************************************************************************************************************************************************************************
很多人都會說操縱lcd顯示就是操縱framebuffer,表面上來看是這樣的。實際上是frambuffer就是Linux內核驅動申請的一片內存空間,然后lcd內有一片sram,cpu內部有個lcd控制器,它有個單獨的dma用來將frambuffer中的數據拷貝到lcd的sram中去 拷貝到lcd的sram中的數據就會顯示在lcd上,
LCD驅動和framebuffer驅動沒有必然的聯系,它只是驅動LCD正常工作的,比如有信號傳過來,那么LCD驅動負責把信號轉成顯示屏上的內容,至於什么內容,怎么顯示,它根本不關心也不知道。
*************************************************************************************************************************************************************************************
百度上framebuffer的解釋:幀緩沖是Linux為顯示設備提供的一個接口,它把一些顯示設備描述成一個緩沖區,允許應用程序通過 FrameBuffer定義好的接口訪問這些圖形設備,從而不用去關心具體的硬件細節。
(經過我的解釋,我們是不是可以好一點理解了呢?上面的顯示設備就是lcd呀,framebuffer不就是一個臨時存放數據的區域)其在文件系統中對應的設備名稱為/dev/fb(0,1,2,3....31最大允許32個fb,主設備號是29)
*************************************************************************************************************************************************************************************
(下面插上一幅圖來更形象的解釋)
我們從上面這幅圖看,幀緩沖設備在Linux中也可以看做是一個完整的子系統,大體由fbmem.c和xxxfb.c組成。向上給應用程序提供完善的設備文件操作接口(即對FrameBuffer設備進行read、write、ioctl等操作),接口在Linux提供的fbmem.c文件中實現;向下提供了硬件操作的接口,只是這些接口Linux並沒有提供實現,因為這要根據具體的LCD控制器硬件進行設置,所以這就是我們要做的事情了(即xxxfb.c 部分的實現)。
說了這么多,我感覺應該要用一些實例在說說,到底如何在lcd上畫線呢?(這個也是老師給我的任務)
****************************************************************************************************************************************************************************************
- #include <stdlib.h>
- #include <unistd.h>
- #include <stdio.h>
- #include <fcntl.h>
- #include <linux/fb.h>
- #include <linux/kd.h>
- #include <sys/mman.h>
- #include <sys/ioctl.h>
- #include <sys/time.h>
- #include <string.h>
- #include <errno.h>
- struct fb_var_screeninfo vinfo;
- struct fb_fix_screeninfo finfo;
- char *frameBuffer = 0;
- //打印fb驅動中fix結構信息,注:在fb驅動加載后,fix結構不可被修改。
- void printFixedInfo ()
- {
- printf ("Fixed screen info:\n"
- "\tid: %s\n"
- "\tsmem_start:0x%lx\n"
- "\tsmem_len:%d\n"
- "\ttype:%d\n"
- "\ttype_aux:%d\n"
- "\tvisual:%d\n"
- "\txpanstep:%d\n"
- "\typanstep:%d\n"
- "\tywrapstep:%d\n"
- "\tline_length: %d\n"
- "\tmmio_start:0x%lx\n"
- "\tmmio_len:%d\n"
- "\taccel:%d\n"
- "\n",
- finfo.id, finfo.smem_start, finfo.smem_len, finfo.type,
- finfo.type_aux, finfo.visual, finfo.xpanstep, finfo.ypanstep,
- finfo.ywrapstep, finfo.line_length, finfo.mmio_start,
- finfo.mmio_len, finfo.accel);
- }
- //打印fb驅動中var結構信息,注:fb驅動加載后,var結構可根據實際需要被重置
- void printVariableInfo ()
- {
- printf ("Variable screen info:\n"
- "\txres:%d\n"
- "\tyres:%d\n"
- "\txres_virtual:%d\n"
- "\tyres_virtual:%d\n"
- "\tyoffset:%d\n"
- "\txoffset:%d\n"
- "\tbits_per_pixel:%d\n"
- "\tgrayscale:%d\n"
- "\tred: offset:%2d, length: %2d, msb_right: %2d\n"
- "\tgreen: offset:%2d, length: %2d, msb_right: %2d\n"
- "\tblue: offset:%2d, length: %2d, msb_right: %2d\n"
- "\ttransp: offset:%2d, length: %2d, msb_right: %2d\n"
- "\tnonstd:%d\n"
- "\tactivate:%d\n"
- "\theight:%d\n"
- "\twidth:%d\n"
- "\taccel_flags:0x%x\n"
- "\tpixclock:%d\n"
- "\tleft_margin:%d\n"
- "\tright_margin: %d\n"
- "\tupper_margin:%d\n"
- "\tlower_margin:%d\n"
- "\thsync_len:%d\n"
- "\tvsync_len:%d\n"
- "\tsync:%d\n"
- "\tvmode:%d\n"
- "\n",
- vinfo.xres, vinfo.yres, vinfo.xres_virtual, vinfo.yres_virtual,
- vinfo.xoffset, vinfo.yoffset, vinfo.bits_per_pixel,
- vinfo.grayscale, vinfo.red.offset, vinfo.red.length,
- vinfo.red.msb_right,vinfo.green.offset, vinfo.green.length,
- vinfo.green.msb_right, vinfo.blue.offset, vinfo.blue.length,
- vinfo.blue.msb_right, vinfo.transp.offset, vinfo.transp.length,
- vinfo.transp.msb_right, vinfo.nonstd, vinfo.activate,
- vinfo.height, vinfo.width, vinfo.accel_flags, vinfo.pixclock,
- vinfo.left_margin, vinfo.right_margin, vinfo.upper_margin,
- vinfo.lower_margin, vinfo.hsync_len, vinfo.vsync_len,
- vinfo.sync, vinfo.vmode);
- }
- //畫一條直線
- void drawline_rgb16 (int x0,int y0, int width,int height, int color,int flag0)
- {
- const int bytesPerPixel = 2;//因為是rgb16,用16位來描述色深,所以2個字節
- const int stride = finfo.line_length / bytesPerPixel;,一行有多少個點
- const int red = (color & 0xff0000) >> (16 + 3);//下面是顏色的操作,我目前還沒弄明白
- const int green = (color & 0xff00) >> (8 + 2);
- const int blue = (color & 0xff) >> 3;
- const short color16 = blue | (green << 5) | (red << (5 +6));
- int flag=flag0;//這里我為了圖個方便就用一個flag來區分是畫橫線還是豎線,0表示橫線,1表示豎線。
- short *dest = (short *) (frameBuffer)+ (y0 + vinfo.yoffset) * stride + (x0 +vinfo.xoffset);//這個就是我們畫點的起始位置,其+stride就是換行(這個是我個人通過代碼測試得出來的結論)
- int x=0,y=0;
- if(flag==0)
- {
- for (x = 0; x < width; ++x)//width就是我們x方向的終點
- {
- dest[x] = color16;
- }
- }
- else if(flag==1)
- {
- for(y=0;y<height;y++)//height就是我們y方向的終點
- {
- dest[x]=color16;//這里x始終為0,和下面一句結合起來就是每一行就畫一個點,一共畫height行,不就是一條豎線了么,這里我還思考了很久。
- dest +=stride;
- }
- }
- }
解釋:我的屏的lcd分辨率是480*272,分辨率的意思是一行有480個點,一共有272行,其實屏蔽上都是一個個點組成的,在上面畫線的意思並不是真正意思上的拿一支筆畫線。打個比方來說你你把一行中80-180個點都改成紅色(我們屏蔽不是黑色么),改完你就可以看見一條紅線了,感覺就是畫了一條紅色的直線對不對?
而且“上色”是從左到右一個點一個點,一行一行“上色”的,屏幕的坐標系如下圖所示:
- short *dest = (short *) (frameBuffer)+ (y0 + vinfo.yoffset) * stride + (x0 +vinfo.xoffset);
可以下面這個代碼畫一個矩形。
- //畫大小為width*height的同色矩陣,5reds+6greens+5blues
- void drawRect_rgb16 (int x0, int y0, int width,int height, int color)
- {
- const int bytesPerPixel = 2;
- const int stride = finfo.line_length / bytesPerPixel;
- const int red = (color & 0xff0000) >> (16 + 3);
- const int green = (color & 0xff00) >> (8 + 2);
- const int blue = (color & 0xff) >> 3;
- const short color16 = blue | (green << 5) | (red << (5 +6));
- short *dest = (short *) (frameBuffer)+ (y0 + vinfo.yoffset) * stride + (x0 +vinfo.xoffset);
- int x, y;
- for (y = 0; y < height; ++y)
- {
- for (x = 0; x < width; ++x)
- {
- dest[x] = color16;
- }
- dest += stride;
- }
- }
下面是main函數:
- int main (int argc, char **argv)
- {
- const char *devfile = "/dev/fb0";
- long int screensize = 0;
- int fbFd = 0;
- /* Open the file for reading and writing */
- fbFd = open (devfile, O_RDWR);
- if (fbFd == -1)
- {
- perror ("Error: cannot open framebuffer device");
- exit (1);
- }
- //獲取finfo信息並顯示
- if (ioctl (fbFd, FBIOGET_FSCREENINFO, &finfo) == -1)
- {
- perror ("Error reading fixed information");
- exit (2);
- }
- printFixedInfo ();
- //獲取vinfo信息並顯示
- if (ioctl (fbFd, FBIOGET_VSCREENINFO, &vinfo) == -1)
- {
- perror ("Error reading variable information");
- exit (3);
- }
- printVariableInfo ();
- /* Figure out the size of the screen in bytes */
- screensize = finfo.smem_len;//fb的緩存長度
- /* Map the device to memory */
- frameBuffer =(char *) mmap (0, screensize, PROT_READ | PROT_WRITE, MAP_SHARED,fbFd, 0);
- if (frameBuffer == MAP_FAILED)
- {
- perror ("Error: Failed to map framebuffer device to memory");
- exit (4);
- }
- //drawRect_rgb16 (vinfo.xres *3 / 8, vinfo.yres * 3 / 8,vinfo.xres / 4, vinfo.yres / 4,0xff00ff00);//實現畫矩形
- drawline_rgb16(50,80,260,0,0xffff0000,0);
- drawline_rgb16(160,10,0,180,0xff00ff00,1);//可以畫出一個交叉的十字,坐標都是自己設的。
- sleep (2);
- printf (" Done.\n");
- munmap (frameBuffer, screensize); //解除內存映射,與mmap對應
- close (fbFd);
- return 0;
- }
*****************************************************************************************************************************************************************************************
****************************************************************************************************************************************************************************************
這里最重要的就是mmap這個函數了
void *mmap(void *addr, size_t len, int prot, int flags, int fd, off_t offset);
addr指定文件應被映射到進程空間的起始地址,一般被指定一個空指針,此時選擇起始地址的任務留給內核來完成。函數的返回值為最后文件映射到進程空間的地址,進程可直接操作起始地址為該值的有效地址。
len是映射到調用進程地址空間的字節數,它從被映射文件開頭offset個字節開始算起。
prot參數指定共享內存的訪問權限。可取如下幾個值的或:PROT_READ(可讀),PROT_WRITE(可寫),PROT_EXEC(可執行),PROT_NONE(不可訪問)。
flags由以下幾個常值指定:MAP_SHARED, MAP_PRIVATE, MAP_FIXED。其中,MAP_SHARED,MAP_PRIVATE必選其一,而MAP_FIXED則不推薦使用。
如果指定為MAP_SHARED,則對映射的內存所做的修改同樣影響到文件。如果是MAP_PRIVATE,則對映射的內存所做的修改僅對該進程可見,對文件沒有影響。
offset參數一般設為0,表示從文件頭開始映射。
prot參數指定共享內存的訪問權限。可取如下幾個值的或:PROT_READ(可讀),PROT_WRITE(可寫),PROT_EXEC(可執行),PROT_NONE(不可訪問)。
flags由以下幾個常值指定:MAP_SHARED, MAP_PRIVATE, MAP_FIXED。其中,MAP_SHARED,MAP_PRIVATE必選其一,而MAP_FIXED則不推薦使用。
如果指定為MAP_SHARED,則對映射的內存所做的修改同樣影響到文件。如果是MAP_PRIVATE,則對映射的內存所做的修改僅對該進程可見,對文件沒有影響。
offset參數一般設為0,表示從文件頭開始映射。
mmap使得進程之間通過映射同一個普通文件實現共享內存。普通文件被映射到進程地址空間后,進程可以像訪問普通內存一樣對文件進行訪問,不必再調用read(),write()等操作。
再來看看我們代碼中的mmap的實例
frameBuffer =(char *) mmap (0, screensize, PROT_READ | PROT_WRITE, MAP_SHARED,fbFd, 0);
前面我們不是說了frambuffer就是linux內核驅動申請的一片內存空間,lcd驅動將frambuffer的地址通過mmap()將這片內存映射到應用程序空間,這樣我們寫入到fb的數據就寫入到內核驅動里的frambuffer中去了,而lcd 的dma就將這些數據寫入到lcd的sram中,從而顯示在lcd上.
*****************************************************************************************************************************************************************************************
下面是效果圖:
這里只是簡單的入門而已,對於具體的lcd和framebuffer的聯系,還是要參考具體的驅動代碼的,今天就分析到這里。
**********************************************************************************************************************************************************
而且對於linux中的子系統驅動這個還不是很清楚,我會在下去再自己總結一下,然后再寫一篇文章來介紹一下。
這里我再小小的說明一下,最近再去了解一下內核的驅動框架,慢慢知道了原來有很多控制器,這些控制器再來控制設備。i2c就有i2c控制器,lcd也有lcd控制器....我開始也不明白,為啥驅動還有驅動控制器呢?
后來才知道因為cpu其實很小,上面根本沒有那么多外設,就只有很數據線,地址線,和控制線,一些寄存器,還有DMA,在這些線上掛着一些外設的控制器,用此與外設來進行“通信”,進行數據交換,控制器也相當於一條總線,上面還掛着的設備,就這樣一層套一層,“嵌套現象很嚴重啊”,控制器與外設之間又有一個核心層來進行交流....哇,好復雜!!!
