博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
软件包应用分享|基于RT-Thread的百度语音识别(二)
阅读量:2111 次
发布时间:2019-04-29

本文共 14249 字,大约阅读时间需要 47 分钟。

本期分享来自RT-Thread的社区小伙伴霹雳大乌龙,如果你也有文章愿意分享/希望获得官方的写作指导,可以发送文章/联系方式邮件至邮箱:xuqianqian@rt-thread.com  

回顾往期:

一、前言

本篇分享的是项目介绍中的第7点:

● 使用SUFD组件+FAL软件包读取存储在外部flash的中文字库,实现LCD的中文显示,用来显示语音识别结果。

为什么要实现这部分?是因为考虑到语音识别的应用可以不仅仅局限于控制简单的外设。最初的想法是,我不用来控制rgb灯了,除了控制台,我还可以用LCD来显示识别结果;待到可以显示识别结果了,更多的应用场景也就随之打开,比如语音显示个天气预报什么的...

二、准备工作

1、首先你必须完成了 连载(一)中讲解的内容;

2、学习汉字显示原理,如何制作字库,如何将字库烧写进flash中(参考正点原子的教程);

3、因为本篇分享的是如何读取字库文件,所以你需要自行将字库文件烧写进外部Flash中(字库文件为正点原子FONT文件夹下的内容);

正点原子提供的中文字库包含了12,16,24,32四种字体大小的字库文件以及unicode和gbk的互换表。这里我为了方便直接运行原子的汉字显示例程,该程序里包含将字库烧写进spi flash的部分。无论你使用何种方式烧写字库,请记住你烧录的地址。

这次我们还将用到RT-Thread的SUFD组件FAL软件包,简单介绍一下:

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

看中文名就知道了,用来驱动spi flash的。SFUD是一种开源的串行SPI Flash通用驱动库,使用这个库,你就不必自己编写flash驱动了,基本市面上绝大多数的flash,都可以轻轻松松地驱动起来,非常方便。

FAL(Flash抽象层)

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

fal软件包的使用是本次工程的重点,但使用fal前我们还需要做些准备工作,对fal进行移植:

  • 首先你需要对 Flash 设备进行定义;

  • 然后定义 Flash 设备表,根据你字库文件在外部Flash的存放位置,为字库文件划分相应的分区;

参考:https://github.com/RT-Thread-packages/fal/blob/master/samples/porting/README.md

三、动手实践

1. ENV配置

● 打开SUFD组件:

● 打开fal软件包:

●使能外部FLASH和LCD

2. 移植fal

2.1 定义flash设备

1/* fal_flash_sufd_port.c */ 2/* 参考自IoT Board SDK */ 3 4#include 
5 6#ifdef FAL_FLASH_PORT_DRIVER_SFUD 7#include 
8#include 
910sfud_flash sfud_norflash0;1112static int fal_sfud_init(void)13{14    sfud_flash_t sfud_flash0 = NULL;15    sfud_flash0 = (sfud_flash_t)rt_sfud_flash_find("qspi10");16    if (NULL == sfud_flash0)17    {18        return -1;19    }2021    sfud_norflash0 = *sfud_flash0;22    return 0;23}2425static int read(long offset, uint8_t *buf, size_t size)26{27    sfud_read(&sfud_norflash0, nor_flash0.addr + offset, size, buf);2829    return size;30}3132static int write(long offset, const uint8_t *buf, size_t size)33{34    if (sfud_write(&sfud_norflash0, nor_flash0.addr + offset, size, buf) != SFUD_SUCCESS)35    {36        return -1;37    }3839    return size;40}4142static int erase(long offset, size_t size)43{44    if (sfud_erase(&sfud_norflash0, nor_flash0.addr + offset, size) != SFUD_SUCCESS)45    {46        return -1;47    }4849    return size;50}51const struct fal_flash_dev nor_flash0 = { "nor_flash", 0, (16 * 1024 * 1024), 4096, {fal_sfud_init, read, write, erase} };52#endif /* FAL_FLASH_PORT_DRIVER_SFUD */

2.2 定义flash设备表

