0%

stm32单片机上实现GBK转UNICODE和UTF-8

最近在做一个项目需要用到stm32f4作为网关处理车牌识别的结果,但是车牌识别的摄像头上传的数据是GB2312编码的,平台使用UTF-8编码的所以需要网关这边处理一下,将GB2312转成UTF-8,于是就有了下边的文章,本文参考了《Unicode与UTF-8互转(C语言实现)》的处理方法,下面将具体实现又整理了一下。

问题难点

因为使用的是单片机,内存空间十分有限,无法使用一些c库或者c++的处理库

GB2312和UTF-8的交集很少,下边是我找到的一些资料

UTF-8:Unicode Transformation Format-8bit,允许含BOM,但通常不含BOM。是用以解决国际上字符的一种多字节编码,它对英文使用8位(即一个字节),中文使用24为(三个字节)来编码。UTF-8包含全世界所有国家需要用到的字符,是国际编码,通用性强。UTF-8编码的文字可以在各国支持UTF8字符集的浏览器上显示。如,如果是UTF8编码,则在外国人的英文IE上也能显示中文,他们无需下载IE的中文语言支持包。

GBK是国家标准GB2312基础上扩容后兼容GB2312的标准。GBK的文字编码是用双字节来表示的,即不论中、英文字符均使用双字节来表示,为了区分中文,将其最高位都设定成1。GBK包含全部中文字符,是国家编码,通用性比UTF8差,不过UTF8占用的数据库比GBD大。

GBK、GB2312等与UTF8之间都必须通过Unicode编码才能相互转换:

GBK、GB2312--Unicode--UTF8

UTF8--Unicode--GBK、GB2312

对于一个网站、论坛来说,如果英文字符较多,则建议使用UTF-8节省空间。不过现在很多论坛的插件一般只支持GBK。

GB2312是GBK的子集,GBK是GB18030的子集
GBK是包括中日韩字符的大字符集合
如果是中文的网站 推荐GB2312 GBK有时还是有点问题
为了避免所有乱码问题,应该采用UTF-8,将来要支持国际化也非常方便
UTF-8可以看作是大字符集,它包含了大部分文字的编码。
使用UTF-8的一个好处是其他地区的用户(如香港台湾)无需安装简体中文支持就能正常观看你的文字而不会出现乱码。

gb2312是简体中文的码
gbk支持简体中文及繁体中文
big5支持繁体中文
utf-8支持几乎所有字符

UTF-8编码与Unicode编码有对应关系:
参考原文:https://blog.csdn.net/ffdanpeng/article/details/78744158

下表总结了编码规则, 字母x表示可用编码的位.

n Unicode符号范围(十六进制) UTF-8编码方式(二进制)
1 0000 0000 - 0000 007F 0xxxxxxx
2 0000 0080 - 0000 07FF 110xxxxx 10xxxxxx
3 0000 0800 - 0000 FFFF 1110xxxx 10xxxxxx 10xxxxxx
4 0001 0000 - 0010 FFFF 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
5 0020 0000 - 03FF FFFF 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
6 0400 0000 - 7FFF FFFF 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx

对应举例:

下面, 还是以汉字”严”为例, 演示如何实现UTF-8编码.

已知”严”的unicode是4E25(1001110 00100101), 根据上表, 可以发现4E25处在第三行的范围内(0000 0800 - 0000 FFFF), 因此”严”的UTF-8编码需要三个字节, 即格式是“1110xxxx 10xxxxxx 10xxxxxx”. 然后, 从”严”的最后一个二进制位开始, 依次从后向前填入格式中的x, 多出的位补0. 这样就得到了, “严”的UTF-8编码是 “11100100 10111000 10100101”, 转换成十六进制就是E4B8A5.

GBK与Unicode虽然没有直接计算关系,但是有人做了映射表cc936.c
参考链接:https://blog.csdn.net/pl0020/article/details/81542105

映射表cc936.c来源于Fatfs源码,我提取出来了User.7z

问题解决

数据源GBK,通过映射表转成Unicode,Unicode通过计算方法得到UTF-8

代码测试

1
2
3
4
unsigned short int ban_gbk = 0xD1CF;//严
unsigned short int ban_unicode;

ban_unicode = ff_convert(ban_gbk,1);

ban_unicode计算后 即为0x4E25。转成了unicode

通过下面方法转成UTF-8

具体实现

