创客项目秀 | 基于 XIAO 开发板的语音向导

背景

柴火创客空间作为大湾区科技创新的窗口,每年到访空间的社区伙伴众多,为了更好的进行空间信息交互,我们希望有一个装置是可以解决:当空间管理员不在现场的时候,到访者可以通过装置获得清晰的介绍与引导。

为了解决这个问题,K同学设计了一个智能语音识别系统,它作为一个智能语音向导,能够向用户介绍产品和项目,同时引导他们参观柴火创客空间。这个系统利用XIAO ESP32S3微控制器和Edge Impulse平台进行语音识别。当用户说出特定的语音指令时,系统能够识别并执行相应的操作,例如介绍产品、指引方向等。

材料清单

  • XIAO ESP32S3 Sense
  • MP3 V4 模块
  • DIP 人体感应模块
  • 电脑音响
  • 按钮

软件

  • Arduino IDE
  • Edge Impluse

Edge Impluse 介绍

 

Edge Impulse是一个专为边缘设备和嵌入式系统开发机器学习模型的平台。 它提供了一套全面的工具和服务,使开发人员能够快速创建、训练和部署机器学习模型,而无需深入了解机器学习。

该平台提供了一系列全面的工具和服务,帮助开发人员迅速构建、训练和部署机器学习模型,无需深入了解机器学习原理。 Edge Impulse提供的数据收集工具能轻松地从各种传感器和设备中收集数据,并上传至平台进行管理和标注。 此外,Edge Impulse还提供了一系列预处理和特征提取算法,能自动处理原始数据并提取有用的特征,为模型的训练做好准备。 一旦模型训练完成,可以轻松地部署到各种边缘设备和嵌入式系统上,包括Arduino、树莓派和各类微控制器。Edge Impulse提供多种部署选项,例如生成优化的C++代码、二进制文件或自定义SDK。

Edge Impulse的一大优势是其用户友好性和易用性。通过直观的图形界面和引导式工作流程,即使是机器学习初学者也能快速上手,创建出高质量的机器学习模型。 此外,Edge Impulse还提供了大量的教程、示例项目和社区支持,帮助开发人员学习和分享知识。它与各种硬件平台和传感器生态系统无缝集成,使得在边缘设备上部署机器学习变得更加简单。 总的来说,Edge Impulse是一个强大的平台,它降低了机器学习的门槛,使得开发和部署智能应用程序在边缘设备上变得更加简单高效。无论您是初学者还是经验丰富的开发人员,Edge Impulse都能帮助您创建出创新的物联网和嵌入式智能解决方案。

XIAO ESP32S3 Sense 介绍

特征:

强大的MCU板:集成ESP32S3 32位双核Xtensa处理器芯片,工作频率高达240 MHz,安装多个开发端口,支持Arduino / MicroPython

高级功能:可拆卸的OV2640摄像头传感器,分辨率为1600*1200,兼容OV5640摄像头传感器,集成附加数字麦克风

大内存带来更多可能性:提供8MB PSRAM和8MB闪存,支持SD卡插槽用于外部32GB FAT内存

出色的射频性能:支持2.4GHz Wi-Fi和BLE双无线通信,连接U.FL天线时支持100m+远程通信

拇指大小的紧凑型设计:21 x 17.5mm,采用XIAO的经典外形,适用于可穿戴设备等空间有限的项目

 

语音识别模型

采集(本地)音频数据

可以使用手机,电脑等可以录音的设备进行录音,值得一提的是XIAO ESP32S3也可以进行录音并存储到SD卡上, 我们需要录制“你好”,“Hello”和背景的三种音频样本

PS:1. 如果用手机,电脑录音的话请记住要将文件命名类似为“hello.1”“hello.2”“hello.3”“noise.1”...等等

  1. 文件格式需要为WAV

 

不过也可以用XIAO ESP32S3 进行录音:

设置硬件

将 microSD 卡插入 microSD 卡插槽。注意插入方向,金手指的一面应朝内,如下图所示。

 

 

用数据线将开发板连接到电脑的USB接口上,如下图所示。

打开Arduino IDE软件,选择 工具 》PSRAM:”OPI PSRAM” 》OPI PSRAM ,如下图所示。

3.1.2 上传录音采集程序

利用XIAO ESP32S3 Sense 开发板采集音频数据,并将音频数据以wav格式转存到microSD卡上。

录音采集程序.zip 下载解压缩录音采集程序文件后,用Arduino IDG软件打开此程序。

步骤如下:

打开录音程序,并上传到XIAO ESP32S3 Sense 开发板上

 上传前,先设置开发板类型和端口号,然后单击上传图标,上传录音程序。

  1. 等待数秒后,录音程序上传成功。

3.1.3 采集hello音频样本

假设要采集三个音频,将其分别命名为hello 、stop和other三个标签,每一个标签代表一种关键词;比如建立一个hello标签,并多次采集hello声音,这样就建立一个hello标签的音频样本,采集步骤如下:

  1. 在Arduino IDE软件录音程序中,单击右上角的“串口监视器”图标,打开串口监视器。

  1. 在串口监视器文本框中输入hello分类标签并按键盘回车键,这样就建立了一个分类。

  1. 在串口监视器文本框中输入“rec”命令并回车,这时进入录音模式,请对着开发板说hello,多说几次会采集10秒钟音频。

  1. hello音频采集完成后,会有提示写入文件,再次采集可以继续输入rec命令再次采集hello音频。

  1. 在串口监视器文本框中输入rec命令并回车,进入hello分类录音模式。

  1. 对着XIAO开发板说hello,多说几次大概10秒钟时间,看到提示写入文件就完成了。

