在这里插入图片描述

M5StickC语音助理玩转语音识别!

最近在群里面经常听到老师们讨论语音识别,不少老师问如何使用语音识别;语音识别离线好还是在线好;哪家的语音识别模块好用等等,对于我这个长年混迹于各大创客交流群与开源社区的“混子”来说我还是有些经验,我用过许多开源硬件,当然群里老师们讨论的语音识别也接触过不少,其中在线离线之争由来已久,现在我为大家分享一下自己使用语音识别的经验,对于离线语音识别模块来说常见的海凌科V20、LD3320语音识别模块,DF的Gravity: I2C语音识别模块等,这类模块的特点是使用拼音代替汉字编程或者在线生成特定语音词条的固件,这类模块可以识别一些简单的语句,拥有唤醒功能与免唤醒命令词可实现一些简单的开灯关灯等功能成本范围在10到200之间,在线语音识别一般采用麦克风+ESP32模块构成,其中数字麦克风效果优于模拟麦克风,数字麦克风的代表是SPM1423与INMP441等,在线语音识别与离线语音识别的不同之处在于它可以识别任意语言,不区分你什么方言什么语种,仅仅与你开通的在线服务有关,识别速度与网络环境与服务提供商有关,网络质量较好的情况下几乎可以秒识别出结果。唯一的缺点就是联网,现在我们都处于物联网大数据的海洋当中,谁家还没有宽带呢,网络终究不是问题,网络问题解决了剩下的就是选择语音识别服务提供商了,目前国内主流的语音识别服务提供商有百度,阿里,腾讯,华为,讯飞等,其中腾讯为相关政策对我们学习较为友好,腾讯云为开通相关服务的用户每月赠送一定免费额度的API调用次数,以语音识别为例每月有5000次免费额度,对我我们学习来说约等于不要钱,故我创作了M5StickC语音助理这个项目帮助大家了解语音识别,并将它运用到我们的智能家居控制或者DIY创客作品当中。

下面让我们来看看这个项目的演示视频,该视频中我们控制了DIY的插座与小米风扇:

M5StickC语音助理玩转语音识别!

M5StickC语音助理名称的由来

M5StickC来源于M5StickC PLUS开发板,语音助理代表着它可以识别语音并帮助你完成某些事情,本项目的思路是将识别的语音通过MQTT进行发布,任何设备通过该语音识别发布的主题即可获取到语音识别结果,同时M5StickC语音助理订阅一个MQTT主题用于反馈与交互,效果参考演示视频。

预期目标及功能

  • 语音识别
  • 语音交互
  • MQTT语音识别结果发布
  • MQTT主题订阅反馈
  • 智能家居控制
  • 自定义流程控制
  • 中文显示

所用硬件

  • M5StickC PLUS

    在这里插入图片描述

M5StickC PLUS特点

  • 基于 ESP32 开发,支持 WiFi,蓝牙
  • 内置3轴加速计与3轴陀螺仪
  • 内置Red LED 红外发射管 RTC
  • 集成麦克风
  • 内置锂电池,配备电源管理芯片
  • 用户按键, LCD(1.14 寸), 电源/复位按键
  • 拓展接口
  • 集成无源蜂鸣器

程序设计

下面开始详细讲解程序设计过程。

开发环境

我们使用 Aduino IDE软件来编写本项目的程序,开发板选择 M5Stick-C。至于如何在 Arduino 中配置 ESP32 的开发环境,不在本文的介绍范围,请自行查阅相关资料。

程序思路

为了达到我们的预期目标,我们先绘制功能的思维导图,再根据思维导图逐步实现M5StickC语音助理的程序设计。

在这里插入图片描述

下面我们将具体讨论M5StickC语音助理各个子功能是如何实现的。

语音识别

在这里我们使用腾讯云的在线识别语音,我们先访问腾讯云官网,找到语音识别服务,这里我们可以在线体验语音识别,然后按照如下步骤简单了解腾讯云语音识别产品文档,第一次使用需要注册腾讯云账号并按照提示进行实名。

在这里插入图片描述

注册成功并开通语音识别服务后腾讯云会给我们每月5000次的免费接口调用

在这里插入图片描述

接下来进入管理后台获取腾讯云secretId与secretKey并记录(注意保存自己的密钥不要泄露)。

在这里插入图片描述

我们将获取的secretId与secretKey填入下面的语音识别demo并修改网络信息,由于腾讯云需要授权才能使用,在这里我用服务器部署了一个转接服务用于对接腾讯云,示例代码如下,修改联网与账号信息并选择M5Stick-C开发板上传。

#include <WiFi.h>
#include <driver/i2s.h>
#include <HTTPClient.h>

#include <M5StickCPlus.h>

char ssid[] = "*******************";//网络名称
char pass[] = "*******************";//网络密码

