【ESP32S3 Sense接入语音识别+MiniMax模型对话】

news2024/12/23 22:31:36

1. 前言

围绕ESP32S3 Sense接入语音识别+MiniMax模型对话展开,首先串口输入“1”字符,随后麦克风采集2s声音数据,对接百度在线语音识别,将返回文本结果丢入MiniMax模型,进而返回第二次结果文本,实现语言对话文本效果。以上一共有两次调用,后期只需加入tts模块就可完整对话。

讲解视频:

1.1 语音接入

百度在线语音接入教程:
【ESP32S3 Sense接入百度在线语音识别】
在这里插入图片描述

使用Seeed XIAO ESP32S3 Sense开发板接入百度智能云实现在线语音识别。自带麦克风模块用做语音输入,通过串口发送字符“1”来控制数据的采集和上传。

1.2 大模型接入

国产大模型接入分享如下:
【ESP32接入国产大模型之MiniMax】
【ESP32接入语言大模型之智谱清言】
【ESP32接入国产大模型之文心一言】
【ESP32接入语言大模型之通义千问】

下面是不标准测评,推荐使用MiniMax大模型,参考而已
在这里插入图片描述

MM智能助理是一款由MiniMax自研的,没有调用其他产品的接口的大型语言模型。MiniMax是一家中国科技公司,一直致力于进行大模型相关的研究。
|模型| 响应时间|内容质量|免费token次数|地址|
|–|–|–|–|–|
| MiniMax | 3s | 8分|500万|https://www.minimaxi.com/|
| 智谱清言 | 7s | 8分|300万|https://open.bigmodel.cn/|
|文心一言 | 10s | 9分|500万|https://cloud.baidu.com/doc/WENXINWORKSHOP/s/Nlks5zkzu|
| 通义千问 | 8s | 8分|800万|https://tongyi.aliyun.com/qianwen/|

这一次还是采用Arduino编程就会轻松许多开发。这样就可以把stt+chat大模型装进口袋啦🤣🤣🤣,接下来就只差tts播报啦!

在这里插入图片描述

2. 先决条件

在继续此项目之前,请确保检查以下先决条件。

我们将使用 Arduino IDE 对 ESP32/ESP8266 开发板进行编程,因此在继续本教程之前,请确保已在 Arduino IDE 中安装这些开发板。

2.1 环境配置

  1. Arduino IDE:下载并安装 Arduino IDE;
  2. ESP32 开发板库:在 Arduino IDE 中添加 ESP32 支持;
    参考博客:【esp32c3配置arduino IDE教程】
    为安装过程留出一些时间,具体时间可能因您的互联网连接而异。

2.2 所需零件

要学习本教程,您需要1个 ESP32 开发板+马克风或者ESP32 Sense,建议使用后者,笔者发现同样的代码后者可以轻松调用,ESP32不行(可能板子坏了)

在这里插入图片描述

3. 核心代码

提供两种硬件测试

  1. ESP32S3 Sense
  2. ESP32 + max9814麦克风模块用做语音输入,一个按键来控制数据的采集和上传

3.1 ESP32S3 Sense

在这里插入图片描述
ESP32S3 Sense自带麦克风直接烧录
Arduino代码如下

#include <Arduino.h>
#include "base64.h"
#include <WiFi.h>
#include "HTTPClient.h"
#include "cJSON.h"
#include <I2S.h>
#include <ArduinoJson.h>
#define data_len 16000
// #define key 4             //端口0
// #define ADC 2             //端口39
// #define led 15            //端口2

HTTPClient http_client;
// 1. Replace with your network credentials
const char* ssid = "J09 502";
const char* password = "qwertyuiop111";
// 2. Check your Aduio port
const int buttonPin = 1;  // the number of the pushbutton pin
const int ledPin = 21;    // the number of the LED pin
hw_timer_t* timer = NULL;
uint16_t adc_data[data_len];    //16000个数据,8K采样率,即2秒,录音时间为2秒,想要实现更长时间的语音识别,就要改这个数组大小
                                //和下面data_json数组的大小,改大一些。