建议:您为每个标签样本提供足够大的声音。每次录音提供10秒钟录音时间,录制过程中多次重复您的关键词,关键字之间需要有一定的间隔时间。

采集stop音频样本

通过rec命令采集了5次hello音频样本,接着在串口监视器的文本框中输入stop,就会生成一个新的分类标签,再输入rec命令录制stop音频样本,步骤如下:

  1. 在串口监视器文本框中输入stop命令

  1. 接着串口监视器文本框中输入rec命令,进入录音模式。

  1. 进入录音模式后,对着XIAO开发板说stop,多说几次需要采集10秒钟,通过多次输入rec命令,就可以多次采集stop音频,这里采集5次。

3.1.5 采集other音频样本

通过rec命令采集了5次stop音频样本,接着在串口监视器的文本框中输入other,就会生成一个新的分类标签,再输入rec命令录制other音频样本,other音频样本可以录制背景音或者杂音,步骤如下:

  1. 在串口监视器文本框中输入other,生成一个新的分类标签。

  1. 接着在串口监视器文本框中输入rec命令,进入录音模式。

  1. 进入录音模式后,对着XIAO开发板录制背景杂音,通过多次输入rec命令,就可以多次采集other音频样本了,这里采集5次。

3.1.6 导出SD卡音频样本

通过录音程序采集了hello 、stop和other三种分类的音频样本,每个分类又至少采集了5次10秒的音频数据,这些数据被转存到了SD卡上,接下来需要将SD卡上的音频文件拷贝到电脑上。

  1. 将XIAO开发板上的SD卡取出,插入到SD卡读卡器中,并插入电脑USB接口上。

  1. 在电脑中打开SD卡盘符,可以看到采集的音频文件,比如hello1.wav、hello2.wav的音频

  1. 在电脑D盘建一个sound文件夹,将SD卡中的音频文件全部复制到此文件夹中

3.2 使用Edge Impulse 训练数据集 ,在XIAO ESP32S3 Sense 部署语音关键词识别模型

3.2.1 上传收集的声音数据

使用Edge Impulse 训练数据集 ,在XIAO ESP32S3 Sense 部署语音关键词识别模型

3.2.1 上传收集的声音数据

上传收集的声音数据,步骤如下:

  1. 进入Edge Impulse 网站,注册一个登录账号,进入后点击右上角账户名称,单击【创建新项目】选项。

  1. 弹出创建一个新项目窗口,在输入新项目的名称中输入”kws”,然后单击右下角的【创建新项目】按钮

  1. 进入kws项目窗口,然后单击添加现有数据 【Add existing data】选项。

  1. 弹出添加现有数据窗口,单击【Upload data】选项。

  1. 进入上传数据窗口,单击【选择文件】按钮,

  1. 打开文件选择窗口,找到存储音频样本的sound文件夹,全部选中然后单击【打开】按钮。

  1. 接着单击上传数据【Upload data】按钮。

  1. 在上传数据窗口的右侧,可以看到上传数据成功了,然后单击右上角的关闭窗口图标。

  1. 可以在左侧数据采集【Data acquisition】菜单中,看到上传的音频数据的每一条的具体内容。

3.2.2 拆分数据

训练数据用到的数据都是1秒钟时间,但是采集的音频样本 10 秒,必须拆分为 1s 样本才能兼容。

  1. 先选中一个音频样本比如stop2,单击右侧3个点图标,在弹出菜单中单击【Split sample】分割样本选项。

  1. 弹出分割窗口,可以看到会自动生成多个1秒的音频区间,选中一个1秒区间可以对其左右移动,扩大或缩小区间范围,还可以播放和删除此区间。

  1. 在stop2音频样本的第一个区间,通过播放发现声音有中断的杂音,将分割区间移动到了右边的地方,发现右边的音频声音比较清晰。

  1. 调整好音频区间后,单击右下角的【Split】按钮。

  1. 分割完成后,在音频数据列表中,已经将stop2音频样本分割成6个1秒钟的音频样本了。

  1. 使用分割数据的方式,将数据列表中所有10秒的样本都分割为1秒的音频样本。分割过程中,要注意音频质量的取舍和调整。

3.2.3 添加学习块

  1. 音频样本数据分割完成后,单击左侧【Create impulse】创造脉冲选项。

  1. 此窗口是设置时间序列数据,使用默认值即可。

  1. 添加预处理模块,这里使用音频处理模块MFCC。

每个 1 秒的音频样本应进行预处理并转换为图像(例如,13 x 49 x 1)。我们将使用 MFCC,从音频信号中提取特征,这对人声非常有用。
  1. 接着单击【Add a learning block】添加机器学习模块选项,如下图所示。

  1. 添加【Classification】分类学习模块,单击【Add】添加按钮,如下图所示。

  1. 这样就添加上了分类学习模块,如下图所示。

  1. 最后单击保持按钮保持设置,如下图所示。

3.2.4 预处理

  1. 单击左侧【MFCC】选项,右侧会进去其设置页面,如下图所示。。

  1. 使用默认设置即可,单击蓝色保存按钮,如下图所示。。

3.2.5 生成特征

1、接着单击【Generate features】生成特征按钮,生成特征图,如下图所示。。

  1. 训练完成后会生成特征图,通过不同颜色的小圆点代表不同分类,如下图所示。。

