【普中STM32F1xx开发攻略--标准库版】-- 第 49 章 FLASH 字库实验
(1)实验平台普中STM32F103 朱雀、玄武开发板https://item.taobao.com/item.htm?id620302685024(2)资料下载 普中科技-各型号产品资料下载链接在前面 FSMC-TFTLCD 实验章节中我们就已经介绍了如何在 TFTLCD 上显示汉字 当时我们是通过取模软件获取字模数据 由于内存原因 显示的汉字也非常有限。 对于单片机系统来说 少则会用几个汉字 多则使用整个汉字库。 一般单片机的内存都很小 所以一般不在程序变量中存放字库。 使用外部 FLASH 存储字库是一种很常见的字库使用方式。 本章我们就来介绍如何将字库存储到外部FLASH 中 本章要实现的功能是 系统开启后 检测 SD 卡是否存在 若存在则挂载 SD、 SPI-FLASH 设备 然后检查 SPI-FLASH 中是否包含字库文件 如果含有则显示字库内汉字字符等信息 可通过 KEY_UP 键控制 SD 卡更新 FLASH 字库 TFTLCD 显示更新进度 更新完成后 DS0 指示灯闪烁 提示系统正常运行。 学习本章可以参考“\8--STM32 相关资料\STM32 深入学习资料\字库资料” 内文档。本章分为如下几部分内容49.1 汉字显示原理49.2 GBK 字库制作49.3 硬件设计49.4 软件设计49.4.1 字库检测函数49.4.2 汉字字符显示函数49.4.3 字库更新函数49.4.4 主函数49.5 实验现象课后作业49.1 汉字显示原理常用的汉字内码系统有 GB2312 GB13000 GBK BIG5繁体 等几种 其中 GB2312 支持的汉字仅有几千个 很多时候不够用 而 GBK 内码不仅完全兼容 GB2312 还支持了繁体字 总汉字数有 2 万多个 完全能满足我们一般应用的要求。 本章实验使用的汉字内码系统就是 GBK 内码。汉字在液晶上的显示原理与前面显示字符的是一样的。 汉字在液晶上的显示其实就是一些点的显示与不显示 这就相当于我们的笔一样 有笔经过的地方就画出来 没经过的地方就不画 我们以 16*16 的汉字为例 假设其取模方向为从上到下 从左到右的方向取模 且高位在前 那么其取模原理如下图所示图中 我们取模的时候 从最左上方的点开始取从上到下 从左到右 且高位在前bit7 在表示第一个位 那么第一个字节就是 0X021 表示白色的点 即要画出来的点 0 则表示不要画出来 第二个字节是 0X00 第三个字节到第 2 列了 每列 2 个字节 是 0X22…… 依次类推 一个 16*16 的汉字 总共有 16 列 每列 2 个字节 总共需要 32 个字节来表示。在显示的时候 我们只需要读取这个汉字的点阵数据16*16 字体 一个汉字的点阵数据为 32 个字节 然后将这些数据 按取模方式 反向解析出来坐标要处理好 每个字节是 1 的位就画出来 不是 1 的位就忽略 这样就可以显示出这个汉字了。所以要显示汉字 我们首先要知道汉字的点阵数据 这些数据可以由专门的软件来生成。 知道显示了一个汉字 就可以推及整个汉字库了。 汉字在各种文件里面的存储不是以点阵数据的形式存储的否则那占用的空间就太大了 而是以内码的形式存储的 就是 GB2312/GBK/BIG5 等这几种的一种 每个汉字对应着一个内码 在知道了内码之后再去字库里面查找这个汉字的点阵数据 然后在液晶上显示出来。 这个过程我们是看不到 但是计算机是要去执行的。单片机要显示汉字也与此类似 汉字内码GBK/GB2312 --查找点阵库--解析--显示。所以只要我们有了整个汉字库的点阵 就可以把电脑上的文本信息在单片机上显示出来了。 这里我们要解决的最大问题就是制作一个与汉字内码对得上号的汉字点阵库。 而且要方便单片机的查找。 在本实验中我们首先使用字库生成软件将生成好的字库文件拷贝到 SD 卡内 然后通过 SD 卡将字库文件写入到外部SPI-FLASH 中。那么我们要显示字库汉字的整个过程主要有以下几步1) 读取汉字的 GBK 编码。 当我们使用 MDK 编译的时候 比如说我们定义一组汉字字符串 其实在内存上面保存的数据其实也就是 GBK 的编码2) 根据 GBK 编码寻找到该文字在字库中的偏移地址。3) 将字库编译地址转换成该文字的点阵数据在 FLASH 中的偏移地址。4) 根据 FLASH 地址将文字的点阵数据读取显示到 LCD 彩屏上。GBK 码由 2 个字节组成 第 1 个字节为 0x81~0xFE 第 2 个字节分为两部分 第一部分是 0x40~0x7E 第二部分是 0x80~0xFE。 我们把第 1 个字节代表的意义称为区 那么 GBK 里面总共有 126 个区0xFE-0x811 每个区内有 190 个汉字0xFE – 0x80 0x7E - 0x40 2 总共就有 126*19023940个汉字。 我们的点阵库只要按照这个编码规则从 0X8140 开始 逐一建立 每个区的点阵大小为每个汉字所用的字节数*190。 这样 我们就可以得到在这个字库里面定位汉字的方法当 GBKL0X7F 时 Hp((GBKH-0x81)*190GBKL-0X40)*(size*2)当 GBKL0X80 时 Hp((GBKH-0x81)*190GBKL-0X41)*(size*2)其中 GBKH、 GBKL 分别代表 GBK 的第一个字节和第二个字节(也就是高位和低位) size 代表汉字字体的大小比如 16 字体 12 字体等 Hp 则为对应汉字点阵数据在字库里面的起始地址(假设是从 0 开始存放)。这样我们只要得到了汉字的 GBK 码 就可以显示这个汉字了 从而实现汉字在液晶上的显示。这样说大家可能理解起来比较模糊 我们来看下第 1 个区的汉字编码:第一区的第 1 个字节也就是 0x81 如下图所示。 我们就可以知道“丂” 字的 GBK 码是 0x8140 依此类推 “丄” 字的编码是 0x8141 等等。这里大家注意观察 在一个区内 编码 0x817F 位置是空的 也就是没有汉字 所以说第 2 个字节是分为两部分的。在这里还有个小知识点 为什么第 1 个字节是从 0x81 开始的呢 这是为了兼容 ASCII 码而设定的 我们知道 ASCII 码一共有 127 个 也就是从0x00~0x7F 当我们读取文字编码的第 1 个字节是小于 0x80 的时候汉字占用2 个字节 就可以判断这个字符是 ASCII 码字符。49.2 GBK 字库制作在前面 FATFS 文件系统章节中 我们提到过要使用 cc936.c 文件 以支持长文件名 但是 cc936.c 文件里面的两个数组太大了172KB 直接刷在单片机里面太占用 flash 了 所以我们必须把这两个数组存放在外部 flash。 cc936 里面包含的两个数组 oem2uni 和 uni2oem 存放 unicode 和 gbk 的互相转换对照表 这两个数组很大 这里我们利用小工具可将 C 语言数组转成 BIN二进制 文件 软件在“5--开发工具\4-常用辅助开发软件\C2B 转换助手 V1.1” 我们将这两个数组拷贝出来存放为一个新的文本文件 假设为 UNIGBK.TXT 然后用 C2B 转换助手打开这个文本文件 如下图所示然后点击转换就可以在当前目录下文本文件所在目录下 得到一个UNIGBK.bin 的文件。 这样就完成将 C 语言数组转换为.bin 文件 然后只需要将 UNIGBK.bin 保存到外部 FLASH 就实现了该数组的转移。在 cc936.c 里面 主要是通过 ff_convert 调用这两个数组 实现 UNICODE和 GBK 的互转 该函数原代码如下WCHAR ff_convert(/* Converted code, 0 means conversion error */ WCHAR src, /* Character code to be converted */ UINT dir /* 0: Unicode to OEMCP, 1: OEMCP to Unicode */ ) { const WCHAR *p; WCHAR c; int i, n, li, hi; if (src 0x80) { /* ASCII */ c src; } else { if (dir) { /* OEMCP to Unicode */ p oem2uni; hi sizeof(oem2uni) / 4 - 1; } else { /* Unicode to OEMCP */ p uni2oem; hi sizeof(uni2oem) / 4 - 1; } li 0; for (n 16; n; n--) { i li (hi - li) / 2; if (src p[i * 2]) break; if (src p[i * 2]) li i; else hi i; } c n ? p[i * 2 1] : 0; } return c; }此段代码通过二分法16 阶 在数组里面查找 UNICODE或 GBK 码对应的 GBK或 UNICODE 码。 当我们将数组存放在外部 flash 的时候 将该函数修改为WCHAR ff_convert(/* Converted code, 0 means conversion error */ WCHAR src, /* Character code to be converted */ UINT dir /* 0: Unicode to OEMCP, 1: OEMCP to Unicode */ ) { WCHAR t[2]; WCHAR c; u32 i, li, hi; u16 n; u32 gbk2uni_offset 0; if (src 0x80) { c src; // ASCII, direct no conversion } else { if (dir) { // GBK to Unicode gbk2uni_offset ftinfo.ugbksize / 2; } else { // Unicode to GBK gbk2uni_offset 0; } /* Unicode to OEMCP */ hi ftinfo.ugbksize / 2; hi hi / 4 - 1; li 0; for (n 16; n; n--) { i li (hi - li) / 2; EN25QXX_Read((u8*)t, ftinfo.ugbkaddr i * 4 gbk2uni_offset, 4); // read 4 bytes if (src t[0]) break; if (src t[0]) li i; else hi i; } c n ? t[1] : 0; } return c; }代码中的 ftinfo.ugbksize 为我们刚刚生成的 UNIGBK.bin 的大小 而ftinfo.ugbkaddr 是我们存放 UNIGBK.bin 文件的首地址。 这里同样采用的是二分法查找 为了不改动 FATFS 源码 我们直接将 cc936.c 文件拷贝一份 将其放在工程文件夹 Fatfs/fatfs_app 下 并将其重命名为 mycc936 防止文件冲突 如下图所示然后再将上述修改函数替换到 mycc936.c 文件中即可。 关于 mycc936.c 的修改 我们就介绍到这。上一节 我们介绍了 GBK 字库的编码原理 下面我们就来介绍如何制作 GBK字库。 字库的制作 我们要用到一款软件 由易木雨软件工作室设计的点阵字库生成器 V3.8。 该软件可以在 WINDOWS 系统下生成任意点阵大小的 ASCII GB2312(简体中文)、GBK(简体中文)、 BIG5(繁体中文)、 HANGUL(韩文)、 SJIS(日文)、 Unicode 以及泰文 越南文、 俄文、 乌克兰文 拉丁文 8859 系列等共二十几种编码的字库 不但支持生成二进制文件格式的文件 也可以生成 BDF 文件 还支持生成图片功能 并支持横向 纵向等多种扫描方式 且扫描方式可以根据用户的需求进行增加。 该软件我们放在光盘目录“\5--开发工具\4-常用辅助开发软件\字库制作软件” 下。 打开后界面如下图所示比如我们要生成 16*16 的 GBK 字库 则选择 936 中文 PRC GBK 字宽和高均选择 16 字体大小选择 12 然后模式选择纵向取模方式二字节高位在前低位在后 最后点击创建 就可以始生成我们需要的字库了(.DZK 文件 这里我们手动修改文件后缀名为.FON)。 具体设置如下图所示这里给大家普及一个知识 电脑端的字体大小与我们生成点阵大小的关系为fsizedsize*6/8其中 fsize 是电脑端字体大小 dsize 是点阵大小12、 16、 24 等 。所以 16*16 点阵大小对应的是 12 号字体。生成完以后我们把文件名改成 GBK16.FON这里是手动修改文件后缀 。同样的方法 生成 12*12 的点阵库GBK12.FON 、 24*24 的点阵库GBK24.FON和 32*32 的点阵库GBK32.FON 总共制作 4 个字库。 这些字库我们已经制作好放在光盘目录“\7--SD 卡根目录文件\FONT” 下。另外 该软件还可以生成其他很多字库 字体也可选 大家可以根据自己的需要按照上面的方法生成即可。 该软件的详细介绍请看软件自带的《点阵字库生成器说明书》 关于 GBK 字库的制作 我们就介绍到这。49.3 硬件设计本实验使用到硬件资源如下1 DS0 指示灯2 KEY_UP 按键3 串口 14 TFTLCD 模块5 SD 卡6 外部 FLASHEN25Q128这些电路在前面章节都介绍过 这里就不多说。 DS0 指示灯用来提示系统运行状态 KEY_UP 按键用来将 SD 卡字库更新到外部 FLASH 内 TFTLCD 和串口 1用来显示相关信息。49.4 软件设计本章所要实现的功能是 系统开启后 检测 SD 卡是否存在 若存在 则挂载 SD、 SPI-FLASH 设备 然后检查 SPI-FLASH 中是否包含字库文件 如果含有则显示字库内汉字字符等信息 可通过 KEY_UP 键控制 SD 卡更新 FLASH 字库 TFTLCD显示更新进度 更新完成后 DS0 指示灯闪烁 提示系统正常运行。 程序框架如下1 汉字字符显示函数2 字库更新函数3 编写主函数汉字字符显示原理 在前面介绍 GBK 编码原理时已经讲解。 下面我们打开“\4--实验程序\1--基础实验\41-FLASH 字库实验” 工程 新建一个工程组 命名为 Font 在 Font 工程组中可以看到添加了 font_show.c 和 font_update.c 文件里面包含了 GBK 汉字显示、 更新的驱动程序 同时还要包含对应的头文件路径。这里我们分析几个重要函数 其他部分程序大家可以打开工程查看。49.4.1 字库检测函数我们已经对外部 SPI-FLASH 芯片写入了字库 并在写入的字库地址前面加上了一个标志 用于后期我们检测该 SPI-FLASH 芯片是否包含字库 来决定是否更新字库 即需要实现一个字库检测函数 代码如下//初始化字体 //返回值:0,字库完好. // 其他,字库丢失 u8 font_init(void) { u8 t 0; EN25QXX_Init(); while (t 10) //连续读取10次,都是错误,说明确实是有问题,得更新字库了 { t; EN25QXX_Read((u8*)ftinfo, FONTINFOADDR, sizeof(ftinfo)); //读出ftinfo结构体数据 if (ftinfo.fontok 0x55) break; delay_ms(20); } if (ftinfo.fontok ! 0x55) return 1; return 0; }该函数功能很简单 通过读取 SPI-FLASH 对应地址内数据 判断是否为我们写入的值 0X55 如果是则函数返回值为 0 表示含有字库 否则没有字库。在程序设计中为了更好的管理字库 我们使用了一个结构体 该结构体内成员功能在程序中都有注释 如下//字库信息结构体定义 //用来保存字库基本信息地址大小等 __packed typedef struct { u8 fontok; //字库存在标志0X55字库正常其他字库不存在 u32 ugbkaddr; //unigbk的地址 u32 ugbksize; //unigbk的大小 u32 f12addr; //gbk12地址 u32 gbk12size; //gbk12的大小 u32 f16addr; //gbk16地址 u32 gbk16size; //gbk16的大小 u32 f24addr; //gbk24地址 u32 gbk24size; //gbk24的大小 u32 f32addr; //gbk32地址 u32 gbk32size; //gbk32的大小 }_font_info; extern _font_info ftinfo; //字库信息结构体在字库初始化函数中 还使用到了一个宏定义标识符 FONTINFOADDR 这个font_update.c 文件开头有定义 用于表示字库写入到 SPI-FLASH 的起始地址如下//字库存放起始地址 #define FONTINFOADDR 1024*1024*12 //字库是从 12M 地址以后开始存放 前面 12M 被 fatf 占用了49.4.2 汉字字符显示函数本章实验我们制作了 4 个字库 分别是 GBK12.FON、 GBK16.FON、 GBK24.FON和 GBK32.FON 因此需要编写对应的显示函数 汉字字符显示代码如下//code 字符指针开始 //从字库中查找出字模 //code 字符串的开始地址,GBK码 //mat 数据存放地址 (size/8((size%8)?1:0))*(size) bytes大小 //size:字体大小 void Get_HzMat(unsigned char *code,unsigned char *mat,u8 size) { unsigned char qh,ql; unsigned char i; unsigned long foffset; u8 csize(size/8((size%8)?1:0))*(size);//得到字体一个字符对应点阵集所占的字节数 qh*code; ql*(code); if(qh0x81||ql0x40||ql0xff||qh0xff)//非 常用汉字 { for(i0;icsize;i)*mat0x00;//填充满格 return; //结束访问 } if(ql0x7f)ql-0x40;//注意! else ql-0x41; qh-0x81; foffset((unsigned long)190*qhql)*csize; //得到字库中的字节偏移量 switch(size) { case 12: EN25QXX_Read(mat,foffsetftinfo.f12addr,csize); break; case 16: EN25QXX_Read(mat,foffsetftinfo.f16addr,csize); break; case 24: EN25QXX_Read(mat,foffsetftinfo.f24addr,csize); break; case 32: EN25QXX_Read(mat,foffsetftinfo.f32addr,csize); break; } } //显示一个指定大小的汉字 //x,y :汉字的坐标 //font:汉字GBK码 //size:字体大小 //mode:0,正常显示,1,叠加显示 void LCD_Show_Font(u16 x,u16 y,u8 *font,u8 size,u8 mode) { u8 temp,t,t1; u16 y0y; u8 dzk[128]; u8 csize(size/8((size%8)?1:0))*(size); //得到字体一个字符对应点阵集所占的字节数 if(size!12size!16size!24size!32)return; //不支持的size Get_HzMat(font,dzk,size); //得到相应大小的点阵数据 for(t0;tcsize;t) { tempdzk[t]; //得到点阵数据 for(t10;t18;t1) { if(temp0x80)LCD_DrawFRONT_COLOR(x,y,FRONT_COLOR); else if(mode0)LCD_DrawFRONT_COLOR(x,y,BACK_COLOR); temp1; y; if((y-y0)size) { yy0; x; break; } } } } //在指定位置开始显示一个字符串 //支持自动换行 //(x,y):起始坐标 //width,height:区域 //str :字符串 //size :字体大小 //mode:0,非叠加方式;1,叠加方式 void LCD_ShowFontString(u16 x,u16 y,u16 width,u16 height,u8*str,u8 size,u8 mode) { u16 x0x; u16 y0y; u8 bHz0; //字符或者中文 while(*str!0)//数据未结束 { if(!bHz) { if(*str0x80)bHz1;//中文 else //字符 { if(x(x0width-size/2))//换行 { ysize; xx0; } if(y(y0height-size))break;//越界返回 if(*str13)//换行符号 { ysize; xx0; str; } else LCD_ShowChar(x,y,*str,size,mode);//有效部分写入 str; xsize/2; //字符,为全字的一半 } }else//中文 { bHz0;//有汉字库 if(x(x0width-size))//换行 { ysize; xx0; } if(y(y0height-size))break;//越界返回 LCD_Show_Font(x,y,str,size,mode); //显示这个汉字,空心显示 str2; xsize;//下一个汉字偏移 } } } //在指定宽度的中间显示字符串 //如果字符长度超过了len,则用Show_Str显示 //len:指定要显示的宽度 void LCD_ShowFontStringMid(u16 x,u16 y,u8*str,u8 size,u8 len) { u16 strlenth0; strlenthstrlen((const char*)str); strlenth*size/2; if(strlenthlen) LCD_ShowFontString(x,y,tftlcd_data.width,tftlcd_data.height,str,size,1); else { strlenth(len-strlenth)/2; LCD_ShowFontString(strlenthx,y,tftlcd_data.width,tftlcd_data.height,str,size,1); } }此部分代码总共有 4 个函数 Get_HzMat 函数用于获取 GBK 码对应的汉字字库 通过我们前面介绍的办法 在外部 flash 查找字库 然后返回对应的字库点阵。 LCD_Show_Font 函数用于在指定地址显示一个指定大小的汉字 采用的方法和以前 TFTLCD 显示字符所采用的方法一样 都是画点显示 这里就不细说了。 LCD_ShowFontString 函数用于在指定地址显示一个字符串 包括汉字或字符等 其内部实现过程就是通过第一个字节是否大于 0X80 判断是汉字还是字符从而调用相应的函数实现。 LCD_ShowFontStringMid 函数用于在指定地址 指定的宽度居中显示一字符串 其内部实际上是通过调用 LCD_ShowFontString 函数实现。49.4.3 字库更新函数当 SPI-FLASH 芯片没有写入字库或需要重新更新字库时 可以通过字库更新函数实现其功能 代码如下//更新某一个 //x,y:坐标 //size:字体大小 //fxpath:路径 //fx:更新的内容 0,ungbk;1,gbk12;2,gbk16;3,gbk24;4,gbk32; //返回值:0,成功;其他,失败. u8 updata_fontx(u16 x,u16 y,u8 size,u8 *fxpath,u8 fx) { u32 flashaddr0; FIL * fftemp; u8 *tempbuf; u8 res; u16 bread; u32 offx0; u8 rval0; fftemp(FIL*)mymalloc(SRAMIN,sizeof(FIL)); //分配内存 if(fftempNULL)rval1; tempbufmymalloc(SRAMIN,4096); //分配4096个字节空间 if(tempbufNULL)rval1; resf_open(fftemp,(const TCHAR*)fxpath,FA_READ); if(res)rval2;//打开文件失败 if(rval0) { switch(fx) { case 0: //更新UNIGBK.BIN ftinfo.ugbkaddrFONTINFOADDRsizeof(ftinfo); //信息头之后紧跟UNIGBK转换码表 ftinfo.ugbksizefftemp-obj.objsize; //UNIGBK大小 flashaddrftinfo.ugbkaddr; break; case 1: ftinfo.f12addrftinfo.ugbkaddrftinfo.ugbksize; //UNIGBK之后紧跟GBK12字库 ftinfo.gbk12sizefftemp-obj.objsize; //GBK12字库大小 flashaddrftinfo.f12addr; //GBK12的起始地址 break; case 2: ftinfo.f16addrftinfo.f12addrftinfo.gbk12size; //GBK12之后紧跟GBK16字库 ftinfo.gbk16sizefftemp-obj.objsize; //GBK16字库大小 flashaddrftinfo.f16addr; //GBK16的起始地址 break; case 3: ftinfo.f24addrftinfo.f16addrftinfo.gbk16size; //GBK16之后紧跟GBK24字库 ftinfo.gbk24sizefftemp-obj.objsize; //GBK24字库大小 flashaddrftinfo.f24addr; //GBK24的起始地址 break; case 4: ftinfo.f32addrftinfo.f24addrftinfo.gbk24size; //GBK24之后紧跟GBK32字库 ftinfo.gbk32sizefftemp-obj.objsize; //GBK32字库大小 flashaddrftinfo.f32addr; //GBK32的起始地址 break; } while(resFR_OK)//死循环执行 { resf_read(fftemp,tempbuf,4096,(UINT *)bread); //读取数据 if(res!FR_OK)break; //执行错误 EN25QXX_Write(tempbuf,offxflashaddr,4096); //从0开始写入4096个数据 offxbread; fupd_prog(x,y,size,fftemp-obj.objsize,offx); //进度显示 if(bread!4096)break; //读完了. } f_close(fftemp); } myfree(SRAMIN,fftemp); //释放内存 myfree(SRAMIN,tempbuf); //释放内存 return res; } //更新字体文件,UNIGBK,GBK12,GBK16,GBK24,GBK32一起更新 //x,y:提示信息的显示地址 //size:字体大小 //src:字库来源磁盘.0:,SD卡;1:,FLASH盘,2:NAND盘,3:,U盘. //提示信息字体大小 //返回值:0,更新成功; // 其他,错误代码. u8 update_font(u16 x,u16 y,u8 size,u8* src) { u8 *pname; u32 *buf; u8 res0; u16 i,j; FIL *fftemp; u8 rval0; res0XFF; ftinfo.fontok0XFF; pnamemymalloc(SRAMIN,100); //申请100字节内存 bufmymalloc(SRAMIN,4096); //申请4K字节内存 fftemp(FIL*)mymalloc(SRAMIN,sizeof(FIL)); //分配内存 if(bufNULL||pnameNULL||fftempNULL) { myfree(SRAMIN,fftemp); myfree(SRAMIN,pname); myfree(SRAMIN,buf); return 5; //内存申请失败 } for(i0;i4;i) //先查找文件UNIGBK,GBK12,GBK16,GBK24,GBK32是否正常 { strcpy((char*)pname,(char*)src); //copy src内容到pname strcat((char*)pname,(char*)GBK_PATH[i]); //追加具体文件路径 resf_open(fftemp,(const TCHAR*)pname,FA_READ); //尝试打开 if(res) { rval|17; //标记打开文件失败 break; //出错了,直接退出 } } myfree(SRAMIN,fftemp); //释放内存 if(rval0) //字库文件都存在. { // LCD_ShowString(x,y,240,320,size,Erasing sectors... );//提示正在擦除扇区 // for(i0;iFONTSECSIZE;i) //先擦除字库区域,提高写入速度 // { // fupd_prog(x20*size/2,y,size,FONTSECSIZE,i);//进度显示 // EN25QXX_Read((u8*)buf,((FONTINFOADDR/4096)i)*4096,4096);//读出整个扇区的内容 // for(j0;j1024;j)//校验数据 // { // if(buf[j]!0XFFFFFFFF)break;//需要擦除 // } // if(j!1024)EN25QXX_Erase_Sector((FONTINFOADDR/4096)i); //需要擦除的扇区 // } for(i0;i4;i) //依次更新UNIGBK,GBK12,GBK16,GBK24,GBK32 { LCD_ShowString(x,y,240,320,size,UPDATE_REMIND_TBL[i]); strcpy((char*)pname,(char*)src); //copy src内容到pname strcat((char*)pname,(char*)GBK_PATH[i]); //追加具体文件路径 resupdata_fontx(x20*size/2,y,size,pname,i); //更新字库 if(res) { myfree(SRAMIN,buf); myfree(SRAMIN,pname); return 1i; } } //全部更新好了 ftinfo.fontok0X55; EN25QXX_Write((u8*)ftinfo,FONTINFOADDR,sizeof(ftinfo)); //保存字库信息 } myfree(SRAMIN,pname);//释放内存 myfree(SRAMIN,buf); return rval;//无错误. }该函数使用 FATFS 文件系统从 SD 卡中读取字库文件 然后将字库更新到FLASH 中。 该函数最后一个入口参数 用来选择要更新的字库来源 可选参数在函数开头已注释好 字库文件所存放的路径通过结构体 GBK_PATH 进行管理 如下//字库存放在磁盘中的路径 u8*const GBK_PATH[5] { /FONT/UNIGBK.BIN, //UNIGBK.BIN的存放位置 /FONT/GBK12.FON, //GBK12的存放位置 /FONT/GBK16.FON, //GBK16的存放位置 /FONT/GBK24.FON, //GBK24的存放位置 /FONT/GBK32.FON, //GBK32的存放位置 };该路径必须与你的 SD 卡内字库文件存放路径一致 否则找不到字库文件无法更新。49.4.4 主函数下面我们来看下主函数 代码如下#include system.h #include SysTick.h #include led.h #include usart.h #include tftlcd.h #include key.h #include malloc.h #include sd_sdio.h #include flash.h #include ff.h #include fatfs_app.h #include key.h #include font_show.h int main() { u8 i0; u8 key; SysTick_Init(72); NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //中断优先级分组 分2组 LED_Init(); USART1_Init(115200); TFTLCD_Init(); //LCD初始化 KEY_Init(); EN25QXX_Init(); //初始化EN25Q128 my_mem_init(SRAMIN); //初始化内部内存池 FRONT_COLORRED;//设置字体为红色 LCD_ShowString(10,10,tftlcd_data.width,tftlcd_data.height,16,PRECHIN STM32F1); LCD_ShowString(10,30,tftlcd_data.width,tftlcd_data.height,16,www.prechin.net); LCD_ShowString(10,50,tftlcd_data.width,tftlcd_data.height,16,Font Test); FRONT_COLORBLUE; //设置字体为蓝色 while(SD_Init())//检测不到SD卡 { LCD_ShowString(10,80,tftlcd_data.width,tftlcd_data.height,16,SD Card Error!); delay_ms(500); LED2!LED2; LCD_Fill(10,80,tftlcd_data.width,8016,BACK_COLOR); } FATFS_Init(); //为fatfs相关变量申请内存 f_mount(fs[0],0:,1); //挂载SD卡 f_mount(fs[1],1:,1); //挂载SPI FLASH while(font_init()) //检查字库 { Update: LCD_ShowString(10,80,tftlcd_data.width,tftlcd_data.height,16,Font Updating...); keyupdate_font(10,100,16,0:);//更新字库 while(key)//更新失败 { LCD_ShowString(10,100,tftlcd_data.width,tftlcd_data.height,16,Font Update Failed! ); delay_ms(200); } LCD_ShowString(10,100,tftlcd_data.width,tftlcd_data.height,16,Font Update Success! ); delay_ms(1500); } LCD_ShowFontString(10,130,tftlcd_data.width,tftlcd_data.height,普中科技-PRECHIN,16,0); LCD_ShowFontString(10,150,tftlcd_data.width,tftlcd_data.height,www.prechin.net,16,0); LCD_ShowFontString(10,170,tftlcd_data.width,tftlcd_data.height,字库显示实验,16,0); LCD_ShowFontString(10,190,tftlcd_data.width,tftlcd_data.height,K_UP键进行字库更新...,16,0); while(1) { keyKEY_Scan(0); if(keyKEY_UP_PRESS) goto Update; i; if(i%100) { LED1!LED1; } delay_ms(10); } }主函数实现的功能很简单 首先调用之前编写好的硬件初始化函数 包括SysTick 系统时钟 LED 初始化等。 然后调用 SD_Init 函数 检测 SD 卡是否存在如果 SD 卡初始化成功 调用 f_mount 函数挂载 SD 卡和外部 FLASH 然后检测是否含有字库 如果包含则显示汉字及字符等信息 否则进行字库更新。 最后进入while 循环 不断检测 KEY_UP 键是否按下 如果按下程序则跳转到 Update 标识运行。 调用 update_font 函数更新所有字库 更新完成后重新显示汉字及字符等信息 同时控制 DS0 指示灯间隔 200ms 闪烁 提示系统正常运行。49.5 实验现象将工程程序编译后下载到开发板内 插上 TF 卡 可以看到 TFTLCD 上显示了汉字及字符等信息 同时 DS0 指示灯不断闪烁 表示程序正常运行。 如果未插TF 卡将显示“SD Card Error!” 提示信息 TFTLCD 模块界面如下图所示如果需要更新字库可以按 KEY_UP 键 此时 TFTLCD 上会显示更新进度 由于我们开发板上外部 FLASH 已经写入字库 所以无需更新 而且更新字库也费时间。课后作业1 理解了本章实验后 自己可以制作一个其他的大小的字体进行显示。