Raspberry Pi でMEMSマイク(PDM出力)を使う

## やったこと
Raspberry Pi にMEMSマイク(PDM出力)を接続して録音した
ラズパイでPDM入力を使った例が検索しても見つからなかった(2021/09/06)ので書いてみた

## 書いてる人
エンジニアではありません、ただのオーディオマニアです
Lチカを初めたばかりで簡単なことしかできません
変なところがあれば突っ込みよろしくお願いします

## MEMSマイクとは
シリコンマイクとも呼ばれる超小型マイクで急速にエレクトレットコンデンサマイクと置き換わった
iphoneは4からMEMSマイク
小型で熱や衝撃に強く特性も安定している
音質もそこそこ優れたものがある(良くないものもある)
周波数特性はフラットではない(高域に共振によるピークが出る)
アナログ出力のものとデジタル出力(PDM、I2S、SPI)のものがある
今回はMEMSマイクのうちPDM出力のものを使った

## PDM(Pulse Density Modulation、パルス密度変調)
アナログ信号をデジタル化する方式で、パルスの密度で振幅を表す
普通のwavやflac、CDはPCM(Pulse Code Modulation)で記録されている
SACDはPDMで記録されている

## 試した環境
Raspberry Pi 3B ( ~~おそらく2や4でも動くと思うが確認してない~~ )
pi4はクロックが19.2MHzではなくて54MHzなのでこのままでは動きません。試してないのに適当なことを言うのはダメですね。(2021/11/12追記)

OS: Raspberry Pi OS Lite(Release date: May 7th 2021)
C言語(gcc 8.3.0)

## Raspberry Pi でPDM入力の使い方

Raspberry Pi のPCMモジュールにPDMモードがあるのでそのまま使う

### 参考サイト・資料

資料
[公式サイトの資料](https://datasheets.raspberrypi.org/bcm2835/bcm2835-peripherals.pdf)
[エラッタ](https://elinux.org/BCM2835_datasheet_errata)
[レジスタのアドレス](https://elinux.org/BCM2835_registers)

参考にしたサイト
[楽しくやろう。](https://blog.boochow.com/)
このサイトをみながらLチカとか試してみた

### 設定法

### メモリマッピング
* mmap でアドレスをマッピングする  
これによりGPIO、クロック、PCMの各レジスタにアクセスできるようになる    
ベースアドレスはPi0 Pi2-3 pi4で違うので注意が必要  
* レジスタへのポインタ変数は全て"volatile"をつけて宣言する
* レジスタは32bitなので uint32_t とする

### GPIOの設定
* GPIO18、GPIO20をALT0に設定する  
  GP_FSEL1 レジスタ、GP_FSEL2 レジスタ  
これでGPIO18がPCM_CLK、GPIO20がPCM_DINに設定される

### クロック設定
PCMクロック関連レジスタのアドレスは公式のデータシートには載ってない

* CM_PCMCTL レジスタ  
ENAB = 0にしてクロック無効に  
BUSY = 0を待つ  
SRC = 1(OSC　19.2MHz)、 MASH = 0

MASHは分周を非整数分の1にするときに使う
ジッタと引き換えに正確な周波数が得られる 
今回はジッタを嫌ってMASHは使わない


* CM_PCMDIV レジスタ  
DIVI = 8、DIVF = 0

非整数で分周したい場合には DIVF に 少数部分を4096倍したものを設定する


* CM_PCMCTL レジスタ  
ENAB = 1（ほかはそのまま）クロック有効に

これで19.2MHzが1/8に分周されてクロックが2.4MHzに設定される  
使うMEMSマイクによってクロックの範囲が違うのでMEMSマイクのデータシートを確認すること


### PDM設定

* PCM_CS_A レジスタ 
EN = 0

* PCM_MODA_A レジスタ 
CLKM = 0, FRXP = 1, PDME = 1, PDMN = 0

CLKM = 0 でクロックがマスターモード 
PDME = 1 でPDMモードを有効にする 
PDMNはデシメーションフィルタ(4次CIC)の設定 
PDMN = 0 で 1/16、 データは 16 bits unsigned に変換される 
PDMN = 1 で 1/32、 データは 20 bits unsigned に変換される 
クロックが2.4MHz、PDMN=0なら 2.4MHz/16 で サンプリング周波数150kHz、16bitのデータになる 
FRXP = 1 にすると16bitデータ2つを32bit fifoに詰め込む 
PDWM = 1 で使うと16bitからはみ出たデータは捨てられてしまう

* PCM_RXC_A レジスタ  
CH1EN = 1, CH2EN = 1

MEMSマイクは設定でクロックの立ち上がりか立ち下がりのどちらかでデータを転送するか選択できる 
立ち上がりに設定したマイクと立ち下がりに設定したマイクを接続してCH1ENとCH2ENを両方有効にすれば2chのデータが得られる 
FRXP = 1 なら 16bit 2ch分のデータが1つのFIFO（32bit)に入る

* PCM_CS_A レジスタ  
EN = 1, RXON = 1 

これでPCMモジュールがPDMモードで作動し始めて、PCM_FIFO_A レジスタにデーターが送られてくる

## データの受信

割り込みやDMAもできるらしいが面倒なのでポーリング 
マイクが作動してから1秒くらいは安定しない
MEMSマイクの消費電力は少ないので常に作動させておけば必要なときにすぐ録音が開始できる

* PCM_FIFO_A レジスタ  
FIFOは64個あるのでデータを64個読み捨てる

* PCM_CS_A レジスタ
RXERR フラグを解除

あとはデーターの読み取りを繰り返すだけ

* RXD = 1 ならPCM_FIFO_Aから読み取る
* RXERR = 1 ならエラーが起きてる 
* RXD = 0 ならちょっと待つ

RXDを確認してから読み取っているのでアンダーフローはありえない
エラーが起きた場合にはオーバーフロー(読み取りが間に合ってない) 
今回作ったソフトはchrtで優先度を上げないとエラーが出る


## 取り込んだデータの処理

16bit unsigned なので 32768を引いて 16bit signed に変換する 
音声ファイルに変換するならSOXを使うのが便利

## 今回つかったMEMSマイク
* 秋月電子で買った超音波対応のもの(Knowles社製 SPH0641LU4H-1)
* 可聴周波数でも使える
* SN比は64.3dBなのでまあまあ  
MEMSマイクでも70dBを超えるものもあるのでちょっと物足りない
* 10%THD が 120dB SPL なので大音量には強くない  
したがって大音量楽器の近接での録音なんかには使えない

https://akizukidenshi.com/catalog/g/gK-15577/
https://akizukidenshi.com/download/ds/knowles/SPH0641LU4H-1.PDF
データシートを見るとultrasonic modeにするにはLow-Power Mode か Standard Performance Mode を経由しろとあるので注意

## 接続
マイクのVDD, GND, CLK, DAT に接続する
VDDは+3.3Vに接続 +5Vと間違わないように
CLK はGPIO18、DATはGPIO20に接続する

## 作ったソフト

Github に上げてみた
https://github.com/assi-dangomushi/pdm_rec

## あとがき

Lチカから始めて意外と簡単にマイクを動作させることができた
基礎知識のないとデータシートを読むのが大変