3.2.6 训练模型

  1. 接着在左侧菜单,单击【Classifier】进入分类训练,如下图所示。

  1. 这个训练模型由100训练周期和0.005学习率组成,使用默认值即可,如下图所示。。

  1. 此选项是采用的卷积神经网络的,本模型采用了两个 Conv1D + MaxPooling 块(分别具有 8 个和 16 个神经元)和一个 0.25 Dropout 组成,单击【Start training】开始训练,如下图所示。。

  1. 开始训练后,在右侧可以看到训练过程,训练时间比较长,这和电脑的CPU性能有很大关系,如下图所示。

  1. 最后的训练成绩(验证集),如下图所示。

  1. 通过训练数据集,结果关键词识别准确率还是很高的,这个模型符合要求可以使用。如果,准确率低于80%,就是音频素材样本不够多,需要多添加样本后在进行训练。

3.2.7 导出Arduino 库模型

  1. 训练完成后,单击左侧【Deployment】部署选项

  1. 单击搜索文本框,弹出菜单选择Arduino 库。

  1. 接着单击【Enable EON™ Compiler】前面的关闭选项,关闭EON功能。

  1. 单击底部的【Build】按钮,生成并下载为库文件

  1. 等待一段时间后,会弹出提示生成Arduino库窗口。

  1. 同时,会自动下载一个Arduino zip库文件。然后,单击

在文件夹显示图标,可以【下载】文件夹看到下载的库文件。

3.2.8 导入库文件

  1. 打开Arduino IDE软件,选择【项目】-【导入库】-【添加ZIP库】选项。

  1. 在【下载】文件夹找到生成的库文件,双击此文件。

  1. 在Arduino IDE软件中等待一段时间后,在【输出】窗口中会提示已安装完成

3.2.9 更新ESP NN文件

由于Edge Impulse 平台还没有发布对ESP NN加速器的支持,而XIAO ESP32S3 Sense 设备启动了ESP NN加速器功能,直接使用导入的模型库文件会造成开发板冲突错误,需要更新ESP NN文件。

  1. 在Arduino库文件中找到刚添加的模块库文件夹,接着此文件夹中按src/edge-impulse-sdk/porting/espressif/ESP-NN ,这个路径找到ESP-NN文件夹

  1. 用我们提供的新的【ESP-NN】文件夹替换此文件夹

PS 、

  1. 建议把原【ESP-NN】文件删掉再将新下载的【ESP-NN】文件复制进去。
  2. 如果在后续的程序测试中出现报错,可以将原【ESP-NN】文件复原,因为可能会因为不同的电脑不同的系统,不需要执行替换【ESP-NN】文件。

3.2.10 导入库文件部署预测模型

我们准备了测试程序,你需要将新导入的模块库文件引入到此测试程序中。

关键字预测程序.zip 下载并打开关键字预测程序。

  1. 打开测试程序,选择【项目】-【导入库】-【新添加的库名称】选项,替换红框中的预测库文件。

  1. 单击【上传】按钮,上传测试程序,等待一段时间后上传成功,单击右上角的串口监视器,可以看到预测结果

  1. 对着XIAO开发板说hello或者stop,看看板载Led灯会不会有反应

 

MP3 V4

参考代码,用来测试MP3模块是否正常工作,并且可以检查TF卡中的文件是否正确。 我们需要用到库可以从链接下载

https://github.com/Seeed-Studio/Seeed_Serial_MP3_Player 。

如果出现报错:

fatal error: circular_queue.h: No such file or directory
#include <circular_queue.h>
^~~~~~~~~~~~~~~~~~

需要在库管理器把EspSoftwareSerial库给移除再下载其8.1.0的版本。

#include "WT2605C_Player.h"
// #ifdef __AVR__
#include <SoftwareSerial.h>
SoftwareSerial SSerial(D7,D6); // RX, TX
#define COMSerial SSerial
// #define ShowSerial Serial
WT2605C<SoftwareSerial> Mp3Player;
void setup() {
 Serial.begin(9600);
 COMSerial.begin(115200);
  // while (!Serial){
  //   //  ShowSerial.println("1");
  // };
 Serial.println("+++++++++++++++++++++++++++++++++++++++++++++++++++++");
 Mp3Player.init(COMSerial);
 Serial.println("0...");
 int vol = 2;
 uint8_t uint_8_num;
// 使用强制类型转换将int转换为uint8_t
  uint_8_num = (uint8_t)vol;
 Mp3Player.volume(uint_8_num);
 Serial.println("Volume set to: " + String(vol));
 int index = 1;
 Mp3Player.playSDRootSong(index);
 Serial.println("Play music: " + String(index));
 delay(20000);
  // index = 2;
  // Mp3Player.playSDRootSong(index);
  // Serial.println("Play music: " + String(index));
  // delay(500);
}
void loop() {
}

由于该模块的AUX音频输出不能改变音量且输出音量很小我们需要添加一个功放板

按钮控制

在噪声环境中,语音识别系统可能会受到干扰,导致识别准确性下降。为了提升用户体验和系统的可靠性,我们可以引入按钮控制机制,以便用户在嘈杂环境下能够通过物理按键轻松地管理音频播放。这种设计不仅增加了系统的交互方式,还确保了用户即使在背景噪音较大的情况下,也能准确无误地控制音乐播放的内容。通过结合按钮控制和语音识别,我们能够创造一个更加灵活和用户友好的语音播放系统。

参考代码

