教程不斷更新中:http://www.armbbs.cn/forum.php?mod=viewthread&tid=98429
第32章 emWin6.x的矢量字體(支持漢字全字庫,Unicode編碼,QSPI Flash方案)
本期教程跟大家講解矢量字體的相關知識,矢量字體最大的好處就是可以任意放大或者縮小字體,而且字體的顯示效果不失真。矢量字體也有缺點,即非常消耗內存。但是本教程配套開發板的STM32H7是支持外接SDRAM和支持內存映射方式的QSPI Flash,這樣就有大容量的空間供矢量字體使用了。
32.1 初學者重要提示
32.2 下載算法存放位置(操作前必看)
32.3 矢量字體介紹
32.4 emWin對矢量字體的支持
32.5 矢量字體庫的移植方法
32.6 矢量字體庫的使用方法
32.7 內部Flash和QSPI Flash程序調試下載配置(重要必看)
32.8 實驗例程說明(RTOS)
32.9 實驗例程說明(裸機)
32.10 總結
32.1 初學者重要提示
1、 使用STM32H7+大容量的SDRAM或者內存映射方式QSPI Flash來實現矢量字體具有一定的實戰意義,可用於實際項目。
2、 實驗中發現了以下三個問題,給大家分享下:
- 不是所有電腦端的矢量字體都可以顯示,測試發現有些無法正常顯示,估計是emWin庫不支持。
- 不能顯示太大的字體,測試發現130點陣之后就無法顯示了。
- 顯示比較大的字體,STM32H7的圖形性能完全跟的上。
3、 矢量字體也是用的Unicode編碼,這點要特別注意。
4、 矢量字體所有API函數在emWin手冊中都有講解,下圖是中文版手冊里面API函數的位置

下圖是英文版手冊里面API函數的位置:

