昨日、MEMマイクから出力されるPDM波形をオシロで観察してみました。波形は見えてもどんな音なんだかサッパリです。とは言え専用ICに任せれば信号処理部分はブラックボックスです。最初くらい生の信号の0/1パターンを取得して自前で信号処理もどきをやり、しみじみと納得してみたい。そこでラズパイPicoのPIOを使ってPDMのRAW波形をキャプチャしてみました。
※「MicroPython的午睡」投稿順 Indexはこちら
サウンド機能向けにI2Sインタフェースを搭載するマイコンであれば、MEMSマイクのPDM波形も入力処理できる機能を搭載していることが多いじゃないかと思います。残念ながらラズパイPicoにはI2SやPDMインタフェースはありません。しかし、転んでもただは起きぬ?ラズパイPicoです。普通はハードウエアを必要とするようなインタフェースもPIOステートマシンを駆使すれば、ソフト的に実現できてしまいます。今回行ってみるのは、以下の操作です。
-
- MEMSマイクに与える1MHz(規定の下限周波数)のクロックを出力する
- 上記クロックの特定の位相のタイミングでDAT線の0/1を読み取る
- 読み取ったビットを数千~数万ビット程度のビット列として出力
勿論、最終的にラズパイPico上で音声処理までデキれば良いですが、とりあえず0,1のビット列にしたものをパソコンで読み取れれば、パソコン上の信号処理ソフトで処理できます。そしてしみじみと味わいたい(処理自体はまた後でだけど)という意図であります。
ラズパイPicoとMEMSマイクモジュールの接続
使用するMEMSマイクは、ノウルス製(ブランドはSiSonic)の6端子、表面実装品です。それをDIPモジュール化した8端子(GNDが増えてる)のもの。秋月電子通商製。ラズパイPicoとの接続は、PIOステートマシンを使用するので、どのGPIO端子でもよいです。ただし末尾に掲載のプログラムの記述と適合するのは以下の接続です。
実際に、ブレッドボード上で現物を接続したところはアイキャッチ画像をご覧ください。
MicroPython上でのPIOステートマシンのプログラム
今回は最低1MHzのクロック信号を作ってMEMSマイクに与えないとなりません。何度かPIOステートマシンをプログラムしてきましたから、クロック信号を生成するだけなら「赤子の手をひねる」ようなものです。しかし、今回はさらに、クロックの特定位置(マイクのL/R端子の設定によります)、例えば立ち上がりの後で、クロックとは別なDAT信号の値を読み取って毎回保存しなければなりません。詳細は末尾のMicroPythonコードの中のPIOステートマシンの記述をご覧いただくことにして、処理ステップの概要は以下のような感じです。
-
- PIOステートマシンは6MHzのクロックで動かす
- クロックはSide指定で生成する。3ステートに1回CLK信号をトグルするようにすればちょうど1MHzのCLK信号出力が得られる。
- クロックのHIGH期間、LOW期間でそれぞれ3ステートを処理できる。処理パスにより処理時間に長短が出ないように適宜NOPでステート数を調整する。
- 末尾コード例ではCLKを立ち上げた1ステート後にDAT線を読み取る(シフトイン)。クロックの立ち上がりエッジ0.33μS後から0.66μS後の期間に1ビット読み取っていることになる。
- PIOステートマシンのシフトレジスタやFIFOは32ビットまで保持できるので、32ビット分シフトインした後にFIFOに32ビット幅でPUSHする。
- MicroPythonの本体コードでは、PIOステートマシンの上記PUSH操作により、RXFIFOに格納された32ビット分のビット列をget()で読み出して、Arrayに次々格納しつづける
- 予め取得すると決めた回数だけFIFOを読み取った後、Array内容をPC向けに吐き出す。
実際にMEMSマイクにCLKを与えてデータ読み出しを開始したところをオシロで観察したところはこちら。クロック線が黄色、データ線が青です。ちょっと引き気味のキャプチャで見難いですが、ギリギリ、クロック線が識別できる時間範囲にしてあります。
出だしの部分、データ波形で1や0が続いたりしていますが、これはマイクの立ち上がりのせい?ちょっと時間が立つと、普通に0/1が「混ざる」ようになります。
上記のオシロで観察した波形を末尾に掲載のMicroPythonコードで実際にキャプチャした0/1文字列がこちら。長いので先頭部分のみです。とりあえずMicroPython上のArrayに32000ビット分格納してみましたが多分取れている感じ?(実際に目で確認したのは、先頭の数十ビットだけ。その部分はオシロの波形と一致しているように見えるのだけれども。。。)
00000001111111111111111111111000 00000000000000011111111111111111 11000001010100111000000100100111 00111000110001101100011001100110 01100100110011010100101100011100 01100110100101100011010101001100 11000111001001101010011000111000 11100011001100101010101010100110 10010110001101010011010100101010 10100110101010010101010101001101 01001100101001110001100110100101 ~以下略~
という分けでMEMマイク出力の0/1のPDMビット列がPCに取り込めました。これを使ってPC上での信号処理を試みますか。ううむ、今思いついたのだけれど特定の周波数の音をマイクに聞かせておいたものをキャプチャすれば、信号処理後の結果が確認しやすかったんじゃないだろうか。
むむ、もう一度測定?それにスピーカと音源も要るな。いつもながら泥縄。
MicroPython的午睡(33) ラズパイPico、S9705フォトICで照度を測る へ戻る
MicroPython的午睡(35) ラズパイPico、MCP23017でIO端子数拡張 へ進む
PIOを使ってMEMSマイクが出力するPDM信号を0/1でダンプする実験プログラム
from rp2 import PIO, asm_pio from machine import Pin, Timer import time import uarray @asm_pio(sideset_init=PIO.OUT_LOW, in_shiftdir=PIO.SHIFT_LEFT) def readPDM(): set(x, 31) label("loop") nop() .side(0) nop() nop() wrap_target() nop() .side(1) in_(pins, 1) jmp(x_dec, "loop") push(noblock) .side(0) set(x, 31) nop() wrap() dataBuffer = uarray.array('L') sm0 = rp2.StateMachine(0, readPDM, freq=6000000, in_base=Pin(0), sideset_base=Pin(1)) sm0.active(1) for _i in range(100): dataBuffer.append(sm0.get()) sm0.active(0) for idx in range(100): print("{0:032b}".format(dataBuffer[idx]))