M5StickCを使ったPCM録音

全体の流れ

1. M5StickCで録音したPCMデータをWiFiでRaspberry Piに送る
2. 受信したPCMデータをファイルに出力する
3. WAVファイルに変換する

1. 2. は逐次送ってファイルにどんどん追記する形です
3. は手動でPCにダウンロードしてデータ先頭にWAVヘッダーをくっつけているアナログなやり方です

以下で1. 2. の具体的な方法を説明します

M5StickCで実行するArduinoスケッチ(データ送信側)

これを洗濯機の前面にくっつけてPower Switchを押します(背面が磁石になってて便利)

app.ino

#include <AXP192.h>
#include <IMU.h>
#include <M5Display.h>
#include <M5StickC.h>
#include <RTC.h>

#include <driver/i2s.h>

#include <WiFi.h>
#include <WiFiMulti.h>

const char* ssid     = "your ssid";
const char* password = "your password";

WiFiMulti wifiMulti;

#define PIN_CLK         (0)
#define PIN_DATA        (34)
#define SAMPLING_RATE   (44100)
#define BUF_LEN         (1024)

void i2sInit()
{
   i2s_config_t i2s_config = {
       .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_PDM),
       .sample_rate = SAMPLING_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 = 2,
       .dma_buf_len = BUF_LEN,
   };

   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, SAMPLING_RATE, I2S_BITS_PER_SAMPLE_16BIT, I2S_CHANNEL_MONO);

   // We start by connecting to a WiFi network
   wifiMulti.addAP(ssid, password);

   while(wifiMulti.run() != WL_CONNECTED) {
       delay(500);
   }

   M5.Lcd.println("");
   M5.Lcd.println("WiFi connected");
   M5.Lcd.println("IP address: ");
   M5.Lcd.println(WiFi.localIP());

   delay(500);
}

uint8_t buffer[BUF_LEN];
size_t bytes_read;

// Use WiFiClient class to create TCP connections
WiFiClient client;

void mic_record_task(void* arg)
{   
   const uint16_t port = 12345;
   const char * host = "192.168.0.x"; // ip or dns

   if (!client.connect(host, port)) {
       M5.Lcd.println("[F]");
       return;
   } else {
       M5.Lcd.println("[S]");
   }

   esp_err_t err = ESP_OK;
   TickType_t xLastWakeTime;
   /* 44100/(1000/10) = 441 smpl = 882 byte < BUF_LEN */
   const TickType_t max_wait_time = 10;

   // Initialise the xLastWakeTime variable with the current time.
   xLastWakeTime = xTaskGetTickCount();
   /* main loop cycle 10ms */
   while (1) {
       vTaskDelayUntil( &xLastWakeTime, max_wait_time );

       /* read the recieved PCM samples from DMA buffer. */
       err = i2s_read(I2S_NUM_0, buffer, BUF_LEN, &bytes_read, 0);
       if (err != ESP_OK) {
           break;
       }

       // This will send a request to the server
       client.write(buffer, bytes_read);
   }

   M5.Lcd.println(esp_err_to_name(err));

   client.stop();
   M5.Lcd.println("[C]");
}

void setup() {
   M5.begin();

   i2sInit();
   xTaskCreatePinnedToCore(mic_record_task, "mic_record_task", 2048, NULL, 1, NULL, 1);
}

void loop() {
 
}

Raspberry Piで実行するNode-REDフロー(データ受信側)

flow.json

[
   {
       "id": "649a2816.126b7",
       "type": "tab",
       "label": "フロー 3",
       "disabled": false,
       "info": ""
   },
   {
       "id": "95164129.c142d8",
       "type": "tcp in",
       "z": "649a2816.126b7",
       "name": "",
       "server": "server",
       "host": "",
       "port": "12345",
       "datamode": "stream",
       "datatype": "buffer",
       "newline": "",
       "topic": "",
       "base64": false,
       "x": 100,
       "y": 80,
       "wires": [
           [
               "93d89fe8.c4a05",
               "4e33a433.1d654c"
           ]
       ]
   },
   {
       "id": "4e33a433.1d654c",
       "type": "file",
       "z": "649a2816.126b7",
       "name": "",
       "filename": "/home/pi/mic_dump.bin",
       "appendNewline": false,
       "createDir": true,
       "overwriteFile": "false",
       "encoding": "none",
       "x": 330,
       "y": 160,
       "wires": [
           []
       ]
   },
   {
       "id": "93d89fe8.c4a05",
       "type": "debug",
       "z": "649a2816.126b7",
       "name": "",
       "active": true,
       "tosidebar": true,
       "console": false,
       "tostatus": false,
       "complete": "payload",
       "targetType": "msg",
       "x": 290,
       "y": 80,
       "wires": []
   }
]

解説(M5StickC)

参考にしたArduinoスケッチ
- マイク入力
/Arduino/libraries/M5StickC/examples/Basics/Micophone
- WiFi接続
/Arduino15/packages/esp32/hardware/esp32/1.0.2/libraries/WiFi/examples/WiFiClientBasic

I2S
DMAバッファはデフォルトの128byteから1024byteに変更
DMAバッファから読み取る処理の時間間隔に耐えられるサイズにする必要があるため(↓の10msを指します)

mic_record_task
メインループ(while (1) { } の部分)は10msで回るように設定
1. DMAバッファからWiFi送信用バッファへのコピー(i2s_read)
2. TCPパケットの送信(client.write)
3. 余った時間(10ms - 1.2.の処理時間)分寝る(vTaskDelayUntil)
ちなみに1.2.の処理時間は計ってみたところ、約2msでした

WiFi
ラズパイとポート12345(適当に決めた番号です)でコネクションを張り、WiFi送信用バッファの内容を毎回送信

解説(ラズパイ)

TCPサーバーとしてポート12345のリスナーノードをファイル出力ノードに接続
また、デバッグ出力としても見れるようにdebugノードにも接続

ファイル出力先はラズパイの以下のパス(ユーザディレクトリ直下)に設定

/home/pi/mic_dump.bin

fileノードの設定パラメーターに”文字コード”がありますが”デフォルト”のままにしておいてください
日本語訳がおかしくて、このパラメーターを変更すると”encoding”という内部パラメーターに影響します
"デフォルト"は"none"に相当し、エンコードせずそのまま出力します
”エンコードなし”とでも書いてほしい…

”動作”パラメーターは"ファイルへ追記"にしてください

作ってみての感想

広義での「センシング→データ通信→蓄積」ができるようになったのでIoTぽくなったなと思いました
M5CameraとかGroveモジュールのカメラで録画も頑張ればできそうです

一応10分ぐらいは内蔵バッテリー(80mAh)で動きました
ただ省電力な動作には程遠いので優先度に応じて対応していきたいですね

ちなみにWiFiルーターから洗濯機までは5~6mぐらいです

この記事が気に入ったらサポートをしてみませんか?