chakokuのブログ(rev4)

テック・コミック・DTM・・・ごくまれにチャリ

I2S接続のMEMS MIC (MSM261S4030H0R )を使って録音してみる→かなり感度が悪い

やりたいこと:I2S仕様のMEMS MICをRPi Pico(RP2040)版MicroPythonから制御
8/11時点の結論:MicroPythonのI2Sライブラリを使ってバッファにデータ蓄積することはできた
課題:かなり感度が悪い。これでは録音というレベルではない。

MSM261S4030H0R 
Fermion: I2S MEMS Microphone Sensor (Breakout) - DFRobot
Fermion - I2S MEMS マイク — スイッチサイエンス
https://www.denovocn.com/sites/default/files/MSM261S4030H0R.pdf

RP2040/uPythonのI2S 仕様
RP2 用クイックリファレンス — MicroPython 1.19.1 ドキュメント

RPiとI2Sで接続してデータを取ってみる。I2Sの設定は以下のように、16bit長、44.1KHzサンプリング、内蔵バッファは20KB、Monoral、

i2s = I2S(0, sck=Pin(18), ws=Pin(19), sd=Pin(20), mode=I2S.RX, bits=16, format=I2S.MONO, rate=44100, ibuf=20000)

使ったI2Sバス接続のMEMS MICは MSM261S4030H0R で、LR指定をLに落としてLeftを出力するように指定。この時の波形は以下

斜めに下がってる所がかなり気になるが、、ここは読まれない領域と勝手に解釈して、i2sで取り込んだはずのバッファをダンプすると以下

for i in range(0x100):
  print(f'{buf[i]:02x} ',end='')
  if (i+1) % 16 == 0:
     print('')


11 00 02 00 00 00 0d 00 09 00 05 00 02 00 04 00
02 00 ff ff 0d 00 08 00 fe ff 0a 00 04 00 04 00
02 00 06 00 0b 00 0b 00 0a 00 fd ff 09 00 03 00
03 00 0a 00 02 00 03 00 02 00 08 00 07 00 fa ff
fd ff 05 00 05 00 05 00 01 00 f8 ff 00 00 0b 00
00 00 00 00 0a 00 f6 ff 03 00 06 00 f7 ff 0c 00
fd ff f1 ff 0d 00 f9 ff f6 ff 04 00 fd ff fb ff
0c 00 ff ff f7 ff 05 00 04 00 fe ff 00 00 fe ff
03 00 01 00 ff ff 07 00 fd ff fd ff 02 00 0b 00
fc ff 08 00 0f 00 fc ff ff ff 0c 00 00 00 03 00
0f 00 fc ff 05 00 05 00 ff ff ff ff 03 00 05 00
06 00 ff ff 04 00 06 00 f7 ff 0e 00 0e 00 f8 ff
01 00 06 00 f9 ff 06 00 0a 00 00 00 01 00 fd ff
01 00 08 00 fa ff ff ff fe ff 02 00 0b 00 fc ff
01 00 00 00 f6 ff 04 00 09 00 f8 ff fe ff 02 00
fe ff fa ff 07 00 02 00 fc ff fc ff 04 00 fd ff

波形で見るとRは00なので08-23-00-00-02-38-00-00のようなRは0埋めかと思ったけど、これでいいのか?これはI2Sで読み込んだLRのうち、Lだけをバッファに取り込んでいるということだろうか。だとすると、format=I2S.STEREO を指定するとRの2byte分は0で埋められる??
以下のようにSTEREO指定でI2Sを初期化、readnitoでバッファに読み込む

i2s = I2S(0, sck=Pin(18), ws=Pin(19), sd=Pin(20), mode=I2S.RX, bits=16, format=I2S.STEREO, rate=44100, ibuf=20000)
i2s.readinto(buf)

読み込んだデータをダンプさせると以下の通り、Rchは0埋めになっているので、I2S.MONO指定して実行するとL-chのみがバッファに入るようである。これはありがたい(モノラルマイクなので、本来片側Channelは不要なので)