uint8_t adc_start_flag = 0;     //开始标志
uint8_t adc_complete_flag = 0;  //完成标志
char data_json[45000];          //用于储存json格式的数据,大一点,JSON编码后数据字节数变成原来的4/3,所以得计算好,避免出现越界

// 3. Replace with your MiniMax API key
const char* apiKey = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJHcm91cE5hbWUiOiIyMzQ1dm9yIiwiVXNlck5hbWUiOiIyMzQ1dm9yIiwiQWNjb3VudCI6IiIsIlN1YmjE3NTk0ODIxODAxMDAxNzAyMDgiLCJQI6IjE3NTk0ODIxODAwOTU5NzU5MDQiLCJQYWdlTmFtZSI6IiIsIk1haWwiOiIiLCJDcmVhdGVUaW1lIjoiMjAyNC0wMy0xNiAxMzoyNDoxOCIsImlzcyI6Im1pbmltYXgifQ.WlEj8Nk0j_WOMXZE9SbIC8sHpwJ6R6Pi8Spl5mahJsW3-Jsz7Ev53sGGz3v__Bd5dDkt7o9-Y8BOW0WZq2ImaN7Rof7YNtYnYnvPNDyGx23_xRqq5co9P5UkC3ciYEcIch2SUZ5QPkXR-sMUPzhdowSYvfdu1N25kdKJ8GE_63NfCnsdDVt8mv0wQSSweJK0yf_C8a8ADdB1uF4vg_WKMDjHlvzERsoNZgX6FYtr-bee85rIyu4U-OrbUvEpR1FLPXa7lTlx65QvhVIYGbIKde7ERIT_7QLOQoVFvPz0gX-H6V7UlmSRgRy4LK_R9mvV5TqCy3v90WK_AFuwEhPXcg";
HTTPClient http;
String token_key = String("Bearer ") + apiKey;
// Send request to MiniMax API
String inputText = "你好,minimax!";
String apiUrl = "https://api.minimax.chat/v1/text/chatcompletion_v2";
int httpResponseCode;
String response,question,answer;
DynamicJsonDocument jsonDoc(1024);

uint32_t num = 0;
portMUX_TYPE timerMux = portMUX_INITIALIZER_UNLOCKED;
void IRAM_ATTR onTimer() {
  // Increment the counter and set the time of ISR
  portENTER_CRITICAL_ISR(&timerMux);
  if (adc_start_flag == 1) {
    //Serial.println("");
    // adc_data[num] = analogRead(ADC);
    adc_data[num] = I2S.read();
    num++;
    if (num >= data_len) {
      adc_complete_flag = 1;
      adc_start_flag = 0;
      num = 0;
      //Serial.println(Complete_flag);
    }
  }
  portEXIT_CRITICAL_ISR(&timerMux);
}

String getGPTAnswer(String inputText) {
  http.begin(apiUrl);
  http.addHeader("Content-Type", "application/json");
  http.addHeader("Authorization", token_key);
  String payload = "{\"model\":\"abab5.5s-chat\",\"messages\":[{\"role\": \"system\",\"content\": \"你是鹏鹏的生活助手机器人,要求下面的回答严格控制在256字符以内。\"},{\"role\": \"user\",\"content\": \"" + inputText + "\"}]}";
  httpResponseCode = http.POST(payload);
  if (httpResponseCode == 200) {
    response = http.getString();
    http.end();
    Serial.println(response);
    // Parse JSON response
    deserializeJson(jsonDoc, response);
    String outputText = jsonDoc["choices"][0]["message"]["content"];
    return outputText;
    // Serial.println(outputText);
  } else {
    http.end();
    Serial.printf("Error %i \n", httpResponseCode);
    return "<error>";
  }
}

