K_b0KBsM · 7月5日

创客项目秀 | 基于XIAO EPS32S3 的迷你语音助手

柴火创客空间是一个展示众多产品和项目案例的地方,但路过来看看的时候人员指引,新来的用户无法获得介绍和引导。为了解决这个问题,作者设计了一个智能语音识别系统,它作为一个智能语音向导,能够向用户介绍产品和项目,同时引导他们参观柴火创客空间。这个系统利用XIAO ESP32S3微控制器和Edge Impulse平台进行语音识别。当客人说出特定的语音指令时,系统能够识别并执行相应的操作,例如介绍产品、指引方向等。通过这种方式,智能语音向导大大提升了用户的参观体验
90eb07a6-f06a-4996-a88d-45b05b580785.jpg

材料清单

硬件

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

软件

  • Arduino IDE
  • Edge Impluse

Edge Impluse 介绍

image.png
Edge Impulse是一个专为边缘设备和嵌入式系统开发机器学习模型的平台。 它提供了一套全面的工具和服务,使开发人员能够快速创建、训练和部署机器学习模型,而无需深入了解机器学习。
该平台提供了一系列全面的工具和服务,帮助开发人员迅速构建、训练和部署机器学习模型,无需深入了解机器学习原理。 Edge Impulse提供的数据收集工具能轻松地从各种传感器和设备中收集数据,并上传至平台进行管理和标注。 此外,Edge Impulse还提供了一系列预处理和特征提取算法,能自动处理原始数据并提取有用的特征,为模型的训练做好准备。 一旦模型训练完成,可以轻松地部署到各种边缘设备和嵌入式系统上,包括Arduino、树莓派和各类微控制器。Edge Impulse提供多种部署选项,例如生成优化的C++代码、二进制文件或自定义SDK。
Edge Impulse的一大优势是其用户友好性和易用性。通过直观的图形界面和引导式工作流程,即使是机器学习初学者也能快速上手,创建出高质量的机器学习模型。 此外,Edge Impulse还提供了大量的教程、示例项目和社区支持,帮助开发人员学习和分享知识。它与各种硬件平台和传感器生态系统无缝集成,使得在边缘设备上部署机器学习变得更加简单。 总的来说,Edge Impulse是一个强大的平台,它降低了机器学习的门槛,使得开发和部署智能应用程序在边缘设备上变得更加简单高效。无论您是初学者还是经验丰富的开发人员,Edge Impulse都能帮助您创建出创新的物联网和嵌入式智能解决方案。

XIAO ESP32S3 Sense 介绍

image.png

特征:
强大的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
image.png

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

设置硬件

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

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

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

上传录音采集程序

利用XIAO ESP32S3 Sense 开发板采集音频数据,并将音频数据以wav格式转存到microSD卡上。
录音采集程序.zip 下载解压缩录音采集程序文件后,用Arduino IDG软件打开此程序。
步骤如下:
1、打开录音程序,并上传到XIAO ESP32S3 Sense 开发板上
image.png

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

3、等待数秒后,录音程序上传成功。
image.png

采集hello音频样本

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

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

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

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

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

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

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

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

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

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

采集other音频样本

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

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

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

导出SD卡音频样本

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

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

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

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

上传收集的声音数据

上传收集的声音数据,步骤如下:
1、进入Edge Impulse 网站,注册一个登录账号,进入后点击右上角账户名称,单击【创建新项目】选项。
image.png

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

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

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

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

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

7、接着单击上传数据【Upload data】按钮。
image.png

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

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

拆分数据

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

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

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

4、调整好音频区间后,单击右下角的【Split】按钮。
图片7.png

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

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

添加学习块
1、音频样本数据分割完成后,单击左侧【Create impulse】创造脉冲选项。
图片10.png

2、此窗口是设置时间序列数据,使用默认值即可。
图片11.png

3、添加预处理模块,这里使用音频处理模块MFCC。
图片12.png
图片13.png
图片14.png

每个 1 秒的音频样本应进行预处理并转换为图像(例如,13 x 49 x 1)。我们将使用 MFCC,从音频信号中提取特征,这对人声非常有用。

4、接着单击【Add a learning block】添加机器学习模块选项,如下图所示。
图片15.png

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

6、这样就添加上了分类学习模块,如下图所示。
图片17.png

7、最后单击保持按钮保持设置,如下图所示。
图片18.png

预处理

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

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

生成特征

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

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

训练模型

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

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

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

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

5、最后的训练成绩(验证集),如下图所示。
图片27.png

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

导出Arduino 库模型

1、训练完成后,单击左侧【Deployment】部署选项
图片30.png

2、单击搜索文本框,弹出菜单选择Arduino 库。
图片31.png

3、接着单击【Enable EON™ Compiler】前面的关闭选项,关闭EON功能。
图片32.png

4、单击底部的【Build】按钮,生成并下载为库文件
图片33.png

5、等待一段时间后,会弹出提示生成Arduino库窗口。
图片34.png

6、同时,会自动下载一个Arduino zip库文件。然后,单击 在文件夹显示图标,可以【下载】文件夹看到下载的库文件。
图片35.png
图片36.png

入库文件

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

2、在【下载】文件夹找到生成的库文件,双击此文件。
图片38.png

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

测试模型

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 = &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

    // 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

图片40.png
图片41.png

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

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

推荐阅读
关注数
8571
内容数
48
深度服务产业的国际化双创平台
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息