String secretId = "*******************";//腾讯云secretId
String secretKey = "*******************";//腾讯云secretKey

#define CONFIG_I2S_BCK_PIN -1//麦克风引脚定义
#define CONFIG_I2S_LRCK_PIN 0
#define CONFIG_I2S_DATA_PIN -1
#define CONFIG_I2S_DATA_IN_PIN 34

#define SPEAK_I2S_NUMBER I2S_NUM_0

#define MODE_MIC 0
#define MODE_SPK 1

#define DATA_SIZE 1024

bool InitI2SSpeakOrMic(int mode)//I2S模式选择函数
{
  esp_err_t err = ESP_OK;

  i2s_driver_uninstall(SPEAK_I2S_NUMBER);
  i2s_config_t i2s_config = {
    .mode = (i2s_mode_t)(I2S_MODE_MASTER),
    .sample_rate = 16000,
    .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT, // is fixed at 12bit, stereo, MSB
    .channel_format = I2S_CHANNEL_FMT_ALL_RIGHT,
    .communication_format = I2S_COMM_FORMAT_I2S,
    .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
    .dma_buf_count = 6,
    .dma_buf_len = 60,
  };
  if (mode == MODE_MIC)
  {
    i2s_config.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_PDM);
  }
  else
  {
    i2s_config.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX);
    i2s_config.use_apll = false;
    i2s_config.tx_desc_auto_clear = true;
  }

  Serial.println("Init i2s_driver_install");

  err += i2s_driver_install(SPEAK_I2S_NUMBER, &i2s_config, 0, NULL);
  i2s_pin_config_t tx_pin_config;

  tx_pin_config.bck_io_num = CONFIG_I2S_BCK_PIN;
  tx_pin_config.ws_io_num = CONFIG_I2S_LRCK_PIN;
  tx_pin_config.data_out_num = CONFIG_I2S_DATA_PIN;
  tx_pin_config.data_in_num = CONFIG_I2S_DATA_IN_PIN;

  Serial.println("Init i2s_set_pin");
  err += i2s_set_pin(SPEAK_I2S_NUMBER, &tx_pin_config);
  Serial.println("Init i2s_set_clk");
  err += i2s_set_clk(SPEAK_I2S_NUMBER, 16000, I2S_BITS_PER_SAMPLE_16BIT, I2S_CHANNEL_MONO);
  return true;
}

uint8_t microphonedata0[1024 * 80];
size_t byte_read = 0;
int16_t *buffptr;
uint32_t data_offset = 0;

String Pcm2String(uint8_t* pcm_buff, uint32_t pcm_lan)//上传音频数据获取结果函数
{
  String apiurl = "http://tool.peien.xyz/tenxun/Speech_Recognition_test.php?secretId=" + secretId + "&secretKey=" + secretKey; //腾讯云语音识别
  HTTPClient resthttp;
  uint64_t time = micros();
  resthttp.begin(apiurl);
  resthttp.addHeader("Content-Type", "audio/pcm");
  resthttp.POST((uint8_t*)pcm_buff, pcm_lan);
  Serial.printf("Time %dms\r\n", (micros() - time ) / 1000);
  String response = resthttp.getString();
  Serial.println(response);//打印语音识别json数据

  return String(response).substring(String(response).indexOf(String("Result\":\"")) + String("Result\":\"").length(), String(response).indexOf(String("\",\"AudioDuration")));//返回识别关键语句
}

void setup()
{
  M5.begin();//开发板初始化
  Serial.println("Init Spaker");
  InitI2SSpeakOrMic(MODE_SPK);//麦克风初始化
  delay(100);

  M5.Axp.ScreenBreath(10);//设置背光亮度为10
  size_t bytes_written;

  WiFi.begin(ssid, pass);//开始连接网络
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("Local IP:");
  Serial.print(WiFi.localIP());

  pinMode(37, INPUT_PULLUP);//板载按钮上拉
}

void loop()
{
  if (!digitalRead(37))//按下板载按钮开始语音识别
  {
    data_offset = 0;
    InitI2SSpeakOrMic(MODE_MIC);//准备开始录音
    while (1)
    {
      i2s_read(SPEAK_I2S_NUMBER, (char *)(microphonedata0 + data_offset), DATA_SIZE, &byte_read, (100 / portTICK_RATE_MS));
      data_offset += 1024;

      if (digitalRead(37) || data_offset >= 81919)//松开按钮或者超时8秒结束录音
        break;
    }
    Serial.println("end");

    String SpakeStr = Pcm2String(microphonedata0, data_offset);//保存语音识别结果
    Serial.println(SpakeStr);//打印语音识别结果
  }
}