将一个字符的Unicode(UCS-2和UCS-4)编码转换成UTF-8编码.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76

/*****************************************************************************
* 将一个字符的Unicode(UCS-2和UCS-4)编码转换成UTF-8编码.
*
* 参数:
* unic 字符的Unicode编码值
* pOutput 指向输出的用于存储UTF8编码值的缓冲区的指针
* outsize pOutput缓冲的大小
*
* 返回值:
* 返回转换后的字符的UTF8编码所占的字节数, 如果出错则返回 0 .
*
* 注意:
* 1. UTF8没有字节序问题, 但是Unicode有字节序要求;
* 字节序分为大端(Big Endian)和小端(Little Endian)两种;
* 在Intel处理器中采用小端法表示, 在此采用小端法表示. (低地址存低位)
* 2. 请保证 pOutput 缓冲区有最少有 6 字节的空间大小! ****************************************************************************/
int enc_unicode_to_utf8_one(unsigned long unic, unsigned char *pOutput,int outSize)
{
if(pOutput == NULL) return 0;
if(outSize < 6) return 0;

if ( unic <= 0x0000007F )
{
// * U-00000000 - U-0000007F: 0xxxxxxx
*pOutput = (unic & 0x7F);
return 1;
}
else if ( unic >= 0x00000080 && unic <= 0x000007FF )
{
// * U-00000080 - U-000007FF: 110xxxxx 10xxxxxx
*(pOutput+1) = (unic & 0x3F) | 0x80;
*pOutput = ((unic >> 6) & 0x1F) | 0xC0;
return 2;
}
else if ( unic >= 0x00000800 && unic <= 0x0000FFFF )
{
// * U-00000800 - U-0000FFFF: 1110xxxx 10xxxxxx 10xxxxxx
*(pOutput+2) = (unic & 0x3F) | 0x80;
*(pOutput+1) = ((unic >> 6) & 0x3F) | 0x80;
*pOutput = ((unic >> 12) & 0x0F) | 0xE0;
return 3;
}
else if ( unic >= 0x00010000 && unic <= 0x001FFFFF )
{
// * U-00010000 - U-001FFFFF: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
*(pOutput+3) = (unic & 0x3F) | 0x80;
*(pOutput+2) = ((unic >> 6) & 0x3F) | 0x80;
*(pOutput+1) = ((unic >> 12) & 0x3F) | 0x80;
*pOutput = ((unic >> 18) & 0x07) | 0xF0;
return 4;
}
else if ( unic >= 0x00200000 && unic <= 0x03FFFFFF )
{
// * U-00200000 - U-03FFFFFF: 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
*(pOutput+4) = (unic & 0x3F) | 0x80;
*(pOutput+3) = ((unic >> 6) & 0x3F) | 0x80;
*(pOutput+2) = ((unic >> 12) & 0x3F) | 0x80;
*(pOutput+1) = ((unic >> 18) & 0x3F) | 0x80;
*pOutput = ((unic >> 24) & 0x03) | 0xF8;
return 5;
}
else if ( unic >= 0x04000000 && unic <= 0x7FFFFFFF )
{
// * U-04000000 - U-7FFFFFFF: 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
*(pOutput+5) = (unic & 0x3F) | 0x80;
*(pOutput+4) = ((unic >> 6) & 0x3F) | 0x80;
*(pOutput+3) = ((unic >> 12) & 0x3F) | 0x80;
*(pOutput+2) = ((unic >> 18) & 0x3F) | 0x80;
*(pOutput+1) = ((unic >> 24) & 0x3F) | 0x80;
*pOutput = ((unic >> 30) & 0x01) | 0xFC;
return 6;
}

return 0;
}