// constants won't change. They're used here to set pin numbers:
#define buttonPin1 D7  // the number of the pushbutton pin
#define buttonPin2 D8
// variables will change:
int buttonState1 = 0;  // variable for reading the pushbutton status
int buttonState2 = 0;
void setup() {
  // initialize the LED pin as an output:
 Serial.begin(9600);
  // initialize the pushbutton pin as an input:
 pinMode(buttonPin1, INPUT);
 digitalWrite(buttonPin1, LOW);
 pinMode(buttonPin2, INPUT);
 digitalWrite(buttonPin2, LOW);
}
void loop() {
  // read the state of the pushbutton value:
  buttonState1 = digitalRead(buttonPin1);
  buttonState2 = digitalRead(buttonPin2);
// Serial.println("button checking");
  // check if the pushbutton is pressed. If it is, the buttonState is HIGH:
 if (buttonState1 == HIGH) {
    // turn LED on:
 digitalWrite(ledPin, HIGH);
 Serial.println("button1 push");
 } 
 else if (buttonState2 == HIGH) {
    // turn LED on:
 digitalWrite(ledPin, HIGH);
 Serial.println("button2 push");
 } 
 else {
    // turn LED off:
 Serial.println(" no button  push");
 digitalWrite(ledPin, LOW);
 }
}

多线程按钮控制

多线程技术是一种在计算机编程中实现并发执行的技术。通过多线程,程序可以同时执行多个任务,从而提高程序的效率和响应速度。在按钮控制场景中,如果将按钮控制逻辑直接嵌入到主循环中,由于识别语音需要占用一定时间来录音,会导致接收按钮信号时出现延迟,需要长按按钮才能捕捉到按钮的信号。为了解决这个问题,我们可以利用多线程技术来接收按钮信号。

具体来说,我们可以将按钮信号的接收和处理作为一个独立的线程来运行。当按钮被按下时,这个独立的线程会立即响应并执行相应的处理逻辑,而不会受到主循环中语音识别任务的干扰。这样,我们就可以实现按钮信号的快速响应,提高用户体验。

总之,多线程技术在按钮控制中的应用,可以有效地解决由于语音识别任务导致的按钮信号接收延迟问题,提高程序的响应速度和用户体验。

可以参考代码:

#include<Arduino.h>
#define USE_MULTOCRE 0
int num = 0;
void xTaskOne(void *xTask1){
 int count = 0;
 while (count < 10) {
 Serial.println("Task1");
 delay(500);
    count++;
    num++;
 }
  // 当任务完成时,删除自身
 vTaskDelete(NULL);
}
void xTaskTwo(void *xTask2){
 int count = 0;
 while (count < 10) {
 Serial.println("Task2");
 delay(1000);
    count++;
    // Serial.println("count");
 }
 vTaskDelete(NULL);
}
void setup() {
  // put your setup code here, to run once:
 Serial.begin(115200);
 delay(10);
#if !USE_MULTCORE
 xTaskCreate(
 xTaskOne,/* Task function. */
 "TaskOne",/* String with name of task. */
 4096,/* Stack size in bytes.*/
 NULL,/* parameter passed as input of the task */
 1,/* priority of the task.(configMAx PRIORITIES - 1 being the highest, and @ being the lowest.) */
 NULL);/* Task handle.*/
 xTaskCreate(
 xTaskTwo,/* Task function.*/
 "TaskTwo",/* String with name of task. */
 4096,/* Stack size in bytes.*/
 NULL,/* parameter passed as input of the task */
 2,/* priority of the task.(configMax PRIORITIES - 1 being the highest, and  being the lowest.) */
 NULL);  /* Task handle.*/
#else
  //最后一个参数至关重要,决定这个任务创建在哪个核上.PRO_CPU 为 ,APP_cPu 为1,城者tskNoAFFINITY允许任务在两者上运行.
 xTaskCreatepinnedToCore(xTaskOne,"TaskOne",4096,NULL,1,NULL,0);
 xTaskCreatepinnedToCore(xTaskTwo,"TaskTwo",4896,NULL,2,NULL,1);
#endif
}
void loop() {
  // put your main code here, to run repeatedly:
 Serial.println("XTask is running");
 Serial.println(num);
 delay(1000);
}

RIP人体感应器

在最终的方案设计中,我们必须充分考虑空间内长期会员的工作习惯和需求,避免频繁的语音播报干扰他们的专注和效率。同时,考虑到项目要求硬件设备长期运行,持续的热量累积可能会导致设备过早损坏,甚至影响整个项目的稳定性和可靠性。为了实现节能和延长设备寿命的双重目标,我们将启用设备的睡眠模式,使其在非工作时段进入低功耗状态,从而有效减少能源消耗并延长设备的使用寿命。

然而,关键问题在于,如何在需要时即时唤醒设备,以确保项目的顺利进行和会员的使用体验。为此,我们计划采用先进的PIR人体感应技术,当有人靠近时,自动激活XIAO esp32S3,从而实现智能唤醒。这种设计既确保了设备的即时响应,又避免了不必要的能源浪费,实现了效率与节能的完美平衡。

参考程序