void setup() {

  //Serial.begin(921600);
  Serial.begin(115200);
  // pinMode(ADC, ANALOG);
  // pinMode(buttonPin, INPUT_PULLUP);
  pinMode(ledPin, OUTPUT);
  // start I2S at 16 kHz with 16-bits per sample
  I2S.setAllPins(-1, 42, 41, -1, -1);
  if (!I2S.begin(PDM_MONO_MODE, 16000, 16)) {
    Serial.println("Failed to initialize I2S!");
    while (1)
      ;  // do nothing
  }
  uint8_t count = 0;
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    Serial.print(".");
    count++;
    if (count >= 75) {
      Serial.printf("\r\n-- wifi connect fail! --");
      break;
    }
    vTaskDelay(200);
  }
  Serial.printf("\r\n-- wifi connect success! --\r\n");
  Serial.println(WiFi.localIP());
  http.setTimeout(10000);
  // gain_token();

  timer = timerBegin(0, 80, true);    //  80M的时钟 80分频 1M
  timerAlarmWrite(timer, 125, true);  //  1M  计125个数进中断  8K
  timerAttachInterrupt(timer, &onTimer, true);
  timerAlarmEnable(timer);
  timerStop(timer);  //先暂停
}


uint32_t time1, time2;
void loop() {

  if (Serial.available() > 0)  //按键按下
  {
    if (Serial.read() == '1') {
      Serial.printf("Start recognition\r\n\r\n");
      digitalWrite(ledPin, HIGH);
      adc_start_flag = 1;
      timerStart(timer);

      // time1=micros();
      while (!adc_complete_flag)  //等待采集完成
      {
        ets_delay_us(10);
      }
      // time2=micros()-time1;

      timerStop(timer);
      adc_complete_flag = 0;  //清标志
      digitalWrite(ledPin, LOW);
      memset(data_json, '\0', strlen(data_json));  //将数组清空
      strcat(data_json, "{");
      strcat(data_json, "\"format\":\"pcm\",");
      strcat(data_json, "\"rate\":16000,");                                                                        //采样率    如果采样率改变了,记得修改该值,只有16000、8000两个固定采样率
      strcat(data_json, "\"dev_pid\":1537,");                                                                      //中文普通话
      strcat(data_json, "\"channel\":1,");                                                                         //单声道
      strcat(data_json, "\"cuid\":\"666666\",");                                                                   //识别码    随便打几个字符,但最好唯一
      strcat(data_json, "\"token\":\"24.8f6143793af76e02f5e191.2592000.1713789066.282335-57722200\",");  //token	这里需要修改成自己申请到的token
      strcat(data_json, "\"len\":32000,");                                                                         //数据长度  如果传输的数据长度改变了,记得修改该值,该值是ADC采集的数据字节数,不是base64编码后的长度
      strcat(data_json, "\"speech\":\"");
      strcat(data_json, base64::encode((uint8_t*)adc_data, sizeof(adc_data)).c_str());  //base64编码数据
      strcat(data_json, "\"");
      strcat(data_json, "}");
      // Serial.println(data_json);
      int httpCode;
      http_client.setTimeout(5000);
      http_client.begin("http://vop.baidu.com/server_api");  //https://vop.baidu.com/pro_api
      http_client.addHeader("Content-Type", "application/json");
      httpCode = http_client.POST(data_json);

      if (httpCode == 200) {
        if (httpCode == HTTP_CODE_OK) {
          response = http_client.getString();
          http_client.end();
          Serial.println(response);
          // Parse JSON response
          // DynamicJsonDocument jsonDoc(512);
          deserializeJson(jsonDoc, response);
          String question = jsonDoc["result"][0];
          // 访问"result"数组,并获取其第一个元
          // 输出结果
          Serial.println("\n Input:"+question);
          answer = getGPTAnswer(question);
          Serial.println("Answer: " + answer);
          Serial.println("Enter a prompt:");

        } else {
          Serial.printf("[HTTP] GET... failed, error: %s\n", http_client.errorToString(httpCode).c_str());
        }
      }
      // while (!digitalRead(buttonPin))
      //   ;
      Serial.printf("Recognition complete\r\n");
    }
  }
  vTaskDelay(1);
}

用于实现一个通过 I2S 接口采集音频信号并将其发送到百度语音识别 API 进行语音识别,然后将识别出的文本通过 MiniMax API 获取 AI 回答的功能。以下是代码的主要结构和功能说明:

  1. 引入必要的库文件,包括 Arduino.h、base64.h、WiFi.h、HTTPClient.h、cJSON.h、I2S.h 和 ArduinoJson.h,这些库分别提供了基本的 Arduino 功能、Base64 编解码、Wi-Fi 连接、HTTP 客户端操作、JSON 数据处理和 I2S 音频接口驱动。

  2. 定义了一些全局变量,如 Wi-Fi 的 SSID 和密码,以及与音频采集和处理相关的变量,如 ADC 数据缓冲区、录音标志位、完成标志位、JSON 格式数据缓冲区,还有 MiniMax API 的密钥(apiKey)。

