[Day 11] 让tinyML听见你的呼唤

在先前[Day 09] tinyML开胃菜Arduino IDE上桌(下)已经简单介绍过Arduino Nano 33 BLE Sense(以下简称BLE Sense)这块开发板及如何连线及完成「Hello World, 闪烁吧 LED!」的范例程序,接下来就帮大家介绍如何用这块开发板让MCU听到声音并录下声音,而至於如何辨识部份就留待下回分解。

我们都知道人工智慧一个很重要的领域就是声音,小到语音命令、异常声音辨识,大到语音转文字、自然语言处理、自然语言理解及文字转语音,不过後者动辄数亿到数百兆个参数,就算是一般电脑恐怕也不一定能满足,所以tinyML的重点通常只用放在唤醒词及声音片段(含异常声音)的辨识,常见的应用包括有智能音箱唤醒、电气设备语音开关、马达异常音侦测、生理监看(如心音、咳嗽音、呼吸声)等。

其中唤醒词(Wake-Up-Word)亦有许多同义词,如:

  • Keyword Spotting 语音关键词检测
  • Keyword Detection 语音关键词侦测
  • Spoken Term Detection 口语侦测
  • Voice Command 语音命令

要让电脑听懂声音,首先要让电脑听到声音,大家自然会想到加个麦克风(Microphone)不就得了。其实这中间没这麽简单,透过Fig. 11-1所示,大概就能知道基本流程,包括收音(电容式或微机电式麦克风)、信号放大、类比数位转换(取样、量化)及传输(储存)等工作。

所谓声音是物体高速振动经由空气传播到耳朵所产生的,但人耳通常只能听到20 ~ 20K Hz(每秒振动次数),年纪大的人可能超过15K Hz就听不太到了。声音包含频率(低沈或尖锐)和振幅(音量大小),所以首先要将这些微小的振动忠实的收集起来,再转换成电压并放大,而这就是传统麦克风在作的工作。

接着将放大後的电压数位化(Analog Digital Convert, ADC 类比数位转换),而转换时又要考虑量化解析度(位元数)及取样频率。前者代表对音量的解析能力,比方8 bit就表示最大音量变化(从最小到最大,可以有正负或非对称)可分解成2的8次方,即256阶,而16 bit就能分解成65,536阶。後者则是对声音变化的取样速度,通常取样要超过待测频率的3到5倍,以免产生伪信号,所以像CD音质的取样频率就定为44.1K Hz,而DVD更高达96K Hz。

因此取样频率及分解能力越高越能接近原始声音讯号,但带来的问题就是需要更大量的储存空间,以44.1 KHz, 16 bit (2 Byte)为例,一秒钟就要 44.1K x 2 Byte = 88.2 KByte。一般MCU的SRAM通常不大的情况下,就要重新考虑够用就好,不用一昧追高,只要足够後续分辨即可。

通常ADC後可直接传到电脑或MCU上储存,但有些微机电(MEMS)型麦克风模组,直接整合了麦克风、放大器、类比数位转换,最後再经由I2S(SPI)等通讯格式将已数位化的声音传输到MCU中,可省去占用MCU的ADC输入端点,同时确保不会因线路传输干扰而得到过多杂讯。

声音取样数位化流程图
Fig. 11-1 声音取样数位化流程图。(OmniXRI整理绘制, 2021/9/24)

如Fig. 11-2所示,目前BLE Sense开发板上就有提供MP34DT05-A这颗无向性微机电麦克风模组,且已使用I2S(SPI)方式连接到MCU上,其内部接线如图所示,更完整的开发板电路图可参考文末参考连结[BLE Sense电路图]。其中LR(P0.17)信号线是方便系统上有接两颗模组时切换左右声道用的,但通常作为唤醒词或异常声音片段侦测时仅需单声道即可。这里和电源VDD和LR接在一起,是为了节省接脚和省电二个用途,当P0.17 低电位时,模组不耗电也不输出。而输出DOUT(P0.25)采脉波密度调变(Pulse Desity Modulation, PDM)方式输出,若LR信号为高电位时,会在CLK(P0.26)高电位时将结果由DOUT送出,而低电位时DOUT则为不输出(Hi-Z 高阻抗)。至於PDM更进一步说明,可参考文末参考连结[AN2057应用笔记]

MP34DT05-A其主要规格如下所示,更完整的规格书(Datasheet)可参考文末参考连结[MP34DT05-A 资料手册]。

  • Single supply voltage
  • Low power consumption
  • AOP = 122.5 dBSPL
  • 64 dB signal-to-noise ratio
  • Omnidirectional sensitivity
  • –26 dBFS ±3 dB sensitivity
  • PDM output
  • HCLGA package
  • Input clock frequency 1.2M ~ 3.25M Hz

Arduino Nano 33 BLE Sense 微机电式麦克风模组MP34DT05-A配置及接线定义
Fig. 11-2 Arduino Nano 33 BLE Sense 微机电式麦克风模组MP34DT05-A配置及接线定义。(OmniXRI整理绘制, 2021/9/24)

