手把手教你实现基于RT-Thread的百度语音识别(三)

前言

本次我们还是先不实现录音功能,因为音频编解码这一块是比较有难度的,再加上RT-Thread的Audio设备也比较复杂,难以理解,我暂时没考虑好怎么讲解该部分(我自己也没理解透~),所以就放到后面再分享吧。那么这次我们就讲讲项目的第7点:

将中文字库烧写进外挂的spi flash,使用SUFD+FAL软件包读写flash,实现LCD的中文显示,用来显示语音识别结果。

原本我做项目时,是没有打算要在LCD上显示识别结果的,但是因为我使用的潘多拉开发板板载了一块1.3寸的240*240高分辨率TFTLCD显示屏,不把它用上岂不是浪费?再加上后面想到如果不用语音识别来控制外设,识别一些其他语音的时候,有个可以显示识别结果的屏幕,岂不美哉。于是乎我便开始整LCD显示了,不整不知道,一整头皮发麻,RT-Thread官方对于潘多拉开发板在驱动各方面的支持都已经做的非常好了,包括LCD驱动,但是却没有中文字库的支持,也没有相关的资料,所以我就只能自己想办法实现了。

下面我分享的内容,跟百度语音识别没有太大的关系,主要是如何在LCD上显示中文的内容,但百度语音确确实实给我在实现的路上埋下了许多坑,所以我觉得这部分还是很有必要分享的,究竟是怎么一回事,听我细细道来。

准备工作

  1. 首先你必须完成了系列文章(二)中讲解的内容;
  2. 阅读正点原子教程中SPI Flash和汉字显示两个例程,确保你清楚汉字显示原理,如何制作字库,如何将字库烧写进flash中;
  3. 准备好中文字库;

动手实践

嘻嘻~还是为了方便,字库嘛,我使用正点原子裸机例程提供的中文字库(该字库包含了12,16,24,32四种字体大小的字库),然后运行汉字显示例程的程序,该程序里包含将字库烧写进spi flash的部分,我们要的就是这个部分,其实你可以自己编程实现flash读写(so easy !),自行烧写字库,但是为了方便我就偷懒了,毕竟只要字库不损坏,咱也就烧这一次。

上面部分你可以裸机实现,无所谓,只要将字库烧进flash(我这里是W25Q128)就行了。下面重头戏来了(再次确保你已经明白了汉字显示原理),这次我们又用到了RT-Thread的两个软件包:SUFD和FAL,简单介绍一下:

SUFD(串行闪存通用驱动库)

看中文名就知道了,用来驱动spi flash的嘛。SFUD是一种开源的串行SPI Flash通用驱动库(是RT-Thread的armink大神开发的,大佬!),使用这个库,你就不必自己编写flash驱动了,基本市面上绝大多数的flash,都可以轻轻松松地给你驱起来。

FAL(Flash抽象层)

简单来说,使用该软件包,你可以方便地使用API对flash进行分区管理,读写操作等,支持自定义分区表(不得不说,强!)。

代码讲解

好了,同样,使用ENV工具把SUFD和FAL添加进工程中,开始写代码:

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
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
/*********
cn_font.c
**********/

#include <rtthread.h>
#include <rtdevice.h>
#include <drv_qspi.h>
#include <drv_spi.h>
#include <drv_lcd.h>
#include <string.h>
#include <fal.h>
#include <cn_font.h>

/************************************************
结构体名称 : _font_info
定 义 :
__packed typedef struct
{
uint8_t fontok; //字库存在标志,0XAA,字库正常;其他,字库不存在
uint32_t ugbkaddr; //unigbk的地址
uint32_t ugbksize; //unigbk的大小
uint32_t f12addr; //gbk12地址
uint32_t gbk12size; //gbk12的大小
uint32_t f16addr; //gbk16地址
uint32_t gbk16size; //gbk16的大小
uint32_t f24addr; //gbk24地址
uint32_t gbk24size; //gbk24的大小
uint32_t f32addr; //gbk32地址
uint32_t gbk32size; //gbk32的大小
} _font_info;
*************************************************/
_font_info ftinfo;

/* 从字库中找出字模 */
void get_hz_mat(unsigned char *code, unsigned char *mat, uint8_t size)
{
ftinfo.ugbkaddr = 0x0000000+sizeof(ftinfo);
ftinfo.ugbksize = 174344;
ftinfo.f12addr = 0x0002A908+sizeof(ftinfo);
ftinfo.gbk12size = 574560;
ftinfo.f16addr = 0x000B6D68+sizeof(ftinfo);
ftinfo.gbk16size = 766080;
ftinfo.f24addr = 0x00171DE8+sizeof(ftinfo);
ftinfo.gbk24size = 1723680;
ftinfo.f32addr = 0x00316B08+sizeof(ftinfo);
ftinfo.gbk32size = 3064320;

unsigned char qh, ql;
unsigned char i;
unsigned long foffset;
const struct fal_partition *partition = fal_partition_find("font");

uint8_t csize = (size / 8 + ((size % 8) ? 1 : 0)) * (size); //得到字体一个字符对应点阵集所占的字节数
qh =*code;
ql = *(++code);

if(qh < 0x81 || ql < 0x40 || ql == 0xff || qh == 0xff) //非常用汉字
{
for(i = 0; i < csize; i++)*mat++ = 0x00; //填充满格
return; //结束访问
}
if(ql < 0x7f)ql -= 0x40; //注意!
else ql -= 0x41;

qh -= 0x81;
foffset = ((unsigned long)190 * qh + ql) * csize; //得到字库中的字节偏移量

switch(size)
{
case 12:
fal_partition_read(partition, foffset + ftinfo.f12addr, mat, csize);
break;

case 16:
fal_partition_read(partition, foffset + ftinfo.f16addr, mat, csize);
break;

case 24:
fal_partition_read(partition, foffset + ftinfo.f24addr, mat, csize);
break;

case 32:
fal_partition_read(partition, foffset + ftinfo.f32addr, mat, csize);
break;

}
}