修改Wi-Fi 的 SSID 和密码

// 1. Replace with your network credentials
const char* ssid = "J09 502";
const char* password = "qwertyuiop111";

修改MiniMax API 的密钥(apiKey)

// 3. Replace with your MiniMax API key
const char* apiKey = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJHcm91cE5hbWUiOiIyMzQ1dm9yIiwiVXNlck5hbWUiOiIyMzQ1dm9yIiwiQWNjb3VudCI6IiIsIlN1YmjE3NTk0ODIxODAxMDAxNzAyMDgiLCJQI6IjE3NTk0ODIxODAwOTU5NzU5MDQiLCJQYWdlTmFtZSI6IiIsIk1haWwiOiIiLCJDcmVhdGVUaW1lIjoiMjAyNC0wMy0xNiAxMzoyNDoxOCIsImlzcyI6Im1pbmltYXgifQ.WlEj8Nk0j_WOMXZE9SbIC8sHpwJ6R6Pi8Spl5mahJsW3-Jsz7Ev53sGGz3v__Bd5dDkt7o9-Y8BOW0WZq2ImaN7Rof7YNtYnYnvPNDyGx23_xRqq5co9P5UkC3ciYEcIch2SUZ5QPkXR-sMUPzhdowSYvfdu1N25kdKJ8GE_63NfCnsdDVt8mv0wQSSweJK0yf_C8a8ADdB1uF4vg_WKMDjHlvzERsoNZgX6FYtr-bee85rIyu4U-OrbUvEpR1FLPXa7lTlx65QvhVIYGbIKde7ERIT_7QLOQoVFvPz0gX-H6V7UlmSRgRy4LK_R9mvV5TqCy3v90WK_AFuwEhPXcg";

修改百度api

strcat(data_json, "\"token\":\"24.8f6143793af76e02f5e191.2592000.1713789066.282335-57722200\",");  //token	这里需要修改成自己申请到的token
  1. 定义了一个 HTTPClient 实例 http_client,用于向 API 发送请求。

  2. 函数 getGPTAnswer() 负责调用 MiniMax API,并传入用户输入文本以获取 AI 的回答。

  3. onTimer() 函数是一个中断服务程序,每当定时器触发时会读取 I2S 接口上的音频数据并存入缓冲区 adc_data 中。当缓冲区满或者录音结束时,会设置完成标志。

  4. setup() 函数负责初始化串口通信、配置 I2S 接口、连接 Wi-Fi,并设置定时器和中断。

  5. loop() 函数是 Arduino 主循环函数,在循环中检查是否有按键输入(这里未实际使用)。如果有特定输入,开始音频采集过程,并在采集完成后将音频数据转换为 Base64 编码,构建一个 JSON 请求体,然后通过 HTTP POST 方式发送到百度语音识别 API。收到识别结果后,调用 getGPTAnswer() 函数获取 AI 回答,并打印在控制台上。

3.2 ESP32 + max9814

ESP32 + max9814麦克风模块用做语音输入,一个按键来控制数据的采集和上传

4. 上传验证

如果提示Compilation error: ArduinoJson.h: No such file or directory
在这里插入图片描述
直接在库管理安装Arduinojson
在这里插入图片描述

打开串口监视器,注意右下角选择换行符,选择115200波特率,输入你想问的问题,他就可以回答你

4.1 对话测试

在这里插入图片描述

串口发送“1”,开始录音,然后返回对话结果,以上是两次连续对话效果😘😘😘

4.2 报错

如果返回error ,大家对照列表查询错误代码,结合提示排查解决
在这里插入图片描述

5. 总结

🥳🥳🥳现在,我们在本教程中,您学习了如何使用ESP32S3 Sense接入语音识别+MiniMax模型对话。🛹🛹🛹从而实现对外部世界进行感知,充分认识这个有机与无机的环境,后期会持续分享esp32跑freertos实用案列🥳🥳🥳科学地合理地进行创作和发挥效益,然后为人类社会发展贡献一点微薄之力。🤣🤣🤣