1/* fal_cfg.h */ 2/* 参考自IoT Board SDK */ 3 4#ifndef _FAL_CFG_H_ 5#define _FAL_CFG_H_ 6 7#include 
8#include 
910/* enable SFUD flash driver sample */11#define FAL_FLASH_PORT_DRIVER_SFUD1213extern const struct fal_flash_dev nor_flash0;1415/* flash device table */16#define FAL_FLASH_DEV_TABLE                                          \17{                                                                    \18    &nor_flash0,                                                     \19}20/* ====================== Partition Configuration ========================== */21#ifdef FAL_PART_HAS_TABLE_CFG22/* partition table */23#define FAL_PART_TABLE                                                                                              \24{                                                                                                                   \25    {FAL_PART_MAGIC_WROD,  "easyflash",    "nor_flash",                                    0,       512 * 1024, 0}, \26    {FAL_PART_MAGIC_WROD,   "download",    "nor_flash",                           512 * 1024,      1024 * 1024, 0}, \27    {FAL_PART_MAGIC_WROD, "wifi_image",    "nor_flash",                  (512 + 1024) * 1024,       512 * 1024, 0}, \28    {FAL_PART_MAGIC_WROD,       "font",    "nor_flash",            (512 + 1024 + 512) * 1024,  7 * 1024 * 1024, 0}, \29    {FAL_PART_MAGIC_WROD, "filesystem",    "nor_flash", (512 + 1024 + 512 + 7 * 1024) * 1024,  7 * 1024 * 1024, 0}, \30}31#endif /* FAL_PART_HAS_TABLE_CFG */32#endif /* _FAL_CFG_H_ */

fal_cfg.h 里我们定义了 font 分区,位置为前面我们自己烧录的字库文件地址,适当的空间大小。

3. cn_font.c / cn_port.c / mycc936.c

3.1 cn_port.c
1#include 
2 3_font_info ftinfo = 4{ 5    .ugbkaddr = 0x0000000+sizeof(ftinfo), 6    .ugbksize = 174344, 7    .f12addr=0x0002A908+sizeof(ftinfo), 8    .gbk12size=574560, 9    .f16addr = 0x000B6D68+sizeof(ftinfo),10    .gbk16size = 766080,11    .f24addr = 0x00171DE8+sizeof(ftinfo),12    .gbk24size = 1723680,13    .f32addr = 0x00316B08+sizeof(ftinfo),14    .gbk32size = 306432015};

cn_port.c定义了字库文件相对于font分区的位置及大小信息。

