概要
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