如果你有任何问题,可以通过下面的二维码加入鹏鹏小分队,期待与你思维的碰撞😘😘😘

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1549880.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux 系统 docker搭建LNMP环境

1、安装nginx docker pull nginx (默认安装的是最新版本) 2、运行nginx docker run --name nginx -p 80:80 -d nginx:latest 备注&#xff1a;--name nginx 表示容器名为 nginx -d 表示后台运行 -p 80:80 表示把本地80端口绑定到Nginx服务端的 80端口 nginx:lates…

【问题分析】InputDispatcher无焦点窗口ANR问题【Android 14】

1 问题描述 Monkey跑出的无焦点窗口的ANR问题。 特点&#xff1a; 1&#xff09;、上层WMS有焦点窗口&#xff0c;为Launcher。 2&#xff09;、native层InputDispacher无焦点窗口&#xff0c;上层为”recents_animation_input_consumer“请求了焦点&#xff0c;但是”rece…

国赛大纲解读

1. 第一部分,是针对5G基础知识的掌握,第二部分是人工智能基本算法的掌握,就是人工智能的应用,用5G+人工智能(AI算法)进行网络优化的问题,要有网络优化的基础知识,比如说:某个区域的覆盖问题,覆盖特别差,但有数据,覆盖电频,srp值这些数据给你,根据数据来判断是…

OCP NVME SSD规范解读-15.DSSD set feature功能要求-1

4.15.2 DSSD Set Feature Requirements章节重点描述了针对数据中心NVMe SSD&#xff08;DSSD&#xff09;特有的设置特性要求。在NVMe SSD规范中&#xff0c;通过Set Feature命令可以对SSD进行各种高级功能的设置和配置&#xff0c;DSSD特有的Set Feature命令集中了一些特定于D…

【Linux进阶之路】理解UDP,成为TCP。

前言 学了TCP 和UDP之后&#xff0c;感觉UDP就像是初入职场的年轻人&#xff0c;两耳不闻 “窗外事”&#xff0c;只管尽力地把自己的事情做好&#xff0c;但收获的却是不可靠&#xff0c;而TCP更像是涉世极深的"职场老油条"&#xff0c;给人的感觉就是 “城府极深&a…

被迫走上前端之路第四课之vue的表单双向绑定

在进行表单处理时&#xff0c;我们希望JavaScript中与表单绑定的变量能随着我们表单的输入或选择而动态的发生改变&#xff0c;或者说二者同步。这个时候就需要实现表单的双向绑定 使用v-on和v-bind实现 这里定义了一个输入框&#xff0c;v-bind实现了text的值到输入框的值的绑…

QT中的服务器与客户端

一、前言 本文主要讲讲QT中服务器与客户端的使用方法&#xff0c;QT已经封装好了&#xff0c;调用相应类直接访问即可。本文以QT中的QT中的TCP为例子&#xff0c;讲下使用方法以及线程中使用。 二、正文 2.1 Sever的使用方法 2.1.1 思路 QT中Sever使用的时候大致步骤为&…

Python抓取抖音直播间数据:技术探索与实践

目录 一、引言 二、技术准备 三、分析抖音直播间网页结构 四、编写爬虫代码 五、处理反爬虫机制 六、数据清洗与存储 七、总结 一、引言 随着互联网的快速发展&#xff0c;直播行业已成为当下的热门领域。抖音作为其中的佼佼者&#xff0c;吸引了大量的用户和主播。对于…

集合系列(十五) -CopyOnWriteArrayList详解

一、摘要 在介绍 CopyOnWriteArrayList 之前&#xff0c;我们一起先来看看如下方法执行结果&#xff0c;代码内容如下&#xff1a; public static void main(String[] args) {List<String> list new ArrayList<String>();list.add("1");list.add(&quo…

Flutter 常用插件Plugin整理并附带实例

