> 文档中心 > OpenHarmony学习笔记——Hi386+ASR-01的语音识别助手

OpenHarmony学习笔记——Hi386+ASR-01的语音识别助手

文章目录

  • 前言
  • Hi3861的UART与PWM简介
    • UART简介
    • PWM
  • ASR-01离线语音识别
    • 天问官方介绍
  • 硬件连接
  • 软件部分
    • ASR-01代码
    • Hi3861端代码
      • 初始化资源
      • 串口指令识别
      • 效果一览
  • 总结
  • 目录

前言

本文将介绍Hi3861的UART通信以及PWM控制,并与天问ASR01的离线语音识别模块组成一个离线语音助手,实现语音识别控制的功能。

Hi3861的UART与PWM简介

UART简介

串口通信是一种常用的异步通信方式,其简介笔者在之前的树莓派的UART中有过简介,有需要的可以去查看,这里给大家截个图:
在这里插入图片描述
前面提到过,Hi3861内部是有丰富的接口的,其中UART就有0、1、2三组,本次使用的是UART1,也就是对应GPIO5与GPIO6,在小熊派的底层代码中,有一个wifiiot_uart.h,里面封装了UART相关的API函数接口,有一点类似STM32的HAL库中的接口,在使用过程中不需要像之前的I2C一样首先还要配置GPIO,直接调用UartInit()即可初始化好对应的IO,同样,接收和发送数据也是直接调用接口即可,详细的代码后面会介绍,这里预览一下接口函数名及其功能。
在这里插入图片描述

PWM

PWM控制技术,在日常生活中的使用频率也是很高的,最近的手机产商们在屏幕调光上也运用到了此技术,其实就是一个调整固定时间单元内高低电平的比率的过程。它的简介,笔者也在之前的博客中有过介绍,想要仔细了解可以去笔者智能车浅谈的方向篇查看,大致内容先截个图:在这里插入图片描述
Hi3861的PWM接口函数一览:
在这里插入图片描述

ASR-01离线语音识别

天问官方介绍

ASR-01是一颗专用于语音处理的人工智能芯片,可广泛应用于家电、家居、照明、玩具等产品领域,实现语音交
互及控制。
ASR-ONE内置自主研发的脑神经网络处理器BNPU,支持200条命令词以内的本地语音识别,内置CPU核和高性能低功耗Audio Codec模块,集成多路UART、IIC、PWM、GPIO等外围控制接口,可以开发各类高性价比单芯片智能语音产品方案。
在这里插入图片描述
在这里插入图片描述
通过芯片手册介绍可以发现,该语音识别模块的接口也是非常丰富,其实本文的语音识别助手完全可以有这一个模块单独完成,其内部采用的是FreeRTOS系统框架,对于开发者而言也是很友好的;除此之外,该模块还支持使用天问Block的图形化编程,而且可以一键生成语音模型,再也不用找第三方的字符转MP3的插件了,如果有使用语音识别的需求,笔者建议可以试试这款,比LD3320更便宜,而且更好用;相对于启英泰伦的离线语音,此款有着集成的开发环境,可以直接一键生成语音,加上图像化的编程可以节约很多写BUG的时间,笔者觉着这款模块是离线语音的不二之选。
在这里插入图片描述

硬件连接

通过上面的模块介绍,想必大家也猜到了笔者的思路,使用UART实现ASR-01与Hi3861的通信,利用ASR-01实现语音识别以及交互的工作,HI3861实现控制。
笔者本意是想做一个语音识别的分类识别垃圾桶的,后来发现,Hi3861的API接口只支持最高65535的分频,而Hi3861的时钟频率在160MHz,想要通过直接使用API产生50HZ的舵机控制PWM貌似不太现实,如果想要实现,只能用定时器来实现一个粗糙的效果,细细一想,感觉工作量是有的,所以暂时鸽了,这个方案笔者后面会试试,当然也期待大佬们提供解决方案。本着点灯大师的信仰,笔者觉定将原本的舵机换成LED和无源蜂鸣器。于是就有了下图“基于Hi3861的二号电子垃圾”。
请添加图片描述
上图中的红色PCB是一个光耦隔离模块,本来是打算用来隔离驱动舵机来着,由于上面提到的原因,改成了驱动LED和无源蜂鸣器。
接线表:

Hi3861 ASR01 其他模块
GPIO5 TX
DHT DHT11数据脚
GPIO7 无源蜂鸣器I/O
GPIO12 LED
GPIO13 LED

软件部分

ASR-01代码

