本帖最后由 正点原子 于 2013-4-6 23:10 编辑
第四十六章 汉字显示实验 汉字显示在很多单片机系统都需要用到,少则几个字,多则整个汉字库的支持,更有甚者还要支持多国字库,那就更麻烦了。本章,我们将向大家介绍,如何用STM32控制LCD显示汉字。在本章中,我们将使用外部FLASH来存储字库,并可以通过SD卡更新字库。STM32读取存在FLASH里面的字库,然后将汉字显示在LCD上面。本章分为如下几个部分: 46.1 汉字显示原理简介 46.2 硬件设计 46.3 软件设计 46.4 下载验证
46.1 汉字显示原理简介 常用的汉字内码系统有GB2312,GB13000,GBK,BIG5(繁体)等几种,其中GB2312支持的汉字仅有几千个,很多时候不够用,而GBK内码不仅完全兼容GB2312,还支持了繁体字,总汉字数有2万多个,完全能满足我们一般应用的要求。 本实例我们将制作一个GBK字库,制作好的字库放在SD卡里面,然后通过SD卡,将字库文件复制到外部FLASH芯片W25Q64里,这样,W25Q64就相当于一个汉字字库芯片了。 汉字在液晶上的显示原理与前面显示字符的是一样的。汉字在液晶上的显示其实就是一些点的显示与不显示,这就相当于我们的笔一样,有笔经过的地方就画出来,没经过的地方就不画。所以要显示汉字,我们首先要知道汉字的点阵数据,这些数据可以由专门的软件来生成。只要知道了一个汉字点阵的生成方法,那么我们在程序里面就可以把这个点阵数据解析成一个汉字。 知道显示了一个汉字,就可以推及整个汉字库了。汉字在各种文件里面的存储不是以点阵数据的形式存储的(否则那占用的空间就太大了),而是以内码的形式存储的,就是GB2312/GBK/BIG5等这几种的一种,每个汉字对应着一个内码,在知道了内码之后再去字库里面查找这个汉字的点阵数据,然后在液晶上显示出来。这个过程我们是看不到,但是计算机是要去执行的。 单片机要显示汉字也与此类似:汉字内码(GBK/GB2312)à查找点阵库à解析à显示。 所以只要我们有了整个汉字库的点阵,就可以把电脑上的文本信息在单片机上显示出来了。这里我们要解决的最大问题就是制作一个与汉字内码对的上号的汉字点阵库。而且要方便单片机的查找。每个GBK码由2个字节组成,第一个字节为0X81~0XFE,第二个字节分为两部分,一是0X40~0X7E,二是0X80~0XFE。其中与GB2312相同的区域,字完全相同。 我们把第一个字节代表的意义称为区,那么GBK里面总共有126个区(0XFE-0X81+1),每个区内有190个汉字(0XFE-0X80+0X7E-0X40+2),总共就有126*190=23940个汉字。我们的点阵库只要按照这个编码规则从0X8140开始,逐一建立,每个区的点阵大小为每个汉字所用的字节数*190。这样,我们就可以得到在这个字库里面定位汉字的方法: 当GBKL<0X7F时:Hp=((GBKH-0x81)*190+GBKL-0X40)*(size*2); 当GBKL>0X80时:Hp=((GBKH-0x81)*190+GBKL-0X41)*(size*2); 其中GBKH、GBKL分别代表GBK的第一个字节和第二个字节(也就是高位和低位),size代表汉字字体的大小(比如16字体,12字体等),Hp则为对应汉字点阵数据在字库里面的起始地址(假设是从0开始存放)。 这样我们只要得到了汉字的GBK码,就可以显示这个汉字了。从而实现汉字在液晶上的显示。 上一章,我们提到要用cc936.c,以支持长文件名,但是cc936.c文件里面的两个数组太大了(172KB),直接刷在单片机里面,太占用flash了,所以我们必须把这两个数组存放在外部flash。cc936里面包含的两个数组oem2uni和uni2oem存放unicode和gbk的互相转换对照表,这两个数组很大,这里我们利用ALIENTEK 提供的一个C语言数组转BIN(二进制)的软件:C2B转换助手V1.1.exe,将这两个数组转为BIN文件,我们将这两个数组拷贝出来存放为一个新的文本文件,假设为UNIGBK.TXT,然后用C2B转换助手打开这个文本文件,如图46.1.1所示:
图46.1.1 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,直接不用转换. else { if(dir) gbk2uni_offset=ftinfo.ugbksize/2; //GBK 2 UNICODE else gbk2uni_offset=0; //UNICODE 2 GBK /* Unicode to OEMCP */ hi=ftinfo.ugbksize/2;//对半开. hi =hi / 4 - 1; li = 0; for (n = 16; n; n--) { i = li + (hi - li) / 2; SPI_Flash_Read((u8*)&t,ftinfo.ugbkaddr+i*4+gbk2uni_offset,4);//读出4个字节 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文件的首地址。这里同样采用的是二分法查找,关于cc936.c的修改,我们就介绍到这。 字库的生成,我们要用到一款软件,由易木雨软件工作室设计的点阵字库生成器 V3.8。该软件可以在WINDOWS 系统下生成任意点阵大小的ASCII,GB2312(简体中文)、GBK(简体中文)、BIG5(繁体中文)、HANGUL(韩文)、SJIS(日文)、Unicode 以及泰文,越南文、俄文、乌克兰文,拉丁文,8859 系列等共二十几种编码的字库,不但支持生成二进制文件格式的文件,也可以生成BDF 文件,还支持生成图片功能,并支持横向,纵向等多种扫描方式,且扫描方式可以根据用户的需求进行增加。该软件的界面如图46.1.1所示
图46.1.2 点阵字库生成器默认界面
比如我们要生成16*16的GBK字库,则选择:936中文PRC GBK,字宽和高均选择16,字体大小选择12(比较适合),然后模式选择纵向取模方式二(字节高位在前,低位在后),最后点击创建,就可以开始生成我们需要的字库了。具体设置如图46.1.3所示:
图46.1.3 生成GBK16*16字库的设置方法
这里注意,软件里面的字体大小并不是我们生成点阵的大小,12字体是XP的叫法,我们字体的大小以宽和高的大小来决定!可以简单的这么认为:XP的12字体,基本上就等于16*16大小。该软件还可以生成其他很多字库,字体也可选,详细的介绍请看软件自带的《点阵字库生成器说明书》。 本章,我们生成两个字库文件GBK12.DZK和GBK16.DZK,并将后缀名改为.fon(方便识别^_^),备用。关于汉字显示原理,我们就介绍到这。
46.2 硬件设计 本章实验功能简介:开机的时候先检测W25Q64中是否已经存在字库,如果存在,则按次序显示汉字(两种字体都显示)。如果没有,则检测SD卡和文件系统,并查找SYSTEM文件夹下的FONT文件夹,在该文件夹内查找UNIGBK.BIN、GBK12.FON和GBK16.FON(这几个文件的的由来,我们前面已经介绍了)。在检测到这些文件之后,就开始更新字库,更新完毕才开始显示汉字。通过按按键KEY0,可以强制更新字库。同样我们也是用DS0来指示程序正在运行。 所要用到的硬件资源如下: 1) 指示灯DS0 2) KEY0按键 3) 串口 4) TFTLCD模块 5) SD卡 6) SPI FLASH 这几部分分,在之前的实例中都介绍过了,我们在此就不介绍了。 46.3 软件设计 打开上一章的工程,首先在HARDWARE文件夹所在的文件夹下新建一个TEXT的文件夹。在TEXT文件夹下新建fontupd.c、fontupd.h、text.c、text.h这4个文件。并将该文件夹加入头文件包含路径。 打开fontupd.c,在该文件内输入如下代码: #include "fontupd.h" #include "ff.h" #include "flash.h" #include "lcd.h" #include "malloc.h" u32 FONTINFOADDR=(1024*6+500)*1024;//默认是6M+500K后开始 //字库信息结构体. //用来保存字库基本信息,地址,大小等 _font_info ftinfo; //在sd卡中的路径 const u8 *GBK16_SDPATH="0:/SYSTEM/FONT/GBK16.FON"; //GBK16存放位置 const u8 *GBK12_SDPATH="0:/SYSTEM/FONT/GBK12.FON"; //GBK12存放位置 const u8 *UNIGBK_SDPATH="0:/SYSTEM/FONT/UNIGBK.BIN"; //UNIGBK.BIN存放位置 //在25Qxx中的路径 const u8 *GBK16_25QPATH="1:/SYSTEM/FONT/GBK16.FON"; //GBK12的存放位置 const u8 *GBK12_25QPATH="1:/SYSTEM/FONT/GBK12.FON"; //GBK12的存放位置 const u8 *UNIGBK_25QPATH="1:/SYSTEM/FONT/UNIGBK.BIN";//UNIGBK.BIN存放位置 //显示当前字体更新进度 //x,y:坐标 //size:字体大小 //fsize:整个文件大小 //pos:当前文件指针位置 u32 fupd_prog(u16 x,u16 y,u8 size,u32 fsize,u32 pos) { float prog; u8 t=0XFF; prog=(float)pos/fsize; prog*=100; if(t!=prog) { LCD_ShowString(x+3*size/2,y,240,320,size,"%"); t=prog; if(t>100)t=100; LCD_ShowNum(x,y,t,3,size);//显示数值 } return 0; } //更新某一个 //x,y:坐标 //size:字体大小 //fxpath:路径 //fx:更新的内容 0,ungbk;1,gbk12;2,gbk16; //返回值:0,成功;其他,失败. u8 updata_fontx(u16 x,u16 y,u8 size,u8 *fxpath,u8 fx) { u32 flashaddr=0; FIL * fftemp; u8 *tempbuf; u8 res; u16 bread; u32 offx=0; u8 rval=0; fftemp=(FIL*)mymalloc(SRAMIN,sizeof(FIL)); //分配内存 if(fftemp==NULL)rval=1; tempbuf=mymalloc(SRAMIN,4096); //分配4096个字节空间 if(tempbuf==NULL)rval=1; res=f_open(fftemp,(const TCHAR*)fxpath,FA_READ); if(res)rval=2;//打开文件失败 if(rval==0) { if(fx==0) //更新UNIGBK.BIN { ftinfo.ugbkaddr=FONTINFOADDR+sizeof(ftinfo); //信息头之后,紧跟UNIGBK转换码表 ftinfo.ugbksize=fftemp->fsize; //UNIGBK大小 flashaddr=ftinfo.ugbkaddr; }else if(fx==1) //GBK12 { ftinfo.f12addr=ftinfo.ugbkaddr+ftinfo.ugbksize; //UNIGBK后,跟GBK12字库 ftinfo.gbk12size=fftemp->fsize; //GBK12字库大小 flashaddr=ftinfo.f12addr; //GBK12的起始地址 }else //GBK16 { ftinfo.f16addr=ftinfo.f12addr+ftinfo.gbk12size; //GBK12后,跟GBK16字库 ftinfo.gkb16size=fftemp->fsize; //GBK16字库大小 flashaddr=ftinfo.f16addr; //GBK16的起始地址 } while(res==FR_OK)//死循环执行 { res=f_read(fftemp,tempbuf,4096,(UINT *)&bread);//读取数据 if(res!=FR_OK)break; //执行错误 SPI_Flash_Write(tempbuf,offx+flashaddr,4096); //从0开始写入4096个数据 offx+=bread; fupd_prog(x,y,size,fftemp->fsize,offx); //进度显示 if(bread!=4096)break; //读完了. } f_close(fftemp); } myfree(SRAMIN,fftemp); //释放内存 myfree(SRAMIN,tempbuf); //释放内存 return res; }
|