ESP32のI2Sマイク研究 その2 パラメータ

概要

前回は基礎的な話をしたので、実際に端末ごとに調整したパラメータを紹介したいと思います。

M5Stack Fire(アナログマイク)

#include <M5Stack.h>
#include <driver/i2s.h>

#define I2S_NUM                     I2S_NUM_0
#define ADC_INPUT                   ADC1_GPIO34_CHANNEL // ADC CHANNEL
#define ADC_UNIT                    ADC_UNIT_1          // ADC1 or ADC2

#define I2S_SAMPLE_RATE             16000
#define I2S_BUFFER_COUNT            4
#define I2S_BUFFER_SIZE             512

#define STORAGE_LEN                 (102400)  // 本体保存容量(MAX 100K前後)
#define BTN_GPIO                    (39)
#define REC_DELAY_MS                500

uint8_t recBuffer[I2S_BUFFER_SIZE]; // DMA転送バッファ
uint8_t recStorage[STORAGE_LEN];    // サウンドデータ保存領域

bool recFlag = false;               // 録音状態
int recPos = 0;                     // 録音の長さ

void i2sInit() {
  i2s_config_t i2s_config = {
    .mode                 = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_ADC_BUILT_IN),
    .sample_rate          = I2S_SAMPLE_RATE,
    .bits_per_sample      = I2S_BITS_PER_SAMPLE_16BIT,
    .channel_format       = I2S_CHANNEL_FMT_ALL_LEFT,
    .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
  };

  i2s_driver_install(I2S_NUM, &i2s_config, 0, NULL);
  i2s_set_adc_mode(ADC_UNIT, ADC_INPUT);
  i2s_adc_enable(I2S_NUM);
}

// 録音用タスク
void i2sRecordTask(void* arg)
{
  // 初期化
  recPos = 0;
  memset(recStorage, 0, sizeof(recStorage));

  // 開始直後は録音しない
  delay(REC_DELAY_MS);

  // 録音処理
  while (recFlag) {
    size_t transBytes;

    // I2Sからデータ取得
    i2s_read(I2S_NUM, (char*)recBuffer, I2S_BUFFER_SIZE, &transBytes, portMAX_DELAY);

    // データ保存
    for (int i = 0 ; i < transBytes ; i += 4 ) {
      if ( recPos < STORAGE_LEN ) {
        uint16_t* val = (uint16_t*)&recBuffer[i];
        int16_t* p = (int16_t*)&recStorage[recPos];
        *p = (((0x0fff - (*val & 0x0fff)) * 16) - 0x8000);
        recPos += 2;
      }
    }

    Serial.printf("transBytes = %d, STORAGE_LEN=%d, recPos=%d\n", transBytes, STORAGE_LEN, recPos);
    delay(1);
  }

  //i2s_driver_uninstall(I2S_NUM);

  // タスク削除
  vTaskDelete(NULL);
}

void i2sRecord() {
  // 録音開始
  recFlag = true;
  xTaskCreatePinnedToCore(i2sRecordTask, "i2sRecordTask", 2048, NULL, 1, NULL, 1);
}

void setup() {
  M5.begin();
  i2sInit();
}

void loop() {
  if (recFlag == false && digitalRead(BTN_GPIO) == 0) {
    // 録音スタート
    Serial.println("Record Start");
    i2sRecord();
  } else if (recFlag == true && digitalRead(BTN_GPIO) == 1) {
    // 録音ストップ
    recFlag = false;
    delay(100); // 録音終了まで待つ

    Serial.println("Record Stop");

    // データをシリアルに出力
    for (int n = 0; n <= recPos; n++) {
      Serial.printf("%02x", recStorage[n]);
    }
    Serial.printf("\n");
    Serial.printf("Print End\n");
  }

  delay(10);
}

M5Stack Fireはアナログ接続なのでADCの設定が必要になります。GPIOではなく内部のどのADCに接続しているかの情報とPADを設定します。

上記が録音した結果の波形です。440Hzの音を録音して、若干ガタがありますがそれなりに整っています。若干マイナスにオフセットされていますね。

周波数分析をしてみると、多少他の成分もでちゃっています。

上記が調整前の波形を、440Hzと比べている画像です。

