前言
本次我们还是先不实现录音功能,因为音频编解码这一块是比较有难度的,再加上RT-Thread的Audio设备也比较复杂,难以理解,我暂时没考虑好怎么讲解该部分(我自己也没理解透~),所以就放到后面再分享吧。那么这次我们就讲讲项目的第7点:
将中文字库烧写进外挂的spi flash,使用SUFD+FAL软件包读写flash,实现LCD的中文显示,用来显示语音识别结果。
原本我做项目时,是没有打算要在LCD上显示识别结果的,但是因为我使用的潘多拉开发板板载了一块1.3寸的240*240高分辨率TFTLCD显示屏,不把它用上岂不是浪费?再加上后面想到如果不用语音识别来控制外设,识别一些其他语音的时候,有个可以显示识别结果的屏幕,岂不美哉。于是乎我便开始整LCD显示了,不整不知道,一整头皮发麻,RT-Thread官方对于潘多拉开发板在驱动各方面的支持都已经做的非常好了,包括LCD驱动,但是却没有中文字库的支持,也没有相关的资料,所以我就只能自己想办法实现了。
下面我分享的内容,跟百度语音识别没有太大的关系,主要是如何在LCD上显示中文的内容,但百度语音确确实实给我在实现的路上埋下了许多坑,所以我觉得这部分还是很有必要分享的,究竟是怎么一回事,听我细细道来。
准备工作
- 首先你必须完成了系列文章(二)中讲解的内容;
- 阅读正点原子教程中SPI Flash和汉字显示两个例程,确保你清楚汉字显示原理,如何制作字库,如何将字库烧写进flash中;
- 准备好中文字库;
动手实践
嘻嘻~还是为了方便,字库嘛,我使用正点原子裸机例程提供的中文字库(该字库包含了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添加进工程中,开始写代码:
/*********
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; //下一个汉字偏移
}
}
}
上面的代码中实现了三个函数:
get_hz_mat()
show_font()
show_str()
这三个函数都是移植正点原子的,后两个基本没有变化,你只需注意get_hz_mat(),因为它涉及到了flash的读写,而我们这里不再是裸机的读写方式,而是使用FAL软件包提供的API,尤其注意读写的位置,FAL软件包读的是分区相对位置!
有了上面这三个函数,初始化LCD完成后,你就可以使用show_str( )显示中文啦,例:
show_str(120, 220, 200, 16, (rt_uint8_t *)"霹雳大乌龙", 16);
需要注意的是,你的源文件编码不能使用utf-8,否则将会显示错误的编码,这里提供一种解决方案:
使用notepad++,按如下设置:
好了,现在LCD已经可以显示中文了,但是你以为事情到这就结束了吗,no!!!一个致命的问题来了,这也是我前面说的百度语音埋下的坑。前面我提到,源文件不能使用utf-8编码,是针对源文件中的中文来说的,中文编码若是utf-8,那么你的LCD显示将会是错误的编码,用我上面提供的解决方案可以避免这个问题,但是!相信有人已经猜到了,我们要显示语音识别结果,它可不是直接写在源文件里的,而是我们接收百度语音返回的数据后,解析出来的,而它恰恰就是utf-8编码,你说悲不悲剧,这时上面说的解决方案可不管用了,那么怎么解决?我们下回分解。(不是卖关子,而是写在一起篇幅就太长了)
本次就分享到这吧~