`` 语音识别与签到系统 近年来,语音识别在语音导航,室内设备控制,人际对话等方面得到了广泛的应用。 我们在今年第1期杂志《为设备添加社交网络功能》中,实现了W5500EVB自己发微博功能。试想如果我们把语音识别与微博签到结合起来,我们上班时,报上姓名,经识别后,摄像头为我们拍张照片,传到新浪微博,这样既能得到我们签到的时间,又能保证是本人签到,可靠高效,同时朋友通过微博能了解到我们上班时的状态,这样是不是很有意思呢? 今天要介绍的就是上面提到的,基于语音识别的微博签到系统,我们用摄像头ov2640拍照,LD3320做语音识别,然后W5500EVB把我们想说的话,以及照片发送到新浪微博。
基于语音识别的微博签到系统设计 a) 单片机:STM32F103RCT6,256K字节Flash,48K字节SRAM,2K字节EEPROM b) 以太网控制器:W5500,SPI接口与单片机相连 (2) 开发工具: IARfor ARM v5.41,这是我们工程所使用的版本。如果使用不同版本的IAR,请对STM的库稍作调整。 (3) 语音识别:LD3320语音识别模块。 (4) 图像生成:OV2640摄像头。 (5) 其他 a) 新浪微博用户名和密码;如若没有,就赶快给你的设备申请一个吧! b) 一根Mini接口的USB线,如图1所示。 c) 一根网线。 d) STM32芯片的串口程序烧录工具,STM官方提供的程序名为:Flash Loader Demo。 图1是系统实物图。
图1系统实物图 首先,我们了解一下整个程序流程,流程图由一个主流程图(见图2)和四个子流程图(图3,图4,图5,图6)组成。在STM32及ov2640初始化完成之后,将进行网络参数配置,根据自己网络的情况配置W5500的IP地址等网络参数,确保W5500能连接外网。然后配置LD3320语音模块,语音模块处于初始状态,将进行写入识别列表,启动语音识别过程,当我们对着麦克风说话的时候,LD3320检测到有语音输入,LD3320将进入中断,在中断中将把我们说的内容与寄存器里的词条比较,如果找到1-4个候选答案,返回“找到识别结果”状态,如果没有找到候选答案,返回“未找到识别结果”状态。在下一次循环中,LD3320如果是“找到识别结果”状态,将拍摄照片及发送微博,如果是“未找到识别结果”状态,将进入初始状态,如果是“正在识别”或者“识别错误”将重新检查LD3320的状态。各个子流程图描述的比较详尽,这里不再一一赘述。对于拍摄照片子流程图,我们需要了解jpg图片的数据格式,图片的前两个字节是0xff,0xd8,最后两个字节是0xff,0xd9,在中断程序接收图片数据的过程中,首先判断数据是不是前两个字节,如果是,保存数据,后面的数据是先保存,然后判断是不是数据结尾,直到接收成功。
图2 系统主流程图
图3写入识别列表函数流程图 图4启动语音识别模式流程图
图5 拍摄照片流程图
图6发送微博流程图 以上四个子流程图,已清晰地给大家展示语音识别微博签到系统的整个工作流程,那么接下来就为大家揭开详细的制作过程。 LD3320介绍 1 通过快速而稳定的优化算法,完成非特定人语音识别,识别准确率95%。 2 不需要外接任何辅助的Flash芯片,RAM芯片和AD芯片,就可以完成语音识别功能。 3 每次识别最多可以设置50项候选识别句,每个识别句可以是单字,词组或短句,长度为不超过10个汉字或者79个字节的拼音串。识别句内容还可以动态编辑修改。 4 芯片内部已经准备了16位A/D转换器、16位D/A转换器和功放电路,麦克风、立体声耳机和单声道喇叭可以很方便地和芯片管脚连接。 5 支持并行和串行接口,串行方式可以简化与其他模块的连接。 在本系统中采用的LD3320模块如图7,LD3320芯片外部已经连接了麦克风,耳机接口,基本电路,只引出了我们需要的引脚。本系统采用串行方式,串行接口通过SPI协议和外部主CPU连接,首先要将MD接高电平,将SPIS接地,选定LD3320工作在串行模式,此时使用的管脚有:片选(SCS*)、SPI时钟(SDCK)、SPI输入(SDI)和SPI输出(SDO),中断引脚(INT),复位引脚(RST),时钟引脚(CLK),通过SPI接口,配置LD3320的工作模式,读取识别结果,图8,图9为SPI读写时序。当LD3320识别到有语音输入,INT引脚将产生中断,在中断处理函数中,读取识别结果,改变LD3320状态。 图7LD3320语音模块
图8 SPI方式读时序
图9 SPI方式写时序 在本系统中,OV2640输出JPEG压缩图像格式。MCU与OV2640的通信采用串行与并行结合,OV2640带有SCCB(Serial Camera Control Bus)双线串行接口,MCU通过SCCB接口配置和读取OV2640的信息;MCU通过并行总线的方式来接收OV2640的图像数据。Y(2..9)为8位MSB(MostSignificant Bit,最高有效位模式)并行总线,SDIO、SCLK为SCCB接口,PCLK为像素时钟输出管脚(每个周期从并行总线上输出一个像素),VSYNC为列同步输出管脚(每帧图像发生一次跳变),HERF为行参考输出管脚(每个周期总线从并行总线上输出一行图像数据)。系统的硬件电路连接简图如图10。
图10系统硬件电路连接简图 系统上电后,MCU配置OV2640的工作方式,初始化LD3320,然后检查LD3320的状态,当LD3320的状态是“找到识别结果”,开启OV2640中断,在OV2640准备好图像后,VSYNC会被拉高一段时间,MCU通过PCLK上升沿中断按字节接收图像数据,接收数据完成,关闭OV2640中断。然后向新浪微博发送已经写进程序里的自己想说的话和接收到的图片。接下来将对主要的程序块做介绍。
程序介绍 在《为你的设备添加社交网络功能》中,已经详细介绍了OV2640的初始化配置程序,本篇文章就不再赘述,图像数据缓存程序与本文稍有不同,这里简单介绍图像数据缓存程序。本文对LD3320的写入词条列表,启动语音识别,中断处理程序,发送微博程序做主要介绍。 图像数据缓存程序(摘至stm32f10x_it.c):
- <div align="left"><font face="Geneva, Verdana,">void EXti0_IRQHandler(void)</font></div><div align="left"><font face="Geneva, Verdana,">{</font></div><div align="left"><font face="Geneva, Verdana,">u8 temp;</font></div><div align="left"><font face="Geneva, Verdana,">EXTI_ClearITPendingBit(EXTI_Line0); //清除EXTI0线路挂起位</font></div><div align="left"><font face="Geneva, Verdana,"> if(GPIO_ReadInputDataBit(GPIOC,GPIO_Pin_1)==0)return;//HREF管脚为低</font></div><div align="left"><font face="Geneva, Verdana,"> temp=(u8)((GPIOC->IDR)>>8 & 0x00ff); //读取一个字节图像数据</font></div><div align="left"><font face="Geneva, Verdana,">switch(jpg_flag)</font></div><div align="left"><font face="Geneva, Verdana,"> {</font></div><div align="left"><font face="Geneva, Verdana,">case 0:</font></div><div align="left"><font face="Geneva, Verdana,"> if(temp==0xff) //图像数据以0xff 0xd8开头</font></div><div align="left"><font face="Geneva, Verdana,"> {</font></div><div align="left"><font face="Geneva, Verdana,">JPEGBuffer[0]=0xff;</font></div><div align="left"><font face="Geneva, Verdana,">jpg_flag=1;</font></div><div align="left"><font face="Geneva, Verdana,"> }</font></div><div align="left"><font face="Geneva, Verdana,">break;</font></div><div align="left"><font face="Geneva, Verdana,">case 1:</font></div><div align="left"><font face="Geneva, Verdana,">if(temp==0xd8)</font></div><div align="left"><font face="Geneva, Verdana,"> {</font></div><div align="left"><font face="Geneva, Verdana,">JPEGBuffer[1]=0xd8;</font></div><div align="left"><font face="Geneva, Verdana,">jpg_flag=2;</font></div><div align="left"><font face="Geneva, Verdana,">JPEGCnt=2;</font></div><div align="left"><font face="Geneva, Verdana,"> }</font></div><div align="left"><font face="Geneva, Verdana,">else if(temp!=0xff)</font></div><div align="left"><font face="Geneva, Verdana,">jpg_flag=0;</font></div><div align="left"><font face="Geneva, Verdana,">break;</font></div><div align="left"><font face="Geneva, Verdana,">case 2:</font></div><div align="left"><font face="Geneva, Verdana,">JPEGBuffer[JPEGCnt++] =temp; //存储数据</font></div><div align="left"><font face="Geneva, Verdana,">if(temp==0xff)jpg_flag=3;</font></div><div align="left"><font face="Geneva, Verdana,">break;</font></div><div align="left"><font face="Geneva, Verdana,">case 3:</font></div><div align="left"><font face="Geneva, Verdana,">JPEGBuffer[JPEGCnt++]=temp; //图像数据以0xff0xd9结尾</font></div><div align="left"><font face="Geneva, Verdana,">if(temp==0xd9)</font></div><div align="left"><font face="Geneva, Verdana,"> {</font></div><div align="left"><font face="Geneva, Verdana,">jpg_flag=4;</font></div><div align="left"><font face="Geneva, Verdana,"> }</font></div><div align="left"><font face="Geneva, Verdana,">else if(temp!=0xff)</font></div><div align="left"><font face="Geneva, Verdana,">jpg_flag=2;</font></div><div align="left"><font face="Geneva, Verdana,">break;</font></div><div align="left"><font face="Geneva, Verdana,">case 4:</font></div><div align="left"><font face="Geneva, Verdana,">break;</font></div><div align="left"><font face="Geneva, Verdana,"> }</font></div><div align="left"><font face="Geneva, Verdana,">}</font></div>
复制代码
在中断函数中通过以上程序即可正确读取每一帧图像的数据了。程序思想已经在拍摄照片流程图中体现。JPEGBuffer为一个全局的图像缓存区,在主函数中,检测到缓存区数据准备完毕后,就可以将图像发送给新浪微博了。 LD3320添加词条程序(摘至LD3320_main.c)
- <div align="left"><font face="Geneva, Verdana,">uint8 LD_AsrAddFixed(void)</font></div><div align="left"><font face="Geneva, Verdana,">{</font></div><div align="left"><font face="Geneva, Verdana,">uint8 k, flag;</font></div><div align="left"><font face="Geneva, Verdana,">uint8nAsrAddLength;</font></div><div align="left"><font face="Geneva, Verdana,">#define DATE_A 4 /*数组二维数值*/</font></div><div align="left"><font face="Geneva, Verdana,">#define DATE_B 20 /*数组一维数值*/</font></div><div align="left"><font face="Geneva, Verdana,">uint8 sRecog[DATE_A][DATE_B] = {</font></div><div align="left"><font face="Geneva, Verdana,"> “wenjuan”,</font></div><div align="left"><font face="Geneva, Verdana,"> “guocui”,</font></div><div align="left"><font face="Geneva, Verdana,"> ”jierui”,</font></div><div align="left"><font face="Geneva, Verdana,"> “chenge”</font></div><div align="left"><font face="Geneva, Verdana,"> }; /*添加关键词*/</font></div><div align="left"><font face="Geneva, Verdana,"> uint8 pCode[DATE_A] = {</font></div><div align="left"><font face="Geneva, Verdana,"> CODE_wenjuan,</font></div><div align="left"><font face="Geneva, Verdana,"> CODE_guocui,</font></div><div align="left"><font face="Geneva, Verdana,"> CODE_jierui,</font></div><div align="left"><font face="Geneva, Verdana,"> CODE_chenge</font></div><div align="left"><font face="Geneva, Verdana,"> }; /*添加识别码*/</font></div><div align="left"><font face="Geneva, Verdana,">flag = 1;</font></div><div align="left"><font face="Geneva, Verdana,">for (k=0; k<date_a; k++)[="" font][="" align]<div align="" left"=""><font face="Geneva," verdana,"="">{[="" verdana,]if(ld_check_asrbusyflag_b2()="=" 0)[="" verdana,]="" {[="" flag="0;</font></div></font><div align="left"><font face="Geneva, Verdana,">[font=Geneva," break;[="" }[="" verdana,]ld_writereg(0xc1,="" pcode[k]="" );[="" verdana,]ld_writereg(0xc3,="" 0="" verdana,]ld_writereg(0×08,="" 0×04);[="" verdana,]ld3320_delay(1);[="" 0×00);[="" verdana,]for="" (nasraddlength="0;" nasraddlength0&&nAsrResCount<=4)</font></div><div align="left"><font face="Geneva, Verdana,"><b> {</b></font></div><div align="left"><font face="Geneva, Verdana,"><b>nAsrStatus=LD_ASR_FOUNDOK; </b></font></div><div align="left"><font face="Geneva, Verdana,"><b> }</b></font></div><div align="left"><font face="Geneva, Verdana,"><b>else</b></font></div><div align="left"><font face="Geneva, Verdana,"><b> {</b></font></div><div align="left"><font face="Geneva, Verdana,"><b>7nAsrStatus=LD_ASR_FOUNDZERO;</b></font></div><div align="left"><font face="Geneva, Verdana,"><b> } </b></font></div><div align="left"><font face="Geneva, Verdana,"><b>}</b></font></div><div align="left"><font face="Geneva, Verdana,"><b>else</b></font></div><div align="left"><font face="Geneva, Verdana,"><b>{</b></font></div><div align="left"><font face="Geneva, Verdana,"><b>8 nAsrStatus=LD_ASR_FOUNDZERO; //执行没有识别</b></font></div><div align="left"><font face="Geneva, Verdana,"><b>}</b></font></div><div align="left"><font face="Geneva, Verdana,"><b>LD_WriteReg(0x2b,0);</b></font></div><div align="left"><font face="Geneva, Verdana,"><b>LD_WriteReg(0x1C,0);/*写0:ADC不可用*/</b></font></div><div align="left"><font face="Geneva, Verdana,"><b>LD_WriteReg(0×29,0);</b></font></div><div align="left"><font face="Geneva, Verdana,"><b>LD_WriteReg(0×02,0);</b></font></div><div align="left"><font face="Geneva, Verdana,"><b>LD_WriteReg(0x2B,0);</b></font></div><div align="left"><font face="Geneva, Verdana,"><b>LD_WriteReg(0xBA,0); </b></font></div><div align="left"><font face="Geneva, Verdana,"><b>LD_WriteReg(0xBC,0); </b></font></div><div align="left"><font face="Geneva, Verdana,"><b>LD_WriteReg(0×08,1); /*清除FIFO_DATA*/</b></font></div><div align="left"><font face="Geneva, Verdana,"><b>LD_WriteReg(0×08,0); /*清除FIFO_DATA后再次写0*/</b></font></div><div align="left"><font face="Geneva, Verdana,"><b>}</b></font></div></div>
复制代码
中断处理函数的第1行读取中断请求编号寄存器0x2B的值,第4位:读取值为1表示语音识别有结果产生;MCU可清零。第2位:读取值为1表示芯片内部FIFO中断发生。MP3播放时会产生中断标志请求外部MCU向FIFO_DATA中Reload数据。第3位:读取值为1表示芯片内部已经出现错误。值得注意的是:如果在中断响应时读到这位为1,需要对芯片进行重启Reset,才可以继续工作。第2,3行关闭LD3320的中断。第4行,读取中断请求编号寄存器0x2B的值,当第4位读取值为1表示语音识别有结果产生,其次读取语音识别过程中DSP忙闲状态寄存器0xb2,读取到0×21 表示闲,然后读取语音识别状态报告寄存器0xbf的值,读到数值为0×35,可以确定是一次语音识别流程正常结束,当这三个寄存器的数值不满足以上要求的时候,返回“LD_ASR_FOUNDZERO”,表示未找到识别结果。当满足以上要求时,第5行,读取中断辅助信息寄存器,其中的数值表示语音识别有几个识别候选,当数值为1 – 4: 表示有N个识别候选,数值为0或者大于4表示没有识别候选,当有识别候选的时候,返回“LD_ASR_FOUNDOK”,表示找到语音识别结果。 发送微博程序(摘至weibo.c)
- <div align="left"><font face="Geneva, Verdana,">unsigned char post_weibo_upload(char* weibo, uint8* pic,uint32 picLen)</font></div><div align="left"><font face="Geneva, Verdana,">{</font></div><div align="left"><font face="Geneva, Verdana,">unsigned char ret=0;</font></div><div align="left"><font face="Geneva, Verdana,">unsignedintlen=0;</font></div><div align="left"><font face="Geneva, Verdana,">1 if(socket(SOCK_WEIBO,Sn_MR_TCP,any_local_port++,0)!=1) //to initialize a TCP socket</font></div><div align="left"><font face="Geneva, Verdana,">{</font></div><div align="left"><font face="Geneva, Verdana,">printf(“Socket initialization failed.”);</font></div><div align="left"><font face="Geneva, Verdana,">return 0;</font></div><div align="left"><font face="Geneva, Verdana,">}</font></div><div align="left"><font face="Geneva, Verdana,">else</font></div><div align="left"><font face="Geneva, Verdana,">{</font></div><div align="left"><font face="Geneva, Verdana,">printf(“Connect with Weibo server.”);</font></div><div align="left"><font face="Geneva, Verdana,">2 ret=connect(SOCK_WEIBO,weibo_server_ip,80); //connect to the weibo server, default TCPport is 80</font></div><div align="left"><font face="Geneva, Verdana,">if(ret!=1)</font></div><div align="left"><font face="Geneva, Verdana,">{</font></div><div align="left"><font face="Geneva, Verdana,">printf(“Connect Weibo server failed.”);</font></div><div align="left"><font face="Geneva, Verdana,">return 0;</font></div><div align="left"><font face="Geneva, Verdana,">}</font></div><div align="left"><font face="Geneva, Verdana,">else</font></div><div align="left"><font face="Geneva, Verdana,">{</font></div><div align="left"><font face="Geneva, Verdana,">3while(getSn_SR(SOCK_WEIBO)!=SOCK_ESTABLISHED); //wait for the TCP connection established!</font></div><div align="left"><font face="Geneva, Verdana,">printf(“Connected with Weiboserver.”);</font></div><div align="left"><font face="Geneva, Verdana,">4 sprintf(post_data,”–%sContent-Disposition:form-data; name=”id”%s”</font></div><div align="left"><font face="Geneva, Verdana,">“–%sContent-Disposition:form-data; name=”pw”%s”</font></div><div align="left"><font face="Geneva, Verdana,">“–%sContent-Disposition:form-data; name=”cmd”upload”</font></div><div align="left"><font face="Geneva, Verdana,">“–%sContent-Disposition:form-data; name=”status”%s”</font></div><div align="left"><font face="Geneva, Verdana,">“–%sContent-Disposition:form-data; name=”file”; filename=”pic.jpg”Content-Type:application/octet-stream”,(char*)BOUNDARY,(char*)WEIBO_ID,(char*)BOUNDARY,(char*)WEIBO_PWD,(char*)BOUNDARY,(char*)BOUNDARY,weibo,(char*)BOUNDARY);//”–%s–”</font></div><div align="left"><font face="Geneva, Verdana,">5 sprintf(tmp_buf,”POST %sHTTP/1.1Host: %sUser-Agent: w5500Content-Type: multipart/form-data;boundary=%sConnection:closeContent-Length:%d%s”,(char*)HTTP_PATH,(char*)WEIBO_SERVER,(char*)BOUNDARY,strlen(post_data)+picLen+strlen((char*)BOUNDARY)+8,post_data);</font></div><div align="left"><font face="Geneva, Verdana,">6 len=send(SOCK_WEIBO,(unsignedchar*)tmp_buf,strlen(tmp_buf)); //upload your weibo content</font></div><div align="left"><font face="Geneva, Verdana,">uint16 file_len=picLen;</font></div><div align="left"><font face="Geneva, Verdana,">uint16 send_len=0;</font></div><div align="left"><font face="Geneva, Verdana,">while(file_len)</font></div><div align="left"><font face="Geneva, Verdana,">{</font></div><div align="left"><font face="Geneva, Verdana,">if(file_len>PACKET_LEN)</font></div><div align="left"><font face="Geneva, Verdana,">{</font></div><div align="left"><font face="Geneva, Verdana,">if(getSn_SR(SOCK_WEIBO)!=SOCK_ESTABLISHED)</font></div><div align="left"><font face="Geneva, Verdana,">{</font></div><div align="left"><font face="Geneva, Verdana,">return 0;</font></div><div align="left"><font face="Geneva, Verdana,">}</font></div><div align="left"><font face="Geneva, Verdana,">7send(SOCK_WEIBO, (uint8*)(pic+send_len), PACKET_LEN); // upload picture</font></div><div align="left"><font face="Geneva, Verdana,">send_len+=PACKET_LEN;</font></div><div align="left"><font face="Geneva, Verdana,">file_len-=PACKET_LEN;</font></div><div align="left"><font face="Geneva, Verdana,">}</font></div><div align="left"><font face="Geneva, Verdana,">else</font></div><div align="left"><font face="Geneva, Verdana,">{</font></div><div align="left"><font face="Geneva, Verdana,">8send(SOCK_WEIBO, (uint8*)(pic+send_len), file_len);// uploadpicture</font></div><div align="left"><font face="Geneva, Verdana,">send_len+=file_len;</font></div><div align="left"><font face="Geneva, Verdana,">file_len-=file_len;</font></div><div align="left"><font face="Geneva, Verdana,">}</font></div><div align="left"><font face="Geneva, Verdana,">}</font></div><div align="left"><font face="Geneva, Verdana,">sprintf(tmp_buf,”–%s–”,(char*)BOUNDARY);</font></div><div align="left"><font face="Geneva, Verdana,">send(SOCK_WEIBO,(unsigned char*)tmp_buf,strlen(tmp_buf));</font></div><div align="left"><font face="Geneva, Verdana,">while(1)</font></div><div align="left"><font face="Geneva, Verdana,">{</font></div><div align="left"><font face="Geneva, Verdana,">9len=getSn_RX_RSR(SOCK_WEIBO);</font></div><div align="left"><font face="Geneva, Verdana,">if(len>0)</font></div><div align="left"><font face="Geneva, Verdana,">{</font></div><div align="left"><font face="Geneva, Verdana,">memset(tmp_buf,0×00,MAX_BUF_SIZE);</font></div><div align="left"><font face="Geneva, Verdana,">10len=recv(SOCK_WEIBO,(unsigned char*)tmp_buf, len); //receive thereturn result from weibo server</font></div><div align="left"><font face="Geneva, Verdana,">11char*p=strstr(tmp_buf,(char*)””)+4; //gethttp payload without http headerprintf(“%s”,p);</font></div><div align="left"><font face="Geneva, Verdana,">disconnect(SOCK_WEIBO); //disconnect with weibo server</font></div><div align="left"><font face="Geneva, Verdana,">close(SOCK_WEIBO); //close the socket</font></div><div align="left"><font face="Geneva, Verdana,">return 1; //sucess! return 1</font></div><div align="left"><font face="Geneva, Verdana,">}</font></div><div align="left"><font face="Geneva, Verdana,">}</font></div><div align="left"><font face="Geneva, Verdana,">}</font></div><div align="left"><font face="Geneva, Verdana,">}</font></div><div align="left"><font face="Geneva, Verdana,">}</font></div>
复制代码
发送微博函数的第1行,初始化一个socket,第2行,对服务器发出连接请求,第3行一直等待连接的建立。与服务器建立连接后,第4,5行负责组建带有微博内容和图片长度的HTTP数据包,第6行负责发送微博内容,第7,8行发送图片数据。第9行是读取W5500接收到的数据长度,第10行从W5500的接收缓存中把接收到的数据读到tmp_buf中。由于接收到的数据包含了HTTP头,第11行是把HTTP头去掉,得到服务器的返回结果。服务器返回结果的类型请参看《为你的设备添加社交网络功能》一文。 好了,代码就这么多,赶快编译烧到单片机里面吧,上电,对着麦克风说出一句已经写到LD3320里的话,当对应的指示灯亮或者闪烁,说明已经识别成功,然后对着摄像头微笑吧,这时摄像头为我们拍张照片,上传微博,然后看串口调试信息,如果收到“255:ok”,那就成功了,登录到微博看看,写进程序里的话以及自己的照片出现在微博上面。如图11。
图11系统发送微博效果图 至此,我们的基于语音识别的微博签到系统已经大功告成,你心动了吗?赶快制作你自己的微博签到系统吧。
源代码请参考附件。
``
0
|
|
|
|