調整の特徴として、アナログマイクの場合に、モノラルになるはずですがモノラルで通信をすると上記みたいに波形が荒れます。そのためステレオで受信して、片チャンネルを捨てる処理をいれています。

  • https://github.com/espressif/arduino-esp32/blob/master/libraries/ESP32/examples/I2S/HiFreq_ADC/HiFreq_ADC.ino

一般的には上記のコードを参考にしている人が多いはずです。このサンプルもモノラルなのにステレオで処理していますね、、、

M5StickC(PDMマイク)

#include <M5StickC.h>
#include <driver/i2s.h>

#define I2S_NUM                     I2S_NUM_0
#define I2S_PIN_CLK                 I2S_PIN_NO_CHANGE
#define I2S_PIN_WS                  0
#define I2S_PIN_DOUT                I2S_PIN_NO_CHANGE
#define I2S_PIN_DIN                 34

#define I2S_SAMPLE_RATE             16000
#define I2S_BUFFER_COUNT            4
#define I2S_BUFFER_SIZE             512

#define STORAGE_LEN                 (102400)  // 本体保存容量(MAX 100K前後)
#define BTN_GPIO                    (37)
#define REC_DELAY_MS                500

uint8_t recBuffer[I2S_BUFFER_SIZE]; // DMA転送バッファ
uint8_t recStorage[STORAGE_LEN];    // サウンドデータ保存領域

bool recFlag = false;               // 録音状態
int recPos = 0;                     // 録音の長さ

void i2sInit() {
  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_ONLY_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
  };
  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,
  };

  i2s_driver_install(I2S_NUM, &i2s_config, 0, NULL);
  i2s_set_pin(I2S_NUM, &pin_config);
}

// 録音用タスク
void i2sRecordTask(void* arg)
{
  // 初期化
  recPos = 0;
  memset(recStorage, 0, sizeof(recStorage));

  // 開始直後は録音しない
  delay(REC_DELAY_MS);

  // 録音処理
  while (recFlag) {
    size_t transBytes;

    // I2Sからデータ取得
    i2s_read(I2S_NUM, (char*)recBuffer, I2S_BUFFER_SIZE, &transBytes, portMAX_DELAY);

    // データ保存
    for (int i = 0 ; i < transBytes ; i += 2 ) {
      if ( recPos < STORAGE_LEN ) {
        int16_t* val = (int16_t*)&recBuffer[i];
        int16_t* p = (int16_t*)&recStorage[recPos];
        *p = *val;
        recPos += 2;
      }
    }

    Serial.printf("transBytes = %d, STORAGE_LEN=%d, recPos=%d\n", transBytes, STORAGE_LEN, recPos);
    delay(1);
  }

  //i2s_driver_uninstall(I2S_NUM);

  // タスク削除
  vTaskDelete(NULL);
}

void i2sRecord() {
  // 録音開始
  recFlag = true;
  xTaskCreatePinnedToCore(i2sRecordTask, "i2sRecordTask", 2048, NULL, 1, NULL, 1);
}

void setup() {
  M5.begin();
  i2sInit();
}

void loop() {
  if (recFlag == false && digitalRead(BTN_GPIO) == 0) {
    // 録音スタート
    Serial.println("Record Start");
    i2sRecord();
  } else if (recFlag == true && digitalRead(BTN_GPIO) == 1) {
    // 録音ストップ
    recFlag = false;
    delay(100); // 録音終了まで待つ

    Serial.println("Record Stop");

    // データをシリアルに出力
    for (int n = 0; n <= recPos; n++) {
      Serial.printf("%02x", recStorage[n]);
    }
    Serial.printf("\n");
    Serial.printf("Print End\n");
  }

  delay(10);
}

M5StickCとATOM Echoは同じマイクを使っています。

// ATOM Echo
#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

ピン設定を変えれば同じコードで動きます。

上記はATOM Echoですがきれいな波形ですね。思いっきりマイナスにオフセットしているのはM5StickCとATOM Echoの特徴です。

上が標準的な設定で録音した場合(波形に段差)で、下が調整後です。

標準的な設定で問題になるのはやっぱりモノラルですね。

    .channel_format = I2S_CHANNEL_FMT_ALL_RIGHT,

上記でALLなのでステレオ指定なのですが、i2s_set_clk()でモノラルに戻しています。

PDMもステレオで受信してから、方チャンネルを捨てたほうがきれいに録音できました。

ESP-EYE(I2Sマイク)