上传完该例子我们打开串口监视器如下图所示,按下板载按钮37说出一句话,便能得到下图结果,在这里我们一般不需要识别末尾的标点,可以使用replace函数消除标点符号.例如SpakeStr.replace(“?”, “”);消除变量SpakeStr的所有问号。

在这里插入图片描述

添加交互图标

我们想要实现联网的交互效果,开始联网前与联网后显示不同颜色图标用于表示不同联网状态,M5StickCPlus显示自定义图标例子如下:

#include <M5StickCPlus.h>

PROGMEM const unsigned char icon[] = {//取模数据定义
//取模数据量大此处省略
};

void setup()
{
  M5.begin();
  M5.Lcd.setRotation(0);
  M5.Lcd.fillScreen(BLACK);
  M5.Axp.ScreenBreath(10);
  M5.Lcd.drawXBitmap(0, 52, icon, 135, 135, WHITE);//将135x135的图标显示到坐标0,52颜色为白色
}

void loop()
{

}

我们使用Image2Lcd软件取模设置如下

在这里插入图片描述

按照上面的设置点击保存可得到取模数据,将取模数据替换到图标显示例子红上传程序效果如下,在这里你可以设置为任意单色图标。

在这里插入图片描述

中文显示

我们希望把语音识别到的文字显示到屏幕上,M5官方提供了显示GBK编码字符串的方法loadHzk16,但Arduino IDE采用的是UTF8编码,故我们需要把UTF8编码的字符串转换为GBK编码,这里我们用到了齐护机器人的QDP_text_code.h库文件,该库可实现编码转换,M5StickCPlus中文显示例子如下:

#include <M5StickCPlus.h>
#include <QDP_text_code.h>//引用转码库文件

char* utf8togb2312(String input_data)//自定义转码函数
{
  int input_num = input_data.length();
  int output_num = 0;
  unsigned char str[input_num];
  byte select = 0;
  for (int x = 0; x < input_num; x++)
  {
    str[x] = input_data.charAt(x);
    select = Transform.GetUtf8ByteNumForWord(str[x]);
    if (select == 0)
      output_num++;
    else if (select >= 2)
      output_num += 2;
  }
  uint8_t gbArray[output_num];
  Transform.Utf8ToGb2312(str, input_num, gbArray);
  char gbArray1[output_num];
  static String data;
  data = "";
  for (int x = 0; x < output_num; x++)
  {
    data = String(data) + String(char(gbArray[x]));
  }
  char* gbkstr = const_cast<char*>(data.c_str());
  return gbkstr;
}

void display_text(String txt) {//显示中文并自动换行
  M5.Lcd.fillScreen(BLACK);//清屏
  M5.Lcd.setTextSize(2);//设置字体大小
  M5.Lcd.setCursor(3, 5);//设置显示坐标
  M5.Lcd.writeHzk(utf8togb2312(String(txt).substring(0, 12)));//截取前4个汉字显示
  M5.Lcd.setCursor(3, 40);
  M5.Lcd.writeHzk(utf8togb2312(String(txt).substring(12, 24)));
  M5.Lcd.setCursor(3, 75);
  M5.Lcd.writeHzk(utf8togb2312(String(txt).substring(24, 36)));
  M5.Lcd.setCursor(3, 110);
  M5.Lcd.writeHzk(utf8togb2312(String(txt).substring(36, 48)));
  M5.Lcd.setCursor(3, 145);
  M5.Lcd.writeHzk(utf8togb2312(String(txt).substring(48, 60)));
  M5.Lcd.setCursor(3, 180);
  M5.Lcd.writeHzk(utf8togb2312(String(txt).substring(60, 72)));
}

void setup()
{
  M5.begin();
  M5.Lcd.setRotation(0);//设置屏幕旋转为横向
  M5.Lcd.setTextSize(2);
  M5.Lcd.setTextColor(WHITE, BLACK);//设置显示文本前景颜色为白色,背景颜色为黑色
  M5.Lcd.loadHzk16();//GBK编码显示初始化
  M5.Axp.ScreenBreath(10);
}

void loop()
{
  if (Serial.available() > 0) {//当串口有数据可读时显示串口数据
    display_text(String(Serial.readString()));
  }
}

上传代码后打开串口监视器,输入任意汉字,效果如下

在这里插入图片描述

MQTT订阅与发布

MQTT是一种轻量级物联网协议,在这里不懂MQTT是什么的可以百度了解,这里我们选择免费的巴法云MQTT服务器,巴法云官网如下。

在这里插入图片描述

在这里我们可以使用邮箱注册一个新账号或者直接微信登陆,我们登陆后选择MQTT设备云新建主题postvoice与feedback并记录巴法云私钥如下图所示

在这里插入图片描述

现在我们来编写MQTT的代码用来连接巴法云平台,编写代码如下:

