ESP32のI2Sマイク研究 その1 基礎

概要

ESP32でI2S経由でマイクを使う場合のポイントを調べてみました。

I2Sとは?

Inter-IC Soundの略で、本当はI2Sと上付きになるはずです。IC間でサウンドデータを通信するための仕様ですが、ESP32の場合にはちょっと違う意味合いがあります。

定期的にデータを送ったり、受け取ったりする動作もI2Sとよんでいますので、サウンド以外のデータも送受信できますし、I2S以外のプロトコルもI2Sと呼んだりします。ちょっとわかりにくいですね。

たとえば、ESP32でVGA出力があるボードがありますが、この出力はI2SのLCDモードで実現しています。ESP32のI2Sと、一般的なI2Sは違うということは覚えておいてください。

マイクの種類

ESP32はI2S周辺機器として、3種類のマイクを利用することができます。

pin_configアナログPDMI2S
bckCLK
wsWSWS
data_out
data_inOUTDATDAT

アナログマイク

M5Stack Fireなどに接続されているマイクです。接続したマイクをESP32の内蔵ADCを利用して、定期的にサンプリングします。I2S経由で接続することで、定期的なサンプリングまでESP32が処理してくれます。

アナログはADCを定期的に動かすだけなので、設定と接続がかんたんです。たぶんモノラル録音しかできません。

秋月さんだと上記みたいなのでしょうか? 一般的なマイクにアンプを接続したものになると思います。

PDMマイク

M5StickCやATOM Echoなどが採用しているマイクです。I2Sとは違いますが、1ビットあたり64もしくは128周期のPWMみたいな信号(シグマデルタ)で強弱を通信します。

PDMはクロック入力をWSにいれて、DAT端子から信号がでるのでESP32で受信させます。PDMマイクにL・R選択用のピンがあるので、VDDに接続するかGNDに接続するのかであらかじめ設定しておく必要があります。L・Rの2個を並行に接続することで、クロックの立ち上がりと立ち下がりでL・Rの信号を区別して通信を行います。ただし、実際にはモノラルの場合ばかりです。

秋月さんだとこれ?

I2Sマイク

本当のI2Sプロトコルを利用したマイクです。ESP-EYEなどで採用されています。

PDMマイクと同じようにL・R選択用のピンをあらかじめ設定しておく必要があります。ただし、CLKピンからクロックを供給し、L・Rの選択はWSピンで行います。DATピンからは32ビットのデジタルデータが出力される場合が多いです。

秋月さんだと上記かな?

ESP32でI2S利用時の注意

立ち上がり時間

クロックを供給してから、すぐに録音できるかというとそうではありません。ちょっと安定するまで時間がかかるので気をつける必要があります。

上記はアナログです。アナログはADCで取得するだけなので安定しています。ボタンを押して録音をするタイプだと、ボタンを押し切ったところでノイズが発生します。0.2秒のところがノイズになりますので、このまま使うか最初少しデータを捨てるのかは検討する必要があります。

上記がM5StickCのPDMマイクです。見事に1秒程度安定していません。ちなみに検証中はこの1秒未満のデータを使っていて、泥沼にハマっていました。

ボタンを押したらマイクを録音して、離すと再生するタイプのアプリの場合には、まず起動したらマイクモードにして、再生直前にスピーカーモード、再生が終了したらマイクモードに戻すなどして、安定したクロックをなるべくマイクに与える方がいいと思います。

これはM5StickCでクロックが安定してから録音したものですが、0.2秒にボタン音が入っています。大きな音が入るとその後のデータがずれちゃいます。。。

可能であれば録音開始0.5秒ぐらいのデータは捨てたほうがいいんじゃないかと思っています。

上記がESP-EYEのI2Sマイクです。I2Sでも開始0.25秒ぐらいはデータが安定していませんでした。

ESP32のI2Sパラメーター

この情報が意外にすくないです。基本的にESP32は2018年とか古い時代にある程度解析されていて、最近解析していない人が多いです。そして、ここ数年で変わってきているところがあるのでミスマッチが多い気がしました。

i2s_config

  i2s_config_t i2s_config = {
    .mode                 = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_PDM),
    .sample_rate          = I2S_SAMPLE_RATE,
    .bits_per_sample      = I2S_BITS_PER_SAMPLE_16BIT,
    .channel_format       = I2S_CHANNEL_FMT_ALL_RIGHT,
    .communication_format = I2S_COMM_FORMAT_I2S,
    .intr_alloc_flags     = ESP_INTR_FLAG_LEVEL1,
    .dma_buf_count        = I2S_BUFFER_COUNT,
    .dma_buf_len          = I2S_BUFFER_SIZE,
    .use_apll             = false,
    .tx_desc_auto_clear   = false,
    .fixed_mclk           = 0
  };

ざっくりと項目の説明をします。

mode

マイクなのですべてマスターモードの受信です。

