在C51中,HZK16汉字库的使用(mydows's Blog转载)
定义如下:unsigned char str[]="我"在运行时str被初始化为2个字节长度,内容为“我”的GBK码,为:0xCE(区码),0xD2(位码)。使用如下换算公式得到“我”在HZK16文件中的地址,从该位置开始的顺序32字节为“我”的字模。 ADD=【(区码-0xa1)×0x5e + (位码-0xa1)】×0x20按照上面的计算方法,“我”的字模地址:0x216E0 。他的C语言字模为:0x04,0x80,0x0E,0xA0,0x78,0x90,0x08,0x90,0x08,0x84,0xFF,0xFE,0x08,0x80,0x08,0x90,0x0A,0x90,0x0C,0x60,0x18,0x40,0x68,0xA0,0x09,0x20,0x0A,0x14,0x28,0x14,0x10,0x0C
HZK16字库是符合GB2312标准的16×16点阵字库,HZK16的GB2312-80支持的汉字有6763个,符号682个。其中一级汉字有3755个,按声序排列,二级汉字有3008个,按偏旁部首排列。我们在一些应用场合根本用不到这么多汉字字模,所以在应用时就可以只提取部分字体作为己用。
HZK16字库里的16×16汉字一共需要256个点来显示,也就是说需要32个字节才能达到显示一个普通汉字的目的。
我们知道一个GB2312汉字是由两个字节编码的,范围为A1A1~FEFE。A1-A9为符号区,B0到F7为汉字区。每一个区有94个字符(注意:这只是编码的许可范围,不一定都有字型对应,比如符号区就有很多编码空白区域)。下面以汉字“我”为例,介绍如何在HZK16文件中找到它对应的32个字节的字模数据。
前面说到一个汉字占两个字节,这两个中前一个字节为该汉字的区号,后一个字节为该字的位号。其中,每个区记录94个汉字,位号为该字在该区中的位置。所以要找到“我”在hzk16库中的位置就必须得到它的区码和位码。(为了区别使用了区码和区号,其实是一个东西,别被我误导了)
区码:区号(汉字的第一个字节)-0xa0 (因为汉字编码是从0xa0区开始的,所以文件最前面就是从0xa0区开始,要算出相对区码)
位码:位号(汉字的第二个字节)-0xa0
这样我们就可以得到汉字在HZK16中的绝对偏移位置:
offset=(94*(区码-1)+(位码-1))*32
注解:1、区码减1是因为数组是以0为开始而区号位号是以1为开始的
2、(94*(区号-1)+位号-1)是一个汉字字模占用的字节数
3、最后乘以32是因为汉字库文应从该位置起的32字节信息记录该字的字模信息(前面提到一个汉字要有32个字节显示)
有了偏移地址就可以从HZK16中读取汉字编码了,剩下的就是文件操作了,就不说了,要看代码(汉字)就是下面的:“ ”,是一个最简单的c语言程序。
hzk16汉字库的简单读写程序
hzk汉字点阵
int i,j,k; unsigned char incode[3]="我"; // 要读出的汉字 unsigned char qh,wh; unsigned long offset; // 占两个字节, 取其区位号 qh = incode[0] - 0xa0;/ /获得区码 wh = incode[1] - 0xa0; / /获得位码 offset = (94*(qh-1)+(wh-1))*32; / *得到偏移位置* / FILE *HZK; char mat[32]; if((HZK=fopen("hzk16", "rb")) == NULL) { printf("Can't Open hzk16\n"); exit(0); } fseek(HZK, offset, SEEK_SET); fread(mat, 32, 1, HZK);//显示
for(j=0;j<16;j++)
for(i=0;i<2;i++) for(k=0;k<8;k++) if(mat[j][i]&(0x80>>k)) /*测试为1的位则显示*/ {printf("%s",'#');
}else{ printf("%s",'-');
}
fclose(HZK); fclose(fp);图形LCD模块ACM19264ASB的汉字显示
1引言
在基于单片机的智能系统中,汉字显示模块是很重要的一个组成部分,它应用广泛、操作容易、调试简便。
然而,在单片机上显示汉字也存在几个问题。首先,单片机资源有限,我们不能为了显示汉字占用太多的资源;其次,汉字存储读取比较繁琐,使用不方便;第三,汉字是通过点阵显示出来的,往往与LCD写入方式不一样,这就得进行转换和调整。
值得注意的是,基于单片机的汉字显示不能在字符LCD上实现。使用图形LCD有很多优点,不仅能显示汉字,而且可以实现汉字动态移动和上下滚屏,实现汉字与图形的混合显示,同时功耗低。
2基于单片机的汉字显示原理
2.1汉字字模
汉字一般是以点阵式存储的,如16×16,24×24点阵(即汉字的字模),每个汉字由32字节(16点阵)或72字节(24点阵)描述。根据汉字的不同字体,也可分为宋体字模、楷体字模、黑体字模等等。
汉字的字模其实是汉字字形的图形化。对于16点阵字模,就是把汉字写在一个16×16的网格内,汉字的笔画能过某网格时该网格就对应1,否则该网格对应0,这样每一网格均对应1或0,把对应1的网格连起来看,就是这个汉字。汉字就是这样通过字节表示点阵存储在字库中的。
为了方便查找所需汉字的点阵,每个汉字都与一个双字节的内码一一对应。通过汉字的内码可以计算出它的点阵起始字节。现以16点阵为例说明。
先由内码计算出它在汉字库中的区位码,计算公式为:
区码=内码第一字节-160
位码=内码第二字节-160
再由区位码可以得到它在汉字库中字模第一个字节的位置:
(区码×94+位码)×32 于是,可以向后连续读出由32个字节组成的该字的点阵数据。
2.2汉字显示
汉字占用资源太多(如16点阵,每个汉字就需32字节),因而通常把汉字库放在EEPROM里,需要显示某个汉字时,先算出它的区位码,再求出点阵起始位置,从EEPROM中顺序调出该字的点阵数据,存在缓冲区里,最后依次送往LCD显示,描出该字。需要说明的是汉字存储方式与LCD显示方式有一定差别。
本文使用另一种显示方法,即事先将程序用到的汉字、符号和数码(为了节省显示空间,可以将数码压成8×16点阵),编成一个文本文件,用一段小程序做出相应小的汉字库,这个小字库的汉字点阵数据取自于一般汉字库。再经过转换和调整,得到新的汉字库,最后把新字库固化在EEPROM中。单片机只需按序号读出点阵字节,送往LCD即可显示所需汉字。减轻了单片机的负担,去除了繁琐的查找内码、求起始位置、转换、调整等工作,提高了系统可靠性。
表116点阵汉字字库存储方式 |
3自定义小字库的制作
典型的汉字库可选用UCDOS下的字库,如16点阵字库HZK16。需要256K空间,用了较大的EEPROM,又不方便读取,而实际应用中需要的汉字又非常少,因而我们可以自己制作小的汉字库,在这个小字库里只包含系统需要的汉字。这样,一方面节省读取时间,另一方面大大地节省了资源。
限于篇幅,这里仅仅给出流程图(假定事先将所需汉字写到了一个文本文件),如图1所示。
将整个汉字字库存放在EPROM或E2PROM内,程序根据要显示汉字的机内码来调用汉字字模。
某些高端单片机,如Motorola的M68300系列32位单片机,寻址范围可达8M,液晶显示常用的16×16汉字库二进制数据文件为两百多k,将汉字字库存入大容量的E2PROM,通过地址线可寻址到汉字库中的每一个汉字。
在计算机中对汉字的识别是通过机内码来实现的,汉字标准机内码为两字节代码。汉字在汉字库中是按照区位来排列的,每一区中有94个汉字,每个汉字都对应唯一的区号和在本区的位号,汉字输入法中就有区位码方法,实际上,汉字机内码和区位码有标准的对应关系,某个汉字在字库中的区号加上0xa0等于其机内码的高字节,位号加上0xa0等于其机内码的低字节,因此很容易通过程序计算出要显示的汉字在汉字库中的区位号,即得到了其在汉字库中的偏移地址。
由于E2PROM中存储了整个汉字库,只须在硬件上设定存放汉字库的存储器片选地址,直接将汉字作为字符数组付给汉字显示函数,通过机内码计算出区号和位号,即可方便地对汉字字模进行调用了。与前两种方法相比,无须事先提取字模和设定其地址用于程序调用,因此在进行程序升级,涉及到汉字显示时,不用更改汉字字模数据。
汉字的字模与显示--汉字的代码体系
目 录 1.1 汉字机内码 1.2 汉字地址码 1.3 汉字交换码 1.4 汉字字型码 ---------------------------------------------------------------------- 在汉字信息处理系统中存在着多种汉字编码。一般来说,在系统的不同部位,可根据其环境给汉字定以相应的编码,因为在汉字信息处理系统中存在着数种汉字编码。这些编码构成了一个汉字的代码体系。 1.1、汉字机内码 汉字机内码(亦称汉字内码)是系统内部处理和存储汉字而使用的代码。众所周知,西文字符的机内码多采用一个字节来表示的ASCII码,有的系统则采用EBCDIC码。一般只使用7位来表示128个字符,而把高位用作奇偶校验(或者不用)。我国的国标GB2312-80规定,一个汉字用两个字节表示,目前规定每个字节也只用七位,其高位未作定义。 为了保证系统的中西文兼容,意味着系统的机内码中必须保持ASCII(IBM-PC采用该码作为西文字符的机内码)的使用,同时又要允许汉字机内码的使用,并且使两者之间没有冲突。如果用GB2312-80中的国标码作为机内码,则在系统中同时存在ASCII码和国标码时,将会产生二义性。例如,机内有两个字节的内容分别为30H和21H,它们既可以表示汉字“啊”的国标码,又可以表示字符“0”和“!”的ASCII码。所以,原原本本地采用国标码作为汉字机内码是不行的,必须要加以适当的变换。 一般情况下是将国标码的每个字节的高位置成1,作为汉字机内码,这种编码称作为变形国标码。这样作既解决了西文机内码与汉字机内码的二义性,又保证汉字机内码与国标码之间有极简单的对应关系。其组织如下: 内码 ch1 ch2 不 不 1字节 1字节 根据CGB2312-80中的汉字、图形符号,根据其位置分为94个“区”,每个区包含94个汉字,每个汉字字符又称作“位”。其中“区”的序号,由01到94,“位”的序号,也是从01到94。若以横向表示“位”号,纵向表示“区”号,则“区”和“位”构成一个二维坐标。给定一个“区”值和“位”值就可以确定一个唯一的汉字或图形符号。所以4位数字就可以唯一确定一个汉字或符号,下面给出汉字的区内和内码对应关系表。 区位码(10进制) 内码(16进制) 区号 位号 高字节 低字节 1—9 1—94 A1—A9 A1—FE 10—12 1—94 AA—AC A1—FE 13—15 1—94 AD—AF A1—FE 16—87 1—94 B0—F7 A1—FE 1.2、汉字地址码 汉字地址码是指汉字字模库(这里主要指整字形的点阵字模库)中存储各汉字字形信息的逻辑地址码。中文系统中的汉字字模库有两种,它们分别存放在RAM(或EPROM)中和磁盘上。故中文系统中的汉字地址码有两种,一种采用内存地址数来表示(对应RAM和EPROM中的字库),另外一种采用盘地址来表示(对应于磁盘中的字库)。由于中文系统的汉字字模库中汉字字形信息排列的规则,使得中文系统的汉字字模库中的汉字字形信息排列序列的规则,使得中文系统的汉字地址码与汉字机内码之间存在着一个简单的函数关系。 1.3、汉字交换码 汉字交换码是一种用于汉字信息处理系统之间,或者与通讯系统之间进行信息交换的汉字代码。汉字交换码位于一台机器的出口和另一台机器(包括输出设备与记录设备)的入口之间。为了要达到系统设备之间或记录介质之间信息交换的目的,汉字交换码必须采取统一的形式。目前国内计算机系统所采用的标准信息处理交换码,是根据国家标准制定的,即GB1988 — 《信息处理交换使用的七位编码字符集》;还制定了相应的代码扩充标准,即GB2311 — 《信息处理交换使用的七位编码字符集的扩充方法》。因为汉字交换码应与GB1988兼容,并根据GB2311所规定的方法进行编制。由于汉字数量远远大于七位编码所能表示的六千多个常用汉字制定了交换码的国家标准,即GB2312 — 《信息交换用汉字编码字符基本集》,其中每个汉字用对应于GB1988的两个七位码来表示。 1.4、汉字字型码 由于目前汉字信息处理系统中产生汉字字形的方式大多是数字式的,即以点阵的方式形成汉字,过汉字字形码是指确定一个汉字字形码也就不同。汉字库的使用
UCDOS软件中的文件HZK16为16×16的国标汉字点阵文件,以二进制格式存储。在文件HZK16中,按汉字区位码从小到大依次存有国标区位码表中的所有汉字,每个汉字占用32个字节,每个区为94个汉字。
在PC机的文本文件中,汉字是以机内码的形式存储的,每个汉字占用两个字节:第一个字节为区码,为了与ASCII码区别,范围从十六进制的0A1H开始(小于80H的为ASCII码字符),对应区位码中区码的第一区;第二个字节为位码,范围也是从0A1H开始,对应某区中的第一个位码。这样,将汉字机内码减去0A0AH就得该汉字的区位码。
例如汉字“房”的机内码为十六进制的“B7BF”,其中“B7”表示区码,“BF”表示位码。所以“房”的区位码为0B7BFH-0A0A0H=171FH。将区码和位码分别转换为十进制得汉字“房”的区位码为“2331”,即“房”的点阵位于第23区的第31个字的位置,相当于在文件HZK16中的位置为第32×[(23-1) ×94+(31-1)]=67136B以后的32个字节为“房”的显示点阵。
下面给出一个根据汉字机内码(两字节)在汉字库中查找汉字的字模的程序。字库文件分成四部分,分别存在四片27512(并行EPROM)中。读出的字模存入hz_buffer[32]数组中。
int8 hz_buffer[32];//定义32字节数组,用于存储点阵字模,该字模为横排字模。
#define int8 unsigned char
#define int16 unsigned int
#define int32 unsigned long
/*
函数void read_hz(int16 hz)的参数hz为两字节的机内码,调用方法:read_hz(‘汉’);
对ASCII字符,则read_hz('A'+0xa380);读取
*/
void read_hz(int16 hz){
void *void_p;//定义一个空类型指针
int8 *int8_p;//定义一个unsigned char 指针
int8 i; //定义一个循环变量
int16 area_l,area_h;//定义两个整型变量,用于存储区码和位码
int32 pos; //定义一个long型变量,用于存储计算所得字模在字库的位置,
int8 chip; //字模所在的芯片,可用74HC138之类的芯片译码。
int16 addr; //在某一芯片64K字节空间内的地址
void_p=&hz; //空指针指向机内码的低字节
int8_p=void_p; //char类型指针指向空指针,即机内码的低字节
area_l=*int8_p-0xa0; //机内码低字节减去0xa0得到区码
area_h=*(int8_p+1)-0xa0; //机内码高字节减去0xa0得到位码
pos=32*((int32)((area_h-1)*94)+area_l-1); //计算在一个完整的字库中的位置(256K)
//pos=116672; //这是“请”字在HZK16文件中的位置,单位为字节。用于测试
if(pos<64*1024) //在第一片27512芯片
{
chip=0;addr=(int16)pos;
}
else if((pos>=64*1024)&&(pos<128*1024)) //在第二片27512芯片
{
chip=1;
addr=(int16)pos;
}
else if((pos>=128*1024)&&(pos<192*1024)) //在第三片27512芯片
{
chip=2;
addr=(int16)pos;
}
else if((pos>=192*1024)&&(pos<256*1024)) //在第四片27512芯片
{
chip=3;
addr=(int16)pos;
}
select_chip(chip); //选择第chip片27512芯片
for(i=0;i<32;i++)
{
hz_buffer[i]=read_data(addr+i); //读取一字节的数据
}
}
在实际中,由于现很少采用EPROM芯片,可以用并口、SPI,I2C接口的大容量Flash、EEPROM芯片。但I2C接口速度较慢,显示汉字的速度将会很慢,可以在一些比较少字场合使用;而SPI接口的存储芯片速度较快,接口简单,对于一般的应用场合还是可以满足的;对于大量使用的场合,可以使用并行接口,它具储存空间大,读取速度快的特点,如使用AT29系列的Flash存储器,单片容量可以达到256K以上,就不需要分开存储,但其需要较多的IO口,接口复杂。所以应根据实际来选择存储器。