#include <WiFi.h>
#include <PubSubClient.h>

char ssid[] = "*******************";//网络名称
char pass[] = "*******************";//网络密码

String client_id = "*******************";//巴法云私钥
String post_voice = "postvoice";//语音识别消息发送主题
String feedback = "feedback";//屏幕反馈接收主题

const char *mqtt_broker = "bemfa.com";//巴法云MQTT服务器地址
const char *mqtt_username = "";//MQTT用户名
const char *mqtt_password = "";//MQTT用户密码
const int mqtt_port = 9501;//MQTT开放端口
String mqtt_topic = "";
String mqtt_data = "";
boolean mqtt_status = false;
WiFiClient espClient;
PubSubClient client(espClient);
void callback(char *topic, byte *payload, unsigned int length) {//MQTT消息接收函数
  String data = "";
  for (int i = 0; i < length; i++) {
    data = String(data) + String((char) payload[i]);
  }
  mqtt_topic = String(topic);
  mqtt_data = data;
  mqtt_status = true;
}

void setup() {
  Serial.begin(115200);
  WiFi.begin(ssid, pass);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("Local IP:");
  Serial.print(WiFi.localIP());

  client.setServer(mqtt_broker, mqtt_port);//MQTT连接函数
  client.setCallback(callback);
  while (!client.connected()) {
    if (client.connect(client_id.c_str(), mqtt_username, mqtt_password)) {
      Serial.println("Public emqx mqtt broker connected");
    } else {
      Serial.print("failed with state ");
      Serial.print(client.state());
      delay(2000);
    }
  }
  client.subscribe(String("feedback").c_str());
}

void loop() {
  client.loop();
  if (mqtt_status) {//MQTT接收并打印数据
    if (String(mqtt_topic).equals(String(post_voice))) {//接收主题feedback消息
      Serial.println(mqtt_data);
      mqtt_status = false;
    }
  }
  if (Serial.available() > 0) {
    client.publish(String(postvoice).c_str(), String(Serial.readString()).c_str());
  }
}

以上代码效果如下,串口监视器发送字符串会发布到巴法云postvoice主题,巴法云feedback主题发布消息会在串口监视器中显示,如下图

在这里插入图片描述

Nodered与M5StickC语音助理的结合

通过上面的程序基础我们便能轻松实现语音识别了,现在我们来使用Nodered接入M5StickC语音助理,Nodered是一个强大的流程控制平台,可以通过简单的拖拽组件即可开发出功能强大的自动化控制程序,至于Nodered是什么这里篇幅有限就不介绍了感兴趣的同学可以百度了解,Nodered中文网官网如下:

在这里插入图片描述

演示视频中的控制流程如下:

在这里插入图片描述

程序下载

以上就是M5StickC语音助理的项目介绍,如果你没有IDE或者只想体验该项目,那么你可以访问https://docs.m5stack.com/zh_CN/download根据你自己的系统下载M5Burner烧录工具进行安装,打开软件按照下面的步骤进行烧录体验。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

使用说明

  1. 下载M5Burner烧录软件
  2. 打开软件选择STICKC开发板
  3. 下滑到底部选择M5StickC语音助理下载并烧录固件
  4. 点击USER CUSTOM登陆或者注册账号
  5. 进入用户主页点击BurnerNVS跳出弹窗选择对应的串口并连接
  6. 输入网络信息与腾讯云与巴法云账号信息,屏幕上会实时显示设置的网络信息
  7. 各数据输入完成确认并保存后单击右侧的板子按钮39,M5StickC PLUS将自动重启并自动连接网络
  8. 联网完成看见联网图标变绿即可使用语音识别
  9. 注意当M5StickC PLUS使用中按下板子按钮39,将清除网络信息与巴法云账号ID并重启,此时需要重新使用M5Burner配置网络与巴法云私钥
  10. 尝试修改模板文件自定义表单文件与数据表格重复上述步骤理解本项目

总结

根据上面的理论基础我们便能完成M5StickC语音助理的项目制作了,其中具体实现细节由于篇幅限制,这里就不再讨论,大家可以通过M5Burner的项目地址下载程序源代码进行查看,其中必要的程序说明已经注释。使用M5StickC语音助理你能够轻松将任意物品接入语音控制,到这里对于在线语音识别与离线语音识别你更倾心哪种呢?相信你已经有了自己的答案,让我们一起在物联网的海洋中徜徉吧!下期我们将讨论如何以最低成本拥有自己的私有物联网服务器,教大家使用docker搭建常用的物联网服务器Blynk,EMQX;自动化流程创建平台Nodered以及Web服务器,最后逐渐教大家玩转物联网。我是默我们下期见。

Logo

CSDN联合极客时间,共同打造面向开发者的精品内容学习社区,助力成长!

更多推荐