(i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_ADC_BUILT_IN)

アナログマイクの場合にはビルドインのADCを使う指定をします。

(i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_PDM)

PDMマイクの場合にはPDMを追加。

(i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX)

I2Sマイクの場合にはシンプルな指定になります。

sample_rate

サンプリングレートを指定します。音声の場合には16000、22050、32000、44100、48000あたりがよく使われます。

bits_per_sample

アナログとPDMはI2S_BITS_PER_SAMPLE_16BIT固定です。I2SはI2S_BITS_PER_SAMPLE_32BITに基本的になるはずです。

channel_format

定義チャンネルデータ
I2S_CHANNEL_FMT_RIGHT_LEFTステレオL・Rのデータ
I2S_CHANNEL_FMT_ALL_RIGHTステレオR・Rのデータ
I2S_CHANNEL_FMT_ALL_LEFTステレオL・Lのデータ
I2S_CHANNEL_FMT_ONLY_RIGHTモノラルRのデータ
I2S_CHANNEL_FMT_ONLY_LEFTモノラルLのデータ

ここが結構間違えている場合が多いです。モノラルの場合にはONLYを選択する必要があります。間違ってALLを選択している例が多いです。

ただ、モノラルの場合でもステレオで処理したほうが音声はきれいな気がします、、、

communication_format

基本はI2S_COMM_FORMAT_I2Sで良いはずです。LSBの場合には(i2s_comm_format_t)(I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_LSB)と複数指定する必要があります。

標準はMSBなので未指定でも指定しても変わりません。ただし、I2S_COMM_FORMAT_I2S_MSBだけだとI2Sも指定されていない状態になります。

intr_alloc_flags

割り込みレベルです。ESP_INTR_FLAG_LEVEL1のままで通常は問題ないと思います。

dma_buf_count, dma_buf_len

バッファサイズと個数です。ここは低負荷だとバッファ不足にならないのであまり気にしなくても大丈夫ですが、いくつぐらいがいいのかが全くわかりません。

512バイトを4つぐらいで試していますが、1024バイトぐらいを豪快に一度で取得しているスケッチもあります。サイズを増やすと安定はするはずですが遅延時間が増えます。

use_apll

クロックソースをAPLLを使うかの選択。わりと最近に追加されたので、基本falseに設定したほうが無難だと思います。昔のサンプルだと最後の3つは指定していないことが多いですが、未指定の場合には意図しない数値になっている可能性があるので、必ず指定してください。

tx_desc_auto_clear

送信時に送信バッファが空になる、アンダーバファになった場合falseの場合には最後のデータを再送します。trueの場合には0クリアしたデータを送信します。

送信時にアンダーバファにならなければ影響はないですが、最後のデータを再送するとノイズになる可能性があるので追加されたようです。

fixed_mclk

APLLを有効にした場合、このクロックも指定できます。内部のソースをみたところArduino IDEで使われているESP-IDF3.2と、3.3以降で計算式が違うのが非常に気になります。

0を指定するのが今の所、無難だと思います。

pin_config

  i2s_pin_config_t pin_config = {
    .bck_io_num           = I2S_PIN_CLK,
    .ws_io_num            = I2S_PIN_WS,
    .data_out_num         = I2S_PIN_DOUT,
    .data_in_num          = I2S_PIN_DIN,
  };

こんな感じで、実際のピンはdefine定義などをしたほうがいいと思います。

#define I2S_PIN_CLK                 I2S_PIN_NO_CHANGE
#define I2S_PIN_WS                  33
#define I2S_PIN_DOUT                I2S_PIN_NO_CHANGE
#define I2S_PIN_DIN                 23

上記が定義例です。未使用のピンは-1のI2S_PIN_NO_CHANGEを指定します。PDMマイクで、I2S_PIN_CLKをWSに設定している例があったりするので注意が必要です。

i2s_set_clk()関数

すごく便利な関数なのですが、個人的には非推奨です。i2s_configを間違っている場合に、この関数で正しい値に修正しているサンプル例が多数あります。特にモノラルマイクでI2S_CHANNEL_FMT_ALL_LEFTを指定してステレオ設定なのを、この関数でモノラルに指定しているなどが多いです。

呼び出さなくても動くはずなので、この関数を呼ばない場合に動きがおかしいってことは、i2s_configが間違っています!

また、この関数で設定を修正した場合には、音質に影響がでている可能性があります。

まとめ

今回は基本的な情報をまとめてみました。手探りで調べた内容ですので、間違っている情報があるかもしれません。

次回に実際の設定例などを出しながら、高音質でマイクを使うためのノウハウを書きたいと思います。

コメントする

メールアドレスが公開されることはありません。

管理者承認後にページに追加されます。公開されたくない相談はその旨本文に記載するかTwitterなどでDM投げてください。またスパム対策として、日本語が含まれない投稿は無視されますのでご注意ください。