最近は涼しい日が続いてますね。
久々にESP32のI2Sマイク回路という技術的な内容を投稿しましたが、今回はその続きでソフトウェア編です。
(前回の記事↓)
事前準備
I2S用のライブラリは、公式の提供するArduino用SDKをインストールすれば自動で付属します。
なお、ESP32用SDKのインストール方法は過去記事でも取り上げています。
ソフトウェア開発
ソースコード(全体)
では、さっそくソースコードを見てみましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 |
#include <driver/i2s.h> #define I2S_PIN_CLK 33 //BCLK #define I2S_PIN_WS 32 //LRCLK #define I2S_PIN_DOUT I2S_PIN_NO_CHANGE #define I2S_PIN_DIN 27 #define I2S_SAMPLE_RATE 32000 #define I2S_BUFFER_COUNT 4 #define I2S_BUFFER_SIZE 8 //512 // I2S configurations // https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/i2s.html // https://lang-ship.com/blog/work/esp32-i2s-mic/ // http://k-hiura.cocolog-nifty.com/blog/2019/06/post-60619f.html const i2s_port_t i2s_port = I2S_NUM_0; const i2s_config_t i2s_config = { .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX), .sample_rate = I2S_SAMPLE_RATE, // kHz .bits_per_sample = I2S_BITS_PER_SAMPLE_32BIT, // 32 bits only .channel_format = I2S_CHANNEL_FMT_ONLY_RIGHT, // monaural (stereo: I2S_CHANNEL_FMT_RIGHT_LEFT) .communication_format = I2S_COMM_FORMAT_I2S, // I2S .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, // interrupt level 1 .dma_buf_count = I2S_BUFFER_COUNT, // number of buffers .dma_buf_len = I2S_BUFFER_SIZE, // samples per buffer .use_apll = false, // external clock source (APLL not used) //.tx_desc_auto_clear = false, // clear TX buffer when under buffer occures .fixed_mclk = 0, // APLL clock }; const i2s_pin_config_t pin_config = { .bck_io_num = I2S_PIN_CLK, // BCLK .ws_io_num = I2S_PIN_WS, // LRCLK .data_out_num = I2S_PIN_DOUT, // not used .data_in_num = I2S_PIN_DIN, // DOUT }; const static int volume = 13; // ボリューム調整 const static int val_offset= 0; // 0点補正 uint8_t samples[I2S_BUFFER_SIZE]; //buffer void setup() { // put your setup code here, to run once: Serial.begin(115200); Serial.println(F("Configuring I2S...")); // I2S setup esp_err_t _err; // installin driver for I2S _err = i2s_driver_install(i2s_port, &i2s_config, 0, NULL); if(_err != ESP_OK){ Serial.printf("Failed installing driver: %d\n", _err); while(true); } // pin configuration _err = i2s_set_pin(i2s_port, &pin_config); if(_err != ESP_OK){ Serial.printf("Failed setting pin: %d\n", _err); while(true); } Serial.println(F("I2S driver installed.")); } void loop() { // put your main code here, to run repeatedly: size_t num_bytes_read; //data length // read esp_err_t err = i2s_read(i2s_port, (char*)samples, I2S_BUFFER_SIZE, &num_bytes_read, portMAX_DELAY); //no timeout if(err == ESP_OK && num_bytes_read == I2S_BUFFER_SIZE){ for(int i = 0; i < num_bytes_read; i += 4){ int32_t* val = (int32_t*)&samples[i]; *val = (*val >> volume) + val_offset; Serial.println(*val); } } delay(10); } |
上のコードをArduino IDEにコピペして、ESP32に書き込めばマイクに入力された音量がシリアル通信で送信されます。今のArduinoにはシリアルプロッタという送信されたデータをグラフに変換してくれる機能があるので、音声信号が波形となって見られます。
(昔はモニタしかなくて、数字しか見られなかったのに…)
続けて、コードの中身を掻い摘んで説明していきます。
I2Sピン定義
ソースコード31-36行目にI2S用の通信ピンを定義しています。
1 2 3 4 5 6 |
const i2s_pin_config_t pin_config = { .bck_io_num = I2S_PIN_CLK, // BCLK .ws_io_num = I2S_PIN_WS, // LRCLK .data_out_num = I2S_PIN_DOUT, // not used .data_in_num = I2S_PIN_DIN, // DOUT }; |
ここではdefineでピン番号の定数を予め定義してますが、直接数字を入れても問題ありません。
なお、定数の I2S_PIN_NO_CHANGE とは、ライブラリ側であらかじめ用意された、利用しないピンを明記する定数です。今回はスピーカーを使わないので音声出力を不使用としているわけです。
I2Sパラメータ設定
上と前後しますが、ソースコード17-29行目でI2Sの設定をしています。
フォーマット(I2S)の指定をはじめ、入出力モード、サンプリング周波数、音声信号のビット数、ステレオかモノラルか、バッファサイズなどを設定します。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
const i2s_config_t i2s_config = { .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX), .sample_rate = I2S_SAMPLE_RATE, // kHz .bits_per_sample = I2S_BITS_PER_SAMPLE_32BIT, // 32 bits only .channel_format = I2S_CHANNEL_FMT_ONLY_RIGHT, // monaural (stereo: I2S_CHANNEL_FMT_RIGHT_LEFT) .communication_format = I2S_COMM_FORMAT_I2S, // I2S .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, // interrupt level 1 .dma_buf_count = I2S_BUFFER_COUNT, // number of buffers .dma_buf_len = I2S_BUFFER_SIZE, // samples per buffer .use_apll = false, // external clock source (APLL not used) //.tx_desc_auto_clear = false, // clear TX buffer when under buffer occures .fixed_mclk = 0, // APLL clock }; |
開発時は tx_desc_auto_clear という引数を入れるとエラーが発生したのでコメントアウトしていますが、最新版では利用可能なようなので、SDK(とArduino IDE)を最新版にしておけばビルドできるかもしれません(コメントアウトのままでも動きます)。
また、定数は I2S_SAMPLE_RATE(サンプリング周波数)、I2S_BUFFER_COUNT(バッファ数)、I2S_BUFFER_SIZE(バッファサイズ) だけを独自で定義しており、他はライブラリの定数をそのまま利用しています。バッファサイズ以外はここでしか使わないため、数値を直接打ち込んでもOKです。
なお、パラメータの詳細についてはこちらのサイトをご参照ください。
I2S回路の選択
ESP32にはI2S回路が2つ内蔵されており、I2S_NUM_x(0, 1)という定数で使う回路を選択できます。
例えばマイクで音声を取りながらスピーカーで音声再生といった処理も可能です。
1 |
const i2s_port_t i2s_port = I2S_NUM_0; |
初期化処理
ソースコード43-62行目のsetup関数でI2Sの初期化しています。
1 2 3 4 5 6 7 8 9 10 11 |
// I2S setup esp_err_t _err; // installin driver for I2S _err = i2s_driver_install(i2s_port, &i2s_config, 0, NULL); //... // pin configuration _err = i2s_set_pin(i2s_port, &pin_config); // error handling if(_err != ESP_OK){ // (process something) } |
関係する関数は、以下の2つです。
- i2s_driver_install:I2Sの音声処理の設定
- i2s_setup_pin:I2Sのピン設定
前者にはI2S回路番号、パラメータ、I2Sイベントのキューへのポインタとサイズ、後者にはI2S回路番号とピン定義を与えます。I2Sイベントのキューはまだよく分かっていないのですが、今回は特に使うことも無いので0とNULLを指定しています。
また、どちらの関数もエラーコードを返してくれるので、異常が発生した時の処理も実装できます。
メイン処理
いよいよマイクからの音声を取得する処理に入っていきます。
ソースコード65-79行目のloop関数内に処理が記述されています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
// グローバル定数と変数 const static int volume = 13; // ボリューム調整 const static int val_offset= 0; // 0点補正 uint8_t samples[I2S_BUFFER_SIZE]; //buffer //... //------------------- loop関数内 ------------------------------ void loop(){ // put your main code here, to run repeatedly: size_t num_bytes_read; //data length // read esp_err_t err = i2s_read(i2s_port, (char*)samples, I2S_BUFFER_SIZE, &num_bytes_read, portMAX_DELAY); //no timeout if(err == ESP_OK && num_bytes_read == I2S_BUFFER_SIZE){ for(int i = 0; i < num_bytes_read; i += 4){ int32_t* val = (int32_t*)&samples[i]; *val = (*val >> volume) + val_offset; Serial.println(*val); } } delay(10); } |
マイクからの音声データ取得は関数 i2s_read で実行しています。
引数はI2S回路番号、サンプリングデータ、バッファサイズ、データ長、タイムアウト時間ですが、portMAX_DELAYにするとタイムアウトしなくなります。また、こちらもエラーコードを出すので、エラーに応じた処理を実装することが可能です。
そのあとは、配列 samples に音声データを格納し、ボリューム調整(シフトダウン)とオフセットを入れてシリアル通信で数値を送信しています。
以上でソフトウェアの解説は終わりです。
それでは、ESP32でI2Sマイクを触ってみてください。
参考
こちらのサイトを参考にプログラム開発をしました。
また、公式のAPIリファレンスもありますので、ライブラリの関数や使い方が参照できます。