接着就以Arduino IDE来介绍如何从麦克风读取声音大小的数值到MCU并回传读到序列监视器。这里Arduino已帮我们准备好现成的范例,只需点击主选单的[档案] ─ [范例] ─ [Arduino Nano 33 BLE的范例] ─ [PDM] ─ [PDMSerialPlotter],就会自动产生一范例程序,如下所示。其中主要包含三大区。

  • setup() 主要用於设定脚位用途及模组初始化,而这段程序只在电源启动或重置时执行一次。
  • loop() 负责执行无穷循环程序,这个部份会一直依序重覆执行。主要动作为传送即时声音数值到序列监视器及绘图器上。
  • onPDMdata() 为PDM回调函数,负责将读取的声音声音大小数值写入缓冲区中。

完整范程序可参考下列程序码及注解。

/*
  PDMSerialPlotter.ino
  由Arduino IDE 主选单的档案 ─ 范例 ─ Arduino Nano 33 BLE的范例 ─ PDM ─ PDMSerialPlotter 取得
  
  主要功能为从MP34DT05-A微机电型麦克风模组透过I2S读取声音讯号,并透过IDE内建序列绘图工具将波形绘出。
  注:序列绘图器尚未加入Arduino IDE 2.0 Beta中
  
  本范例支援
  - Arduino Nano 33 BLE board, or
  - Arduino Nano RP2040 Connect, or
  - Arduino Portenta H7 board plus Portenta Vision Shield
*/

#include <PDM.h> // 导入PDM函式库

static const char channels = 1; // 设定为单音通道,通道数为1
static const int frequency = 16000; // 设定PCM输出频率为16KHz
short sampleBuffer[512]; // 取样缓冲区,每笔资料为Short(16 bit)
volatile int samplesRead; // 已读取样本数量

// 设定脚位用途及模组初始化(只在电源启动或重置时执行一次)
void setup() {
  Serial.begin(9600); // 设定序列通讯速度为9600 bps
  while (!Serial); // 若序列埠未能成功开启则一直等待

  PDM.onReceive(onPDMdata); // 指定PDM接收回调函数

  // PDM.setGain(30); // 选择性设定PDM增益值,BLE Sense预设值为20
 
  if (!PDM.begin(channels, frequency)) { // 初始化PDM参数,单通道收音,16KHz取样频率。
    Serial.println("Failed to start PDM!"); // 若PDM初始化失败则显示字串
    while (1); // 永久等待
  }
}

// 设定无穷循环程序 (会一直依序重覆执行)
void loop() {
  // Wait for samples to be read
  if (samplesRead) { // 若读取样本数不为零

    // 根据样本数列印样本数值到序列监视器和绘图器
    for (int i = 0; i < samplesRead; i++) {
      if(channels == 2) { // 若设为左右声道,通道数为2
        Serial.print("L:"); // 列印「左」提示字串到序列监视器
        Serial.print(sampleBuffer[i]); // 列印取样数值到到序列监视器
        Serial.print(" R:"); // 列印「右」提示字串到序列监视器
        i++; // 计数值加1
      }
      Serial.println(sampleBuffer[i]); // 列印取样数值到到序列监视器
    }
    
    samplesRead = 0; // 清除已读样本计数器
  }
}

/*
PDM接收回调函数,负责处理从麦克风送回的PDM格式资料
这个函式是透过中断服务函式(ISR)完成的,但不支援Serial列印讯息功能。
*/
void onPDMdata() {
  int bytesAvailable = PDM.available(); // 查询可用的资料大小

  PDM.read(sampleBuffer, bytesAvailable); // 读取可用的资料到缓冲区
  samplesRead = bytesAvailable / 2; // 由於读入的资料为16bit格式,所以已读样本数量要除2
}

最後读取到的声音数值变化如Fig. 11-3所示。目前这个程序是连续一直读,没有停止,有兴趣的朋友可自行加点料,改成看到某个输入(触发)信号,再收音1秒或撷取一段指定长度的资料,这样就会更接近tinyML云端一站式开发平台在做的事。

Arduino IDE 序列绘图器即时显示麦克风声音变化图
Fig. 11-3 Arduino IDE 序列绘图器即时显示麦克风声音变化图。(OmniXRI整理绘制,2021/9/24)

参考连结

Arduino Nano 33 BLE Sense Schematics电路图
AN5027 应用笔记 ─ 使用STM32 32位Arm® Cortex® MCU连接PDM数字麦克风(简体版)
MEMS audio sensor omnidirectional digital microphone MP34DT05-A Datasheet 资料手册


<<:  DAY14 - 第三个小范例 : 虚拟货币爬虫

>>:  JavaScript学习日记 : Day14 - 原型与继承(一)

成为工具人应有的工具包-03 CredentialsFileView

CredentialsFileView 今天就来认识 CredentialsFileView 这个工...

Day 28 - 上架 App 到 Google Play Store for Android TV Part 1

不知道大家有没有试过 在手机上的 App 觉得很好用,想找找看是否可以安装在 TV 上 但去了 Go...

Day 20 实作表单 (3)

前言 今天要来接续表单的制作,不同於前两天的是,今天的主题比较明确,我们要写各式各样的 dashbo...

[Python 爬虫这样学,一定是大拇指拉!] DAY23 - 实战演练:HTML Response - 抓取股票代码清单 (2)

开始前我简单带过一下我们这支爬虫 Beautiful soup 的用法好了: from bs4 im...

【第十六天 - 动态规划 介绍】

Q1. 动态规划(Dynamic Programming)是什麽 ? Dynamic program...