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

  • Post author:
  • Post category:其他




前言

本文将介绍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的语音识别助手



版权声明:本文为qq_41954556原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。