忙了幾天,今天在公司竟然沒什麼活幹 ,因此早上就用公司的電腦寫寫以前在公司編寫framebuffer的使用心得體會總結,這也算是一點開發經驗,不過我還沒寫全,精華部分仍是本身藏着吧。直到下午纔開始有點活幹,改瑞芯微的攝像頭驅動,偶滴天!!特別麻煩,攝像頭的代碼好幾千行,果然不太好看,驅動想改也很差改
,看看個人測試效果就知道了,顯示的圖片是反的,又到了週末了,這個問題只能留着下週上班再去解決了,今天咱們就來了解一下Framebuffer設備驅動,其實Framebuffer驅動跟我這個安卓項目就掛鉤了,好,咱們來看看:node
記住!沒有簡單的程序,只有頭腦簡單的程序員!
linux
開篇以前,咱們來介紹一下什麼是framebuffer?程序員
FrameBuffer其實就是,幀緩衝。數據結構
Frame幀:你所看到的屏幕的圖像,或者在一個窗口中的圖像,就叫一幀。app
Buffer緩衝:一段RAM,用來暫存圖像數據,這些數據會被直接寫入到顯示設備。框架
幀緩衝就至關於介於 圖形操做 和 圖像輸出中間的一箇中間人。將程序對圖形數據的處理操做,反饋到顯示輸出上。ide
顯卡(顯存中的數據) <-> 幀緩衝(程序對其中的數據進行處理) <-> 顯示器(輸出圖像)函數
幀緩衝可用於,實現原先視頻卡並不支持的分辨率。學習
顯卡可能並不支持你當前某個更大分辨率的顯示器,可是能夠經過幀緩衝獲取顯卡的顯存中的數據,處理以後,實現更大的分辨率的圖像,而後將數據直接輸出到顯示器上。測試
好了,如今咱們已經瞭解什麼是framebuffer了,接下來咱們來分析framebuffer在linux下的幾個重要的驅動數據結構。
一、Framebuffer幀緩衝設備
Framebuffer 驅動在 Linux 中是標準的顯示設備的驅動。對於 PC 系統,它是顯卡的驅動 ; 對於嵌入式SOC處理器系統,它是LCD控制器或者其餘顯示控制器的驅動。同時該設備屬於一個字符設備,在文件系統中設備節點是/dev/fbx.
一個系統能夠有多個顯示設備,最多見的有 /dev/fb0 /dev/fb1 ,若是在/dev目錄下沒有發現這個文件,那麼須要去修改Linux系統中相應的配置腳本。
在安卓系統中,Framebuffer設備驅動的主設備號一般爲29 ,次設備號遞增而生成。
在此,我借用某內核驅動大牛GQB作的驅動框架圖作分析,若有侵權,請聯繫我刪除,謝謝!
Framebuff的結構框架和實現:
咱們能夠先來看看在framebuffer驅動框架中相關的重要數據結構:
[plain] view plain copy
struct fb_ops { struct module *owner; //檢查可變參數並進行設置 int (*fb_check_var)(struct fb_var_screeninfo *var, struct fb_info *info); //根據設置的值進行更新,使之有效 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_blank)(int blank, struct fb_info *info); //矩形填充 void (*fb_fillrect) (struct fb_info *info, const struct fb_fillrect *rect); //複製數據 void (*fb_copyarea) (struct fb_info *info, const struct fb_copyarea *region); //圖形填充 void (*fb_imageblit) (struct fb_info *info, const struct fb_image *image); };
從設備驅動程序結構看,這個驅動主要跟fb_info結構體有相應的關係,這個結構體記錄了這個設備驅動的所有相關的信息,
其中就包括設備的設置參數,狀態,還有對應底層硬件操做的回調函數。在Linux中,每個幀緩衝設備都必須對應一個fb_info,fb_info在/linux/fb.h中:
上面的fp_ops這個結構體就是經常使用的很是重要的一個。
和其它的內核代碼中的字符驅動相似,一樣,若是你要使用這個驅動,你必須去註冊這個設備驅動。
打開linux3.5的內核代碼,顯示驅動的分析都是由 drivers/video/fbmem.c開始 。咱們
能夠查閱到fbmem.c裏定義 register_framebuffer這個函數。經過查看這個函數,咱們能夠發現其實這個函數在drivers/video/s3c-fb.c中的probe函數中調用了 register_framebuffer,在s3c-fb.c這個文件註冊了一個平臺總線
設備的驅動程序。這裏要注意了,所謂的平臺總線驅動實際上是由linux內核自己去實現的,是一種總線驅動的機制。
如下是fb_info結構體:
[plain] view plain copy
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 */ ………… };
在這個info結構體中,咱們看到結構體中有結構體,因此這個給剛學過C語言和數據結構的夥伴來看是很頭疼的,正所謂這是一點經驗性的東西,因此不是那麼簡單。
首先,咱們分析一下這個結構體裏的幾個關鍵的參數,其它的日後用到了我再作相應的分析:
一、node這個 參數,實質上,node這個參數指的是專用的freambuffer,說白點那就是驅動的次設備號。
二、struct fb_var_screeninfo var;結構體成員記錄用戶可修改的顯示控制器參數,包括屏幕分辨率還有每一個像素點的位數。
[plain] view plain copy
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 */ ………… };
在這個結構體中,xres表示你須要定義的LCD屏的一行包含多少個像素點,bits_per_pixel表示每一個像素點須要用多少個字節去描述它。
這裏面還有其它的參數,好比時間的設置,偏移量等等,太多了我就不說了,有興趣本身慢慢去看慢慢去研究。
三、struct fb_fix_screeninfo fix; 這個結構體記錄了用戶不能去修改的LCD控制器的一些相關的參數。
例如:屏幕緩衝區的物理地址,長度。當對幀緩衝設備進行映射操做的時候,就是從fb_fix_screeninfo中取得緩衝區物理地址的。
看看相應的英文註釋很快就會弄懂。
[plain] view plain copy
struct fb_fix_screeninfo { char id[16]; unsigned long smem_start; /* 開始的framebuffer內存,這裏是物理地址要注意 */ __u32 smem_len; /* 物理地址的長度 */ unsigned long mmio_start; /* 開始的物理地址映射 */ __u32 mmio_len; /* IO內存映射的長度 */ ………… };
[plain] view plain copy
struct fb_videomode *mode 這個結構體主要是設置LCD相應的模式,看看我寫的註釋就清楚了。 struct fb_videomode { const char *name; /* optional */ u32 refresh; /* optional */ u32 xres; // x分辨率 u32 yres; // y分辨率 u32 pixclock; // 像素時鐘頻率,即每一個時鐘週期顯示一個像素點 u32 left_margin; // 行掃描開始脈衝到一行像素數據開始輸出的延遲 hsync<==>DEN u32 right_margin; // 一行像素數據輸出完畢到下一行的行掃描開始脈衝間的延遲 DEN <==>hsync u32 upper_margin; // 幀掃描開始脈衝到第一行像素數據開始輸出的延遲 vsync<==>DEN(1st line) u32 lower_margin; // 最後一行像素數據輸出結束到下一幀的那幀掃描開始脈衝間的延遲DEN(last line)<==>vsync u32 hsync_len; // 行掃描脈衝寬度,單位爲pixclock u32 vsync_len; // 幀掃描脈衝寬度,單位爲line u32 sync; // 各同步信號的極性定義,如hsync、vsync、DEN的極性等。 u32 vmode; // 顯示模式,逐行仍是隔行掃描 u32 flag; // 通常爲0 };
[plain] view plain copy
struct fb_ops *fbops; 這個結構體是LCDframebuffer操做中最重要的一個,也就是說上面作了LCD的相關設置那只是在設置LCD中的硬件寄存器, 若是你想點亮LCD屏順便操做它,那麼這個結構體必不可少。 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); …………… }
之前個人文章曾寫過如何寫一個簡單的字符設備驅動程序,這裏爲就不對這個結構體進行解釋了,就是定義一個能夠供上層調用的文件操做結構體。 進而當你對驅動進行讀,寫,內存映射操做時,經過你的一系列的操做,內核就會根據你編寫上層的程序去相應調用到這樣的一個結構體從而操做LCD
設備。
說了這麼多,好像都是廢話一大堆對吧?那LCD屏幕到底要怎麼點亮呢?我參考了網上大牛的博客
lantianyu520.blog.chinaunix.net總結了幾個能夠點亮LCD的步驟:
1.參考LCD datasheet修改fb_videomode結構體的參數。
2.配置GPIO,點亮LCD背光。
3.參考LCD datasheet看這個LCD是否須要spi總線發送命令進行初始化,通常廠商給datasheet時也會給一份初始化代碼,不過有些參數是錯誤的,需 要調整,發送不正確的命令會致使LCD白屏。
4.用示波器測試從LCD控制器出來的Hsync, Vsync, DE, PCLK是否正確,用萬用表測量Vio, Vci是否正常。
5.有的LCD Driver須要LCD控制器發出一個CS片選使能信號。
6.用萬用表測量LCD的柵壓是否正常,通常爲9.2V。
7.若是上述步驟後還出不來,再檢查LCD初始化命令是否正確,spi時序是否符合。
若是出現圖像異常的狀況,那麼請改:
圖像異常處理:
1.驅動問題
上下抖動,左右沒對齊:調整left/right/upper/lower margin值
2.LCD初始化命令問題
有紋波:調整VDD/AVDD/VGL/VGH電壓
色彩失真:看LCD的RGB模式設置和LCD控制器出來的RGB模式是否一致
上面說的步驟僅僅只是點亮開發板屏幕的相關步驟,若是你想要像玩觸屏手機同樣能夠觸屏,那麼你要配置觸屏的相關驅動,觸屏通常使用I2C總線協議,觸屏的原理就很是複雜了,你在屏幕上按下,
屏幕上就會產生相應的電壓值,由於觸摸屏下面有不少電容,能夠儲存電荷,也就產生了相應的電壓,這時候咱們就能夠用ADC接口,根據電壓值判斷你按下的X,Y橫縱座標的位置。如今觸摸屏基本上支持多點觸摸了,
因此使用的觸摸芯片的等級也會愈來愈高,對於咱們驅動工程師調試觸摸屏的程序也會愈來愈難。咱們的辛苦,消費者的開心,嗚嗚......
從觸摸屏被按下到系統相應的過程以下:
(1) 當觸摸屏感受到觸摸,觸發IRQ_TC中斷,而後讀取觸摸屏控制寄存器的值,判斷是否被按下,若是被按下,啓動定時器,執行touch_timer_fire()函數啓動ADC轉換。
(2) ADC轉換完成後,會觸發IRQ_ADC中斷,執行相應的中斷處理函數,若是ADC轉換次數小於4,再次啓動ADC轉換;若是ADC轉換次數爲4,則啓動一個系統滴答定時器,執行touch_timer_fire()函數
(3) 執行定時器服務程序時,若是此時觸摸屏仍被按下,則上報事件和座標數據,重複(2);若是沒有被按下,上報時間和座標數據,將觸摸屏控制寄存器設置爲中斷等待狀態
可見,觸摸屏驅動的服務是一個封閉的循環過程。
驅動分析到此爲止,中間穿插了一點觸摸屏的概念,其實還有不少我沒分析到的,由於太多了,一篇文章寫不完。爲了更好的去理解Framebuffer驅動,咱們直接利用linux內核爲咱們寫好的freamebuffer設備驅動進行操做。
認真看過上面分析的數據結構的話,你應該會知道,我要操做的是什麼。
有了framebuffer的底層驅動,那在上層操做它那就太太太簡單了,一點難度都沒有。這裏,咱們用linuxX86的平臺來實現這個上層程序的編寫。請記住,我寫的這個程序是在用戶態,不在內核態。
並且,你寫完上層的程序執行後是沒有任何反應的,你須要切換到linux下的其它的運行級別。須要按ALT+F1進入字符界面模式。
羅嗦一下,編寫framebuffer用戶態程序須要如下步驟:
一、初始化framebuffer
二、向framebuffer寫數據
三、退出framebuffer
好,咱們直接上代碼:
[plain] view plain copy
#include <stdio.h> #include <fcntl.h> #include <unistd.h> #include <sys/mman.h> #define RGB(r,g,b)((r<<16)|(g<<8)|b) #define WIDTH 1280 #define HIGHT 1024 static int Frame_fd ; static int *FrameBuffer = NULL ; static int W , H ; //寫framebuffer int Write_FrameBuffer(const char *buffer); int main(void) { 一、設置長寬,打開fb設備 W = 1024 ; H = 768 ; Frame_fd = open("/dev/fb0" , O_RDWR); if(-1 == Frame_fd){ perror("open frame buffer fail"); return -1 ; } 二、對framebuffer進行內存映射mmap //頭文件 <sys/mman.h> //函數原型:void* mmap(void* start,size_t length,int prot,int flags,int fd,off_t offset); //start:映射區的開始地址,設置爲0時表示由系統決定映射區的起始地址。 //length:映射區的長度。//長度單位是 以字節爲單位,不足一內存頁按一內存頁處理 //prot:指望的內存保護標誌,不能與文件的打開模式衝突。是如下的某個值,能夠經過or運算合理地組合在一塊兒 //flags:相關的標誌,就跟open函數的標誌差很少的,本身百度去查 //fd:有效的文件描述詞。通常是由open()函數返回,其值也能夠設置爲-1,此時須要指定flags參數中的MAP_ANON,代表進行的是匿名映射。 //off_toffset:被映射對象內容的起點。 //PROT_READ //頁內容能夠被讀取 //PROT_WRITE //頁能夠被寫入 //MAP_SHARED //與其它全部映射這個對象的進程共享映射空間。對共享區的寫入,至關於輸出到文件。直到msync()或者munmap()被調用,文件實際上不會被更新。 FrameBuffer = mmap(0, 1280*1024*4 , PROT_READ | PROT_WRITE , MAP_SHARED , Frame_fd ,0 ); if(FrameBuffer == (void *)-1){ perror("memory map fail"); return -2 ; } 三、對framebuffer寫數據 char buffer[WIDTH*HIGHT*3]; //咱們要寫入的數據 while(1) { Write_FrameBuffer(buffer); printf("count:%d \n" , count++); } 四、退出framebuffer munmap(FrameBuffer , W*H*4); //解除內存映射 close(Frame_fd); //關閉文件描述符 return 0 ; } //寫framebuffer int Write_FrameBuffer(const char *buffer) { int row , col ; char *p = NULL ; //遍歷分辨率1024*1280的全部像素點 for(row = 0 ; row <1024 ; row++){ for(col = 0 ; col < 1280 ; col++){ if((row < H) && (col < W)){ p = (char *)(buffer + (row * W+ col ) * 3); //轉RGB格式 FrameBuffer[row*1280+col] = RGB((unsigned char)(*(p+2)),(unsigned char)(*(p+1)),(unsigned char )(*p)); } } } return 0 ; }
至此,咱們的framebuffer上層調用程序就寫完了,固然這個程序你看不到什麼圖片,只能看到一塊被映射的區域,而後printf不斷的在打印數據。
若是你有興趣,能夠寫一個圖片數據進去,這時候要用到bmp,yuyv格式的圖片知識,讓圖片能夠顯示在屏幕上,同時你也能夠寫一個攝像頭,讓攝像頭的視頻流數據顯示到framebuffer上,那些所謂的軟件,好比車位監控,智能監控,還有小米那個智能攝像頭,其實用的多半就是這個原理,只不過他們作了拓展,能夠跟TCP/IP,wifi進行結合,進而可以在互聯網傳輸視頻數據,攝像頭的經常使用的驅動叫V4L2,也是一個相似frambuffer驅動同樣很是複雜的驅動,用到不少的知識點,我也在不斷的努力學習中,日後如果有碰到,我也儘可能拿出來跟你們一塊兒學習和分享我本身的見解。
驅動的學習是痛苦的 ,固然痛苦中就必有甘甜,若是你會寫驅動程序了,那麼軟件層次的調用實質上就是函數傳參的原理,太簡單了,因此搞驅動的工程師實際上是能夠往上層去發展的,由於他懂硬件也懂軟件,相反,純軟件轉底層開發,那就沒那麼容易了,你要了解不少硬件的知識和原理。 今天就到此爲止,這是我對framebuffer的理解,或許理想某些地方並不到位,若有高手,歡迎指正,共同探討這個問題。