N.Yamazaki's blog

主に音声合成について思ったことを書いてみようと思います。
<< AquesTalk-ESPを簡単に使うクラス(プログラム) | main | M5Stackの音量を抵抗1つで調節する >>
ESP32のPDMでサウンド出力+ノイズ対策

keywords: ESP32、PDM、オーディオ、音声

 

■概要


 

ESP32は、PDM(Pulse-density modulation)の変調器を内蔵しています。
前の記事では内蔵DACでしたが、今回はこのPDMで音を出してみます。

PDMは1bitのデジタル信号ですが、パルスの密度がアナログ信号の振幅に直接対応しているので、アナログアンプに直に与えても鳴ります(アンプ自体がLPFの役割を担っているため)。

今回の実験は、ESP32-DevCとM5Stackで行ないました。


 

 

■崩れたPDM波形!?


 

最初のPDM出力波形はこんなでした。

 

 

波形がひどく崩れています。
立ち上がりは急峻で良いのですが、立下りが指数的に下がっていて、これでは、まともな音は出ません。

ネットでも同様の現象が報告されていたので、そんなものなのかと一時は諦めましたが・・・

 

実は、なんてことはない、単純なミスでした。
内蔵DAC用のコードを改良して使っていたため、出力ピンの指定を忘れていました。
i2s_set_pin()でこれを指定すれば、下のきれいな矩形のPDM信号が得られるようになりました。

 

 

 

 

■無音の再生時のノイズ



ゼロの値を連続して出力すると、ピーという周期性のノイズが現れます。
このノイズ、常に同じ高さでなく毎回違った高さの音が出ます。
どうも、前に出力した波形データに影響しているようです。

 

ESP32のデータシートをみると、PDMの処理は次の図で示されています。

これだけでは、実際どんな処理が行われているのか把握できませんが、
このPDM処理の部分で周期性ノイズが発生していることには間違いありません。

 

(ESP32 Technical Reference Manualより)

 

 

このノイズ対策には、ディザを加えれば良いようです。
ゼロの値を連続する代わりに、例えば、0,1,0,1,...とすることで、この周期性ノイズはなくなります。
ホワイトノイズは残りますが、これは信号出力時の背景ノイズと同じ大きさなので、我慢するしかないでしょう。
後述のプログラムでは、再生する信号にもディザを加えています。合成音声や小さい音などで量子化誤差がランダムでなくなる信号に対しても周期的ノイズを削減する効果が得られます。

 

 

■クリックノイズ対策



PDMの停止中の出力は0Vの状態が続くため、PDMの開始と終了時にクリックノイズが発生します。
DAC出力のときに行ったように、開始時には0VからVDD/2まで、終了時はその逆のVDD/2から0Vまでのスロープ状に値を変化させることでクリックノイズを削減できます。

 

ただ、PDMではスロープ状の値をデータとして指定しても、その出力はスロープ状になりません。先に示したようにPDMの変調器にはHPFやLPF、アップサンプリングが使われているので、DACの場合と異なり、入力の値がそのままストレートに出力されません。

 

そこで、PDMでスロープを表現するのはあきらめ、この部分は内蔵DACを用いることにします。
同じピンにDACとPDMの出力を割り当て、開始時には、内蔵DACで0からVDD/2[V]でスロープを出力した後、PDMに切り替えます。終了時はその逆です。

 

 

■サンプルプログラム



上で検討したことを盛り込んだ、PDMでサウンド/オーディオ/音声出力するライブラリ(ソースプログラム)を公開します。サンプルプログラムは、上の動画で使ったものです。

 

ダウンロード    TestPDM.zip (TestPDM.ino, PDMout.c, PDMout.h, magic07.h)

 

