教程不断更新中: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软包安装目录里面:KeilSTM32H7xx_DFP2.6.0CMSISFlash(软包版本不同,数值2.6.0不同)。
- 第2种:MDK的安装目录 ARMFlash里面。
32.3 矢量字体介绍
下面的内容来中文版wiki百科,讲的非常好,特此转载过来:https://zh.wikipedia.org/wiki/矢量字体 。
目前主流的矢量字体格式有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:WindowsFonts或C:WinntFonts),可以看到:一个红色A的图标的是点阵字体,两个重叠的T的图标是TrueType字体,一个O的图标就是OpenType字体。
下面是XP系统中字体的部分截图,其中矢量字体扩展名ttf,点阵字体的扩展名是fon。
Win7系统中已经变成如下这种样子:
32.4 emWin对矢量字体的支持
emWin对矢量字体库的支持是基于David Turner、Robert Wilhelm和Werner Lembergr的FreeType字体库,该库可在www.freetype.org下免费获得。emWin对该库的使用符GUITrueTypeFTL. 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点阵大小的格式字体。
代码语言:javascript复制/*
*********************************************************************************************************
* 定义矢量字体
*********************************************************************************************************
*/
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:WindowsFonts(已经将这个字体存到本章节配套例子的Doc文件夹下)。
大小是10MB,其它类型的矢量字体也是可以的,只要不超过QSPI Flash的32MB容量即可:
代码语言:javascript复制/*
*********************************************************************************************************
* 函 数 名: 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 :
代码语言:javascript复制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文件中的配置如下:
代码语言:javascript复制#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文件中的配置如下:
代码语言:javascript复制#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动态内存使用。