概要

STM32マイコンにデジタルマイクSPH0645を接続して音声データを取得する際に、いろいろと引っかかったのでやり方の記録。
最終的にLRの2チャンネル分の音声データを取得できるところまで確認できた。

構成

SPH0645LM4H モジュール基板
  • AliExpressで購入
  • I2S出力 18ビット 32kHz-64kHz

STM32G030F6 自作基板

  • 開発環境はSTM32CubeIDE、HALを使用

I2Sについて

  • 24ビット符号ありデータ 32ビットフレーム
    実際のデータは18ビットで残りは0

CubeIDEの設定

  • オーディオ周波数 48kHz
    実際には50kHzとなり、クロックは3.2MHzになる
  • フィリップス標準
  • 24ビットデータ 32ビットフレーム
  • DMAを有効

STM32のI2Sの処理について

  • 32ビットフレームの場合でも、データの扱いは16ビットごとになる
  • 24ビットデータなので、1回目で上位16ビットを受信し、次で残りの8ビット(実際のデータは2ビット分)を受信する
  • DMAの場合でも同様で、上位16ビット、残りの8ビットの順でバッファに入る
    24ビットデータに再構成する処理が必要

開始処理

uint16_t i2sBuf[I2S_BUF_SIZE];

HAL_I2S_Receive_DMA(&hi2s1, (uint16_t *)i2sBuf, I2S_BUF_SIZE/2);
i2sIndex = 0;
  • DMAの開始処理は最初に一度だけ実行
    後はDMAを動かし続けて、必要なときだけDMAバッファからコピーしてくる形にする
  • DMAバッファは16ビットで作成
  • DMAのサイズ指定は32ビットフレームの単位で数えるので、16ビットのバッファサイズの半分を指定する
    HALのユーザーマニュアル29.2.1で、サイズは16ビットでの長さとあるが、実際には違っていた

    割込み処理

    void HAL_I2S_RxHalfCpltCallback(I2S_HandleTypeDef *hi2s)
    {
    i2sIndex = I2S_BUF_SIZE/2;
    }
    void HAL_I2S_RxCpltCallback(I2S_HandleTypeDef *hi2s)
    {
    i2sIndex = 0;
    }
  • DMAバッファの半分にデータが入る度に割込みが入る
  • 次にデータが入る領域の先頭にインデックスをセットする

受信データの処理

int32_t recBuf[REC_SIZE];
int recIndex = 0;
int i2sReadIndex;

while (1){
    i2sReadIndex = i2sIndex;
    while (1){
        if (i2sIndex!=i2sReadIndex){
            break;
        }
    }
    for (i=0; i<I2S_BUF_SIZE/2/2/2; i++){
        recBuf[0][recIndex] = ( ((int32_t)(int16_t)i2sBuf[i2sReadIndex+4*i]) <<2) + (i2sBuf[i2sReadIndex+4*i+1]>>14);
        recBuf[1][recIndex] = ( ((int32_t)(int16_t)i2sBuf[i2sReadIndex+4*i+2]) <<2) + (i2sBuf[i2sReadIndex+4*i+3]>>14);
        recIndex++;
        if (recIndex>=REC_SIZE){
            break;
        }
    }
    if (recIndex>=REC_SIZE){
        break;
    }
}
  • DMAバッファの完了している半分の領域のデータを録音バッファにコピーする
  • LRの各チャンネルについてそれぞれ16ビットデータ2つ分で、4個単位で処理
  • 各チャンネルで先の16ビットがb17-b2、後の16ビットのうち上位2ビットがb1-b0
  • 上位16ビットは符号ありに変換してから符号拡張

補足 シリアルでウェブアプリにデータを送信する場合

  • 送信側
    for (i=0; i<REC_SIZE; i++){
    for (ch=0; ch<CH_NUM; ch++){
        val = dataBuf[ch][i];
        HAL_UART_Transmit(&huart1, (uint8_t)(&val), 4, 100);
    }
    }
  • 受信側(JavaScript)
    for (let i=0; i<data.length; i++){
    byteData[byteIndex] = data[i];
    byteIndex++;
    if (byteIndex>=4){
        byteIndex = 0;
        const uint8arr = new Uint8Array(byteData);
        const s32Data = new Int32Array(uint8arr.buffer);
        receiveData[receiveDataCh].push(s32Data[0]);
        receiveDataCh++;
        if (receiveDataCh>=CH_NUM){
            receiveDataIndex++;
            receiveDataCh = 0;
        }
    }
    }
  • UARTでは8ビット単位で送る形になる
  • 32ビットごとに区切って、32ビット符号ありに変換する

参考

  • UM2319 Description of STM32G0 HAL and low-layer drivers
  • RM0454 STM32G0x0 advanced Arm-based 32-bit MCUs
  • SPH0645LM4H-B Rev B Datasheet