硬件连接完毕后,就可以开始喜闻乐见的写BUG时光了,这次先来个简单的吧,换个平台写写ASR-01语音识别端的代码,笔者使用的是图形,直接CV,
有兴趣的同学可以用字符编程模式哈,感觉整个代码的结构和Arduino的有点像。
程序思路是:通过识别语音指令,进行语音播报,然后通过串口发送一个标志数据,笔者这里是“G*”来表示,为的是方便后面的Hi3861解析出指令内容;然后是需要一个DHT11来实现温湿度的采集和播报,这个在图形化的模块里面有,可以直接拖过来用。
这里的代码如下图:
在这里插入图片描述
到此已经完成了AR-01的代码,点击生成模型,然后一键下载,下载完成后,就可以进行语音识别了,这里可以用USB-TTL模块测试一下串口是否输出了对应的指令,这一过程笔者不做介绍,需要的同学自己去倒腾。

Hi3861端代码

Hi3861端的代码思路很简单,就是接收语音识别的指令,然后解析出来,实现对应的操作即可,至于具体的操作,我这里是用的PWM来实现控制的。然后代码框架还是使用新建任务的方式来。
理清思路,接下来开干。

初始化资源

这里需要根据需要初始化UART1以及三路PWM,这里笔者选用了PWM0、PWM2、PWM3。串口初始化不涉及GPIO的那个套娃操作,但是PWM还是需要使用之前的套娃操作,首先初始化GPIO,然后指定复用功能,再然后配置为输出模式,最后初始化对应的PWM接口即可。
具体代码如下:

//初始化GPIO,复用为PWM,注意笔者一开始是想用四路PWM控制四个舵机做分类垃圾桶来着,所以此处初始化了四组PWM。void Garbage_GPIO_Init(void){    //初始化GPIO    GpioInit();    //设置GPIO_7引脚复用功能为PWM    IoSetFunc(WIFI_IOT_IO_NAME_GPIO_7, WIFI_IOT_IO_FUNC_GPIO_7_PWM0_OUT);    //设置GPIO_8引脚复用功能为PWM    IoSetFunc(WIFI_IOT_IO_NAME_GPIO_8, WIFI_IOT_IO_FUNC_GPIO_8_PWM1_OUT);    //设置GPIO_12引脚复用功能为PWM    IoSetFunc(WIFI_IOT_IO_NAME_GPIO_12, WIFI_IOT_IO_FUNC_GPIO_12_PWM3_OUT);    //设置GPIO_13引脚复用功能为PWM    IoSetFunc(WIFI_IOT_IO_NAME_GPIO_13, WIFI_IOT_IO_FUNC_GPIO_13_PWM4_OUT); //设置GPIO_7引脚为输出模式    GpioSetDir(WIFI_IOT_IO_NAME_GPIO_7, WIFI_IOT_GPIO_DIR_OUT);    //设置GPIO_8引脚为输出模式    GpioSetDir(WIFI_IOT_IO_NAME_GPIO_8, WIFI_IOT_GPIO_DIR_OUT);    //设置GPIO_12引脚为输出模式    GpioSetDir(WIFI_IOT_IO_NAME_GPIO_12, WIFI_IOT_GPIO_DIR_OUT);    //设置GPIO_13引脚为输出模式    GpioSetDir(WIFI_IOT_IO_NAME_GPIO_13, WIFI_IOT_GPIO_DIR_OUT);    //初始化PWM0端口    PwmInit(WIFI_IOT_PWM_PORT_PWM0);     //初始化PWM1端口    PwmInit(WIFI_IOT_PWM_PORT_PWM1);      //初始化PWM2端口    PwmInit(WIFI_IOT_PWM_PORT_PWM3);    //初始化PWM3端口    PwmInit(WIFI_IOT_PWM_PORT_PWM4);    //Initialize uart driver}// static const char *data = "Hello, BearPi!\r\n";static void UART_Task(void){    uint32_t ret;//以下代码是配UART的参数,波特率、数据位、停止位    WifiIotUartAttribute uart_attr = { //baud_rate: 9600 .baudRate = 9600, //data_bits: 8bits .dataBits = 8, .stopBits = 1, .parity = 0,    };    Garbage_GPIO_Init();//初始化PWM接口    ret = UartInit(WIFI_IOT_UART_IDX_1, &uart_attr, NULL);    if (ret != WIFI_IOT_SUCCESS)    { printf("Failed to init uart! Err code = %d\n", ret); return;    }    printf("UART Test Start\n");    while (1)    { printf("=======================================\r\n"); printf("*************UART_example**************\r\n"); printf("=======================================\r\n"); //通过串口1接收数据 UartRead(WIFI_IOT_UART_IDX_1, uart_buff_ptr, UART_BUFF_SIZE); Garbage_Uart_Cmd((char *)uart_buff_ptr);//串口指令识别 usleep(500000);    }}static void UART_ExampleEntry(void){    osThreadAttr_t attr;    attr.name = "UART_Task";    attr.attr_bits = 0U;    attr.cb_mem = NULL;    attr.cb_size = 0U;    attr.stack_mem = NULL;    attr.stack_size = UART_TASK_STACK_SIZE;    attr.priority = UART_TASK_PRIO;    if (osThreadNew((osThreadFunc_t)UART_Task, NULL, &attr) == NULL)    { printf("[ADCExample] Falied to create UART_Task!\n");    }}APP_FEATURE_INIT(UART_ExampleEntry);

