本文共 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软件包,简单介绍一下:
看中文名就知道了,用来驱动spi flash的。SFUD是一种开源的串行SPI Flash通用驱动库,使用这个库,你就不必自己编写flash驱动了,基本市面上绝大多数的flash,都可以轻轻松松地驱动起来,非常方便。
简单来说,使用该软件包,你可以方便地使用API对flash进行分区管理,读写操作等,支持自定义分区表,不得不说,很强!
fal软件包的使用是本次工程的重点,但使用fal前我们还需要做些准备工作,对fal进行移植:
首先你需要对 Flash 设备进行定义;
然后定义 Flash 设备表,根据你字库文件在外部Flash的存放位置,为字库文件划分相应的分区;
参考:https://github.com/RT-Thread-packages/fal/blob/master/samples/porting/README.md
三、动手实践
● 打开SUFD组件:
● 打开fal软件包:
●使能外部FLASH和LCD
2.1 定义flash设备
1/* fal_flash_sufd_port.c */ 2/* 参考自IoT Board SDK */ 3 4#include5 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#include8#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 分区,位置为前面我们自己烧录的字库文件地址,适当的空间大小。
1#include2 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分区的位置及大小信息。
1/********* 2cn_font.c 3**********/ 4 5#include6#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++,按如下设置:
现在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#include4#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}
修改连载(一)中的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/