本文将介绍Hi3861的UART通信以及PWM控制,并与天问ASR01的离线语音识别模块组成一个离线语音助手,实现语音识别控制的功能。
串口通信是一种常用的异步通信方式,其简介笔者在之前的树莓派的UART中有过简介,有需要的可以去查看,这里给大家截个图:
前面提到过,Hi3861内部是有丰富的接口的,其中UART就有0、1、2三组,本次使用的是UART1,也就是对应GPIO5与GPIO6,在小熊派的底层代码中,有一个wifiiot_uart.h,里面封装了UART相关的API函数接口,有一点类似STM32的HAL库中的接口,在使用过程中不需要像之前的I2C一样首先还要配置GPIO,直接调用UartInit()即可初始化好对应的IO,同样,接收和发送数据也是直接调用接口即可,详细的代码后面会介绍,这里预览一下接口函数名及其功能。
PWM控制技术,在日常生活中的使用频率也是很高的,最近的手机产商们在屏幕调光上也运用到了此技术,其实就是一个调整固定时间单元内高低电平的比率的过程。它的简介,笔者也在之前的博客中有过介绍,想要仔细了解可以去笔者智能车浅谈的方向篇查看,大致内容先截个图:
Hi3861的PWM接口函数一览:
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 |
硬件连接完毕后,就可以开始喜闻乐见的写BUG时光了,这次先来个简单的吧,换个平台写写ASR-01语音识别端的代码,笔者使用的是图形,直接CV,
有兴趣的同学可以用字符编程模式哈,感觉整个代码的结构和Arduino的有点像。
程序思路是:通过识别语音指令,进行语音播报,然后通过串口发送一个标志数据,笔者这里是“G*”来表示,为的是方便后面的Hi3861解析出指令内容;然后是需要一个DHT11来实现温湿度的采集和播报,这个在图形化的模块里面有,可以直接拖过来用。
这里的代码如下图:
到此已经完成了AR-01的代码,点击生成模型,然后一键下载,下载完成后,就可以进行语音识别了,这里可以用USB-TTL模块测试一下串口是否输出了对应的指令,这一过程笔者不做介绍,需要的同学自己去倒腾。
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的语音识别助手