3.2 cn_font.c
1/*********  2cn_font.c  3**********/  4  5#include 
6#include 
7#include 
8#include 
9#include 
10#include 
11#include 
12 13extern _font_info ftinfo; 14 15/* 从字库中找出字模 */ 16void get_hz_mat(unsigned char *code, unsigned char *mat, uint8_t size) 17{     18    unsigned char qh, ql; 19    unsigned char i; 20    unsigned long foffset; 21    const struct fal_partition *partition = fal_partition_find("font"); 22 23    uint8_t csize = (size / 8 + ((size % 8) ? 1 : 0)) * (size); //得到字体一个字符对应点阵集所占的字节数 24    qh =*code; 25    ql = *(++code); 26 27    if(qh < 0x81 || ql < 0x40 || ql == 0xff || qh == 0xff) //非常用汉字 28    { 29        for(i = 0; i < csize; i++)*mat++ = 0x00; //填充满格 30        return; //结束访问 31    } 32    if(ql < 0x7f)ql -= 0x40; //注意! 33    else ql -= 0x41; 34 35    qh -= 0x81; 36    foffset = ((unsigned long)190 * qh + ql) * csize;    //得到字库中的字节偏移量 37 38    switch(size) 39    { 40        case 12: 41                        fal_partition_read(partition, foffset + ftinfo.f12addr, mat, csize); 42            break; 43 44        case 16: 45            fal_partition_read(partition, foffset + ftinfo.f16addr, mat, csize); 46            break; 47 48        case 24: 49            fal_partition_read(partition, foffset + ftinfo.f24addr, mat, csize); 50            break; 51 52        case 32: 53            fal_partition_read(partition, foffset + ftinfo.f32addr, mat, csize); 54            break; 55 56    } 57} 58 59/* 显示一个指定大小的汉字 */ 60void show_font(uint16_t x, uint16_t y, uint8_t *font, uint8_t size) 61{ 62    uint16_t colortemp; 63    uint8_t sta; 64    uint8_t temp, t, t1; 65    uint8_t dzk[128]; 66    uint8_t csize = (size / 8 + ((size % 8) ? 1 : 0)) * (size);            //得到字体一个字符对应点阵集所占的字节数 67 68    if(size != 12 && size != 16 && size != 24 && size != 32)return;    //不支持的size 69 70    get_hz_mat(font, dzk, size);                            //得到相应大小的点阵数据 71 72    if((size == 16) || (size == 24) || (size == 32))    //16、24、32号字体 73    { 74        sta = 8; 75 76        lcd_address_set(x, y, x + size - 1, y + size - 1); 77 78        for(t = 0; t < csize; t++) 79        { 80            temp = dzk[t];            //得到点阵数据 81 82            for(t1 = 0; t1 < sta; t1++) 83            { 84                if(temp & 0x80) colortemp = 0x0000; 85 86                else colortemp = 0xFFFF; 87 88                lcd_write_half_word(colortemp); 89                temp <<= 1; 90            } 91        } 92    } 93    else if(size == 12)    //12号字体 94    { 95        lcd_address_set(x, y, x + size - 1, y + size - 1); 96 97        for(t = 0; t < csize; t++) 98        { 99            temp = dzk[t];            //得到点阵数据100101            if(t % 2 == 0)sta = 8;102103            else sta = 4;104105            for(t1 = 0; t1 < sta; t1++)106            {107                if(temp & 0x80) colortemp = 0x0000;108109                else colortemp = 0xFFFF;110111                lcd_write_half_word(colortemp);112                temp <<= 1;113            }114        }115    }116}117118/* 在指定位置开始显示一个字符串 */119void show_str(uint16_t x, uint16_t y, uint16_t width, uint16_t height, uint8_t *str, uint8_t size)120{121    uint16_t x0 = x;122    uint16_t y0 = y;123    uint8_t bHz = 0;   //字符或者中文124125    while(*str != 0) //数据未结束126    {127        if(!bHz)128        {129            if(*str > 0x80)bHz = 1; //中文130131            else              //字符132            {133                if(x > (x0 + width - size / 2)) //换行134                {135                    y += size;136                    x = x0;137                }138139                if(y > (y0 + height - size))break; //越界返回140141                if(*str == 13) //换行符号142                {143                    y += size;144                    x = x0;145                    str++;146                }147148                else lcd_show_char(x, y, *str, size); //有效部分写入149150                str++;151                x += size / 2; //字符,为全字的一半152            }153        }154        else //中文155        {156            bHz = 0; //有汉字库157158            if(x > (x0 + width - size)) //换行159            {160                y += size;161                x = x0;162            }163164            if(y > (y0 + height - size))break; //越界返回165166            show_font(x, y, str, size); //显示这个汉字,空心显示167            str += 2;168            x += size; //下一个汉字偏移169        }170    }171}

注:这里需要将LCD驱动drv_lcd.c小改一下,将下面两个函数前面的static删除,并添加到drv_lcd.h中。

1static rt_err_t lcd_write_half_word(const rt_uint16_t da);2static void lcd_show_char(rt_uint16_t x, rt_uint16_t y, rt_uint8_t data, rt_uint32_t size);

cn_font.c中实现了三个函数:

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

这三个函数都是移植自正点原子,后两个基本没有变化,你只需注意get_hz_mat(),因为它涉及到了flash的读写,而我们这里不再是裸机的读写方式,而是使用FAL软件包提供的API,尤其注意读写的位置,FAL软件包读的是分区相对位置!有了上面这三个函数,初始化LCD完成后,你就可以使用show_str( )显示中文啦,例:

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

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

3.3 mycc936.c

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

汉字显示的原理简单来说就是根据gbk编码从字库中取出相应的字模进行显示,所以我们需要将解析出的识别结果转换成gbk,这个过程需要经历utf-8 -> unicode -> gbk。utf-8和unicode之间有直接的关系,我们只需根据它们之间的关系,编写转换函数即可。而unicode和gbk两者却没有直接的关系了,想要实现它们的转换,就必须进行查表,unicode和gbk各自对应一个数组,这两个数组组成一个对应表,正点原子的字库文件中也包含了这个表。

那为什么叫mycc936呢,相信学习过FATFS文件系统的小伙伴对cc936这个文件都比较熟悉了,cc936是用来支持长文件名的,该文件里包含了两个数组 oem2uni 和 uni2oem ,其实存放的就是unicode和gbk的互相转换的对照表,同时cc936.c还提供了ff_convert函数实现unicode和gbk的互换。现在我们在外部flash里已经存放了这个表,那我们要做的就是移植cc936.c,实现读取flash中的转换表进行unicode转gbk,rtthread的文件系统已经存在一个cc936.c以及ff_convert函数,所以我把我这个命名为mycc936.c和myff_convert。

1/* mycc936.c */  2  3#include 
4#include 
5#include 
6 7extern _font_info ftinfo; 8 9/**************************/ 10/*****utf-8 转 unicode*****/ 11/**************************/ 12int Utf82Unicode(char* pInput, char* pOutput) 13{ 14    int outputSize = 0; //记录转换后的Unicode字符串的字节数 15    *pOutput = 0; 16    while (*pInput) 17    { 18        if (*pInput > 0x00 && *pInput <= 0x7F) //处理单字节UTF8字符(英文字母、数字) 19        { 20            *pOutput = *pInput; 21             pOutput++; 22            *pOutput = 0; //小端法表示,在高地址填补0 23        } 24        else if (((*pInput) & 0xE0) == 0xC0) //处理双字节UTF8字符 25        //else if(*pInput >= 0xC0 && *pInput < 0xE0) 26        { 27            char high = *pInput; 28            pInput++; 29            char low = *pInput; 30            if ((low & 0xC0) != 0x80)  //检查是否为合法的UTF8字符表示 31            { 32                return -1; //如果不是则报错 33            } 34 35            *pOutput = (high << 6) + (low & 0x3F); 36            pOutput++; 37            *pOutput = (high >> 2) & 0x07; 38        } 39        else if (((*pInput) & 0xF0) == 0xE0) //处理三字节UTF8字符 40        //else if(*pInput>=0xE0 && *pInput<0xF0) 41        { 42            char high = *pInput; 43            pInput++; 44            char middle = *pInput; 45            pInput++; 46            char low = *pInput; 47            if (((middle & 0xC0) != 0x80) || ((low & 0xC0) != 0x80)) 48            { 49                return -1; 50            } 51            *pOutput = (middle << 6) + (low & 0x3F);//取出middle的低两位与low的低6位,组合成unicode字符的低8位 52            pOutput++; 53            *pOutput = (high << 4) + ((middle >> 2) & 0x0F); //取出high的低四位与middle的中间四位,组合成unicode字符的高8位 54        } 55        else //对于其他字节数的UTF8字符不进行处理 56        { 57            return -1; 58        } 59        pInput ++;//处理下一个utf8字符 60        pOutput ++; 61        outputSize +=2; 62    } 63    //unicode字符串后面,有两个\0 64    *pOutput = 0; 65     pOutput++; 66    *pOutput = 0; 67    return outputSize; 68} 69 70 71/**************************/ 72/***单个unicode字符转gbk***/ 73/**************************/ 74unsigned short myff_convert (    /* Converted code, 0 means conversion error */ 75    unsigned short  src,    /* Character code to be converted */ 76    unsigned int    dir     /* 0: Unicode to OEMCP, 1: OEMCP to Unicode */ 77) 78{ 79    const struct fal_partition *partition = fal_partition_find("font"); 80    unsigned short t[2]; 81    unsigned short c; 82    uint32_t i, li, hi; 83    uint16_t n;           84    uint32_t gbk2uni_offset=0;         85 86    if (src < 0x80)c = src;//ASCII,直接不用转换. 87    else  88    { 89         if(dir) //GBK 2 UNICODE 90        { 91            gbk2uni_offset=ftinfo.ugbksize/2;     92        }else   //UNICODE 2 GBK   93        {    94            gbk2uni_offset=0;    95        }     96        /* Unicode to OEMCP */ 97        hi=ftinfo.ugbksize/2;//对半开. 98        hi =hi / 4 - 1; 99        li = 0;100        for (n = 16; n; n--)101        {102            i = li + (hi - li) / 2; 103            fal_partition_read(partition, ftinfo.ugbkaddr+i*4+gbk2uni_offset, (uint8_t*)&t, 4);104            //W25QXX_Read((u8*)&t,ftinfo.ugbkaddr+i*4+gbk2uni_offset,4);//读出4个字节  105            if (src == t[0]) break;106            if (src > t[0])li = i;  107            else hi = i;    108        }109        c = n ? t[1] : 0;       110    }111    return c;112}           113114/**************************/115/*****unicode 转 gbk*******/116/**************************/117void unicode2gbk(uint8_t *pInput,uint8_t *pOutput)118{119    uint16_t temp; 120    uint8_t buf[2];121    while(*pInput)122    {123            buf[0]=*pInput++;124            buf[1]=*pInput++;125             temp=(uint16_t)myff_convert((unsigned short)*(uint16_t*)buf,0);126            if(temp<0X80){*pOutput=temp;pOutput++;}127            else {*(uint16_t*)pOutput=swap16(temp);pOutput+=2;}128        } 129    *pOutput=0;//添加结束符130}

4.使用方式

修改连载(一)中的json解析函数

1void bd_data_parse(uint8_t *data) 2{ 3    cJSON *root = RT_NULL, *object = RT_NULL, *item =RT_NULL; 4 5    root = cJSON_Parse((const char *)data); 6    if (!root) 7    { 8        rt_kprintf("No memory for cJSON root!\n"); 9        return;10    }1112    object = cJSON_GetObjectItem(root, "result");1314    item = object->child;1516    rt_kprintf("\nresult    :%s \r\n", item->valuestring);1718    unsigned short* buf = malloc(128);19    rt_memset(buf,0,128);20    int size = Utf82Unicode(item->valuestring, (char*)buf);21    unsigned char *buffer = malloc(size);22    rt_memset(buffer,0,size);23    unicode2gbk((uint8_t*)buf,(uint8_t*)buffer);2425    show_str(20, 140, 200, 32, (uint8_t*)buffer, 32);2627    if (root != RT_NULL)28        cJSON_Delete(root);29}

用bd命令发送百度官方提供16k.pcm样例:

验证成功。

RT-Thread线上/下活动

1、RT-Thread开发者大会报名】深圳站马上开始!2019年RT-Thread开发者大会将登入成都、上海、深圳与开发者们见面,还有RT-Thread在中高端智能领域的应用、一站式RTT开发工具、打造IoT极速开发模式等干货演讲,期待您的参与!

立即报名

#题外话# 喜欢RT-Thread不要忘了在GitHub上留下你的STAR哦,你的star对我们来说非常重要!链接地址:https://github.com/RT-Thread/rt-thread

你可以添加微信17775982065为好友,注明:公司+姓名,拉进 RT-Thread 官方微信交流群

RT-Thread

让物联网终端的开发变得简单、快速,芯片的价值得到最大化发挥。Apache2.0协议,可免费在商业产品中使用,不需要公布源码,无潜在商业风险。

长按二维码,关注我们

点击“阅读原文”报名开发者大会

转载地址:http://konef.baihongyu.com/

你可能感兴趣的文章
进程和线程的概念、区别和联系
查看>>
CMake 入门实战
查看>>
绑定CPU逻辑核心的利器——taskset
查看>>
Linux下perf性能测试火焰图只显示函数地址不显示函数名的问题
查看>>
c结构体、c++结构体和c++类的区别以及错误纠正
查看>>
Linux下查看根目录各文件内存占用情况
查看>>
A星算法详解(个人认为最详细,最通俗易懂的一个版本)
查看>>
利用栈实现DFS
查看>>
逆序对的数量(递归+归并思想)
查看>>
数的范围(二分查找上下界)
查看>>
算法导论阅读顺序
查看>>
Windows程序设计:直线绘制
查看>>
linux之CentOS下文件解压方式
查看>>
Django字段的创建并连接MYSQL
查看>>
div标签布局的使用
查看>>
HTML中表格的使用
查看>>
(模板 重要)Tarjan算法解决LCA问题(PAT 1151 LCA in a Binary Tree)
查看>>
(PAT 1154) Vertex Coloring (图的广度优先遍历)
查看>>
(PAT 1115) Counting Nodes in a BST (二叉查找树-统计指定层元素个数)
查看>>
(PAT 1143) Lowest Common Ancestor (二叉查找树的LCA)
查看>>