前一篇把ASCII码在LCD屏上的显示的方法和驱动进行了详细的说明。ASCII码的显示相对会简单一些。关键需要了解ASCII码的编码规则和点阵数据的数据结构。剩下的就是要熟悉显示设备的指令。
在显示设备上显示了英文字符后,自然会想到显示汉字。汉字的显示基本原理也对点阵的显示,因此原理和ASCII 码一样。但因为汉字比ASCII128个字符多得多,所以汉字的处理上,在编码方案,检索,点阵的结构等方面会有很多不同。这篇就着文字显示的方向展开来分析汉字国标字库的相关问题。
《ILI9341的使用之【一】TFT-LCD原理(转载)》
《ILI9341的使用之【二】ILI9341介绍》
《ILI9341的使用之【三】ILI9341系统通信接口模式操作详解》
《ILI9341的使用之【四】RGB接口操作详解》
《ILI9341的使用之【五】命令一》
《ILI9341的使用之【六】命令二》
《ILI9341的使用之【七】实体面板案例-arduino 2.4inch TFT Touch Shield》
《ILI9341的使用之【八】ASCII字符显示及驱动分析》
《ILI9341的使用之【九】BG2312字库》
文章目录
1、ASCII码回顾
标准的ACSII码是用一个字节中的7个二进制位,最高位0或者作为校验位,可以表示2^7即0000 0000~0111 1111 (0x00-0x7F)共128个字符。
扩展的ASCII码,一些欧洲国针对本国的文字,利用字节中闲置的最高位编入新的符号(0x80-0xff),把8个二进制位全部用来编码,从而可以表达256个字符。
实际上在汉字显示系统中,由于为了区分汉字与标准ASCII码的识别,也是使用了这个最高位二进制位进行编码。
2、BG2312编码规则
1980年,中国国家标准总局发布了《信息交换用汉字编码字符集》一套中国的字符编码标准,也就是GB 2312—1980。为了满足中国常用的大几千个汉字的编码,采用了十六位表示一个字符的编码方案,也就是说一个中文汉字占两个字节。
在国标GB2312-80中规定,所有的国标汉字及符号的逻辑结构为:分配有94个区块,每个区块里有94个国标字符。总共可以容纳9494=8836个字符。
在这个9494的逻辑阵列中,阵列的每个区块称为一个“区”,共有01区到94区;区块中的每一个位称为“位”,共有01位到94位。这样,阵列中的每一个汉字和符号所在的区号和位号的组合就形成它们的“区位码”。区位码的前两位是它的区号,后两位是它的位号。
2.1区位码:
举例来说,“啊”字是GB2312之中的第一个汉字,它的区位码就是1601(十进制)。这个区位码是十进制的,在实际计算机处理过程中,需要把这个区位码表达成字节编码(机内码),通常采用EUC(Extended Unix Code)储存方法(是一个使用8位编码来表示字符的方法,把每个区位加上0xA0来表示,以便兼容于ASCII)。
2.2机内码
具体方式是,每个汉字及符号以两个字节来表示 。 第一个字节称为“高位字节”,第二个字节称为“低位字节”。 “高位字节”使用了0xA1-0xF7(把01-87区的区号加上0xA0),“低位字节”使用了0xA1-0xFE(把01-94加上0xA0)。例如 “啊”字在大多数程序中,会以0xB0A1储存:
(与区位码对比:0xB0=0xA0+16,0xA1=0xA0+1)。
这里注意,字节0x00-0x7F正好是标准ASCII码的编码范围。所以机内码的这种编码规则正好实现了对ASCII的编码规则的兼容。
//读出汉字的机内码,并显示出来
#include <stdio.h>
void main(void){
unsigned char high8bit,low8bit;
unsigned char *hz = "啊";
high8bit = *hz;
low8bit = *(hz+1);
printf("h = %3d,l = %3d\n",high8bit,low8bit); /*h = 176,l = 161*/
}
2.3区位的分类
GB2312编码通过分区,很好地把字符即进行了逻辑分类,又实现了编码与物理寻址的关联性。下表是分区的编码的对应关系。在使用中可以根据这个规则去判断字符的类别等。
2.4编码表举例
通过编码表的局部样例。能过这图展现出来的编码与汉字或符号的对应关系,可以更好更直观的理解区位码与机内码的对应关系。如需完整查看编码表,可以到这个链接:http://witmax.cn/gb2312.html中去查看。
2.5 常规字符的操作
判断是否是GB2312
bool isGBCode(const string& strIn)
{
unsigned char ch1;
unsigned char ch2;
if (strIn.size() >= 2)
{
ch1 = (unsigned char)strIn.at(0);
ch2 = (unsigned char)strIn.at(1);
if (ch1>=176 && ch1<=247 && ch2>=160 && ch2<=254)
return true;
else return false;
}
判断一个字符是ASCII字符还是中文字符
判断一个字符是哪类字符,根据字符的编码规则的区别,只要判断编码首位是“0”还是“1”就可以准确区分。汉字编码的两个字节的最高有效位都 是1,ASCII编码字节最高有效位是0。知道了这个区别,在程序中去判断字符的分类还是很容易的。
3、点阵字库
汉字的机内码确定了一个汉字的检索规则,而真正要显示一个汉字,则需要汉字的相应字模数据。使用时常汉字的点阵数据放在一起形成点阵字库。因此不同的字体,不同的字号都需要有不同的点阵字库。根据点阵的行列的点数,我们常用行点数列点数来标识一个字库的清晰程序。下面拿1616点阵的宋体字库来说明字库数据的存储结构与使用方法。
3.1、16点阵字库
本文用到的字库,可以到这里下载。
下载的1616点阵,采用逐行的方式采样扫描。每个点对应字节里的一个位的话,那每行16个点由2个字节存储。从上到下一个字共16行点阵。因此每个字模的点阵数据就有216=32个字节。这32个字节按以下顺序存储:
1、第一行左8位点阵形成一个字节
2、第一行的右侧8位点阵形成一个字节
3、第二行左8位点阵形成一个字节
4、…
5、第十六行右侧8位点阵形成一个字节
如:国标码【B0A1】 “啊” 字点阵数据32个字节的字节数据如下:
【0x00,0x04,0x2F,0x7E,0xF9,0x04,0xA9,0x04,0xAA,0x14,0xAA,0x7C,0xAC,0x54,0xAA,0x54,0xAA,0x54,0xA9,0x54,0xE9,0x74,0xAD,0x54,0x0A,0x04,0x08,0x04,0x08,0x14,0x08,0x0C】
这32个字节从0开始计算,第0,2,4,,30的偶数字节为字模点阵的左侧半行的点阵数据。第1,3,,31奇数字节为字模点阵的右侧半行的点阵数据。具体如下图:
以上逐行方式只是一种建模顺序。当然也可以是逐列式,行列式,列行式等。因此在具体使用字库时,要注意区分。
3.2、字模库的数据检索
一般情况下,每个字模数据都是按GB2312的区位码顺序存储在字库文件里的,一个字节紧接着一个字节。对16*16点阵字库而言,每个字的字模数据32个字节。因此从机内码变换成区位码后需要再乘以32才能算出字库里某一个字符的离文件头0x00地址位的偏移量:
字库内某字符点阵字节首位偏移量 =
(((机位码高位字节- 0xA1)*94字符)+(机位码低位字节- 0xA1))*32字节
如:国标码【B0A1】 “啊” 字模32个字节按区位码检索在字库里的地址偏移量为:( (0xB0-0xA1) * 94 + (0xA1-0xA1) )*32。
注:这里之所以是减去0xA1,原因是偏移量是从0开始算的。第一个字节的偏移量是0。
以下这个例程,包含了机内码与区位码转换,字库检索与点阵数据读取
#include <stdio.h>
void printhz(int pos);
main(void){
int p; //字库内的偏移量
unsigned char hbyte,lbyte;
const char *hz= "心";
hbyte = *hz-0xA1;
lbyte = *(hz+1)-0xA1;
p = (hbyte*94 + lbyte)*2*16; //16*16点阵,每个字有2*16个字节点阵数据
printhz(p);
}
void printhz(int pos){
FILE *fk;
unsigned char arr[32] = {0};
unsigned char kk;
if((fk=fopen("HZK16","r"))==NULL)
{
printf("file cannot be opened\n");
return ;
}
fseek(fk,pos,0);
fread(arr,sizeof(char),32,fk);
for(int i=0;i<16;i++)//行循环
{
for(int j=0;j<8;j++) //每行的第一个字节处理
{
kk = arr[2*i] << j;//处理偶数位的字节,显示文字的左侧
if(kk & 0x80) //移位后,确定最高有效位是1还是0
{
printf("*");
}
else
{
printf(" ");
}
}
for(int j=0;j<8;j++) //每行的第二个字节处理
{
kk = arr[2*i+1] << j; //处理奇数位的字节,显示文字的右侧
if(kk & 0x80) //移位后,确定最高有效位是1还是0
{
printf("*");
}
else
{
printf(" ");
}
}
printf("\n");
}
fclose(fk); }
3.3、字模数据获得
配合本文,我在资源里提供了字库文件HZK16,该文件是一个16*16点阵宋体字库,是一个无格式的二进制文件,点阵数据读写方式为如上所述的“逐行式”。
在一些单片机的运行环境中比如arduino UNO,由于RAM很小才几K,在应用时字模数不能以文件形式存在,只能以程序里的数据结构的形式读入FLASH中。这样就需要把HZK16这个二进制文件转换格式。变成文本格式的,arduino IDE能识别的字符串数组形式。
方法一:二进制转文本后获取字模
/*copyHZK.cpp*/
#include <stdio.h>
#define HZKSIZE 282752 //buff=94*94*32 =282752
const char *hzkHead[9] = {
"#ifdef __AVR__\n"
" #include <avr/io.h>\n"
" #include <avr/pgmspace.h>\n"
"#elif defined(ESP8266)\n"
" #include <pgmspace.h>\n"
"#else\n"
" #define PROGMEM\n"
"endif\n"
"static const unsigned char HZK[] PROGMEM = {\n"
};
unsigned char hzk[HZKSIZE]; //buff=94*94*32 =282752
char hexNumber[16]={'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
char z[4]={'0','x','0','0'};
FILE *fs,*fd;
//把字库byte变成16进制显示的字符串 00001111->"0x0F"
void byteTohex(char *tohex, unsigned char hzk){
*(tohex+2)=hexNumber[hzk>>4];
*(tohex+3)=hexNumber[hzk & 0x0F];
}
int main(void){
//从原字库里读出放入hzk数组里
if ((fs=fopen("HZK16","r"))==NULL)
{
printf("file cannot be opened\n");
return 0;
};
fseek(fs,0,0);
fread(hzk,sizeof(char),HZKSIZE,fs);
fclose(fs);
//把数组写入文件里hxk16.c
if ((fd=fopen("hzk16.c","w"))==NULL) //open or create file
{
printf("file cannot be found and not be create\n");
return 1;
}
for (int i=0;i<9;i++){ //write data structure head to hzk16.c
fputs(hzkHead[i],fd);
}
//write data to hzk16.c
for (int k=0;k<HZKSIZE;k++){
byteTohex(z,hzk[k]);
fwrite(z,sizeof(char),4,fd);
if (k<HZKSIZE-1){
if ((k+1)%32!=0){
fwrite(",",sizeof(char),1,fd);
}else{
fwrite(",\n",sizeof(char),2,fd);
};
};
};
fwrite("};",sizeof(char),2,fd);
fclose(fd);
}
经过以上程序处理后的HZK16.c有1.3M,无法直接在arduino UNO上使用,对于文字较少的或只显示固定一些字符的应用,可以通过把上面这个字库内的相应字模数据单独检索出后放入数组变量后进行显示。这个可以根据自己的应用需求灵活掌握。而ESP8266模块上有8M左右的flash。可以直接用上这个字库。
方法二:直接用字模取模软件
对于文字较少的或只显示固定一些字符的应用,也可以通过取模软件快速获得字模数据。具体取模软件及使用,网上有大量的教程和介绍,这里不再赘述。
结语:
以上内容就是汉字国际字库的规则和使用方法。
如果要在本系列之案列硬件环境中使用,在显示处理方面,只需要修改write()函数和drawChar()函数的显示坐标设定方法和字库的检索方法就可以了,因为也不难,这里要以详细参考上一篇文章的相应说明进行编程。
由于字库一般都比较大。在LCD等这类单片机小系统上使用时,由于flash空间都较小等,实际在使用字库时有很多限制。因此有种处理方法。也根据设备的不同而不同。这里在后续有机会,会陆续进行分析。