柴火创客空间是一个展示众多产品和项目案例的地方,但路过来看看的时候人员指引,新来的用户无法获得介绍和引导。为了解决这个问题,作者设计了一个智能语音识别系统,它作为一个智能语音向导,能够向用户介绍产品和项目,同时引导他们参观柴火创客空间。这个系统利用XIAO ESP32S3微控制器和Edge Impulse平台进行语音识别。当客人说出特定的语音指令时,系统能够识别并执行相应的操作,例如介绍产品、指引方向等。通过这种方式,智能语音向导大大提升了用户的参观体验
材料清单
硬件
- XIAO ESP32S3 Sense
- MP3 V4 模块
- PIR 人体感应模块
- 电脑音响
- 按钮
软件
- 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”...等等
2.文件格式需要为WAV
不过也可以用XIAO ESP32S3 进行录音:
设置硬件
1、将 microSD 卡插入 microSD 卡插槽。注意插入方向,金手指的一面应朝内,如下图所示。
2、用数据线将开发板连接到电脑的USB接口上,如下图所示。
3、打开Arduino IDE软件,选择 工具 》PSRAM:”OPI PSRAM” 》OPI PSRAM ,如下图所示。
上传录音采集程序
利用XIAO ESP32S3 Sense 开发板采集音频数据,并将音频数据以wav格式转存到microSD卡上。
录音采集程序.zip 下载解压缩录音采集程序文件后,用Arduino IDG软件打开此程序。
步骤如下:
1、打开录音程序,并上传到XIAO ESP32S3 Sense 开发板上
2、上传前,先设置开发板类型和端口号,然后单击上传图标,上传录音程序。
3、等待数秒后,录音程序上传成功。
采集hello音频样本
假设要采集三个音频,将其分别命名为hello 、stop和other三个标签,每一个标签代表一种关键词;比如建立一个hello标签,并多次采集hello声音,这样就建立一个hello标签的音频样本,采集步骤如下:
1、在Arduino IDE软件录音程序中,单击右上角的“串口监视器”图标,打开串口监视器。
2、在串口监视器文本框中输入hello分类标签并按键盘回车键,这样就建立了一个分类。
3、在串口监视器文本框中输入“rec”命令并回车,这时进入录音模式,请对着开发板说hello,多说几次会采集10秒钟音频。
4、hello音频采集完成后,会有提示写入文件,再次采集可以继续输入rec命令再次采集hello音频。
5、在串口监视器文本框中输入rec命令并回车,进入hello分类录音模式。
6、对着XIAO开发板说hello,多说几次大概10秒钟时间,看到提示写入文件就完成了。
建议:您为每个标签样本提供足够大的声音。每次录音提供10秒钟录音时间,录制过程中多次重复您的关键词,关键字之间需要有一定的间隔时间。
采集stop音频样本
通过rec命令采集了5次hello音频样本,接着在串口监视器的文本框中输入stop,就会生成一个新的分类标签,再输入rec命令录制stop音频样本,步骤如下:
1、在串口监视器文本框中输入stop命令
2、接着串口监视器文本框中输入rec命令,进入录音模式。
3、进入录音模式后,对着XIAO开发板说stop,多说几次需要采集10秒钟,通过多次输入rec命令,就可以多次采集stop音频,这里采集5次。
采集other音频样本
通过rec命令采集了5次stop音频样本,接着在串口监视器的文本框中输入other,就会生成一个新的分类标签,再输入rec命令录制other音频样本,other音频样本可以录制背景音或者杂音,步骤如下:
1、在串口监视器文本框中输入other,生成一个新的分类标签。
2、接着在串口监视器文本框中输入rec命令,进入录音模式。
3、进入录音模式后,对着XIAO开发板录制背景杂音,通过多次输入rec命令,就可以多次采集other音频样本了,这里采集5次。
导出SD卡音频样本
通过录音程序采集了hello 、stop和other三种分类的音频样本,每个分类又至少采集了5次10秒的音频数据,这些数据被转存到了SD卡上,接下来需要将SD卡上的音频文件拷贝到电脑上。
1、将XIAO开发板上的SD卡取出,插入到SD卡读卡器中,并插入电脑USB接口上。
2、在电脑中打开SD卡盘符,可以看到采集的音频文件,比如hello1.wav、hello2.wav的音频
3、在电脑D盘建一个sound文件夹,将SD卡中的音频文件全部复制到此文件夹中
使用Edge Impulse 训练数据集 ,在XIAO ESP32S3 Sense 部署语音关键词识别模型
上传收集的声音数据
上传收集的声音数据,步骤如下:
1、进入Edge Impulse 网站,注册一个登录账号,进入后点击右上角账户名称,单击【创建新项目】选项。
2、弹出创建一个新项目窗口,在输入新项目的名称中输入”kws”,然后单击右下角的【创建新项目】按钮
3、进入kws项目窗口,然后单击添加现有数据 【Add existing data】选项。
4、弹出添加现有数据窗口,单击【Upload data】选项。
5、进入上传数据窗口,单击【选择文件】按钮,
6、打开文件选择窗口,找到存储音频样本的sound文件夹,全部选中然后单击【打开】按钮。
7、接着单击上传数据【Upload data】按钮。
8、在上传数据窗口的右侧,可以看到上传数据成功了,然后单击右上角的关闭窗口图标。
9、可以在左侧数据采集【Data acquisition】菜单中,看到上传的音频数据的每一条的具体内容。
拆分数据
训练数据用到的数据都是1秒钟时间,但是采集的音频样本 10 秒,必须拆分为 1s 样本才能兼容。
1、先选中一个音频样本比如stop2,单击右侧3个点图标,在弹出菜单中单击【Split sample】分割样本选项。
2、弹出分割窗口,可以看到会自动生成多个1秒的音频区间,选中一个1秒区间可以对其左右移动,扩大或缩小区间范围,还可以播放和删除此区间。
3、在stop2音频样本的第一个区间,通过播放发现声音有中断的杂音,将分割区间移动到了右边的地方,发现右边的音频声音比较清晰。
4、调整好音频区间后,单击右下角的【Split】按钮。
5、分割完成后,在音频数据列表中,已经将stop2音频样本分割成6个1秒钟的音频样本了。
6、使用分割数据的方式,将数据列表中所有10秒的样本都分割为1秒的音频样本。分割过程中,要注意音频质量的取舍和调整。
添加学习块
1、音频样本数据分割完成后,单击左侧【Create impulse】创造脉冲选项。
2、此窗口是设置时间序列数据,使用默认值即可。
3、添加预处理模块,这里使用音频处理模块MFCC。
每个 1 秒的音频样本应进行预处理并转换为图像(例如,13 x 49 x 1)。我们将使用 MFCC,从音频信号中提取特征,这对人声非常有用。
4、接着单击【Add a learning block】添加机器学习模块选项,如下图所示。
5、添加【Classification】分类学习模块,单击【Add】添加按钮,如下图所示。
6、这样就添加上了分类学习模块,如下图所示。
7、最后单击保持按钮保持设置,如下图所示。
预处理
1、单击左侧【MFCC】选项,右侧会进去其设置页面,如下图所示。
2、使用默认设置即可,单击蓝色保存按钮,如下图所示。
生成特征
1、接着单击【Generate features】生成特征按钮,生成特征图,如下图所示。
2、训练完成后会生成特征图,通过不同颜色的小圆点代表不同分类,如下图所示。
训练模型
1、接着在左侧菜单,单击【Classifier】进入分类训练,如下图所示。
2、这个训练模型由100训练周期和0.005学习率组成,使用默认值即可,如下图所示。
3、此选项是采用的卷积神经网络的,本模型采用了两个 Conv1D + MaxPooling 块(分别具有 8 个和 16 个神经元)和一个 0.25 Dropout 组成,单击【Start training】开始训练,如下图所示。
4、开始训练后,在右侧可以看到训练过程,训练时间比较长,这和电脑的CPU性能有很大关系,如下图所示。
5、最后的训练成绩(验证集),如下图所示。
6、通过训练数据集,结果关键词识别准确率还是很高的,这个模型符合要求可以使用。如果,准确率低于80%,就是音频素材样本不够多,需要多添加样本后在进行训练。
导出Arduino 库模型
1、训练完成后,单击左侧【Deployment】部署选项
2、单击搜索文本框,弹出菜单选择Arduino 库。
3、接着单击【Enable EON™ Compiler】前面的关闭选项,关闭EON功能。
4、单击底部的【Build】按钮,生成并下载为库文件
5、等待一段时间后,会弹出提示生成Arduino库窗口。
6、同时,会自动下载一个Arduino zip库文件。然后,单击 在文件夹显示图标,可以【下载】文件夹看到下载的库文件。
入库文件
1、打开Arduino IDE软件,选择【项目】-【导入库】-【添加ZIP库】选项。
2、在【下载】文件夹找到生成的库文件,双击此文件。
3、在Arduino IDE软件中等待一段时间后,在【输出】窗口中会提示已安装完成
测试模型
1、 打开测试程序,选择【项目】-【导入库】-【新添加的库名称】选项,替换红框中的预测库文件。
/* Edge Impulse Arduino examples
* Copyright (c) 2022 EdgeImpulse Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
// 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 <ESP_I2S.h>
I2SClass I2S;
#define SAMPLE_RATE 16000U
#define SAMPLE_BITS 16
#define LED_BUILT_IN 21
/** 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(115200);
// comment out the below line to cancel the wait for USB connection (needed for native USB)
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
// setup 42 PDM clock and 41 PDM data pins
I2S.setPinsPdmRx(42, 41);
if (!I2S.begin(I2S_MODE_PDM_RX, 16000, I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_MONO)) {
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 2 seconds...\n");
ei_sleep(2000);
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");
}
/**
* @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
// 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 > pred_value){
pred_index = ix;
pred_value = result.classification[ix].value;
}
}
// Display inference result
if (pred_index == 3){
digitalWrite(LED_BUILT_IN, LOW); //Turn on
}
else{
digitalWrite(LED_BUILT_IN, HIGH); //Turn off
}
#if EI_CLASSIFIER_HAS_ANOMALY == 1
ei_printf(" anomaly score: ");
ei_printf_float(result.anomaly);
ei_printf("\n");
#endif
}
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);
}
#if !defined(EI_CLASSIFIER_SENSOR) || EI_CLASSIFIER_SENSOR != EI_CLASSIFIER_SENSOR_MICROPHONE
#error "Invalid model for current sensor."
#endif
2、 单击【上传】按钮,上传测试程序,等待一段时间后上传成功,单击右上角的串口监视器,可以看到预测结果
MP3 V4
参考代码,用来测试MP3模块是否正常工作,并且可以检查TF卡中的文件是否正确。 我们需要用到库可以从链接下载
https://github.com/Seeed-Stud... 。
如果出现报错:
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训练的语音模型,该模型能够识别特定的语音指令,并据此执行相应的操作。