概要
ESP32でI2S経由でマイクを使う場合のポイントを調べてみました。
I2Sとは?
Inter-IC Soundの略で、本当はI2Sと上付きになるはずです。IC間でサウンドデータを通信するための仕様ですが、ESP32の場合にはちょっと違う意味合いがあります。
定期的にデータを送ったり、受け取ったりする動作もI2Sとよんでいますので、サウンド以外のデータも送受信できますし、I2S以外のプロトコルもI2Sと呼んだりします。ちょっとわかりにくいですね。
たとえば、ESP32でVGA出力があるボードがありますが、この出力はI2SのLCDモードで実現しています。ESP32のI2Sと、一般的なI2Sは違うということは覚えておいてください。
マイクの種類
ESP32はI2S周辺機器として、3種類のマイクを利用することができます。
| pin_config | アナログ | PDM | I2S |
| bck | – | – | CLK |
| ws | – | WS | WS |
| data_out | – | – | – |
| data_in | OUT | DAT | DAT |
アナログマイク
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ビットのデジタルデータが出力される場合が多いです。
- ADAU7002使用ステレオPDMーI2S(TDM)コンバータモジュールキット
秋月さんだと上記かな?
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が間違っています!
また、この関数で設定を修正した場合には、音質に影響がでている可能性があります。
まとめ
今回は基本的な情報をまとめてみました。手探りで調べた内容ですので、間違っている情報があるかもしれません。
次回に実際の設定例などを出しながら、高音質でマイクを使うためのノウハウを書きたいと思います。



コメント
はじめまして、こんにちは。いつも貴重な技術資料として見させていただいています。
I2Sのdma_buf_countとdma_buf_lenは実動作と関連して謎のパラメータだと思っています。
最新(https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/i2s.html githubの最終更新日だと2021/11/24)の資料で
dma_buf_countはThis is an alias to ‘dma_desc_num’ for backward compatibilityでdma_desc_numはThe total number of descriptors used by I2S DMA to receive/transmit dataと。
dma_buf_lenはThis is an alias to ‘dma_frame_num’ for backward compatibilityでdma_frame_numはNumber of frames for one-time sampling. The frame here means the total data from all the channels in a WS cycleと。
いまさらdma_buf_lenをフレーム数として扱って下さいってちょっと酷だな〜と思いました(苦笑)
小生はArduino core for the ESP32 ver 2.0.2でI2S ADC デバイス(マイク:SPH0645LM4H-B、オーディオ入力:PCM1808)で上記資料との一致を確認しました。
お時間あるときに資料を読まれて確認されたら良い思います。
2系はまだ確認してなかったので、今度時間ある時に確認したいと思います!