本节主要介绍以下内容:
字符编码
字模
各种模式的液晶显示字符实验
本节字符编码说明参考网站
字符编码及转换测试:导航菜单 - 千千秀字
Unicode官网:http://www.unicode.org。
由于计算机只能识别0和1,文字也只能以0和1的形式在计算机里存储,所以我们需要对文字进行编码才能让计算机处理,编码的过程就是规定特定的01数字串来表示特定的文字,最简单的字符编码例子是ASCII码。
在程序设计中使用ASCII编码表约定了一些控制字符、英文及数字。它们在存储器中,本质也是二进制数,只是我们约定这些二进制数可以表示某些特殊意义,如以ASCII编码解释数字“0x41”时,它表示英文字符“A”。
十进制 | 十六进制 | 缩写/字符 | 解释 |
0 | 0 | NUL(null) | 空字符 |
1 | 1 | SOH(start of headline) | 标题开始 |
2 | 2 | STX (start of text) | 正文开始 |
3 | 3 | ETX (end of text) | 正文结束 |
4 | 4 | EOT (end of transmission) | 传输结束 |
5 | 5 | ENQ (enquiry) | 请求 |
6 | 6 | ACK (acknowledge) | 收到通知 |
7 | 7 | BEL (bell) | 响铃 |
8 | 8 | BS (backspace) | 退格 |
9 | 9 | HT (horizontal tab) | 水平制表符 |
10 | 0A | LF (NL line feed, new line) | 换行键 |
11 | 0B | VT (vertical tab) | 垂直制表符 |
12 | 0C | FF (NP form feed, new page) | 换页键 |
13 | 0D | CR (carriage return) | 回车键 |
14 | 0E | SO (shift out) | 不用切换 |
15 | 0F | SI (shift in) | 启用切换 |
16 | 10 | DLE (data link escape) | 数据链路转义 |
17 | 11 | DC1 (device control 1) | 设备控制1 |
18 | 12 | DC2 (device control 2) | 设备控制2 |
19 | 13 | DC3 (device control 3) | 设备控制3 |
20 | 14 | DC4 (device control 4) | 设备控制4 |
21 | 15 | NAK (negative acknowledge) | 拒绝接收 |
22 | 16 | SYN (synchronous idle) | 同步空闲 |
23 | 17 | ETB (end of trans. block) | 传输块结束 |
24 | 18 | CAN (cancel) | 取消 |
25 | 19 | EM (end of medium) | 介质中断 |
26 | 1A | SUB (substitute) | 替补 |
27 | 1B | ESC (escape) | 换码(溢出) |
28 | 1C | FS (file separator) | 文件分割符 |
29 | 1D | GS (group separator) | 分组符 |
30 | 1E | RS (record separator) | 记录分离符 |
31 | 1F | US (unit separator) | 单元分隔符 |
十进制 | 十六进制 | 缩写/字符 | 解释 |
0 | 0 | NUL(null) | 空字符 |
1 | 1 | SOH(start of headline) | 标题开始 |
2 | 2 | STX (start of text) | 正文开始 |
3 | 3 | ETX (end of text) | 正文结束 |
4 | 4 | EOT (end of transmission) | 传输结束 |
5 | 5 | ENQ (enquiry) | 请求 |
6 | 6 | ACK (acknowledge) | 收到通知 |
7 | 7 | BEL (bell) | 响铃 |
8 | 8 | BS (backspace) | 退格 |
9 | 9 | HT (horizontal tab) | 水平制表符 |
10 | 0A | LF (NL line feed, new line) | 换行键 |
11 | 0B | VT (vertical tab) | 垂直制表符 |
12 | 0C | FF (NP form feed, new page) | 换页键 |
13 | 0D | CR (carriage return) | 回车键 |
14 | 0E | SO (shift out) | 不用切换 |
15 | 0F | SI (shift in) | 启用切换 |
16 | 10 | DLE (data link escape) | 数据链路转义 |
17 | 11 | DC1 (device control 1) | 设备控制1 |
18 | 12 | DC2 (device control 2) | 设备控制2 |
19 | 13 | DC3 (device control 3) | 设备控制3 |
20 | 14 | DC4 (device control 4) | 设备控制4 |
21 | 15 | NAK (negative acknowledge) | 拒绝接收 |
22 | 16 | SYN (synchronous idle) | 同步空闲 |
23 | 17 | ETB (end of trans. block) | 传输块结束 |
24 | 18 | CAN (cancel) | 取消 |
25 | 19 | EM (end of medium) | 介质中断 |
26 | 1A | SUB (substitute) | 替补 |
27 | 1B | ESC (escape) | 换码(溢出) |
28 | 1C | FS (file separator) | 文件分割符 |
29 | 1D | GS (group separator) | 分组符 |
30 | 1E | RS (record separator) | 记录分离符 |
31 | 1F | US (unit separator) | 单元分隔符 |
54 | 36 | 6 | 102 | 66 | f | |
55 | 37 | 7 | 103 | 67 | g | |
56 | 38 | 8 | 104 | 68 | h | |
57 | 39 | 9 | 105 | 69 | i | |
58 | 3A | : | 106 | 6A | j | |
59 | 3B | ; | 107 | 6B | k | |
60 | 3C | <? | 108 | 6C | l | |
61 | 3D | = | 109 | 6D | m | |
62 | 3E | >? | 110 | 6E | n | |
63 | 3F | ? | 111 | 6F | o | |
64 | 40 | @ | 112 | 70 | p | |
65 | 41 | A | 113 | 71 | q | |
66 | 42 | B | 114 | 72 | r | |
67 | 43 | C | 115 | 73 | s | |
68 | 44 | D | 116 | 74 | t | |
69 | 45 | E | 117 | 75 | u | |
70 | 46 | F | 118 | 76 | v | |
71 | 47 | G | 119 | 77 | w | |
72 | 48 | H | 120 | 78 | x | |
73 | 49 | I | 121 | 79 | y | |
74 | 4A | J | 122 | 7A | z | |
75 | 4B | K | 123 | 7B | { | |
76 | 4C | L | 124 | 7C | | | |
77 | 4D | M | 125 | 7D | } | |
78 | 4E | N | 126 | 7E | ~ | |
79 | 4F | O | 127 | 7F | DEL (delete) 删除 |
ASCII码表分为两部分,第一部分是控制字符或通讯专用字符,它们的数字编码从0~31,它们并没有特定的图形显示,但会根据不同的应用程序,而对文本显示有不同的影响。ASCII码的第二部分包括空格、阿拉伯数字、标点符号、大小写英文字母以及“DEL(删除控制)”,这部分符号的数字编码从32~127,除最后一个DEL符号外,都能以图形的方式来表示,它们属于传统文字书写系统的一部分。
后来,计算机引进到其它国家的时候,由于他们使用的不是英语,他们使用的字母在ASCII码表中没有定义,所以他们采用127号之后的位来表示这些新的字母,还加入了各种形状,一直编号到255。从128到255这些字符被称为ASCII扩展字符集。至此基本存储单位Byte(char)能表示的编号都被用完了。
我国首先定义的是GB2312标准。它把ASCII码表127号之后的扩展字符集直接取消掉,并规定小于127的编码按原来ASCII标准解释字符。当2个大于127的字符连在一起时,就表示1个汉字,第1个字节使用 (0xA1-0xFE) 编码,第2个字节使用(0xA1-0xFE)编码,这样的编码组合起来可以表示了7000多个符号,其中包含6763个汉字。在这些编码里,我们还把数学符号、罗马字母、日文假名等都编进表中,就连原来在ASCII里原本就有的数字、标点以及字母也重新编了2个字节长的编码,这就是平时在输入法里可切换的“全角”字符,而标准的ASCII码表中127号以下的就被称为“半角”字符。
下表说明了GB2312是如何兼容ASCII码的,当我们设定系统使用GB2312标准的时候,它遇到一个字符串时,会按字节检测字符值的大小,若遇到连续两个字节的数值都大于127时就把这两个连续的字节合在一起,用GB2312解码,若遇到的数值小于127,就直接用ASCII把它解码。
第1字节 | 第2字节 | 表示的字符 | 说明 |
0x68 | 0x69 | (hi) | 两个字节的值都小于127(0x7F), 使用ASCII解码 |
0xB0 | 0xA1 | (啊) | 两个字节的值都大于127(0x7F), 使用GB2312解码 |
区位码
在GB2312编码的实际使用中,有时会用到区位码的概念。GB2312编码对所收录字符进行了“分区”处理,共94个区,每区含有94个位,共8836个码位。而区位码实际是GB2312编码的内部形式,它规定对收录的每个字符采用两个字节表示,第一个字节为“高字节”,对应94个区;第二个字节为“低字节”,对应94个位。所以它的区位码范围是:0101-9494。为兼容ASCII码,区号和位号分别加上0xA0偏移就得到GB2312编码。在区位码上加上0xA0偏移,可求得GB2312编码范围:0xA1A1-0xFEFE,其中汉字的编码范围为0xB0A1-0xF7FE,第一字节0xB0-0xF7(对应区号:16-87),第二个字节0xA1-0xFE(对应位号:01-94)。?
例如,“啊”字是GB2312编码中的第一个汉字,它位于16区的01位,所以它的区位码就是1601,加上0xA0偏移,其GB2312编码为0xB0A1。其中区位码为0101的码位表示的是“空格”符。
据统计,GB2312编码中表示的6763个汉字已经覆盖中国大陆99.75%的使用率,单看这个数字已经很令人满意了,但是不能因为那些文字不常用就不让它进入信息时代,而且生僻字在人名、文言文中的出现频率是非常高的。
?为此我们在GB2312标准的基础上又增加了14240个新汉字(包括所有后面介绍的Big5中的所有汉字)和符号,这个方案被称为GBK标准。增加这么多字符,按照GB2312原来的格式来编码,2个字节已经存储不下,我们的程序员修改了一下格式,不再要求第2个字节的编码值必须大于127,只要第1个字节大于127就表示这是一个汉字的开始,这样就做到兼容ASCII和GB2312标准了。
说明了GBK是如何兼容ASCII和GB2312标准的,当我们设定系统使用GBK标准的时候,它按顺序遍历字符串,按字节检测字符值的大小,若遇到一个字符的值大于127时,就再读取它后面的一个字符,把这两个字符值合在一起,用GBK解码,解码完后,再读取第3个字符,重新开始以上过程,若该字符值小于127,则直接用ASCII解码。
第1字节 | 第2字节 | 第3字节 | 表示的字符 | 说明 |
0x68(<7F) | 0xB0(>7F) | 0xA1(>7F) | (h啊) | 第1个字节小于127,使用ASCII解码, 每2个字节大于127,直接使用GBK解码 ,兼容GB2312 |
0xB0(>7F) | 0xA1(>7F) | 0x68(<7F) | (啊h) | 第1个字节大于127,直接使用GBK码 解释,第3个字节小于127,使用ASCII 解码 |
0xB0(>7F) | 0x56(<7F) | 0x68(<7F) | (癡h) | 第1个字节大于127,第2个字节虽然小 于127,直接使用GBK解码,第3个字节 小于127,使用ASCII解码 |
随着计算机技术的普及,我们后来又在GBK的标准上不断扩展字符,这些标准被称为GB18030,如GB18030-2000、GB18030-2005等(“-”号后面的数字是制定标准时的年号),GB18030的编码使用4个字节,它利用前面标准中的第2个字节未使用的“0x30-0x39”编码表示扩充四字节的后缀,兼容GBK、GB2312及ASCII标准。
GB18030-2000主要在GBK基础上增加了“CJK(中日韩)统一汉字扩充A”的汉字。加上前面GBK的内容,GB18030-2000一共规定了27533个汉字(包括部首、部件等)的编码,还有一些常用非汉字符号。
? GB18030-2005的主要特点是在GB18030-2000基础上增加了“CJK(中日韩)统一汉字扩充B”的汉字。增加了42711个汉字和多种我国少数民族文字的编码(如藏、蒙古、傣、彝、朝鲜、维吾尔文等)。加上前面GB18030-2000的内容,一共收录了70244个汉字。
GB2312、GBK及GB18030是汉字的国家标准编码,新版向下兼容旧版,各个标准简要说明见下表,目前比较流行的是GBK编码,因为每个汉字只占用2个字节,而且它编码的字符已经能满足大部分的需求,但国家要求一些产品必须支持GB18030标准。
在台湾、香港等地区,使用较多的是Big5编码,它的主要特点是收录了繁体字。而从GBK编码开始,已经把Big5中的所有汉字收录进编码了。即对于汉字部分,GBK是Big5的超集,Big5能表示的汉字,在GBK都能找到那些字相应的编码,但他们的编码是不一样的,两个标准不兼容,如GBK中的“啊”字编码是“0xB0A1”,而Big5标准中的编码为“0xB0DA”。
主要做嵌入式的同学学习到这就可以了,因为我们平时用的主要就是GB2312/GBK.
由于各个国家或地区都根据使用自己的文字系统制定标准,同一个编码在不同的标准里表示不一样的字符,各个标准互不兼容,而又没有一个标准能够囊括所有的字符,即无法用一个标准表达所有字符。国际标准化组织(ISO)为解决这一问题,它舍弃了地区性的方案,重新给全球上所有文化使用的字母和符号进行编号,对每个字符指定一个唯一的编号(ASCII中原有的字符编号不变),这些字符的号码从0x000000到0x10FFFF,该编号集被称为Universal Multiple-Octet Coded Character Set,简称UCS,也被称为Unicode。最新版的Unicode标准还包含了表情符号(聊天软件中的部分emoji表情),可访问Unicode官网了解:http://www.unicode.org。
? Unicode字符集只是对字符进行编号,但具体怎么对每个字符进行编码,Unicode并没指定,因此也衍生出了如下几种unicode编码方案(Unicode Transformation Format)。
对Unicode字符集编码,最自然的就是UTF-32方式了。编码时,它直接对Unicode字符集里的每个字符都用4字节来表示,转换方式很简单,直接将字符对应的编号数字转换为4字节的二进制数。
? 由于UTF-32把每个字符都用要4字节来存储,因此UTF-32不兼容ASCII编码,也就是说ASCII编码的文件用UTF-32标准来打开会成为乱码。
字符 | GBK编码 | Unicode编号 | UTF-32编码 |
A | 0x41 | 0x0000 0041 | 大端格式0x0000 0041 |
啊 | 0xB0A1 | 0x0000 554A | 大端格式0x0000 554A |
对UTF-32数据进行解码的时候,以4个字节为单位进行解析即可,根据编码可直接找到Unicode字符集中对应编号的字符。
? UTF-32的优点是编码简单,解码也很方便,读取编码的时候每次都直接读4个字节,不需要加其它的判断。它的缺点是浪费存储空间,大量常用字符的编号只需要2个字节就能表示。其次,在存储的时候需要指定字节顺序,是高位字节存储在前(大端格式),还是低位字节存储在前(小端格式)。
针对UTF-32的缺点,人们改进出了UTF-16的编码方式,它采用2字节或4字节的变长编码方式(UTF-32定长为4字节)。对Unicode字符编号在0到65535的统一用2个字节来表示,将每个字符的编号转换为2字节的二进制数,即从0x0000到0xFFFF。而由于Unicode字符集在0xD800-0xDBFF这个区间是没有表示任何字符的,所以UTF-16就利用这段空间,对Unicode中编号超出0xFFFF的字符,利用它们的编号做某种运算与该空间建立映射关系,从而利用该空间表示4字节扩展,感兴趣的读者可查阅相关资料了解具体的映射过程。
字符 | GB18030编码 | Unicode编号 | UTF-16编码 |
A | 0x41 | 0x0000 0041 | 大端格式0x0041 |
啊 | 0xB0A1 | 0x0000 554A | 大端格式0x554A |
𧗌 | 0x9735 F832 | 0x0002 75CC | 大端格式0xD85D DDCC |
注:𧗌 五笔:TLHH(不支持GB18030码的输入法无法找到该字,感兴趣可搜索它的Unicode编号找到)?
UTF-16解码时,按两个字节去读取,如果这两个字节不在0xD800到0xDFFF范围内,那就是双字节编码的字符,以双字节进行解析,找到对应编号的字符。如果这两个字节在0xD800到 0xDFFF之间,那它就是四字节编码的字符,以四字节进行解析,找到对应编号的字符。
UTF-16编码的优点是相对UTF-32节约了存储空间,缺点是仍不兼容ASCII码,仍有大小端格式问题。
UTF-8是目前Unicode字符集中使用得最广的编码方式,目前大部分网页文件已使用UTF-8编码,如使用浏览器查看百度首页源文件,可以在前几行HTML代码中找到如下代码:
<meta http-equiv=Content-Type content="text/html;charset=utf-8">
其中“charset”等号后面的“utf-8”即表示该网页字符的编码方式UTF-8。?
UTF-8也是一种变长的编码方式,它的编码有1、2、3、4字节长度的方式,每个Unicode字符根据自己的编号范围去进行对应的编码。它的编码符合以下规律:
Unicode(16进制) | UTF-8(2进制) | ||||
编号范围 | 第一字节 | 第二字节 | 第三字节 | 第四字节 | 第五字节 |
00000000-0000007F | 0xxxxxxx | ||||
00000080-000007FF | 110xxxxx | 10xxxxxx | |||
00000800-0000FFFF | 1110xxxx | 10xxxxxx | 10xxxxxx | ||
00010000-0010FFFF | 11110xxx | 10xxxxxx | 10xxxxxx | 10xxxxxx | |
… | 111110xx | 10xxxxxx | 10xxxxxx | 10xxxxxx | 10xxxxxx |
UTF-8解码的时候以字节为单位去看,如果第一个字节的bit位以0开头,那就是ASCII字符,以单字节进行解析。如果第一个字节的数据位以“110”开头,就按双字节进行解析,3、4字节的解析方法类似。
? UTF-8的优点是兼容了ASCII码,节约空间,且没有字节顺序的问题,它直接根据第1个字节前面数据位中连续的1个数决定后面有多少个字节。不过使用UTF-8编码汉字平均需要3个字节,比GBK编码要多一个字节。
有了编码,我们就能在计算机中处理、存储字符了,但是如果计算机处理完字符后直接以编码的形式输出,人类将难以识别。因此计算机与人交互时,一般会把字符转化成人类习惯的表现形式进行输出,如显示、打印的时候。
但是如果仅有字符编码,计算机还不知道该如何表达该字符,因为字符实际上是一个个独特的图形,计算机必须把字符编码转化成对应的字符图形人类才能正常识别,因此我们要给计算机提供字符的图形数据,这些数据就是字模,多个字模数据组成的文件也被称为字库。计算机显示字符时,根据字符编码与字模数据的映射关系找到它相应的字模数据,液晶屏根据字模数据显示该字符。
已知字模是图形数据,而图形在计算机中是由一个个像素点组成的,所以字模实质是一个个像素点数据。为方便处理,我们把字模定义成方块形的像素点阵,且每个像素点只有0和1这两种状态(可以理解为单色图像数据)。
下图两个宽、高为16x16的像素点阵组成的两个汉字图形,其中的黑色像素点即为文字的笔迹。计算机要表示这样的图形,只需使用16x16个二进制数据位,每个数据位记录一个像素点的状态,把黑色像素点以“1”表示,无色像素点以“0”表示即可。这样的一个汉字图形,使用16x16/8=32个字节来就可以记录下来。
?16x16的“字”的字模数据以C语言数组的方式表示,见下面的代码,在这样的字模中,以两个字节表示一行像素点,16行构成一个字模。
如果使用LCD的画点函数,按位来扫描这些字模数据,把为1的位以黑色来显示(也可以使用其它颜色),为0的数据位以白色来显示,即可把整个点阵还原出来,显示在液晶屏上。
为方便讲解,编写一个使用串口printf利用字模打印字符到串口上位机的实验,
代码:
/*阴码*/
uint8_t test_modul[] =
{0x02,0x00,0x01,0x00,0x7F,0xFE,0x40,0x02,
0x80,0x04,0x1F,0xE0,0x00,0x40,0x00,0x80,
0x01,0x00,0xFF,0xFE,0x01,0x00,0x01,0x00,
0x01,0x00,0x01,0x00,0x05,0x00,0x02,0x00};/* 字 */
///*阳码*/
//uint8_t test_modul[] =
//{0xFD,0xFF,0xFE,0xFF,0x80,0x01,0xBF,0xFD,
// 0x7F,0xFB,0xE0,0x1F,0xFF,0xBF,0xFF,0x7F,
// 0xFE,0xFF,0x00,0x01,0xFE,0xFF,0xFE,0xFF,
//0xFE,0xFF,0xFE,0xFF,0xFA,0xFF,0xFD,0xFF}; /*"字",0*/
void Display_char_test(void)
{
uint8_t row_count,byte_count,bit_count;
for(row_count=0;row_count < 16;row_count++ )
{
printf("\n");
for(byte_count=0;byte_count<2;byte_count++)
{
for(bit_count=0;bit_count<8;bit_count++)
{
if(test_modul[row_count*2+byte_count] & (0x80>>bit_count) )
{
printf("*");
}
else
{
printf(" ");
}
}
}
}
}
实验效果如下:
为方便使用,需要制作所有常用字符的字模,如程序只需要英文显示,那就需要制作包含ASCII码表中所有字符的字模,如程序只需要使用一些常用汉字,则可以选择制作GB2312编码里所有字符的字模,而且希望字模数据与字符编码有固定的映射关系,以便我们在程序中使用字符编码作为索引,查找字模。
? 在网上搜索可找到一些制作字模的软件工具,可满足这些需求。在我们提供的《LCD—液晶显示汉字》的工程目录下提供了一个取模软件“PCtoLCD”,这里以它为例讲解如何制作字模,其它字模软件也是类似的。
(阴码和阳码的区别,有笔记的地方是1? ?没有笔记地方1,是阳码)
配置完字模选项后,点击软件中的导入文本图标,会弹出一个“生成字库”的对话框,点击右下角的生成国标汉字库按钮即可生成包含了GB2312编码里所有字符的字模文件。
? 在《LCD—液晶显示汉字》的工程目录下的《GB2312_H1616.FON》是我们用这个取模软件生成的字模原文件,若不想自己制作字模,可直接使用该文件。
字模寻址公式
????????使用字模软件制作的字模数据一般会按照编码格式排列。如我们利用以上软件生成的字模文件《GB2312_H1616.FON》中的数据,是根据GB2312的区位码表的顺序存储的,它存储了区位码为0101-9494的字符,每个字模的大小为16x16/8=36字节。其中第一个字符“空格”的区位码为0101,它是首个字符,所以文件的前36字节存储的是它的字模数据;同理,36-72字节存储的则是0102字符“、”的字模数据。所以我们可以导出任意字符的寻址公式:
? Addr = (((CodeH-0xA0-1)*94) +(CodeL-0xA0-1))*16*16/8
????????其中CodeH和CodeL分别是GB2312编码的第一字节和第二字节;94是指一个区中有94个位(即94个字符)。公式的实质是根据字符的GB2312编码,求出区位码,然后区位码乘以每个字符占据的字节数,求出地址偏移。
????????上面生成的《GB2312_H1616.FON》文件的大小为576KB,比很多STM32芯片内部的所有FLASH空间都大,如果我们还是在程序中直接以C语言数组的方式存储字模数据,STM32芯片的程序空间会非常紧张,一般的做法是把字模数据存储到外部存储器,如SD卡或SPI-FLASH芯片,当需要显示某个字符时,控制器根据字符的编码算好字模的存储地址,再从存储器中读取,而FLASH芯片在生产前就固化好字模内容,然后直接把FLASH芯片贴到电路板上,作为整个系统的一部分。