实战:汉字显示实验
简介:
常用的汉字内码系统有GB2312,GB13000,GBK,BIG5(繁体)等几种,其中GB2312支持的汉字仅有几千个,很多时候不够用,而GBK内码不仅完全兼容GB2312,还支持了繁体字,总汉字数有2万多个汉字在液晶上的显示原理与前面显示字符的是一样的。汉字在液晶上的显示其实就是一些点的显示与不显示,这就相当于我们的笔一样,有笔经过的地方就画出来,没经过的地方就不画。所以要显示汉字,我们首先要知道汉字的点阵数据,这些数据可以由专门的软件来生成。知道显示了一个汉字,就可以推及整个汉字库了。汉字在各种文件里面的存储不是以点阵数据的形式存储的(否则那占用的空间就太大了),而是以内码的形式存储的,就是GB2312/GBK/BIG5 等这几种的一种,每个汉字对应着一个内码,在知道了内码之后再去字库里面查找这个汉字的点阵数据,然后在液晶上显示出来。这个过程我们是看不到,但是计算机是要去执行的。单片机要显示汉字也与此类似:汉字内码(GBK/GB2312)->查找点阵库->解析->显示。
Unicode(统一码、万国码、单一码):是计算机科学领域里的一项业界标准,包括字符集、编码方案等。Unicode 是为了解决传统的字符编码方案的局限而产生的,它为每种语言中的每个字符设定了统一并且唯一的二进制编码,以满足跨语言、跨平台进行文本转换、处理的要求。长文件名是以UNICODE状态存储的,要转换为GBK码,才方便显示。原子哥代码里面有个mycc936.c这个程序,就是用于UNCIODE和GBK转换使用的。如果不用FAT可以不加这个.c文件
GBK编码:是在GB2312-80标准基础上的内码扩展规范,使用了双字节编码方案,其编码范围从8140至FEFE(剔除xx7F),共23940个码位,共收录了21003个汉字,完全兼容GB2312-80标准,支持国际标准ISO/IEC10646-1和国家标准GB13000-1中的全部中日韩汉字,并包含了BIG5编码中的所有汉字。
bin文件:二进制文件,其用途依系统或应用而定 。一种文件格式binary的缩写。一个后缀名为".bin"的文件,只是表明它是binary格式。比如虚拟光驱文件常用".bin"作为后缀,但并不意味着所有bin文件都是虚拟光驱文件。
每个GBK码由2个字节组成,第一个字节为0X81~0XFE,第二个字节分为两部分,一是0X40~0X7E,二是0X80~0XFE。把第一个字节代表的意义称为区,那么 GBK 里面总共有126个区(0XFE-0X81+1),每个区内有
190 个汉字(
0XFE-0X80+0X7E-0X40+2),总共就有 126*190=23940 个汉字。点阵库只要按照这个编码规则从 0X8140 开始,逐一建立,每个区的点阵大小为每个汉字所用的字节数*190。这样,就可以得到在这个字库里面定位汉字的方法:
当
GBKL<0X7F 时:
Hp=((GBKH-0x81)*190+GBKL-0X40)*csize;
当
GBKL>0X80 时:
Hp=((GBKH-0x81)*190+GBKL-0X41)*csize;
其中
GBKH、GBKL分别代表GBK的第一个字节和第二个字节(也就是高位和低位), Hp为对应汉字点阵数据在字库里面的起始地址(假设是从
0 开始存放),csize代表一个汉字点阵所占的字节数。
下面附上我对原子哥代码的解析
一开始显示检测有无初始化过
[mw_shl_code=c,true]//初始化字体
//返回值:0,字库完好.
// 其他,字库丢失
u8 font_init(void)
{
SPI_Flash_Init();//SPI的IO口初始化
//FONTINFOADDR:MiniSTM32是从4.8M+100K地址开始的,最开始的33个字节给ftinfo用
//#define FONTINFOADDR (4916+100)*1024
SPI_Flash_Read((u8*)&ftinfo,FONTINFOADDR,sizeof(ftinfo));//读出ftinfo结构体数据
if(ftinfo.fontok!=0XAA)return 1;//字库错误.没有进行初始化过
return 0; //已经初始化过一次了
}
[/mw_shl_code]
没有初始化过执行初始化代码,由于原子哥代码的字库是从SD卡里面读取出来的,所以需要先初始化SD卡
SD_Initialize();
之后开始更新字体,有GBK24/GBK16/GBK12 三种字体
[mw_shl_code=c,true]//更新字体文件,UNIGBK,GBK12,GBK16,GBK24一起更新
//x,y:提示信息的显示地址
//size:字体大小
//提示信息字体大小
//返回值:0,更新成功;
// 其他,错误代码.
u8 update_font(u16 x,u16 y,u8 size)
{
u8 *gbk24_path=(u8*)GBK24_PATH; //const u8 *GBK24_PATH="0:/SYSTEM/FONT/GBK24.FON"; GBK24的存放位置
u8 *gbk16_path=(u8*)GBK16_PATH; //const u8 *GBK16_PATH="0:/SYSTEM/FONT/GBK16.FON"; GBK16的存放位置
u8 *gbk12_path=(u8*)GBK12_PATH; //const u8 *GBK12_PATH="0:/SYSTEM/FONT/GBK12.FON"; GBK12的存放位置
u8 *unigbk_path=(u8*)UNIGBK_PATH;//const u8 *UNIGBK_PATH="0:/SYSTEM/FONT/UNIGBK.BIN"; UNIGBK.BIN的存放位置
u8 res;
res=0XFF;
ftinfo.fontok=0XFF; //字库存在标志,0XAA,字库正常;其他,字库不存在
SPI_Flash_Write((u8*)&ftinfo,FONTINFOADDR,sizeof(ftinfo));//清除之前字库成功的标志.防止更新到一半重启,导致的字库部分数据丢失.
SPI_Flash_Read((u8*)&ftinfo,FONTINFOADDR,sizeof(ftinfo)); //重新读出ftinfo结构体数据
LCD_ShowString(x,y,240,320,size,"Updating UNIGBK.BIN");
res=updata_fontx(x+20*size/2,y,size,unigbk_path,0); //更新UNIGBK.BIN
if(res)return 1;//res不为0即更新不成功
LCD_ShowString(x,y,240,320,size,"Updating GBK12.BIN ");
res=updata_fontx(x+20*size/2,y,size,gbk12_path,1); //更新GBK12.FON
if(res)return 2;
LCD_ShowString(x,y,240,320,size,"Updating GBK16.BIN ");
res=updata_fontx(x+20*size/2,y,size,gbk16_path,2); //更新GBK16.FON
if(res)return 3;
LCD_ShowString(x,y,240,320,size,"Updating GBK24.BIN ");
res=updata_fontx(x+20*size/2,y,size,gbk24_path,3); //更新GBK24.FON
if(res)return 4;
//全部更新好了
ftinfo.fontok=0XAA; //写入0XAA告诉单片机字库更新完了并且后面再重启不需要更新自哭了
SPI_Flash_Write((u8*)&ftinfo,FONTINFOADDR,sizeof(ftinfo)); //保存字库信息
return 0;//无错误.
}
[/mw_shl_code]
程序中最主要的是updata_fontx这个函数,里面首先开辟一些内存用于存储字库文件的信息(例如文件地址,文件大小),这样可以通过地址告诉单片机从SD卡的哪个位置开始读取数据到FLASH,而文件大小可以用于给里面这个函数“fupd_prog(x,y,size,fftemp->fsize,offx);”用于显示当前字体的更新进度,程序中用动态内存分配开辟4096个字节的内存用于通过SPI从SD里面读出4096个字节到FLASH里面,每次读4096个字节直至读取整个字库读取完成,读取完以后释放内存,代码如下
[mw_shl_code=c,true]//更新某一个
//x,y:坐标
//size:字体大小
//fxpath:路径
//fx:更新的内容 0,ungbk;1,gbk12;2,gbk16;3,gbk24;
//返回值: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(sizeof(FIL)); //分配内存(大小为输入进来的文件路径信息容量)
if(fftemp==NULL)rval=1;
tempbuf=mymalloc(4096); //分配4096个字节空间,.
if(tempbuf==NULL)rval=1;
res=f_open(fftemp,(const TCHAR*)fxpath,FA_READ);//通过文件路径读出文件信息到fftemp
if(res)rval=2;//打开文件失败
if(rval==0)
{
switch(fx)
{
case 0: //更新UNIGBK.BIN
ftinfo.ugbkaddr=FONTINFOADDR+sizeof(ftinfo); //信息头之后,紧跟UNIGBK转换码表
ftinfo.ugbksize=fftemp->fsize; //UNIGBK大小
flashaddr=ftinfo.ugbkaddr;
break;
case 1:
ftinfo.f12addr=ftinfo.ugbkaddr+ftinfo.ugbksize; //UNIGBK之后,紧跟GBK12字库
ftinfo.gbk12size=fftemp->fsize; //GBK12字库大小
flashaddr=ftinfo.f12addr; //GBK12的起始地址
break;
case 2:
ftinfo.f16addr=ftinfo.f12addr+ftinfo.gbk12size; //GBK12之后,紧跟GBK16字库
ftinfo.gbk16size=fftemp->fsize; //GBK16字库大小
flashaddr=ftinfo.f16addr; //GBK16的起始地址
break;
case 3:
ftinfo.f24addr=ftinfo.f16addr+ftinfo.gbk16size; //GBK16之后,紧跟GBK24字库
ftinfo.gkb24size=fftemp->fsize; //GBK24字库大小
flashaddr=ftinfo.f24addr; //GBK24的起始地址
break;
}
while(res==FR_OK)//死循环执行
{
res=f_read(fftemp,tempbuf,4096,(UINT *)&bread); //读取数据(从fftemp读取4096个数据到tempbuf)
if(res!=FR_OK)break; //执行错误
SPI_Flash_Write(tempbuf,offx+flashaddr,4096); //从0开始写入4096个数据
offx+=bread;//下一个4096
fupd_prog(x,y,size,fftemp->fsize,offx); //进度显示
if(bread!=4096)break; //读完了.
}
f_close(fftemp);
}
myfree(fftemp); //释放内存
myfree(tempbuf); //释放内存
return res;
}
[/mw_shl_code]
初始化完以后外面FLASH里面的指定位置就有了字库,便可以通过类似show_char的方式去读取汉字的点阵集,下面是实现显示汉字的函数解析
[mw_shl_code=c,true]//显示一个指定大小的汉字
//x,y :汉字的坐标
//font:汉字GBK码
//size:字体大小
//mode:0,正常显示,1,叠加显示
void Show_Font(u16 x,u16 y,u8 *font,u8 size,u8 mode)
{
u8 temp,t,t1;
u16 y0=y;
u8 dzk[72]; //存放点阵数据
u8 csize=(size/8+((size%8)?1:0))*(size);//得到字体一个字符对应点阵集所占的字节数
if(size!=12&&size!=16&&size!=24)return; //不支持的size
Get_HzMat(font,dzk,size); //得到相应大小的点阵数据
for(t=0;t<csize;t++) //字节数遍历
{
temp=dzk[t]; //得到点阵数据
for(t1=0;t1<8;t1++) //一个一个字节画点
{
if(temp&0x80)LCD_Fast_DrawPoint(x,y,POINT_COLOR); //画点
else if(mode==0)LCD_Fast_DrawPoint(x,y,BACK_COLOR);//跟背景同色,相当于不画点
temp<<=1;//下一个bit
y++; //一个字节8行
if((y-y0)==size)//从上到下,从左到右的扫描方式,行数加到字的大小了
{
y=y0; //回到字第一行,开始下一列扫
x++; //列数+1
break;//提前结束循环
}
}
}
}
[/mw_shl_code]
本章总结:这章由于自己有SD卡所以可以预先把字库放入SD卡后通过单片机使用FATFS文件管理将字库从SD卡读到FLASH里面,每个字库都有相应的地址,直接根据GBK内码对应上地址增量就可以读出想要的字了。但是如果没有SD卡的情况下,可能就需要通过其他途径将字库写入FLASH,当然也可以通过FAT的方式去管理FLASH写入字库。但是那样貌似有点大材小用而且增加了程序的难度,以下有几种方式可以实现通过串口将字库写入FLASH的:
1.通过串口的文件发送一个一个字节写入单片机里面再存入FALSH,每发送一个字节往FLASH写入一个字节,这种方式虽然简单,但是耗时比较长,每次收到数据就中断一次,并且往FLASH写入也需要一定时间
2.建立两个buff缓冲区,一个区假设4096个字节,每次串口发送完4096个字节存入缓冲区,然后将这个缓冲区一次写入FLASH,在写入的期间串口收到的数据放入第二个缓冲区,这样交替存放便可以忽略写入FLASH的时间了,但是还是存在收到一个字节中断一次的问题,如果不在程序运行期间去更新字库的话这样完成没问题,如果需要在程序运行期间去更新字库的话这样的中断对程序可能有影响,因此有了下面第三种方法,也是受论坛网友启发
3.建立两个buff缓冲区,采用DMA的双缓冲循环方式,但是貌似F1没有这种模式,F4是有的,但是个人感觉应该也可以用F1双缓存方式来写入数据,自己没有实际运行过,F1由于不支持双缓冲循环方式,而且有个限制,如下:
也就是不能在写完4096个字节后马上更换缓冲区,需要停止通道后更换缓冲区再开启通道,这个执行时间应该能在下一个bit来之前执行完,由于没有亲测过所以没法验证,等有空再测试下,但是F4是不存在这样的问题的,它可以支持双缓冲循环方式
4.再就是原子哥的方式,提前放在SD卡里面直接从SD卡更新字库到FLASH里面
5.可以使用FATFS对FLASH进行管理,直接将需要的字库放入FLASH里面
下面是几个有关往FLASH写入字库的网页
http://www.openedv.com/posts/list/27005.htm
http://www.openedv.com/posts/list/64048.htm
http://www.openedv.com/posts/list/40143.htm
http://www.amobbs.com/thread-5463766-1-1.html
http://www.360doc.com/content/12/0924/13/6828497_237893071.shtml
|