串口指令识别

再完成所需资源初始化后,可以先验证一下,资源是否初始化成功,有条件的当然是可以直接用示波器查看了,但没有示波器的也可以用这种廉价的逻辑分析仪试试,30不到,大多数的调试都可以解决。
请添加图片描述
再确保资源被正常初始化后,就可以开始写逻辑代码了,这里需要先初始化一个缓存数组用来存放接收到数据内容,然后就是使用string.h和stdlib.h的字符串处理函数来对指令操作,提取出对应的指令,再根据指令执行操作即可。
代码如下:

//这个枚举在整个过程中没起到啥作用,可以忽略。enum{    WAIT,Open_Alarm,    Close_Alarm,    Kitchen_LED_Open,    Kitchen_LED_Close,    Living_LED_Open,    Living_LED_Close,    LED_Add,    LED_Reduce};//检测串口指令void Garbage_Uart_Cmd(char *str)      {    char  *Str;    unsigned char ID=255;    Str=&str[1];//定位到指令的数字部分“G1”    ID=atoi(Str);//将G后面的字符转换成十进制数据。if(strstr((const char *)str,"G")!=NULL)//如果字符串str中包含有“G”{switch(ID){case 1:// 开蜂鸣器step = Open_Alarm;  printf("Open_Alarm\r\n");  PwmStart(WIFI_IOT_PWM_PORT_PWM0, 10000, 50000);break;case 2:// 关蜂鸣器step = Close_Alarm;  printf("Close_Alarm\r\n");  PwmStop(WIFI_IOT_PWM_PORT_PWM0);break;case 3:// 打开厨房灯step = Kitchen_LED_Open;  printf("Kitchen_LED_Open\r\n");  PwmStart(WIFI_IOT_PWM_PORT_PWM4, 30000, 40000);break;case 4://  关闭厨房灯step = Kitchen_LED_Close;   printf("Kitchen_LED_Close\r\n");    PwmStop(WIFI_IOT_PWM_PORT_PWM4);break;     case 5:// 打开客厅灯step = Living_LED_Open;   printf("Living_LED_Open\r\n");   PwmStart(WIFI_IOT_PWM_PORT_PWM3, Livling_LED_Duty, 60000);break;      case 6:// 关闭客厅灯step = Living_LED_Close;   printf("Living_LED_Close\r\n");   PwmStop(WIFI_IOT_PWM_PORT_PWM3);break;     case 7:// 客厅灯变亮step = LED_Add;   PwmStop(WIFI_IOT_PWM_PORT_PWM3);   printf("LED_Add\r\n");   if(Livling_LED_Duty<55000)   Livling_LED_Duty+=10000;   PwmStart(WIFI_IOT_PWM_PORT_PWM3, Livling_LED_Duty, 60000);break;     case 8:// 客厅灯变暗step = LED_Reduce;  PwmStop(WIFI_IOT_PWM_PORT_PWM3);   printf("LED_Reduce\r\n");   if(Livling_LED_Duty>=10000)   Livling_LED_Duty-=10000;  PwmStart(WIFI_IOT_PWM_PORT_PWM3, Livling_LED_Duty, 60000);break;default:printf("%s ERROR",str);  step =  WAIT;break;}}    memset(uart_buff,0,sizeof(uart_buff));//清空缓存数据,避免出错。}

然后编译下载,就可以实现功能了。

效果一览

emmm,CSDN上传视频也太麻烦了,移步去立创EDA查看吧——【毕设】Hi3861语音识别小助手。

总结

至此,一个简易的语音识别助手就完工了,文中如有不妥之处,欢迎匹配指正,有关源码,笔者后面会整理上传至我的资源,或者直接私聊获取。

目录

OpenHarmony学习笔记——南向开发环境搭建
OpenHarmony学习笔记——编辑器访问Linux服务器进行编译
OpenHarmony学习笔记——点亮你的LED
OpenHarmony学习笔记——多线程的创建
OpenHarmony学习笔记——I2C驱动0.96OLED屏幕
OpenHarmony学习笔记——Hi3861使用DHT11获取温湿度
OpenHarmony学习笔记——Hi3861接入OneNET
手把手教你OneNET数据可视化
OpenHarmony学习笔记——Hi386+ASR-01的语音识别助手