>>> for i in range(0x100):
...   print(f'{buf[i]:02x} ',end='')
...   if (i+1) % 16 == 0:
...      print('')
...
ff 7f 00 00 06 00 00 00 0a 00 00 00 82 ff 00 00
84 ff 00 00 71 00 00 00 70 00 00 00 a0 00 00 00
1d 00 00 00 fc 00 00 00 7e ff 00 00 e4 01 00 00
29 fe 00 00 d9 03 00 00 6a fb 00 00 f9 07 00 00
20 f4 00 00 62 18 00 00 e1 fe 00 00 14 98 00 00
3d 78 00 00 84 63 00 00 b5 71 00 00 8f 68 00 00
b9 6e 00 00 80 6a 00 00 24 6d 00 00 69 6b 00 00
53 6c 00 00 c7 6b 00 00 f8 6b 00 00 d6 6b 00 00
bf 6b 00 00 b1 6b 00 00 a4 6b 00 00 96 6b 00 00
89 6b 00 00 7b 6b 00 00 6e 6b 00 00 60 6b 00 00
53 6b 00 00 45 6b 00 00 38 6b 00 00 2b 6b 00 00
1d 6b 00 00 10 6b 00 00 03 6b 00 00 f5 6a 00 00
e8 6a 00 00 db 6a 00 00 cd 6a 00 00 c0 6a 00 00
b3 6a 00 00 a5 6a 00 00 98 6a 00 00 8b 6a 00 00
7d 6a 00 00 70 6a 00 00 63 6a 00 00 55 6a 00 00
48 6a 00 00 3b 6a 00 00 2d 6a 00 00 20 6a 00 00

スマフォで440Hzを発生させて録音させたときのデータダンプは以下
16bit、44.1KHzサンプリング、MONO

I2S(0, sck=Pin(18), ws=Pin(19), sd=Pin(20), mode=I2S.RX, bits=16, format=I2S.MONO, rate=44100, ibuf=20000)
00 00 f4 ff 13 00 50 00 6a 00 d0 00 ab 00 63 01
b0 00 39 02 57 00 a4 03 24 ff a3 06 51 f9 98 24
e7 78 ed 5a 4e 6f 54 6a 7e 6d 49 6b aa 6c b0 6b
35 6c d5 6b f2 6b cd 6b c1 6b b3 6b a5 6b 98 6b
8a 6b 7d 6b 6f 6b 62 6b 54 6b 47 6b 39 6b 2c 6b
1e 6b 11 6b 04 6b f7 6a e9 6a dc 6a cf 6a c1 6a
b4 6a a7 6a 99 6a 8c 6a 7f 6a 71 6a 64 6a 57 6a
49 6a 3c 6a 2e 6a 21 6a 14 6a 07 6a f9 69 ec 69
df 69 d2 69 c5 69 b8 69 aa 69 9d 69 90 69 83 69
76 69 68 69 5b 69 4e 69 41 69 34 69 26 69 19 69
0c 69 ff 68 f2 68 e5 68 d8 68 cb 68 bd 68 b0 68
a3 68 96 68 89 68 7c 68 6f 68 62 68 55 68 48 68
3b 68 2e 68 21 68 14 68 07 68 f9 67 ec 67 e0 67
d3 67 c6 67 b9 67 ac 67 9f 67 92 67 85 67 78 67
6b 67 5e 67 51 67 44 67 38 67 2b 67 1e 67 11 67
04 67 f7 66 ea 66 dd 66 d0 66 c3 66 b7 66 aa 66

これを大体の波形にするなら、、44.1KHzサンプリングは440Hzの100倍なので、100サンプルあれば、440Hzの波形が一つとれているはず。1サンプル2Byteなので、200B取ってくればその中に一つの波形が見られるはず。2バイトの並びが先頭MSBだと仮定して、2バイトを16bit長の符号付き整数とみなして100サンプル分を取り出してみる。
といいつつ。符号化が面倒なので、まずは2Bを連結だけして100サンプル分をダンプ
上位バイト->下位バイトの並びと想定して2バイト化すると以下