#include <driver/i2s.h>

#define I2S_NUM                     I2S_NUM_0
#define I2S_PIN_CLK                 26
#define I2S_PIN_WS                  32
#define I2S_PIN_DOUT                I2S_PIN_NO_CHANGE
#define I2S_PIN_DIN                 33


#define I2S_SAMPLE_RATE             16000
#define I2S_BUFFER_COUNT            4
#define I2S_BUFFER_SIZE             512

#define STORAGE_LEN                 (102400)  // 本体保存容量(MAX 100K前後)
#define BTN_GPIO                    (15)
#define REC_DELAY_MS                500

uint8_t recBuffer[I2S_BUFFER_SIZE]; // DMA転送バッファ
uint8_t recStorage[STORAGE_LEN];    // サウンドデータ保存領域

bool recFlag = false;               // 録音状態
int recPos = 0;                     // 録音の長さ

// 録音をする
void i2sInit() {
  // 録音用設定
  i2s_config_t i2s_config = {
    .mode                 = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX),
    .sample_rate          = I2S_SAMPLE_RATE * 2,
    .bits_per_sample      = I2S_BITS_PER_SAMPLE_32BIT,
    .channel_format       = I2S_CHANNEL_FMT_ONLY_LEFT,
    .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
  };
  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,
  };

  // 録音設定実施
  i2s_driver_install(I2S_NUM_0, &i2s_config, 0, NULL);
  i2s_set_pin(I2S_NUM_0, &pin_config);
}

// 録音用タスク
void i2sRecordTask(void* arg)
{
  // 初期化
  recPos = 0;
  memset(recStorage, 0, sizeof(recStorage));

  // 開始直後は録音しない
  delay(REC_DELAY_MS);

  // 録音処理
  while (recFlag) {
    size_t transBytes;

    // I2Sからデータ取得
    i2s_read(I2S_NUM, (char*)recBuffer, I2S_BUFFER_SIZE, &transBytes, portMAX_DELAY);

    // データ保存
    for (int i = 0 ; i < transBytes ; i += 4 ) {
      if ( recPos < STORAGE_LEN ) {
        int32_t* val = (int32_t*)&recBuffer[i];
        int16_t* p = (int16_t*)&recStorage[recPos];
        *p = *val / 256;
        recPos += 2;
      }
    }

    Serial.printf("transBytes = %d, STORAGE_LEN=%d, recPos=%d\n", transBytes, STORAGE_LEN, recPos);
    delay(1);
  }

  //i2s_driver_uninstall(I2S_NUM_0);

  // タスク削除
  vTaskDelete(NULL);
}

void i2sRecord() {
  // 録音開始
  recFlag = true;
  xTaskCreatePinnedToCore(i2sRecordTask, "i2sRecordTask", 2048, NULL, 1, NULL, 1);
}

void setup() {
  Serial.begin(115200);
  i2sInit();
}

void loop() {
  if ( recFlag == false && digitalRead(BTN_GPIO) == 0 ) {
    // 録音スタート
    Serial.println("Record Start");
    i2sRecord();
  } else if ( recFlag == true && digitalRead(BTN_GPIO) == 1 ) {
    // 録音ストップ
    recFlag = false;
    delay(100); // 録音終了まで待つ

    Serial.println("Record Stop");

    // データをシリアルに出力
    for (int n = 0; n <= recPos; n++) {
      Serial.printf("%02x", recStorage[n]);
    }
    Serial.printf("\n");
    Serial.printf("Print End\n");
  }

  delay(10);
}

ESP-EYEはI2S接続のちゃんとしたマイクのはずです。。。がっ、セッティングが出ていません。

オフセットはないんですが、信号がフラフラしています。。。

周波数分析をしたところ440Hzは出ているのですが、不安定過ぎます。

上記を参考にしていますが、I2Sだとステレオにすると信号が壊れたので本当に2つマイクがないとステレオ指定できないのかもしれません。

にしても結果が悪いので、ESP-EYEではなくて他のI2S接続マイクを使って後日試験をしてみたいと思います。。。

ただ同じの1つしか買っていないので、ステレオ実験はもう少し先かな。。。

まとめ

なんと今のところM5StickCとATOM Echoのマイクが一番キレイに録音できています。。。ESP-EYEはもうちょっと調整が必要なのかな。。。

コメント