M5StickCのマイクを使ってみる その1

サンプルスケッチを動かす以上していなかったマイクを触ってみました。もう少し掘り下げて調べる必要がありそうです。

現時点での情報のため、最新情報はM5StickC非公式日本語リファレンスを確認お願いします。

概要

M5StickCにはI2S経由で接続されているマイクが内蔵されています。I2Sからの受信は別タスクで実行され、DMA転送されてバッファに保存されています。

マイクのスペック

型番SPM1423
データシートhttps://github.com/m5stack/M5-Schematic/blob/master/Core/SPM1423HM4H-B.pdf
製造元Knowles Electronics, LLC.
方向無指向性
周波数範囲100Hz ~ 10kHz
感度-22dB ±3dB @ 94dB SPL
S/N比61.5dB
電圧範囲1.6V ~ 3.6V(2.8V供給)
電流 – 供給600µA

サンプルスケッチ(GitHub)

#include <M5StickC.h>
#include <driver/i2s.h>
#define PIN_CLK  0
#define PIN_DATA 34
#define READ_LEN (2 * 256)
uint8_t BUFFER[READ_LEN] = {0};
uint16_t oldy[160];
int16_t *adcBuffer = NULL;
void i2sInit()
{
   i2s_config_t i2s_config = {
    .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_PDM),
    .sample_rate =  44100,
    .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT, // is fixed at 12bit, stereo, MSB
    .channel_format = I2S_CHANNEL_FMT_ALL_RIGHT,
    .communication_format = I2S_COMM_FORMAT_I2S,
    .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
    .dma_buf_count = 2,
    .dma_buf_len = 128,
   };
   i2s_pin_config_t pin_config;
   pin_config.bck_io_num   = I2S_PIN_NO_CHANGE;
   pin_config.ws_io_num    = PIN_CLK;
   pin_config.data_out_num = I2S_PIN_NO_CHANGE;
   pin_config.data_in_num  = PIN_DATA;
  
   
   i2s_driver_install(I2S_NUM_0, &i2s_config, 0, NULL);
   i2s_set_pin(I2S_NUM_0, &pin_config);
   i2s_set_clk(I2S_NUM_0, 44100, I2S_BITS_PER_SAMPLE_16BIT, I2S_CHANNEL_MONO);
}
void mic_record_task (void* arg)
{   
  size_t bytesread;
  while(1){
    i2s_read(I2S_NUM_0,(char*) BUFFER, READ_LEN, &bytesread, (100 / portTICK_RATE_MS));
    adcBuffer = (int16_t *)BUFFER;
    showSignal();
    vTaskDelay(100 / portTICK_RATE_MS);
  }
}
void setup() {
  M5.begin();
  M5.Lcd.setRotation(3);
  M5.Lcd.fillScreen(WHITE);
  M5.Lcd.setTextColor(BLACK, WHITE);
  M5.Lcd.println("mic test");
  i2sInit();
  xTaskCreate(mic_record_task, "mic_record_task", 2048, NULL, 1, NULL);
}
void showSignal(){
  // Offset
  int32_t offset_sum = 0;
  for (int n = 0; n < 160; n++) {
    offset_sum += (int16_t)adcBuffer[n];
  }
  int offset_val = -( offset_sum / 160 );
  // Auto Gain
  int max_val = 200;
  for (int n = 0; n < 160; n++) {
    int16_t val = (int16_t)adcBuffer[n] + offset_val;
    if ( max_val < abs(val) ) {
      max_val = abs(val);
    }
  }
  
  int y;
  for (int n = 0; n < 160; n++){
    y = adcBuffer[n] + offset_val;
    y = map(y, -max_val, max_val, 10, 70);
    M5.Lcd.drawPixel(n, oldy[n],WHITE);
    M5.Lcd.drawPixel(n,y,BLACK);
    oldy[n] = y;
  }
}
void loop() {
  printf("loop cycling\n");
  vTaskDelay(1000 / portTICK_RATE_MS); // otherwise the main task wastes half of the cpu cycles
}

オフィシャルのGitHubにある最新版のサンプルスケッチを参考にしています。0.1.0のサンプルスケッチはデータがuintだったりといろいろおかしいので、修正されたものになります。

私の手元の2台を動かしたところ、無音でも-1500前後数値でしたので、表示部分のデータ平均を取得して、そこを中央に表示するオフセットを行っています。

また、かなり大きな音がしないとグラフがでないので、最大値と最小値を計算して、グラフが拡大するようにしています。

検証

検証にはefuさんの多機能 高精度 テスト信号発生ソフトWaveGeneを利用させていただきました。

上記のように1000Hzのサイン波をパソコンから再生し、イヤホンを使ってM5StickCのマイク入力を行いました。

結果、上記のようにサイン波を画面に表示することができました。再生周波数を変更することで、画面上の表示も変化しています。

まとめ

サンプルスケッチを動かすところまではできましたが、細かい詳細までは検証できていません。

また、このサンプルは100msのスリープを入れているので秒間10回しか描画しません。そして1度に受け取っているデータが256個のため、全44,100サンプリング中2,560サンプリングしか受信できていないことになります。

256個受け取ったデータも、画面上には横160個分しか表示していないので、96サンプリングは捨てています。なんとなく波形を表示させたり、FFTで周波数分析をする分には問題ありませんが、ちゃんと利用するためにはかなり手をいれないとダメな気がします。

続編

コメント