/* 显示一个指定大小的汉字 */
void show_font(uint16_t x, uint16_t y, uint8_t *font, uint8_t size)
{
uint16_t colortemp;
uint8_t sta;
uint8_t temp, t, t1;
uint8_t dzk[128];
uint8_t csize = (size / 8 + ((size % 8) ? 1 : 0)) * (size); //得到字体一个字符对应点阵集所占的字节数

if(size != 12 && size != 16 && size != 24 && size != 32)return; //不支持的size

get_hz_mat(font, dzk, size); //得到相应大小的点阵数据

if((size == 16) || (size == 24) || (size == 32)) //16、24、32号字体
{
sta = 8;

lcd_address_set(x, y, x + size - 1, y + size - 1);

for(t = 0; t < csize; t++)
{
temp = dzk[t]; //得到点阵数据

for(t1 = 0; t1 < sta; t1++)
{
if(temp & 0x80) colortemp = 0x0000;

else colortemp = 0xFFFF;

lcd_write_half_word(colortemp);
temp <<= 1;
}
}
}
else if(size == 12) //12号字体
{
lcd_address_set(x, y, x + size - 1, y + size - 1);

for(t = 0; t < csize; t++)
{
temp = dzk[t]; //得到点阵数据

if(t % 2 == 0)sta = 8;

else sta = 4;

for(t1 = 0; t1 < sta; t1++)
{
if(temp & 0x80) colortemp = 0x0000;

else colortemp = 0xFFFF;

lcd_write_half_word(colortemp);
temp <<= 1;
}
}
}
}

/* 在指定位置开始显示一个字符串 */
void show_str(uint16_t x, uint16_t y, uint16_t width, uint16_t height, uint8_t *str, uint8_t size)
{
uint16_t x0 = x;
uint16_t y0 = y;
uint8_t bHz = 0; //字符或者中文

while(*str != 0) //数据未结束
{
if(!bHz)
{
if(*str > 0x80)bHz = 1; //中文

else //字符
{
if(x > (x0 + width - size / 2)) //换行
{
y += size;
x = x0;
}

if(y > (y0 + height - size))break; //越界返回

if(*str == 13) //换行符号
{
y += size;
x = x0;
str++;
}

else lcd_show_char(x, y, *str, size); //有效部分写入

str++;
x += size / 2; //字符,为全字的一半
}
}
else //中文
{
bHz = 0; //有汉字库

if(x > (x0 + width - size)) //换行
{
y += size;
x = x0;
}

if(y > (y0 + height - size))break; //越界返回

show_font(x, y, str, size); //显示这个汉字,空心显示
str += 2;
x += size; //下一个汉字偏移
}
}
}

上面的代码中实现了三个函数:

1
2
3
get_hz_mat()
show_font()
show_str()

这三个函数都是移植正点原子的,后两个基本没有变化,你只需注意get_hz_mat(),因为它涉及到了flash的读写,而我们这里不再是裸机的读写方式,而是使用FAL软件包提供的API,尤其注意读写的位置,FAL软件包读的是分区相对位置!

有了上面这三个函数,初始化LCD完成后,你就可以使用show_str( )显示中文啦,例:

1
show_str(120, 220, 200, 16, (rt_uint8_t *)"霹雳大乌龙", 16);

需要注意的是,你的源文件编码不能使用utf-8,否则将会显示错误的编码,这里提供一种解决方案:
使用notepad++,按如下设置:

好了,现在LCD已经可以显示中文了,但是你以为事情到这就结束了吗,no!!!一个致命的问题来了,这也是我前面说的百度语音埋下的坑。前面我提到,源文件不能使用utf-8编码,是针对源文件中的中文来说的,中文编码若是utf-8,那么你的LCD显示将会是错误的编码,用我上面提供的解决方案可以避免这个问题,但是!相信有人已经猜到了,我们要显示语音识别结果,它可不是直接写在源文件里的,而是我们接收百度语音返回的数据后,解析出来的,而它恰恰就是utf-8编码,你说悲不悲剧,这时上面说的解决方案可不管用了,那么怎么解决?我们下回分解。(不是卖关子,而是写在一起篇幅就太长了)

本次就分享到这吧~