【第3版emWin教程】第32章 emWin6.x的矢量字体(支持汉字全字库,Unicode编码,QSPI Flash方案)
教程不断更新中: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动态内存使用。