関数は以下の通りです。

 

  • PDMOut_create(): DACにより出力を0VからVDD/2までスロープさせる。
  • PDMOut_release(): DACにより出力をVDD/2から0Vまでスロープさせる。
  • PDMOut_start(): PDMの初期化後、出力をDACからPDMに切り替える。
  • PDMOut_stop(): PDMを停止し、DACに切り替える(VDD/2を維持)。
  • PDMOut_write(): 引数に1サンプルのデータを指定し、I2Sに送る。PDM変調器にはI2SのDMAで送られる。
  • PDMOut_clear(): I2SのDMAバッファをすべてクリア

内蔵DACも使っているので、出力ピンはGPIO25かGPIO26のいずれかになります。

 

write()のPCMデータは符号付き16bitです。内蔵DACのときは符号無しなので注意してください。

 

PDM変調器のほかに内蔵DACも使用しているので、本プログラムを動かした後はこれらのペリフェラルの状態が使用前と異なる可能性があります。M5Stackでは、PDM出力の終了後にM5.Speaker.tone()関数で音が出なくなりました。M5.begin()での内部状態が変わるためです。これについては、次の関数を呼び出せば、再び出るようになります。

(M5.begin()でGPIO25にPWMを繋げた状態にしているのは、今後変更されるかもしれません)

   ledcAttachPin(SPEAKER_PIN, TONE_PIN_CHANNEL);

 

 

■PDMでステレオ



ここで扱っているのはモノラルの信号ですが、pin_configの指定でPDMのクロックを出力し、i2s_push_sample()でLR別々の値を指定すればステレオのPDMを出力できます。
ただ、PDMのフォーマットでは1つのデータラインで左右の信号が交互に入るので、今回のようにPDM出力を直にアナログ信号として利用する方法ではステレオ化できません。
PDMのデジタルデータに対応したアンプ(MAX98356やSSM2537など)を使う必要があります。

 

 

■PDMは内蔵の8bitDACより量子化精度が高い?



信号を1/2づつ小さくしながら、どこまで聞こえるか試してみました。その結果、PDMの量子化は10bit以上あるようです。
ただ、S/N比(ピーク信号対雑音比)を測定してみると(FS:24KHz, 1023Hzのsin波、最大振幅)、49dBとなり、8bitDACと同等となりました。

 


■M5Stackで使う場合



現在のM5Stackは、アンプに入力する信号レベルが大きすぎて音割れが酷いです。PDMの場合も同じように音が割れます。
PDMの出力にLPFを通した波形のpeak-to-peakの最大振幅は約3.3Vで、DACとほとんど変わりません。

M5Stackで使用しているアンプ(NS4148)にPDMのデジタル信号を直接与えた場合、アンプ自体のLPF特性を利用して鳴るのですが、振幅が3.3Vのパルス信号を与えるのはかなり無理があるようです。特に音量が小さくなると、ノイズが絶対的に大きくなるようです。


試しに、PDM出力を1次のLPFを通してアナログ値にし、抵抗の分圧でゲインを下げててからアンプに入れたら、ずいぶん良くなりましたが・・・

 

 

■さいごに



量子化ビット数は10bit程度ありますが、S/N比が8bitDACと同じのため、音質の差はほどんどありません。
外付けLPFが必要なことを考えると、DACの代わりにPDMを使用するメリットは無さそうです。とほほ・・・

 

 

■リンク



・  前の記事「ESP32でサウンド出力時のクリックノイズ対策(I2S+内蔵DAC)

・  ESP32のデータシート
・  M5Stackのオーディオアンプ「NS4148
 

 

| 電子工作 | 15:04 | - | - |
PROFILE
Follow
CATEGORIES
LATEST ENTRIES
SEARCH THIS SITE
RECOMMEND
RECOMMEND
RECOMMEND
RECOMMEND
RECOMMEND
RECOMMEND
RECOMMEND
SONY MDR-CD900ST
SONY MDR-CD900ST (JUGEMレビュー »)

普段これで開発しています。
RECOMMEND
RECOMMEND
RECOMMEND
RECOMMEND