>>> dump2B(buf)
0000 f4ff 1300 5000 6a00 d000 ab00 6301 b000 3902 5700 a403 24ff a306 51f9 9824
e778 ed5a 4e6f 546a 7e6d 496b aa6c b06b 356c d56b f26b cd6b c16b b36b a56b 986b
8a6b 7d6b 6f6b 626b 546b 476b 396b 2c6b 1e6b 116b 046b f76a e96a dc6a cf6a c16a
b46a a76a 996a 8c6a 7f6a 716a 646a 576a 496a 3c6a 2e6a 216a 146a 076a f969 ec69
df69 d269 c569 b869 aa69 9d69 9069 8369 7669 6869 5b69 4e69 4169 3469 2669 1969
0c69 ff68 f268 e568 d868 cb68 bd68 b068 a368 9668 8968 7c68 6f68 6268 5568 4868
3b68 2e68 2168 1468 

上位バイト→下位バイトの並びなのかどうかちょっと怪しい感じが・・
下位バイト→上位バイトの並びと仮定すると以下

>>> dump2Bl(buf)
0000 fff4 0013 0050 006a 00d0 00ab 0163 00b0 0239 0057 03a4 ff24 06a3 f951 2498
78e7 5aed 6f4e 6a54 6d7e 6b49 6caa 6bb0 6c35 6bd5 6bf2 6bcd 6bc1 6bb3 6ba5 6b98
6b8a 6b7d 6b6f 6b62 6b54 6b47 6b39 6b2c 6b1e 6b11 6b04 6af7 6ae9 6adc 6acf 6ac1
6ab4 6aa7 6a99 6a8c 6a7f 6a71 6a64 6a57 6a49 6a3c 6a2e 6a21 6a14 6a07 69f9 69ec
69df 69d2 69c5 69b8 69aa 699d 6990 6983 6976 6968 695b 694e 6941 6934 6926 6919
690c 68ff 68f2 68e5 68d8 68cb 68bd 68b0 68a3 6896 6889 687c 686f 6862 6855 6848
683b 682e 6821 6814 

こっちのほうが値に連続性が見られる。ただ、、波形は途中で切れているようなのだが。。しかも高止まりでしかないような。。
スマフォのスピーカをMICにびったりくっつけてみたら、以下のようなデータになった。2バイト目(奇数バイト)はやはり符号拡張された上位8bitであり、データの有効桁数は下位8bitで、1バイト目(偶数バイト)ではないかと思えるのだが。。やっぱりちゃんと仕様書を読まないとこれ以上分からない。

fc ff fb ff f5 ff f7 ff f6 ff fc ff fb ff f8 ff
f7 ff f5 ff f8 ff fe ff f8 ff fa ff fb ff fd ff
05 00 ff ff fd ff fc ff 03 00 05 00 fe ff fe ff
fb ff fd ff fe ff 01 00 fc ff f7 ff fa ff fa ff
02 00 fd ff f9 ff f7 ff fa ff 02 00 01 00 fb ff
f7 ff fb ff 05 00 07 00 01 00 ff ff fd ff 00 00
06 00 05 00 ff ff ff ff fe ff 07 00 0b 00 ff ff
03 00 fe ff 05 00 07 00 06 00 04 00 04 00 05 00
0a 00 0d 00 06 00 06 00 07 00 0d 00 08 00 05 00
05 00 03 00 09 00 0b 00 09 00 02 00 03 00 04 00
0b 00 0b 00 04 00 06 00 01 00 09 00 0b 00 07 00
07 00 07 00 09 00 09 00 0a 00 05 00 05 00 0a 00

