文章目錄
1 Framebuffer應用開發
1.1 LCD Framebuffer操作原理
1.2.1 open系統調用
1.2.2 ioctl系統調用
1.2.3 mmap系統調用
1.3 在LCD上描點操作
1.3.1 在LCD上顯示點陣理論基礎
1.3.2 獲取fb_var_screeninfo結構體
1.3.3 根據fb_var_screeninfo計算變量
1.3.4 使用mmap系統調用,映射內存
1.3.5 描點函數編寫
1.4 在LCD上使用點陣寫字
1.4.1 在LCD上顯示英文字母
1.4.2 在LCD上顯示漢字
1.5 搭建freetype相關環境
1.5.1 交叉編譯freetype,并安裝
1.5.2 freetype庫,頭文件移植至開發板
1.6 使用freetype
1.5.1 矢量字體引入
1.5.2 Freetype理論介紹
1.5.2 在LCD上顯示一個矢量字體
1.5.3 在LCD上令矢量字體旋轉某個角度
1 Framebuffer應用開發
1.1 LCD Framebuffer操作原理
? LCD Framebuffer 就是一塊顯存,在嵌入式系統中,顯存是被包含在內存中。LCD Framebuffer里的若干字節(根據驅動程序對LCD控制器的配置而定)表示LCD屏幕中的一個像素點,一一對應整個LCD屏幕。舉個例子,LCD屏幕是800*600的分辨率,即LCD屏幕存在480000個像素點,若每個像素點4個字節表示,那么LCD Framebuffer顯存大小為480000 *4=960000字節,即1.92MB。因此我們的內存將會分割至少1.92MB的空間用作顯存。具體地址在哪里,這個就是又驅動程序去定,應用程序只需直接使用即可,硬件相關操作已由驅動程序封裝好。
? 如上圖,我們只需要往Framebuffer中填入不同的值,驅動程序和硬件控制器就會把這些數據傳輸到對應LCD屏幕上的像素點,從而顯示不同的顏色。由此可知,我們應用程序只需要針對Framebuffer操作即可,其他交給驅動程序和硬件。
1.2 Framebuffer API接口
1.2.1 open系統調用
頭文件:#include ,#include ,#include
函數原型:
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
函數說明:
pathname 表示打開文件的路徑;
Flags表示打開文件的方式,常用的有以下6種,
①:O_RDWR表示可讀可寫方式打開;
②:O_RDONLY表示只讀方式打開;
③:O_WRONLY表示只寫方式打開;
④:O_APPEND 表示如果這個文件中本來是有內容的,則新寫入的內容會接續到原來內容的后面;
⑤:O_TRUNC表示如果這個文件中本來是有內容的,則原來的內容會被丟棄,截斷;
⑥:O_CREAT表示當前打開文件不存在,我們創建它并打開它,通常與O_EXCL結合使用,當沒有文件時創建文件,有這個文件時會報錯提醒我們;
Mode表示創建文件的權限,只有在flags中使用了O_CREAT時才有效,否則忽略。
返回值:打開成功返回文件描述符,失敗將返回-1。
1.2.2 ioctl系統調用
頭文件:#include
函數原型:
int ioctl(int fd, unsigned long request, …);
函數說明:
fd 表示文件描述符;
request表示與驅動程序交互的命令,用不同的命令控制驅動程序輸出我們需要的數據;
… 表示可變參數arg,根據request命令,設備驅動程序返回輸出的數據。
返回值:打開成功返回文件描述符,失敗將返回-1。
1.2.3 mmap系統調用
頭文件:#include
函數原型:
void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset);
函數說明:
addr表示指定映射的內存起始地址,通常設為 NULL表示讓系統自動選定地址,并在成功映射后返回該地址;
length表示將文件中多大的內容映射到內存中;
prot 表示映射區域的保護方式,可以為以下4種方式的組合
①PROT_EXEC 映射區域可被執行
②PROT_READ 映射區域可被讀寫
③PROT_WRITE 映射區域可被寫入
④PROT_NONE 映射區域不能存取
Flags 表示影響映射區域的不同特性,常用的有以下兩種
①MAP_SHARED 表示對映射區域寫入的數據會復制回文件內,原來的文件會改變。
②MAP_PRIVATE 表示對映射區域的操作會產生一個映射文件的復制,對此區域的任何修改都不會寫回原來的文件內容中。
返回值:若成功映射,將返回指向映射的區域的指針,失敗將返回-1。
1.3 在LCD上描點操作
1.3.1 在LCD上顯示點陣理論基礎
? 如上圖,當我們需要顯示一個字母‘A’時,是通過判斷點陣的每一個位數值狀態,來填充顏色,達到顯示字符效果。其中‘1’表示一種顏色,‘0’表示填充另一種顏色。上圖的是8*16的點陣,我們也可以用其他不同大小點陣,只要有這個點陣,我們就可以在LCD上面描點,達到顯示字符的效果。
1.3.2 獲取fb_var_screeninfo結構體
? 在用點陣顯示字符之前,我們需要先從設備fb0中獲取相關的LCD信息,下圖截取我們將用到的fb_info結構體部分內容。
? 通過系統調用ioctl,獲取xres(x方向總像素點),yres(y方向總像素點),bits_per_pixel(每個像素點占據的位數),根據獲取的三個資源,外加點陣,根據這四個資源,我們就可以顯示一個字符。
程序文件:show_ascii.c
4718 fd_fb = open("/dev/fb0", O_RDWR); 4719 if (fd_fb < 0) 4720 { 4721 printf("can't open /dev/fb0n"); 4722 return -1; 4723 } 4724 if (ioctl(fd_fb, FBIOGET_VSCREENINFO, &var)) 4725 { 4726 printf("can't get varn"); 4727 return -1; 4728 }
? 先打開LCD設備(fb0),獲得文件描述符,再通過ioctl獲取fb_var_screeninfo信息并保存在var變量,后續只需訪問var這個結構體,就可以獲得xres(x方向總像素點),yres(y方向總像素點),bits_per_pixel(每個像素點占據的位數)這三個關于fb0的資源。
1.3.3 根據fb_var_screeninfo計算變量
fb_var_screeninfo已保存在var結構體變量中,接著來訪問var結構體變量即可
根據xres與bits_per_pixel算出每行像素點所占據的字節數
程序文件:show_ascii.c
4730 line_width = var.xres * var.bits_per_pixel / 8;
根據bits_per_pixel算出每個像素點所占據的字節數
程序文件:show_ascii.c
4731 pixel_width = var.bits_per_pixel / 8;
根據xres,yres,bits_per_pixel算出全部像素點所占據的字節總和
程序文件:show_ascii.c
4732 screen_size = var.xres * var.yres * var.bits_per_pixel / 8;
1.3.4 使用mmap系統調用,映射內存
程序文件:show_ascii.c
4733 fbmem = (unsigned char *)mmap(NULL , screen_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd_fb, 0); 4734 if (fbmem == (unsigned char *)-1) 4735 { 4736 printf("can't mmapn"); 4737 return -1; 4738 } 4739 4740 /* 清屏: 全部設為黑色 */ 4741 memset(fbmem, 0, screen_size);
? 調用mmap將顯存映射在內存中,以可讀可寫(PROT_READ | PROT_WRITE)及內存回寫(MAP_SHARED)的方式映射,從而獲得一個指向映射在內存空間的首地址fbmem,后續操作就是在這個首地址的基礎上計算各種不同的偏移量,填充顏色值。
1.3.5 描點函數編寫
程序文件:show_ascii.c
4641 void lcd_put_pixel(int x, int y, unsigned int color)
描點函數有3個參數,x坐標,y坐標,像素點顏色值。
程序文件:show_ascii.c
4643 unsigned char *pen_8 = fbmem+y*line_width+x*pixel_width; 4644 unsigned short *pen_16; 4645 unsigned int *pen_32; 4646 4647 unsigned int red, green, blue; 4648 4649 pen_16 = (unsigned short *)pen_8; 4650 pen_32 = (unsigned int *)pen_8;
? 在此處函數參數x與y表示的是像素點的坐標,而單個像素點所占據的顯存大小可能會有不同的情況出現,如1字節表示一個像素點,2字節表示一個像素點,4字節表示一個像素點等,為了更多的兼容不同的情況,因此申請3個指針,pen_8指向的是占據1個字節的像素點空間, pen_16指向的是占據2個字節的像素點空間,pen_32指向的是占據4個字節的像素點空間。
fbmem是系統調用mmap返回的顯存首地址,根據fbmem計算填充顏色的內存空間。
當像素點占據1個字節空間時
對應描點地址= fbmem+Y * 一行所占據的字節數 + x * 每個像素點所占據的字節數
程序文件:show_ascii.c
4652 switch (var.bits_per_pixel) 4653 { 4654 case 8: 4655 { 4656 *pen_8 = color; 4657 break; 4658 } 4659 case 16: 4660 { 4661 /* 565 */ 4662 red = (color >> 16) & 0xff; 4663 green = (color >> 8) & 0xff; 4664 blue = (color >> 0) & 0xff; 4665 color = ((red >> 3) << 11) | ((green >> 2) << 5) | (blue >> 3); 4666 *pen_16 = color; 4667 break; 4668 } 4669 case 32: 4670 { 4671 *pen_32 = color; 4672 break; 4673 } 4674 default: 4675 { 4676 printf("can't surport %dbppn", var.bits_per_pixel); 4677 break; 4678 } 4679 } 4680 }
? 根據設備fb0實際的bits_per_pixel值,選擇對應的pen(pen_8,pen_16,pen_32其中一個),最后把color顏色變量傳入選擇的pen中。
1.4 在LCD上使用點陣寫字
1.4.1 在LCD上顯示英文字母
①找出英文字母在點陣數組中的地址,c所代表的是一個英文字母(ASCII值)。
程序文件:show_ascii.c
4693 unsigned char *dots = (unsigned char *)&fontdata_8x16[c*16];
②根據獲得的英文字母點陣,每一位依次判斷,描點,‘1’表示白色,‘0’表示黑色。
? 根據上圖,我們分析下如何利用點陣在LCD上顯示一個英文字母,因為有十六行,所以首先要有一個循環16次的大循環,然后每一行里有8位,那么在每一個大循環里也需要一個循環8次的小循環,小循環里的判斷單行的描點情況,如果是1,就填充白色,如果是0就填充黑色,如此一來,就可以顯示出黑色底,白色輪廓的英文字母。
程序文件:show_ascii.c
4697 for (i = 0; i < 16; i++) 4698 { 4699 byte = dots[i]; 4700 for (b = 7; b >= 0; b--) 4701 { 4702 if (byte & (1<
③調用我們編寫的lcd_put_ascii函數
程序文件:show_ascii.c
4743 lcd_put_ascii(var.xres/2, var.yres/2, 'A'); /*在屏幕中間顯示8*16的字母A*/
④編譯c文件show_ascii.c
編譯命令:arm-linux-gnueabihf-gcc -o show_ascii show_ascii.c
⑤將編譯出來的show_ascii傳輸到開發板,并進入show_ascii的目錄下
執行命令:./show_ascii
如果實驗成功,我們將看到屏幕中間會顯示出一個白色的字母‘A’。
1.4.2 在LCD上顯示漢字
? 與顯示英文字母有點不同,因為漢字的點陣我們是需要通過漢字庫提取出來,并沒有直接提供點陣數組,因此我們程序開頭需要打開漢字庫文件(HZK16),然后再找到相應的位置,提取出漢字的點陣,最后再按顯示英文字母一樣顯示它,不過這個漢字是16*16的。
① 打開漢字庫文件
程序文件:show_font.c
4760 fd_hzk16 = open("HZK16", O_RDONLY);
② 獲取漢字庫文件的屬性,存在hzk_stat結構體變量中
程序文件:show_font.c
4793 if(fstat(fd_hzk16, &hzk_stat))
此處主要是用知道該文件的大小,因為后面mmap時需要知道映射的文件大小。
③使用mmap系統調用
程序文件:show_font.c
4798 hzkmem = (unsigned char *)mmap(NULL , hzk_stat.st_size, PROT_READ, MAP_SHARED, fd_hzk16, 0);
hzkmem與fbmem類似,也是一個指向映射內存的指針,但是它是指向漢字庫,方便
后續計算漢字點陣偏移位置用。
④使用漢字庫,調出點陣顯示漢字
? HZK16 字庫是符合GB2312標準的16×16點陣字庫HZK16的編碼,每個字需要32個字節的點陣來表示,例如我們將要顯示的‘中’字,編碼是D6D0,難道就是2個字節表示嗎?不是說32字節嗎?D6D0編碼是一個類似于索引碼,D6是區碼,D0是位碼,先要找到D6-A1才是真正區,在D6-A1區里找到D0-A1的真正位置,這才是‘中’字點陣的起始位置(減去A1是為了兼容ascii),每一個區有94個漢字。
程序文件:show_font.c
4734 unsigned int area = str[0] - 0xA1; 4735 unsigned int where = str[1] - 0xA1; 4736 unsigned char *dots = hzkmem + (area * 94 + where)*32;
? 上圖是漢字點陣排布的示意圖,總共有十六行,因此需要一個循環16次的大循環,考慮到一行有兩個字節,我們大循環中加入一個循環2次的循環用于區分是哪個字節,最后判斷當前字節的每一位,如果為 ‘1’描白色,如果為‘0’描黑色
程序文件:show_font.c
4740 for (i = 0; i < 16; i++) 4741 for (j = 0; j < 2; j++) 4742 { 4743 byte = dots[i*2 + j]; 4744 for (b = 7; b >=0; b--) 4745 { 4746 if (byte & (1<
⑤調用我們編寫的lcd_put_chinese函數
程序文件:show_font.c
4810 printf("chinese code: %02x %02xn", str[0], str[1]); 4811 lcd_put_chinese(var.xres/2 + 8, var.yres/2, str);
⑥編譯c文件show_font.c
編譯命令:arm-linux-gnueabihf-gcc -o show_font show_font.c
注:使用此命令HZK16文件必須與show_font.C在同一目錄下。
⑦將編譯出來的show_font傳輸到開發板,并進入show_font的目錄下
執行命令:./show_font
如果實驗成功,我們將看到屏幕中間會顯示出一個白色的字母‘A’與漢字‘中’,同時在串口打印信息中看到‘中’對應的編碼。
chinese code: d6 d0
1.5 搭建freetype相關環境
1.5.1 交叉編譯freetype,并安裝
①解壓freetype源文件
tar xjf freetype-2.4.10.tar.bz2
②進入解壓后的freetype-2.4.10目錄
cd freetype-2.4.10
③配置freetype-2.4.10
./configure --host=arm-linux-gnueabihf --prefix=/home/book/100ask_imx6ull-sdk/ToolChain/gcc-linaro-6.2.1-2016.11-x86_64_arm-linux-gnueabihf/arm-linux-gnueabihf/libc/usr/
④建個目錄,避免后面安裝出錯提示缺少這個internal目錄
mkdir /home/book/100ask_imx6ull-sdk/ToolChain/gcc-linaro-6.2.1-2016.11-x86_64_arm-linux-gnueabihf/arm-linux-gnueabihf/libc/usr/include/freetype2/freetype/internal -p
④編譯
make
⑤安裝
make install
⑥移動freetype頭文件,避免以后編譯總是需要指定頭文件路徑
mv /home/book/100ask_imx6ull-sdk/ToolChain/gcc-linaro-6.2.1-2016.11-x86_64_arm-linux-gnueabihf/arm-linux-gnueabihf/libc/usr/include/freetype2/freetype /home/book/100ask_imx6ull-sdk/ToolChain/gcc-linaro-6.2.1-2016.11-x86_64_arm-linux-gnueabihf/arm-linux-gnueabihf/libc/usr/include/
1.5.2 freetype庫,頭文件移植至開發板
? 由于100ask開發板已經有freetype相關的庫和頭文件,因此不需要移植,如果開發板沒有freetype庫和頭文件就需要按以下方法移植
/home/book/100ask_imx6ull-sdk/ToolChain/gcc-linaro-6.2.1-2016.11-x86_64_arm-linux-gnueabihf/arm-linux-gnueabihf/libc/usr/include/* 復制到開發板的頭文件目錄中
/home/book/100ask_imx6ull-sdk/ToolChain/gcc-linaro-6.2.1-2016.11-x86_64_arm-linux-gnueabihf/arm-linux-gnueabihf/libc/usr/lib/so 復制到開發板的庫文件目錄中
注:鏈接文件需要保持它的鏈接屬性(即加-d選項)。
1.6 使用freetype
1.5.1 矢量字體引入
? 點陣顯示英文字母,漢字時,大小固定,如果放大會有鋸齒出現,為了解決這個問題,引用矢量字體。
矢量字體形成分三步,若干的關鍵點,數學曲線(貝塞爾曲線),填充顏色組合而成。
①假設A字母的關鍵點如圖中的黃色圈圈,確定關鍵點。
②用數學曲線將關鍵點都連接起來,成為封閉的曲線。
③最后把封閉空間填滿顏色,就顯示出一個A字母。
? 如果需要放大或者縮小字體,關鍵點的相對位置是不變的,跟進放大比例放大或縮小,但是相對位置不變,好像分數中的1/2 和 2/4,比例是不變的,但是值卻大了,類似這個味道。
1.5.2 Freetype理論介紹
? 開源的Freetype字體引擎庫它提供統一的接口來訪問多種字體格式文件,從而實現矢量字體顯示。我們只需要移植這個字體引擎,調用對應的API接口,提供字體關鍵點,就可以讓freetype庫幫我們實現閉合曲線,填充顏色,達到顯示矢量字體的目的。
關鍵點(glyph)存在字體文件中,Windows使用的字體文件在FONTS目錄下,擴展名為TTF的都是矢量字庫,本次使用實驗使用的是新宋字體simsun.ttc。
字體文件結構如上圖
? Charmaps表示字符映射表,字體文件可能支持哪一些編碼,GBK,UNICODE,BIG5還是別的編碼,如果字體文件支持該編碼,跟進編碼,通過charmap,找到對應的glyph,一般而言都支持UNICODE碼。
有了以上基礎,我們想象一個文字的顯示過程
①給定一個文字嗎‘A’(0x41),‘中’(GBK,UNICODE ,BIG5)可以確定它的編碼值;
②跟進編碼值,從枝頭文件中通過charmap找到對應的關鍵點(glyph);
③設置字體大;
④用某些函數把關鍵點(glyph)縮放為我們設置的字體大小;
⑤轉換為位圖點陣
⑥在LCD上顯示出來
? 如上圖,參照step1,step2,step3里的內容,可以學習如何使用freetype庫,大致總結下,為如下步驟。
①初始化:FT_InitFreetype
②加載(打開)字體Face:FT_New_Face
③設置字體大小:FT_Set_Char_Sizes 或 FT_Set_Pixel_Sizes
④選擇charmap:FT_Select_Charmap
⑤根據編碼值charcode找到glyph : glyph_index = FT_Get_Char_Index(face,charcode)
⑥根據glyph_index取出glyph:FT_Load_Glyph(face,glyph_index)
⑦轉為位圖:FT_Render_Glyph
⑧移動或旋轉:FT_Set_Transform
1.5.2 在LCD上顯示一個矢量字體
我們可以參考上圖位置的c程序,編寫程序。
①初始化freetype庫
程序文件:freetype_show_font.c
4872 error = FT_Init_FreeType( &library ); /* initialize library */
②用freetype庫中的FT_New_Face函數創建一個face字體文件對象,保存在&face中
程序文件:freetype_show_font.c
4875 error = FT_New_Face( library, argv[1], 0, &face ); /* create face object */
③提取face對象中的glyph,即關鍵點集
程序文件:freetype_show_font.c
4877 slot = face->glyph;
④設置像素點大小,24*24
程序文件:freetype_show_font.c
4879 FT_Set_Pixel_Sizes(face, 24, 0);
⑤確定坐標
? 目前我們前面所用的都是LCD的坐標系對應的x與y坐標,然后在freetype上卻是使用的笛卡爾坐標系,因此我們還需要轉換x與y坐標。
我們將要顯示的是‘繁’字,根據上圖可知,先計算在lcd坐標系的情況下‘繁’字
的左下角的x坐標與y坐標,因為在笛卡爾坐標中左下角為字符的原點,‘A’是的左上角為整個屏幕的中心點,即(xres/2,yres/2)。
lcd_x = var.xres/2 + 8 + 16;lcd_y = var.yres/2 + 16
則笛卡爾座標系:x = lcd_x = var.xres/2 + 8 + 16 ; y = var.yres - lcd_y = var.yres/2 – 16
單位是1/64像素,所以需要乘以64
程序文件:freetype_show_font.c
4888 pen.x = (var.xres/2 + 8 + 16) * 64; 4889 pen.y = (var.yres/2 - 16) * 64; 4890 4891 /* set transformation */ 4892 FT_Set_Transform( face, 0, &pen);
⑥找到glyph的位置,然后取出,并轉換為位圖
4895 error = FT_Load_Char( face, chinese_str[0], FT_LOAD_RENDER ); 4896 if (error) 4897 { 4898 printf("FT_Load_Char errorn"); 4899 return -1; 4900 }
FT_Load_Char函數調用替代了上圖這3步。
最后把轉換出來的位圖打印出來,也是參考example1.c編寫
程序文件:freetype_show_font.c
4902 draw_bitmap( &slot->bitmap, 4903 slot->bitmap_left, 4904 var.yres - slot->bitmap_top);
程序文件:example1.c
修改上圖3處位置
Width寬度:因為在LCD上顯示,寬度自然就是x方向的像素點數,var.xres;
Height高度:因為在LCD上顯示,高度自然就是y方向的像素點數,var.yres;
用點陣實驗中的的描點函數lcd_put_pixel替代image數組
lcd_put_pixel(i, j, bitmap->buffer[q * bitmap->width + p]);
⑥編譯C程序文件freetype_show_font.c
編譯命令:arm-linux-gnueabihf-gcc -finput-charset=GBK -fexec-charset=GBK -o freetype_show_font freetype_show_font.c -lfreetype -lm
⑦將編譯好的freetype_show_font的文件與simsun.ttc字體文件拷貝至開發板,simsun.ttc字體文件放在freetype_show_font執行文件的上一層目錄下,執行以下命令。
執行命令:./freetype_show_font …/simsun.ttc
如果實驗成功,我們將看到屏幕中間會比之前實驗多出一個藍色的‘繁’字。
1.5.3 在LCD上令矢量字體旋轉某個角度
在實現顯示一個矢量字體后,我們可以添加讓該字旋轉某個角度的功能。
我們根據輸入的第二個參數,判斷其旋轉角度,主要代碼還是參照example1.c
根據上圖,增加旋轉角度功能,旋轉的角度由執行命令的第二個參數指定。
程序文件:freetype_show_font_angle.c
/* use 25 degrees */ 4894 angle = ( 1.0 * strtoul(argv[2], NULL, 0) / 360 ) * 3.14159 * 2; 4895 /* set up matrix */ 4896 matrix.xx = (FT_Fixed)( cos( angle ) * 0x10000L ); 4897 matrix.xy = (FT_Fixed)(-sin( angle ) * 0x10000L ); 4898 matrix.yx = (FT_Fixed)( sin( angle ) * 0x10000L ); 4899 matrix.yy = (FT_Fixed)( cos( angle ) * 0x10000L ); 4900 4901 /* set transformation */ 4902 FT_Set_Transform( face, &matrix, &pen);
最后編譯,在開發板上運行
編譯命令如下:
編譯命令:arm-linux-gnueabihf-gcc -finput-charset=GBK -fexec-charset=GBK -o freetype_show_font_angle freetype_show_font_angle.c -lfreetype -lm
編譯出的文件名為freetype_show_font_angle,將文件拷貝至開發板
在含有該文件的目錄下執行以下命令,以下命令正確執行前提是執行文件freetype_show_font在此目錄,而且字體文件simsun.ttc,在上一級目錄:
執行命令:./freetype_show_font_angle …/simsun.ttc 90
如果實驗成功,我們將看到屏幕中間的藍色‘繁’字,旋轉了90度。
審核編輯黃昊宇
-
lcd
+關注
關注
34文章
4414瀏覽量
167136 -
Linux
+關注
關注
87文章
11232瀏覽量
208957 -
Display
+關注
關注
1文章
53瀏覽量
24702
發布評論請先 登錄
相關推薦
評論