背景
柴火创客空间作为大湾区科技创新的窗口,每年到访空间的社区伙伴众多,为了更好的进行空间信息交互,我们希望有一个装置是可以解决:当空间管理员不在现场的时候,到访者可以通过装置获得清晰的介绍与引导。
为了解决这个问题,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”...等等
- 文件格式需要为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 开发板上

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

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

3.1.3 采集hello音频样本
假设要采集三个音频,将其分别命名为hello 、stop和other三个标签,每一个标签代表一种关键词;比如建立一个hello标签,并多次采集hello声音,这样就建立一个hello标签的音频样本,采集步骤如下:
- 在Arduino IDE软件录音程序中,单击右上角的“串口监视器”图标,打开串口监视器。
 

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

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

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

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

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

| 建议:您为每个标签样本提供足够大的声音。每次录音提供10秒钟录音时间,录制过程中多次重复您的关键词,关键字之间需要有一定的间隔时间。 | 
|---|
采集stop音频样本
通过rec命令采集了5次hello音频样本,接着在串口监视器的文本框中输入stop,就会生成一个新的分类标签,再输入rec命令录制stop音频样本,步骤如下:
- 在串口监视器文本框中输入stop命令
 

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

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

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

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

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

3.1.6 导出SD卡音频样本
通过录音程序采集了hello 、stop和other三种分类的音频样本,每个分类又至少采集了5次10秒的音频数据,这些数据被转存到了SD卡上,接下来需要将SD卡上的音频文件拷贝到电脑上。
- 将XIAO开发板上的SD卡取出,插入到SD卡读卡器中,并插入电脑USB接口上。
 

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

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

3.2 使用Edge Impulse 训练数据集 ,在XIAO ESP32S3 Sense 部署语音关键词识别模型
3.2.1 上传收集的声音数据
使用Edge Impulse 训练数据集 ,在XIAO ESP32S3 Sense 部署语音关键词识别模型
3.2.1 上传收集的声音数据
上传收集的声音数据,步骤如下:
- 进入Edge Impulse 网站,注册一个登录账号,进入后点击右上角账户名称,单击【创建新项目】选项。
 

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

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

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

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

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

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

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

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

3.2.2 拆分数据
训练数据用到的数据都是1秒钟时间,但是采集的音频样本 10 秒,必须拆分为 1s 样本才能兼容。
- 先选中一个音频样本比如stop2,单击右侧3个点图标,在弹出菜单中单击【Split sample】分割样本选项。
 

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

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

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

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

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

3.2.3 添加学习块
- 音频样本数据分割完成后,单击左侧【Create impulse】创造脉冲选项。
 

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

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



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

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

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

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

3.2.4 预处理
- 单击左侧【MFCC】选项,右侧会进去其设置页面,如下图所示。。
 

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

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

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

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

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

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

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

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

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


3.2.7 导出Arduino 库模型
- 训练完成后,单击左侧【Deployment】部署选项
 

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

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

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

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

- 同时,会自动下载一个Arduino zip库文件。然后,单击
 
![]()
在文件夹显示图标,可以【下载】文件夹看到下载的库文件。


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

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

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

3.2.9 更新ESP NN文件
由于Edge Impulse 平台还没有发布对ESP NN加速器的支持,而XIAO ESP32S3 Sense 设备启动了ESP NN加速器功能,直接使用导入的模型库文件会造成开发板冲突错误,需要更新ESP NN文件。
- 在Arduino库文件中找到刚添加的模块库文件夹,接着此文件夹中按src/edge-impulse-sdk/porting/espressif/ESP-NN ,这个路径找到ESP-NN文件夹
 

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

PS 、
- 建议把原【ESP-NN】文件删掉再将新下载的【ESP-NN】文件复制进去。
 - 如果在后续的程序测试中出现报错,可以将原【ESP-NN】文件复原,因为可能会因为不同的电脑不同的系统,不需要执行替换【ESP-NN】文件。
 
3.2.10 导入库文件部署预测模型
我们准备了测试程序,你需要将新导入的模块库文件引入到此测试程序中。
关键字预测程序.zip 下载并打开关键字预测程序。
- 打开测试程序,选择【项目】-【导入库】-【新添加的库名称】选项,替换红框中的预测库文件。
 


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

- 对着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 = µphone_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 = µphone_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训练的语音模型,该模型能够识别特定的语音指令,并据此执行相应的操作。









![yocto系列讲解[实战篇]95 - 使用外部第三方交叉编译器toolchain](https://img-blog.csdnimg.cn/f64e1f114d7045eaa57a680daec4395b.png)