#define MOTIONPIN GPIO_NUM_4
void setup() {
 Serial.begin(9400);
 pinMode(LED_BUILTIN, OUTPUT);
 pinMode(MOTIONPIN, INPUT);
}
void loop() {
 Serial.println("it wake");
 digitalWrite(LED_BUILTIN, HIGH);
 delay(250);
 digitalWrite(LED_BUILTIN, LOW);
 delay(250);
 digitalWrite(LED_BUILTIN, HIGH);
 delay(250);
 digitalWrite(LED_BUILTIN, LOW);
 delay(250);
 digitalWrite(LED_BUILTIN, HIGH);
 delay(250);
 digitalWrite(LED_BUILTIN, LOW);
 delay(250);
 digitalWrite(LED_BUILTIN, HIGH);
 delay(250);
 digitalWrite(LED_BUILTIN, LOW);
 delay(250);
 digitalWrite(LED_BUILTIN, HIGH);
 delay(250);
 digitalWrite(LED_BUILTIN, LOW);
 delay(250);
 digitalWrite(LED_BUILTIN, HIGH);
 Serial.println("Going to sleep...");
 delay(1000);
 esp_sleep_enable_ext0_wakeup(MOTIONPIN, 1);
 delay(5000);
 Serial.println("Going to sleep...");
 esp_deep_sleep_start();
}

最终程序

// If your target is limited in memory remove this macro to save 10K RAM
#define EIDSP_QUANTIZE_FILTERBANK 0
/*
 ** NOTE: If you run into TFLite arena allocation issue.
 **
 ** This may be due to may dynamic memory fragmentation.
 ** Try defining "-DEI_CLASSIFIER_ALLOCATION_STATIC" in boards.local.txt (create
 ** if it doesn't exist) and copy this file to
 ** `<ARDUINO_CORE_INSTALL_PATH>/arduino/hardware/<mbed_core>/<core_version>/`.
 **
 ** See
 ** (https://support.arduino.cc/hc/en-us/articles/360012076960-Where-are-the-installed-cores-located-)
 ** to find where Arduino installs cores on your machine.
 **
 ** If the problem persists then there's not enough memory for this model and application.
 */
/* Includes ---------------------------------------------------------------- */
//#include <XIAO-ESP32S3-KWS_inferencing.h>
// #include <Marco-KWS-KIC_inferencing.h>
#include <Caihuo_nihao_hello_inferencing.h>
#include <I2S.h>
#include "WT2605C_Player.h"
#include <Arduino.h>
// #ifdef __AVR__
#include <SoftwareSerial.h>
SoftwareSerial SSerial(D7,D6); // RX, TX
#define COMSerial SSerial
// #define ShowSerial Serial
WT2605C<SoftwareSerial> Mp3Player;
#define SAMPLE_RATE 16000U
#define SAMPLE_BITS 16
#define LED_BUILT_IN 21 
#define MOTIONPIN GPIO_NUM_4
#define buttonPin1 D9  // the number of the pushbutton pin CHINESE
#define buttonPin2 D8 // ENGLISH
int buttonState1 = 0;  // variable for reading the pushbutton status
int buttonState2 = 0;
int collectTimes = 0;
#define USE_MULTOCRE 0
int Language = 3;
int remember_language = 3;
void xTaskOne(void *xTask1){
 int count = 0;
 int buttonstate = 3;// if press english return 0;      if press chinese return 1 ;   no buttun pressed return 3
 int i = 0;
 while (1) {
 if(Language == 3){
 buttonstate = Check_button();
      // Serial.println("+=+=+=+=+=+=+=+=+==+++=+");
 if(buttonstate != 3 /*按钮按下*/ && buttonstate != Language /*更换语言*/){
        Language = buttonstate;
        // Serial.println("-------------");
        // Serial.print("xTaskOne : ");
        // Serial.println(Language);
        // Serial.println("-------------");
        // vTaskDelete(NULL);
 }
 delay(10);
 i++;
 }else{
 delay(1000);
      // Serial.println("+++++++++++");
      // Serial.print("xTaskOne : ");
      // Serial.println(Language);
      // Serial.println("++++++++++");
 }
 }
  // 当任务完成时,删除自身
 vTaskDelete(NULL);
}
int Language_2 = 3;
void xTaskTwo(void *xTask2){
 int count = 0;
 while (count < 10) {
    // Serial.println("*****");
    // bool m = microphone_inference_record();
    // if (!m) {
    //     ei_printf("ERR: Failed to record audio...\n");
    //     return;
    // }
    // signal_t signal;
    // signal.total_length = EI_CLASSIFIER_RAW_SAMPLE_COUNT;
    // signal.get_data = &microphone_audio_signal_get_data;
    // ei_impulse_result_t result = { 0 };
    // EI_IMPULSE_ERROR r = run_classifier(&signal, &result, debug_nn);
    // if (r != EI_IMPULSE_OK) {
    //     ei_printf("ERR: Failed to run classifier (%d)\n", r);
    //     return;
    // }
    // int pred_index = 0;     // Initialize pred_index
    // float pred_value = 0;   // Initialize pred_value
    // int buttonstate = Check_button();
    // int language = 3;  // 1 is chinese, 0 is english, 3 is not selected yet
    // Serial.println("Task2");
    // delay(1000);
    // // count++;
    // // Serial.println("count");
    // ei_printf("Predictions ");
    // ei_printf("(DSP: %d ms., Classification: %d ms., Anomaly: %d ms.)",
    // result.timing.dsp, result.timing.classification, result.timing.anomaly);
    // ei_printf(": \n");
    // for (size_t ix = 0; ix < EI_CLASSIFIER_LABEL_COUNT; ix++) {
    //     ei_printf("    %s: ", result.classification[ix].label);
    //     ei_printf_float(result.classification[ix].value);
    //     ei_printf("\n");
    //     if (result.classification[ix].value > 0.2){
    //       pred_index = ix;
    //       pred_value = result.classification[ix].value;
    //   }
    // }
    // // Display inference result
    // ei_printf("now test the sound : %d \n", EI_CLASSIFIER_LABEL_COUNT );
    // if ((pred_index == 0) && (pred_value > 0.6)){
    //   ei_printf("idex 0 \n");//English
    //   language = 0;
    // }else if((pred_index == 2) && (pred_value > 0.6)){
    //   ei_printf("idex 2 \n");
    //   digitalWrite(LED_BUILT_IN, LOW); //noise trun on noise
    //   Language_2 = 3;
    // }
    // else if((pred_index == 1) && (pred_value > 0.6)){
    //   ei_printf("idex 1 \n");
    //   digitalWrite(LED_BUILT_IN, HIGH); //Turn off //nihao
    //   Language_2 = 1;
    // }
 }
 vTaskDelete(NULL);
}
// check which button is press 
// if press english return 0;      if press chinese return 1 ;   no buttun pressed return 3
int Check_button(){
  buttonState1 = digitalRead(buttonPin1);
  buttonState2 = digitalRead(buttonPin2);
 if (buttonState1 == HIGH) {
    // turn LED on:
 digitalWrite(LED_BUILT_IN, HIGH);
 Serial.println("Chinese push");
 return 1;
 } 
 else if (buttonState2 == HIGH) {
    // turn LED on:
 digitalWrite(LED_BUILT_IN, HIGH);
 Serial.println("English push");
 return 0;
 } 
 else { 
    // turn LED off:
    // Serial.println(" no button  push");
 digitalWrite(LED_BUILT_IN, LOW);
 return 3;
 }
}
/** Audio buffers, pointers and selectors */
typedef struct {
 int16_t *buffer;
 uint8_t buf_ready;
 uint32_t buf_count;
 uint32_t n_samples;
} inference_t;
static inference_t inference;
static const uint32_t sample_buffer_size = 2048;
static signed short sampleBuffer[sample_buffer_size];
static bool debug_nn = false; // Set this to true to see e.g. features generated from the raw signal
static bool record_status = true;
/**
 * @brief      Arduino setup function
 */