将一个字符的UTF8编码转换成Unicode(UCS-2和UCS-4)编码.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
/***************************************************************************** 
* 将一个字符的UTF8编码转换成Unicode(UCS-2和UCS-4)编码.
*
* 参数:
* pInput 指向输入缓冲区, 以UTF-8编码
* Unic 指向输出缓冲区, 其保存的数据即是Unicode编码值,
* 类型为unsigned long .
*
* 返回值:
* 成功则返回该字符的UTF8编码所占用的字节数; 失败则返回0.
*
* 注意:
* 1. UTF8没有字节序问题, 但是Unicode有字节序要求;
* 字节序分为大端(Big Endian)和小端(Little Endian)两种;
* 在Intel处理器中采用小端法表示, 在此采用小端法表示. (低地址存低位)
****************************************************************************/
int enc_utf8_to_unicode_one(const unsigned char* pInput, unsigned long *Unic)
{
if((pInput == NULL) || (Unic == NULL)) return 0;

// b1 表示UTF-8编码的pInput中的高字节, b2 表示次高字节, ...
char b1, b2, b3, b4, b5, b6;

*Unic = 0x0; // 把 *Unic 初始化为全零
int utfbytes = enc_get_utf8_size(*pInput);
unsigned char *pOutput = (unsigned char *) Unic;

switch ( utfbytes )
{
case 0:
*pOutput = *pInput;
utfbytes += 1;
break;
case 2:
b1 = *pInput;
b2 = *(pInput + 1);
if ( (b2 & 0xE0) != 0x80 )
return 0;
*pOutput = (b1 << 6) + (b2 & 0x3F);
*(pOutput+1) = (b1 >> 2) & 0x07;
break;
case 3:
b1 = *pInput;
b2 = *(pInput + 1);
b3 = *(pInput + 2);
if ( ((b2 & 0xC0) != 0x80) || ((b3 & 0xC0) != 0x80) )
return 0;
*pOutput = (b2 << 6) + (b3 & 0x3F);
*(pOutput+1) = (b1 << 4) + ((b2 >> 2) & 0x0F);
break;
case 4:
b1 = *pInput;
b2 = *(pInput + 1);
b3 = *(pInput + 2);
b4 = *(pInput + 3);
if ( ((b2 & 0xC0) != 0x80) || ((b3 & 0xC0) != 0x80)
|| ((b4 & 0xC0) != 0x80) )
return 0;
*pOutput = (b3 << 6) + (b4 & 0x3F);
*(pOutput+1) = (b2 << 4) + ((b3 >> 2) & 0x0F);
*(pOutput+2) = ((b1 << 2) & 0x1C) + ((b2 >> 4) & 0x03);
break;
case 5:
b1 = *pInput;
b2 = *(pInput + 1);
b3 = *(pInput + 2);
b4 = *(pInput + 3);
b5 = *(pInput + 4);
if ( ((b2 & 0xC0) != 0x80) || ((b3 & 0xC0) != 0x80)
|| ((b4 & 0xC0) != 0x80) || ((b5 & 0xC0) != 0x80) )
return 0;
*pOutput = (b4 << 6) + (b5 & 0x3F);
*(pOutput+1) = (b3 << 4) + ((b4 >> 2) & 0x0F);
*(pOutput+2) = (b2 << 2) + ((b3 >> 4) & 0x03);
*(pOutput+3) = (b1 << 6);
break;
case 6:
b1 = *pInput;
b2 = *(pInput + 1);
b3 = *(pInput + 2);
b4 = *(pInput + 3);
b5 = *(pInput + 4);
b6 = *(pInput + 5);
if ( ((b2 & 0xC0) != 0x80) || ((b3 & 0xC0) != 0x80)
|| ((b4 & 0xC0) != 0x80) || ((b5 & 0xC0) != 0x80)
|| ((b6 & 0xC0) != 0x80) )
return 0;
*pOutput = (b5 << 6) + (b6 & 0x3F);
*(pOutput+1) = (b5 << 4) + ((b6 >> 2) & 0x0F);
*(pOutput+2) = (b3 << 2) + ((b4 >> 4) & 0x03);
*(pOutput+3) = ((b1 << 6) & 0x40) + (b2 & 0x3F);
break;
default:
return 0;
break;
}

return utfbytes;
}

int enc_get_utf8_size(const unsigned char pInput)
{
unsigned char c = pInput;
// 0xxxxxxx 返回0
// 10xxxxxx 不存在
// 110xxxxx 返回2
// 1110xxxx 返回3
// 11110xxx 返回4
// 111110xx 返回5
// 1111110x 返回6
if(c< 0x80) return 0;
if(c>=0x80 && c<0xC0) return -1;
if(c>=0xC0 && c<0xE0) return 2;
if(c>=0xE0 && c<0xF0) return 3;
if(c>=0xF0 && c<0xF8) return 4;
if(c>=0xF8 && c<0xFC) return 5;
if(c>=0xFC) return 6;
}

这是一个在线转换的网站可以验证一下https://www.qqxiuzi.cn/bianma/zifuji.php