32.2 下載算法存放位置(操作前必看)
(注:例子下載地址 http://www.armbbs.cn/forum.php?mod=viewthread&tid=86980 )
編譯例子:V7-060_QSPI Flash的MDK下載算法制作,生成的算法文件位於此路徑下:
生成算法文件后,需要大家將其存到到MDK安裝目錄,有兩個位置可以存放,任選其一,推薦第2種:
- 第1種:存放到MDK的STM32H7軟包安裝目錄里面:\Keil\STM32H7xx_DFP\2.6.0\CMSIS\Flash(軟包版本不同,數值2.6.0不同)。
- 第2種:MDK的安裝目錄 \ARM\Flash里面。
32.3 矢量字體介紹
下面的內容來中文版wiki百科,講的非常好,特此轉載過來:https://zh.wikipedia.org/wiki/%E7%9F%A2%E9%87%8F%E5%AD%97%E4%BD%93 。
目前主流的矢量字體格式有3種:Type1,TrueType和OpenType,這三種格式都是與平台無關的。
Type1全稱PostScript Type1,是1985年由Adobe公司提出的一套矢量字體標准,由於這個標准是基於PostScript Description Language(PDL),而PDL又是高端打印機首選的打印描述語言,所以Type1迅速流行起來。但是Type1是非開放字體,Adobe對使用Type1的公司征收高額的使用費。
TrueType是1991年由Apple公司與Microsoft公司聯合提出另一套矢量字標准。
Type1使用三次貝塞爾曲線來描述字形,TrueType則使用二次貝塞爾曲線來描述字形。所以Type1的字體比TrueType字體更加精確美觀。一個誤解是,Type1字體比TrueType字體占用空間多。這是因為同樣描述一個圓形,二次貝塞爾曲線只需要8個關鍵點和7段二次曲線;而三次貝塞爾曲線則需要12個關鍵點和11段三次曲線。然而實際情況是一般來說 Type1比TrueType要小10%左右。這是因為對於稍微復雜的字形,為了保持平滑,TrueType必須使用更多的關鍵點。由於現代大部分打印機都是使用PDL作為打印描述語言,所以Type1字體打印的時候不會產生形變,速度快;而TrueType則需要翻譯成PDL,由於曲線方程的變化,還會產生一定的形變,不如Type1美觀。
這么說來,Type1應該比TrueType更具有優勢,為什么如今的計算機上TrueType反而比Type1使用更廣泛呢?這是因為第一:Type1由於字體方程的復雜,所以在屏幕上渲染的時候,花費的時間多,解決方案是大部分Type1字體嵌入了點陣字體,這樣渲染快,但是邊緣不光滑,比較難看。很多ps文檔和ps轉換的pdf文檔都是這樣,在計算機上瀏覽的時候字體很難看,但是打印出來很美觀。TrueType則渲染比較快,可以平滑的顯示在屏幕上,看上去很美觀。
第二個原因是Type1的高額使用費,使得Type1沒有被所有的操作系統所支持。Windows家族只有OS/2和windows 2000及之后的版本從操作系統級別開始支持Type1。由於這個問題,Adobe只好在其所有的產品中嵌入Adobe Type Manager(ATM)作為渲染引擎。
OpenType則是Type1與TrueType之爭的最終產物。1995年,Adobe公司和Microsoft公司開始聯手開發一種兼容Type1和TrueType,並且真正支持Unicode的字體,后來在發布的時候,正式命名為OpenType。OpenType可以嵌入Type1和TrueType,這樣就兼有了二者的特點,無論是在屏幕上察看還是打印,質量都非常優秀。可以說OpenType是一個三贏的結局,無論是Adobe、Microsoft還是最終用戶,都從OpenType中得到了好處。Windows家族從Windows 2000開始,正式支持OpenType。打開系統的字體目錄(一般是C:\Windows\Fonts\或C:\Winnt\Fonts),可以看到:一個紅色A的圖標的是點陣字體,兩個重疊的T的圖標是TrueType字體,一個O的圖標就是OpenType字體。
下面是XP系統中字體的部分截圖,其中矢量字體擴展名ttf,點陣字體的擴展名是fon。
Win7系統中已經變成如下這種樣子:
32.4 emWin對矢量字體的支持
emWin對矢量字體庫的支持是基於David Turner、Robert Wilhelm和Werner Lembergr的FreeType字體庫,該庫可在www.freetype.org下免費獲得。emWin對該庫的使用符GUI\TrueType\FTL. txt下的FreeType授權許可。emWin對該庫進行了少許改編,添加了帶有GUI函數的應用層。emWin軟件包中也是沒有矢量字體庫的,需要大家在SEGEER官網地址https://www.segger.com/downloads/emwin/emWin_FreeType 下載。
矢量字體基於矢量圖形,矢量的優勢在於可以無損的放縮。而點陣字體雖然也可以放縮,但不是矢量的,放縮后鋸齒很明顯。並且項目中需要多種字體大小支持的話,需要幾種字體支持,就需要生成幾種點陣字庫,非常占空間,而矢量字體僅需要一個字體庫就可以了。特別是顯示大字體,矢量字體庫的優勢更明顯。
通過矢量字體帶來無損放縮的同時,也是有缺點的。使用矢量字體的話,每個字符在繪制前需要光柵化為位圖,為避免每次繪制字符時都進行光柵化,通常用字體引擎緩存點陣數據。這要求CPU速度快、RAM足夠。當前emWin對矢量字體的支持是以總線方式尋址的,與第30章講解的SIF格式字體是類似的。
TrueType矢量字體的硬件要求如下:

32.5 矢量字體庫的移植方法
跟第23章講解的PNG庫一樣,emWin的庫中也是不含有矢量庫的,需要用戶自行添加,添加也比較簡單,只需用戶把源碼文件添加到工程里面就可以使用了。
矢量庫的下載地址:https://www.segger.com/downloads/emwin/emWin_FreeType 。下載軟件包后進行解壓,當前這個版本的庫已經被存到本章節配套例子的Doc文件夾:

32.5.1 MDK版本移植說明
- 第1步:在 emWin工程-->emWin文件夾-->新建一個TrueType文件夾,將矢量字體庫里面的源碼文件全部復制到此文件夾里面(其它任意文件夾都是可以的,不限制)。

- 第2步:將矢量庫的所有.C格式的源碼文件添加到MDK工程里面,下面是部分源碼文件的截圖。

- 第3步:添加矢量庫的頭文件路徑,添加完畢后別忘了點擊OK。
- 第4步:修改系統堆(heap)大小,這一步非常關鍵。因為矢量庫要用到函數malloc和free,而這種函數是從系統堆空間里面申請內存的,鑒於矢量庫非常的消耗動態內存,這里將32MB SDRAM的最后1MB空間給系統堆使用,設置如下:

Heap_Size:表示堆大小設置為1MB。
_heap_base:表示堆起始地址為0xC1F00000,即32MB SDRAM最后1MB空間的起始地址。
_heap_linmit:表示堆結束地址0xC1FFFFFF,即32MB SDRAM最后1MB空間的結束地址。
除了malloc和free要用到堆空間,部分C標准庫的其它函數也要用到堆空間,所以一定要及時初始化SDRAM,防止用到堆空間的時候,SDRAM還沒有初始化,將導致系統崩潰。當前是將SDRAM的初始化放在了bsp.c文件的bsp_Init函數開始的地方,之前執行的程序都沒有用到C標准庫,所以可以放在這里。
- 第5步:最后一步,添加好庫文件並且修改完畢后,驗證是否已經添加成功,可以進行一次全編譯,全編譯后MDK會有幾個警告和兩個錯誤。
解決辦法是將下面兩個函數形參的void刪掉即可
至此,矢量字體庫就添加成功了。剩下就可以調用矢量庫的API函數了。
32.6 矢量字體庫的使用方法
矢量字體的使用通過下面四步就可以實現:
第1步:定義16點陣大小,24點陣大小,32點陣大小,48點陣大小,72點陣大小和120點陣大小的格式字體。
/* ********************************************************************************************************* * 定義矢量字體 ********************************************************************************************************* */ GUI_TTF_CS Cs0, Cs1, Cs2, Cs3, Cs4, Cs5; GUI_TTF_DATA Data; GUI_FONT Font16, Font24, Font32, Font48, Font72, Font120;
這里對定義矢量字體用到的兩個結構體變量做如下介紹。
GUI_TTF_CS結構體變量:

GUI_TTF_DATA結構體變量:

第2步:創建16點陣大小,24點陣大小,32點陣大小,48點陣大小,72點陣大小和120點陣大小的格式字體。
創建前要先將矢量字體庫存到SD卡中,然后將其加載到SDRAM里面,這個矢量字體是來自電腦系統自帶,電腦系統是WIN7 64bit,路徑:C:\Windows\Fonts(已經將這個字體存到本章節配套例子的Doc文件夾下)。

大小是10MB,其它類型的矢量字體也是可以的,只要不超過QSPI Flash的32MB容量即可:
/* ********************************************************************************************************* * 函 數 名: LoadFontTTF * 功能說明: 初始化 * 形 參: 無 * 返 回 值: 無 ********************************************************************************************************* */ void LoadFontTTF() { #if 1 char *_acBuffer; GUI_HMEM hMem; /* 申請一塊內存空間 並且將其清零 */ hMem = GUI_ALLOC_AllocZero(sizeof(_acsong)); /* 將申請到內存的句柄轉換成指針類型 */ _acBuffer = GUI_ALLOC_h2p(hMem); memcpy(_acBuffer, _acsong, sizeof(_acsong)); /* 設置參數 */ Data.pData = _acBuffer; Data.NumBytes = sizeof(_acsong); #else /* 設置參數 */ Data.pData = _acsong; Data.NumBytes = sizeof(_acsong); #endif /* 設置第1種字體顯示方式 */ Cs0.pTTF = &Data; /* 矢量字體數據地址 */ Cs0.PixelHeight = 16; /* 字體高度 */ Cs0.FaceIndex = 0; /* 設置第2種字體顯示方式 */ Cs1.pTTF = &Data; /* 矢量字體數據地址 */ Cs1.PixelHeight = 24; /* 字體高度 */ Cs1.FaceIndex = 0; /* 設置第3種字體顯示方式 */ Cs2.pTTF = &Data; /* 矢量字體數據地址 */ Cs2.PixelHeight = 32; /* 字體高度 */ Cs2.FaceIndex = 0; /* 設置第4種字體顯示方式 */ Cs3.pTTF = &Data; /* 矢量字體數據地址 */ Cs3.PixelHeight = 48; /* 字體高度 */ Cs3.FaceIndex = 0; /* 設置第5種字體顯示方式 */ Cs4.pTTF = &Data; /* 矢量字體數據地址 */ Cs4.PixelHeight = 72; /* 字體高度 */ Cs4.FaceIndex = 0; /* 設置第6種字體顯示方式 */ Cs5.pTTF = &Data; /* 矢量字體數據地址 */ Cs5.PixelHeight = 120; /* 字體高度 */ Cs5.FaceIndex = 0; /* 創建6種字體 */ GUI_TTF_CreateFontAA(&Font16, &Cs0); GUI_TTF_CreateFontAA(&Font24, &Cs1); GUI_TTF_CreateFontAA(&Font32, &Cs2); GUI_TTF_CreateFontAA(&Font48, &Cs3); GUI_TTF_CreateFontAA(&Font72, &Cs4); GUI_TTF_CreateFontAA(&Font120, &Cs5); f_close(&file); }
第3步:加載到SDRAM后,使用就比較簡單了。
用戶只需調用函數GUI_UC_SetEncodeUTF8()使能UTF-8編碼就可以使用矢量字體了,比如設置按鈕的字體,調用如下設置函數即可。
BUTTON_SetFont(hWin, &Font32); /* hWin是按鈕的句柄 */
第4步:最后一步切不可忘記設置漢字顯示所在源文件的編碼類型,具體MDK和IAR的設置方法請看第28章22.4小節(本章節配套的例子也是設置的MainTask,c文件),這一步絕對不可以省略,因為我們使用的矢量字體庫也是Unicode編碼。
通過這4步就實現矢量字體的顯示了。另外注意,如果系統運行中不需要矢量字體了,可以通過函數GUI_TTF_DestroyCache 釋放矢量字體所消耗的內存資源,通過函數GUI_ALLOC_AllocZero申請的空間,可以使用函數GUI_ALLOC_Free來釋放。
32.7 內部Flash和QSPI Flash程序調試下載配置(重要必看)
將下面兩個地方配置后,就可以像使用內部Flash一樣使用QSPI Flash進行調試了。並且這種方式可以方便的調試程序,內部Flash和外部Flash都做調試。
32.7.1 將字庫文件轉換為C數組格式文件
為了方便將bin文件添加到MDK工程中,我們這里使用小軟件B2C.exe將其轉換為C格式文件(此軟件已經放到本章配套例子V7-540_emWin6.x實驗_矢量全字庫,支持中文,Unicode編碼(QSPI Flash RTOS)的Doc文件里面。
轉換后生成的文件命名為song.c :
const unsigned char _acsong[10576012UL + 1] = { 0x00, 0x01, 0x00, 0x00, 0x00, 0x12, 0x01, 0x00, 0x00, 0x04, 0x00, 0x20, 0x44, 0x53, 0x49, 0x47, 0x28, 0x0C, 0xE3, 0x96, 0x00, 0xA1, 0x45, 0x40, 0x00, 0x00, 0x1B, 0x4C, 0x47, 0x53, 0x55, 0x42, 0xBB, 0xCF, 0xB8, 0xF7, 0x00, 0xA0, 0x64, 0xF4, 0x00, 0x00, 0x00, 0xFC, 0x4F, 0x53, 0x2F, 0x32, 0xD3, 0x94, 0x1D, 0x16, 0x00, 0x00, 0x01, 0xA8, 0x00, 0x00, 0x00, 0x60, 0x63, 0x6D, 0x61, 0x70, 0x3D, 0xE8, 0x75, 0xB8, 0x00, 0x00, 0xE7, 0xDC, 0x00, 0x00, 0x05, 0x5C, 0x63, 0x76, 0x74, 0x20, 0x07, 0x29, 0x03, 0xF0, 省略未寫 }
32.7.2 設置字庫文件到外部QSPI Flash。
下面將流位圖文件下載到QSPI Flash,需要大家先在這里添加QSPI Flash地址范圍:
然后設置資源文件到外部QSPI Flash:鼠標右擊文件分組GUI/Font,選擇Options。
32.7.3 下載配置
注意這里一定要夠大,否則會提示算法文件無法加載:
我們這里是將其加到DTCM中,即首地址為0x20000000,大家也可以存儲到任意其它RAM地址,只要空間還夠加載算法文件即可。推薦使用AXI SRAM(地址0x24000000),因為這塊RAM空間足夠大。
如果要下載程序到內部Flash和外部QSPI Flash里面,需要做如下配置,兩個下載算法都要添加進來:
32.7.4 調試配置
注意這里一定要夠大,否則會提示算法文件無法加載:
我們這里是將其加到DTCM中,即首地址為0x20000000,大家也可以存儲到任意其它RAM地址,只要空間還夠加載算法文件即可。
如果要做調試下載,需要做如下配置:
32.8 實驗例程說明(RTOS)
配套例子:
V7-540_emWin6.x實驗_矢量全字庫,支持中文,Unicode編碼(QSPI Flash RTOS)
實驗目的:
- 學習emWin矢量字體庫的使用方法,Unicode編碼
- emWin功能的實現在MainTask.c文件里面。
實驗內容:
1、K1按鍵按下,串口或者RTT打印任務執行情況(串口波特率115200,數據位8,奇偶校驗位無,停止位1)。
2、(1) 凡是用到printf函數的全部通過函數App_Printf實現。
(2) App_Printf函數做了信號量的互斥操作,解決資源共享問題。
3、默認上電是通過串口打印信息,如果使用RTT打印信息:
MDK AC5,MDK AC6或IAR通過使能bsp.h文件中的宏定義為1即可
#define Enable_RTTViewer 1
4、各個任務實現的功能如下:
App Task Start 任務 :啟動任務,這里用作BSP驅動包處理。
App Task MspPro任務 :消息處理,這里用作LED閃爍。
App Task UserIF 任務 :按鍵消息處理。
App Task COM 任務 :暫未使用。
App Task GUI 任務 :GUI任務。
μCOS-III任務調試信息(按K1按鍵,串口打印):
RTT 打印信息方式:
程序設計:
任務棧大小分配:
μCOS-III任務棧大小在app_cfg.h文件中配置:
#define APP_CFG_TASK_START_STK_SIZE 512u
#define APP_CFG_TASK_MsgPro_STK_SIZE 2048u
#define APP_CFG_TASK_COM_STK_SIZE 512u
#define APP_CFG_TASK_USER_IF_STK_SIZE 512u
#define APP_CFG_TASK_GUI_STK_SIZE 2048u
任務棧大小的單位是4字節,那么每個任務的棧大小如下:
App Task Start 任務 :2048字節。
App Task MspPro任務 :8192字節。
App Task UserIF 任務 :2048字節。
App Task COM 任務 :2048字節。
App Task GUI 任務 :8192字節。
系統棧大小分配:
μCOS-III的系統棧大小在os_cfg_app.h文件中配置:
#define OS_CFG_ISR_STK_SIZE 512u
系統棧大小的單位是4字節,那么這里就是配置系統棧大小為2KB
emWin動態內存配置:
GUIConf.c文件中的配置如下:
#define EX_SRAM 1/*1 used extern sram, 0 used internal sram */ #if EX_SRAM #define GUI_NUMBYTES (1024*1024*24) #else #define GUI_NUMBYTES (100*1024) #endif
通過宏定義來配置使用內部SRAM還是外部的SDRAM做為emWin的動態內存,當配置:
#define EX_SRAM 1 表示使用外部SDRAM作為emWin動態內存,大小24MB。
#define EX_SRAM 0 表示使用內部SRAM作為emWin動態內存,大小100KB。
默認情況下,本教程配套的所有emWin例子都是用外部SDRAM作為emWin動態內存。
emWin界面顯示效果:
800*480分辨率界面效果。

32.9 實驗例程說明(裸機)
配套例子:
V7-539_emWin6.x實驗_矢量全字庫,支持中文,Unicode編碼(QSPI Flash裸機)
實驗目的:
- 學習emWin矢量字體庫的使用方法,Unicode編碼
- emWin功能的實現在MainTask.c文件里面。
emWin界面顯示效果:
800*480分辨率界面效果。
emWin動態內存配置:
GUIConf.c文件中的配置如下:
#define EX_SRAM 1/*1 used extern sram, 0 used internal sram */ #if EX_SRAM #define GUI_NUMBYTES (1024*1024*24) #else #define GUI_NUMBYTES (100*1024) #endif
通過宏定義來配置使用內部SRAM還是外部的SDRAM做為emWin的動態內存,當配置:
#define EX_SRAM 1 表示使用外部SDRAM作為emWin動態內存,大小24MB。
#define EX_SRAM 0 表示使用內部SRAM作為emWin動態內存,大小100KB。
默認情況下,本教程配套的所有emWin例子都是用外部SDRAM作為emWin動態內存。
32.10 總結
本章節為大家講解的矢量字體是可以用於項目實戰的,實際項目中建議使用大容量的SDRAM或者內存映射方式的QSPI Flash,這樣即使加載矢量字庫后,還有大量空間供emWin動態內存使用。