void setup()
{
    // put your setup code here, to run once:
 Serial.begin(9600);
    // comment out the below line to cancel the wait for USB connection (needed for native USB)
 COMSerial.begin(115200);
    // while (!Serial){
    //   //  ShowSerial.println("1");
    // };
 Serial.println("+++++++++++++++++++++++++++++++++++++++++++++++++++++");
 Mp3Player.init(COMSerial);
 Serial.println("0...");
 while (!Serial);
 Serial.println("Edge Impulse Inferencing Demo");
 pinMode(LED_BUILT_IN, OUTPUT); // Set the pin as output
 digitalWrite(LED_BUILT_IN, HIGH); //Turn off
    // digitalWrite(LED_BUILT_IN, LOW);
 I2S.setAllPins(-1, 42, 41, -1, -1);
 if (!I2S.begin(PDM_MONO_MODE, SAMPLE_RATE, SAMPLE_BITS)) {
 Serial.println("Failed to initialize I2S!");
 while (1) ;
 }
    // summary of inferencing settings (from model_metadata.h)
 ei_printf("Inferencing settings:\n");
 ei_printf("\tInterval: ");
 ei_printf_float((float)EI_CLASSIFIER_INTERVAL_MS);
 ei_printf(" ms.\n");
 ei_printf("\tFrame size: %d\n", EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE);
 ei_printf("\tSample length: %d ms.\n", EI_CLASSIFIER_RAW_SAMPLE_COUNT / 16);
 ei_printf("\tNo. of classes: %d\n", sizeof(ei_classifier_inferencing_categories) / sizeof(ei_classifier_inferencing_categories[0]));
 ei_printf("\nStarting continious inference in 1 seconds...\n");
 ei_sleep(1000);
 if (microphone_inference_start(EI_CLASSIFIER_RAW_SAMPLE_COUNT) == false) {
 ei_printf("ERR: Could not allocate audio buffer (size %d), this could be due to the window length of your model\r\n", EI_CLASSIFIER_RAW_SAMPLE_COUNT);
 return;
 }
 ei_printf("Recording...\n");
 pinMode(LED_BUILTIN, OUTPUT);
 pinMode(MOTIONPIN, INPUT);
 digitalWrite(LED_BUILTIN, HIGH);
 delay(250);
 digitalWrite(LED_BUILTIN, LOW);
 delay(1000);
 digitalWrite(LED_BUILTIN, HIGH);
 delay(250);
  // initialize the pushbutton pin as an input:
 pinMode(buttonPin1, INPUT);
 digitalWrite(buttonPin1, LOW);
 pinMode(buttonPin2, INPUT);
 digitalWrite(buttonPin2, LOW);
 delay(10);
 int vol = 10;
  //     uint8_t uint_8_num;
  // // 使用强制类型转换将int转换为uint8_t
  //   uint_8_num = (uint8_t)vol;
 Mp3Player.volume(vol);
    // Mp3Player.volume(vol);
 Serial.println("Volume set to: " + String(vol));
#if !USE_MULTCORE
 xTaskCreate(
 xTaskOne,/* Task function. */
 "TaskOne",/* String with name of task. */
 4096,/* Stack size in bytes.*/
 NULL,/* parameter passed as input of the task */
 1,/* priority of the task.(configMAx PRIORITIES - 1 being the highest, and @ being the lowest.) */
 NULL);/* Task handle.*/
 xTaskCreate(
 xTaskTwo,/* Task function.*/
 "TaskTwo",/* String with name of task. */
 4096,/* Stack size in bytes.*/
 NULL,/* parameter passed as input of the task */
 2,/* priority of the task.(configMax PRIORITIES - 1 being the highest, and  being the lowest.) */
 NULL);  /* Task handle.*/
#else
  //最后一个参数至关重要,决定这个任务创建在哪个核上.PRO_CPU 为 ,APP_cPu 为1,城者tskNoAFFINITY允许任务在两者上运行.
 xTaskCreatepinnedToCore(xTaskOne,"TaskOne",4096,NULL,1,NULL,0);
 xTaskCreatepinnedToCore(xTaskTwo,"TaskTwo",4896,NULL,2,NULL,1);
#endif
}
/**
 * @brief      Arduino main function. Runs the inferencing loop.
 */