最近有点空闲时间&#xff0c;正好写一篇文章&#xff0c;整理一下我们在Flutter开发中常用的插件Plugin使用并附带上实例。 在日常开发中&#xff0c;整个demo目前应该满足大家所有的开发需求&#xff0c;例如&#xff1a;http请求、列表刷新及加载、列表分组、轮播图、视频播…

如何使用Python结合Pillow、matplotlib和OpenCV实现图片读取

使用Pillow库 matplotlib是一个绘图库&#xff0c;经常用于数据可视化&#xff0c;但它也可以用来展示图片。 from PIL import Image# 读取图片 image Image.open(.jpg)# 展示图片 image.show()使用OpenCV库 OpenCV是一个强大的计算机视觉和机器学习库。它不仅提供了大量的图像…

记录关于智能家居的路程的一个bug___Segmentation fault(段错误)

前言 其实发生段错误的情况有很多&#xff1a; 其实在项目的开发中最有可能的错误就是①和②&#xff0c;考虑到本项目数组用的比较少&#xff0c;所以主要是考虑错误①指针的误用。 有时候错误就是那么离谱&#xff0c;声音也算是一种设备&#xff1f;&#xff1f;&#xff…

Vue使用el-statistic和el-card显示大屏中的统计数据

​ 一、页面内容&#xff1a; <el-row :gutter"20"><el-col :span"6"><el-card class"box-card"><div><el-statisticgroup-separator",":precision"2":value"value2":title"tit…

机器人寻路算法双向A*(Bidirectional A*)算法的实现C++、Python、Matlab语言

机器人寻路算法双向A*&#xff08;Bidirectional A*&#xff09;算法的实现C、Python、Matlab语言 最近好久没更新&#xff0c;在搞华为的软件挑战赛&#xff08;软挑&#xff09;&#xff0c;好卷只能说。去年还能混进32强&#xff0c;今年就比较迷糊了&#xff0c;这东西对我…

JavaEE:网络原理——协议(应用层+传输层)

应用层 协议就是一种约定 应用层&#xff1a;对应应用程序&#xff0c;是程序员打交道最多的一层&#xff0c;调用系统提供的网络api写出的代码都是属于应用层的。应用层有很多现成的协议&#xff0c;但程序员一般用的还是自定义协议 自定义协议要约定好哪些内容&#xff1f…

【pytest、playwright】多账号同时操作

目录 方案实现思路&#xff1a; 方案一&#xff1a; 方案二&#xff1a; 方案实现思路&#xff1a; 依照上图所见&#xff0c;就知道&#xff0c;一个账号是pytest-playwright默认的环境&#xff0c;一个是 账号登录的环境 方案一&#xff1a; 直接上代码&#xff1a; imp…

Unity学习笔记 9.2D射线

下载源码 UnityPackage 1.Ray2D 让小球向右发射射线&#xff1a; Ray2D ray;void Start() {// Ray2D(起点&#xff0c;终点)ray new Ray2D(this.transform.position, Vector2.right);// Debug.DrawLine(起点&#xff0c;终点&#xff0c;颜色&#xff0c;显示时间)Debug.DrawL…

视图的作用

目录 视图的作用 创建视图 为 scott 分配创建视图的权限 查询视图 复杂视图的创建 视图更新的限制问题 更新视图中数据的部门编号&#xff08;视图的存在条件&#xff09; 限制通过视图修改数据表内容 创建只读的视图 复杂视图创建 oracle从入门到总裁:​​​​​​h…

阿里云ECS选型推荐配置

本文介绍构建Kubernetes集群时该如何选择ECS类型以及选型的注意事项。 集群规格规划 目前在创建Kubernetes集群时&#xff0c;存在着使用很多小规格ECS的现象&#xff0c;这样做有以下弊端&#xff1a; 网络问题&#xff1a;小规格Worker ECS的网络资源受限。 容量问题&…

网络链路层之(1)基础概念

网络链路层之(1)基础概念 Author: Once Day Date: 2024年3月27日 一位热衷于Linux学习和开发的菜鸟&#xff0c;试图谱写一场冒险之旅&#xff0c;也许终点只是一场白日梦… 漫漫长路&#xff0c;有人对你微笑过嘛… 全系列文章可参考专栏: 通信网络技术_Once-Day的博客-CSD…