どうも手探り状態では埒が明かないので、I2SバスのMEMS MICとI2Sスピーカを直結してみた。正確には、クロックだけはRP2040から出力して、それ以外のバスは直結。I2Sスピーカで聞いてみると、スマフォのスピーカをMEMS MICにピッタリくっつけるぐらいにしないと音を拾ってくれないようであった。前からずっと気になっていたのだが、MEMS MICってデジタルでいろいろお世話してあげないとめちゃ感度わるいのだろうか。。
音源のスマフォとMEMS MICがぴったりくっついて録音した時のデータは以下

>>> dump(buf,16*16)
39 0a 42 0a 60 0a 4f 0a 3f 0a 25 0a 04 0a b9 09
86 09 41 09 ec 08 a4 08 39 08 e5 07 76 07 0c 07
96 06 1e 06 71 05 e3 04 50 04 cc 03 2d 03 99 02
ff 01 6c 01 db 00 3a 00 97 ff dc fe 55 fe b5 fd
35 fd 96 fc 1a fc 9b fb 1f fb a2 fa 13 fa a6 f9
38 f9 cb f8 58 f8 f7 f7 9b f7 5c f7 f5 f6 b9 f6
7f f6 4a f6 23 f6 eb f5 eb f5 b3 f5 d8 f5 ae f5
e4 f5 f0 f5 1e f6 5e f6 81 f6 f1 f6 17 f7 99 f7
dd f7 50 f8 ba f8 3c f9 cd f9 59 fa fd fa 8d fb
3d fc d9 fc 84 fd 37 fe dd fe 85 ff 37 00 ea 00
a8 01 53 02 08 03 aa 03 45 04 e4 04 6b 05 fd 05
6f 06 e7 06 5a 07 cd 07 30 08 90 08 e6 08 3a 09
87 09 c2 09 f1 09 1d 0a 33 0a 4f 0a 4e 0a 43 0a
4a 0a 2d 0a 15 0a d9 09 a6 09 4a 09 11 09 ba 08
67 08 05 08 96 07 21 07 b7 06 3e 06 ae 05 22 05
75 04 df 03 55 03 d6 02 35 02 9d 01 fd 00 52 00

こうやってデータを見ていると、16bit幅の上位8ビットは2バイト目のように思える。そう解釈して符号付き16bit整数に変換してグラフ描画した結果は以下。44.1Hzに対して200サンプルとってグラフ化したので、440Hzは2波入っている、これだと計算通り

def dump2Bls(buf, len=128, onHex=False):
  for i in range(len):
     data = buf[i*2+1] << 8
     data += buf[i*2]
     if onHex:
         print(f'{data:02x} ',end='')
     if data >= 0x8000:
         print(data - 0x10000)
     else:
         print(data)

PDMのMEMS MICの時も感度の悪さを解消できなかったのだが、I2SバスのMEMS MICも同じ症状である。上位8bitしか扱えていないぐらいに感度悪い。ノイズ除去や増幅等の信号処理が必要なのかもしれない。GitにはMicroPythonのI2Sライブラリを使ってI2SバスのMEMSマイクで録音するサンプルもあるので、先人のコードも動かしてみるべきか・・
オーディオに使用される周波数について(サンプリング周波数、PCM,DSDなど) | マルツセレクト
離散フーリエ変換(DFT)の仕組みを完全に理解する #競技プログラミング - Qiita
うさぎでもわかる信号処理・制御工学 第12羽 離散時間フーリエ変換(DTFT) | 工業大学生ももやまのうさぎ塾

■追記
犬笛を買ったのでそれを吹いてMEMS MICで集音してみた。結果、12KHz相当の波形が取れた。これだとなんとか行けそうだ。

上記波形は、無音(低いボリュームの雑音)、笛を吹いた時の波形、机を叩いた時の波形を比べたもの。目で見てはっきり違いが分かる。笛を吹いているのかどうかはフーリエ級数の考え方を使えば検出できると期待。