void loop()
{
 bool m = microphone_inference_record();
 if (!m) {
 ei_printf("ERR: Failed to record audio...\n");
 return;
 }
 signal_t signal;
 signal.total_length = EI_CLASSIFIER_RAW_SAMPLE_COUNT;
 signal.get_data = &microphone_audio_signal_get_data;
 ei_impulse_result_t result = { 0 };
    EI_IMPULSE_ERROR r = run_classifier(&signal, &result, debug_nn);
 if (r != EI_IMPULSE_OK) {
 ei_printf("ERR: Failed to run classifier (%d)\n", r);
 return;
 }
 int pred_index = 0;   // Initialize pred_index
 float pred_value = 0; // Initialize pred_value
 int buttonstate = Language;
 Serial.println(buttonstate);
 int language = 3;  // 1 is chinese, 0 is english, 3 is not selected yet
 if(buttonstate == language){ // which means language didn't change ==> didn't select ==> then try to rec sound to select language
      // print the predictions
 ei_printf("Predictions ");
 ei_printf("(DSP: %d ms., Classification: %d ms., Anomaly: %d ms.)",
 result.timing.dsp, result.timing.classification, result.timing.anomaly);
 ei_printf(": \n");
 for (size_t ix = 0; ix < EI_CLASSIFIER_LABEL_COUNT; ix++) {
 ei_printf("    %s: ", result.classification[ix].label);
 ei_printf_float(result.classification[ix].value);
 ei_printf("\n");
 if (result.classification[ix].value > 0.2){
 pred_index = ix;
 pred_value = result.classification[ix].value;
 }
 }
 }
    // int language = 3;  // 1 is chinese, 0 is english, 3 is not selected yet
    // check any buton is press?
    // buttonstate = Check_button();
 if(buttonstate == language){ // if the button no press return 2, then check sound language
      // Display inference result
 ei_printf("now test the sound : %d \n", EI_CLASSIFIER_LABEL_COUNT );
 if ((pred_index == 0) && (pred_value > 0.6)){
 ei_printf("idex 0 \n");//English
        language = 0;
                Language = 1;
 }else if((pred_index == 2) && (pred_value > 0.6)){
 ei_printf("idex 2 \n");
 digitalWrite(LED_BUILT_IN, LOW); //noise trun on noise
        language = 3;
 }
 else if((pred_index == 1) && (pred_value > 0.6)){
 ei_printf("idex 1 \n");
 digitalWrite(LED_BUILT_IN, HIGH); //Turn off //nihao
        language = 1;
 }
 }else {
      language = buttonstate; // langague already change 
 }
  // if language is selected 
  // if(language != 3 && language != remember_language){
 if(language != 3){
    // play the introduction .
 remember_language = language;
 delay(10);
 Serial.println("music stop ");
    // Mp3Player.stop();
    // delay(10);
    //if language change by button press  change language.
    //if language change play the introduction again.
 Serial.println("Play the MP3");
 delay(10);
 if(language == 0) { // english
 int index = 3;
 Mp3Player.playSDRootSong(index);
 Serial.println("Play music: " + String(index));
      // delay(2000);
      // Mp3Player.stop();
 }else{ // Chinese
 int index = 2;
 Mp3Player.playSDRootSong(index);
 Serial.println("Play music: " + String(index));
      // delay(2000);
      // Mp3Player.stop();
 }
    Language = 3;
 delay(1000);
 delay(2000);
 collectTimes = 0;
 }
  //if the 
#if EI_CLASSIFIER_HAS_ANOMALY == 1
 ei_printf("    anomaly score: ");
 ei_printf_float(result.anomaly);
 ei_printf("\n");
#endif
 collectTimes++;
  // if all loop is finish
  // deep sleep with RIP wakeup
 if(collectTimes > 10){
 Mp3Player.stop();
 Serial.println("Going to sleep...");
 delay(1000);
 collectTimes = 0;
 esp_sleep_enable_ext0_wakeup(MOTIONPIN, 1);
    // Serial.println("it wake");
 delay(5000);
 Serial.println("Going to sleep...");
 esp_deep_sleep_start();
 }
}
static void audio_inference_callback(uint32_t n_bytes)
{
 for(int i = 0; i < n_bytes>>1; i++) {
 inference.buffer[inference.buf_count++] = sampleBuffer[i];
 if(inference.buf_count >= inference.n_samples) {
 inference.buf_count = 0;
 inference.buf_ready = 1;
 }
 }
}
static void capture_samples(void* arg) {
 const int32_t i2s_bytes_to_read = (uint32_t)arg;
 size_t bytes_read = i2s_bytes_to_read;
 while (record_status) {
    /* read data at once from i2s - Modified for XIAO ESP2S3 Sense and I2S.h library */
    // i2s_read((i2s_port_t)1, (void*)sampleBuffer, i2s_bytes_to_read, &bytes_read, 100);
    esp_i2s::i2s_read(esp_i2s::I2S_NUM_0, (void*)sampleBuffer, i2s_bytes_to_read, &bytes_read, 100);
 if (bytes_read <= 0) {
 ei_printf("Error in I2S read : %d", bytes_read);
 }
 else {
 if (bytes_read < i2s_bytes_to_read) {
 ei_printf("Partial I2S read");
 }
        // scale the data (otherwise the sound is too quiet)
 for (int x = 0; x < i2s_bytes_to_read/2; x++) {
 sampleBuffer[x] = (int16_t)(sampleBuffer[x]) * 8;
 }
 if (record_status) {
 audio_inference_callback(i2s_bytes_to_read);
 }
 else {
 break;
 }
 }
 }
 vTaskDelete(NULL);
}
/**
 * @brief      Init inferencing struct and setup/start PDM
 *
 * @param[in]  n_samples  The n samples
 *
 * @return    { description_of_the_return_value }
 */
static bool microphone_inference_start(uint32_t n_samples)
{
 inference.buffer = (int16_t *)malloc(n_samples * sizeof(int16_t));
 if(inference.buffer == NULL) {
 return false;
 }
 inference.buf_count  = 0;
 inference.n_samples  = n_samples;
 inference.buf_ready  = 0;
//    if (i2s_init(EI_CLASSIFIER_FREQUENCY)) {
//        ei_printf("Failed to start I2S!");
//    }
 ei_sleep(100);
 record_status = true;
 xTaskCreate(capture_samples, "CaptureSamples", 1024 * 32, (void*)sample_buffer_size, 10, NULL);
 return true;
}
/**
 * @brief      Wait on new data
 *
 * @return     True when finished
 */
static bool microphone_inference_record(void)
{
 bool ret = true;
 while (inference.buf_ready == 0) {
 delay(10);
 }
 inference.buf_ready = 0;
 return ret;
}
/**
 * Get raw audio signal data
 */
static int microphone_audio_signal_get_data(size_t offset, size_t length, float *out_ptr)
{
 numpy::int16_to_float(&inference.buffer[offset], out_ptr, length);
 return 0;
}
/**
 * @brief      Stop PDM and release buffers
 */
static void microphone_inference_end(void)
{
 free(sampleBuffer);
 ei_free(inference.buffer);
}
//
//static int i2s_init(uint32_t sampling_rate) {
//  // Start listening for audio: MONO @ 8/16KHz
//  i2s_config_t i2s_config = {
//      .mode = (i2s_mode_t)(I2S_CHANNEL_MONO),
//      .sample_rate = sampling_rate,
//      .bits_per_sample = (i2s_bits_per_sample_t)16,
//      .channel_format = I2S_CHANNEL_FMT_ONLY_RIGHT,
//      .communication_format = I2S_COMM_FORMAT_I2S,
//      .intr_alloc_flags = 0,
//      .dma_buf_count = 8,
//      .dma_buf_len = 512,
//      .use_apll = false,
//      .tx_desc_auto_clear = false,
//      .fixed_mclk = -1,
//  };
//  i2s_pin_config_t pin_config = {
//      .bck_io_num = -1,    // IIS_SCLK 26
//      .ws_io_num = 42,     // IIS_LCLK 32
//      .data_out_num = -1,  // IIS_DSIN -1
//      .data_in_num = 41,   // IIS_DOUT 33
//  };
//  esp_err_t ret = 0;
//
//  ret = i2s_driver_install((i2s_port_t)1, &i2s_config, 0, NULL);
//  if (ret != ESP_OK) {
//    ei_printf("Error in i2s_driver_install");
//  }
//
//  ret = i2s_set_pin((i2s_port_t)1, &pin_config);
//  if (ret != ESP_OK) {
//    ei_printf("Error in i2s_set_pin");
//  }
//
//  ret = i2s_zero_dma_buffer((i2s_port_t)1);
//  if (ret != ESP_OK) {
//    ei_printf("Error in initializing dma buffer with 0");
//  }
//
//  return int(ret);
//}
//
//static int i2s_deinit(void) {
//    i2s_driver_uninstall((i2s_port_t)1); //stop & destroy i2s driver
//    return 0;
//}
#if !defined(EI_CLASSIFIER_SENSOR) || EI_CLASSIFIER_SENSOR != EI_CLASSIFIER_SENSOR_MICROPHONE
#error "Invalid model for current sensor."
#endif

总结

实现该项目的过程中,我遇到了一些挑战,主要来自于对硬件的不熟悉,这无疑增加了项目的完成时间。此外,在处理语音识别和图像识别时,我们注意到它们在处理上的差异,这导致了单线程执行时可能会出现一定的延迟。为了优化系统的性能,我考虑引入多线程处理。通过多线程,我们可以同时处理多个任务,从而提高控制系统的流畅性和合理性,使其能够更好地满足用户的交互体验。在实现该项目时,我们采用了XIAO ESP32S3作为核心硬件平台。这款微控制器具有强大的处理能力和丰富的外设接口,非常适合用于智能语音识别应用。为了提供智能语音向导的功能,我使用了在Edge impluse训练的语音模型,该模型能够识别特定的语音指令,并据此执行相应的操作。

  • 23
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值