こんばんは。
今回は、予定を急遽変更して、M5Stack からbeep音(電子音)を出してみる実験です。
本当は、前回の記事に引き続いて、M5StackのディスプレイにBME680 のグラフを表示させる予定だったのですが、BME680 のガスセンサで異常値を検出した場合、M5Stack から警告音を出そうと試行錯誤していたら、意外と手こずり、それだけで記事一つ書けるくらいになったので、それに絞って報告しようと思いました。
M5Stack ライブラリを使えば簡単に出せるかもしれませんが、私の場合は日本語漢字フォントを表示させたりして、独自にライブラリを作成しているので、M5Stack ライブラリは使いません。
Arduino core for the ESP32 ( 以下Arduino – ESP32 )ライブラリだけで実験します。
M5Stack から電子音を出す場合には、M5Stack 内蔵のオーディオアンプ用IC ( NS4148 )を使いますが、その Input 端子に、カップリングコンデンサを介して ESP32 の GPIO #25 が既に接続されている状態です。
ということは、GPIO #25 を使おうとすると、いろいろな問題があります。
後で詳しく紹介しますが、ノイズが出たり破裂音が出たりします。
ノイズを消す方法は新たに発見しましたが、逆に破裂音が出るようになりました。
逆に、破裂音を出ないようにするとノイズが出ます。
まぁ、センサの異常値検知の通知音を出すだけなので、私個人的には大して気にならないレベルですが・・・。
ということで、自分なりに M5Stack のスピーカーから Beep音を出す方法を紐解いてみたいと思います。
因みに何度も申し上げておりますが、私は素人アマチュアです。
誤ったり、勘違いしていたりすることが有るかも知れません。
もし何かお気づきの点がありましたら、コメント投稿等でご連絡いただけると助かります。
使用するもの
M5Stack
(追記)
M5Stack Basicは、この記事を書いた当時より格段にバージョンアップしております。
以下のスイッチサイエンスさんの公式サイトをご参照ください。
https://www.switch-science.com/collections/%E5%85%A8%E5%95%86%E5%93%81/products/9010
その他、パソコン、USBケーブル等
Arduino – ESP32 を 2018/07/10以降の最新版にしておく
Arduino core for the ESP32 は、こちらの記事にあるように、2018/06/28以降に大幅更新され、そのバグが20178/07/10以降の最新版では改善されています。
ですから、古い Arduino – ESP32 はフォルダごと削除し、新たに最新版をインストールし直してください。
Arduino – ESP32 のインストール方法については以下の記事を参照してください。
Arduino core for the ESP32 のインストール方法
M5Stack 回路図を参照して、内蔵アンプ周りの回路確認
では、M5Stack の回路図をまず見てみます。
本家の以下のリンクの DownloadからM5-Core-Schematic という名称のところで PDFファイルが入手できます。
その回路図の最後の方に、オーディオアンプ周りの回路があります。
下図はそれの Input 部分のみ拡大して模写したものです。
これから、オーディオアンプIC の Input に 0.1μF の直流成分カット用のカップリングコンデンサを介して、GPIO #25 に接続されています。
ということは、GPIO #25 をHIGH-LOW 切り替えてパルスを出力しただけで、スピーカーから音を発信できてしまう構造になっています。
つまり、GPIO #25 は、基本的に何も接続しない方が良い端子ということです。
個人的には、GPIO #25 を使いたい場合があるので、オーディオアンプとは独立できるハードウェアスイッチがあったら良いなと思いました。
いずれにせよ、GPIO #25 から一定パルスを出力しさえすれば、矩形波(方形波)音声は出せるということです。
矩形波(方形波)は、いわゆる歪んだ音ですが、GPIO からサイン波を出すことは難しいので、Beep音のような通知や警告用の音なら矩形波(方形波)音でも問題ありませんね。
Beep音用のパルスの作り方
音として成立させるためには、一定間隔で、GPIO 電圧の HIGH-LOW を切り替えれば良いわけです。
ただし、電圧レベル 3.3V で HIGH-LOW を切り替えただけでオーディオアンプに入力してしまうと、エライことになります。
スピーカーから爆音が出ます!
ビックリするくらいの音量ですので、要注意してください。
幸い、ESP32 の GPIO #25 には DAコンバータ(DAC) があるので、それを使って電圧レベルを下げてパルスを出力すれば良いわけです。
下図を見てください。
GPIO #25 の DAコンバータは、8bit ですから、0~255段階でレベルを変えられます。
後のスケッチでも紹介しますが、レベル1でゲーム音として丁度良い音ですので、255はかなりの爆音となります。
また、上図にもあるように、音として成り立たせるためには、空気を振動させなければいけないので、電圧も振動させなければスピーカーのコーンを振動させられません。
1ms で HIGH にして、1ms でLOW にすると、1波長2msの長さになり、それを繰り返すことによって、基音500Hz の音を発生できます。
パルス波(矩形波、方形波)は、歪んだ音ですので、基音のサイン波500Hz の奇数倍の倍音の合成波となります。
ですから、
「プーーー」
という綺麗な音ではなく、
「ビィィィーーー」
という歪んだ音です。
でも、警告音や通知音ならこれで十分ですし、サイン波を作るよりもプログラムは圧倒的に簡単にできます。
だから、パソコンの起動音などもこれで作られることが多いような気がします。
M5Stack の内蔵スピーカーからBeep音を出すスケッチおよびノイズ
まず、M5Stack から音を出すには、M5Stack ライブラリを解読すると、どの関数を使えば良いか見えてきます。
Arduino – ESP32 標準ライブラリ関数を使う場合は、
dacWrite
関数を使って、GPIO #25 のDAC を使ったパルスを出力します。
まず、以下のスケッチを入力してみてください。
M5Stack ライブラリは使いません。
【ソースコード】 (※無保証 ※PCの場合、ダブルクリックすればコード全体を選択できます)
#define GPIO_PIN 25 uint32_t beep_last_time = 0; uint8_t beep_volume = 1; //min 1, max 255 uint32_t beep_total_time = 0; void setup(){ delay(2000); Serial.begin(115200); beep_total_time = millis(); } void loop(){ if( millis() - beep_total_time < 10000 ){ //10秒後にbeep音停止 uint32_t b_period = millis() - beep_last_time; if( b_period < 500 ){ dacWrite(GPIO_PIN, 0); delay(1); //約500Hz //delayMicroseconds(500); //約1kHz dacWrite(GPIO_PIN, beep_volume); delay(1); //約500Hz //delayMicroseconds(500); //約1kHz }else if( b_period >= 500 && b_period < 1000 ){ dacWrite(GPIO_PIN, 0); }else{ beep_last_time = millis(); } }else{ delay( 10000 ); //10秒無音ではなく、GPIO #25のADCは生きているので、スピーカーからノイズが出る beep_total_time = millis(); } }
これは、とりあえず、Volume ( 音量 )は最少の1にしています。
2にすると結構大きいです。
このスケッチは、以下の処理順です。
起動
↓
2秒後、基音500Hz の Beep音を0.5秒間隔で断続音を10秒発信
↓
10秒後に停止
↓
以後繰り返し
これをコンパイル書き込み実行させてみてください。
すると、起動後、破裂音が入ってから Beep音が発信されると思います。
10秒鳴動して10秒停止した後、再度 Beep 音が出る時には破裂音は出ません。
しかし、停止している最中にスピーカーからノイズ音が聞こえます。
スピーカーに耳を近づけて聞いてみると、実際は Beep音が出ている時にもノイズが出ています。
これは、おそらく自分の予想では、GPIO #25 や、GND にノイズが乗っているために、それがオーディオアンプによって拡声されていると考えられます。
大き目の音量で常時スピーカーから音が出ていれば、それほど気にならないノイズですが、たまになる警告音や通知音だけ出したい場合、音が出ていない時のこのノイズは気になるかも知れません。
ネットで情報が出ている、ノイズを消す方法としての
dacWrite(GPIO_PIN, 0)
という命令は、音量ゼロで出力しているだけで、DACをOFFにしているわけではなく、やはりノイズは出ているのです。
GPIO #25 が GND に落ちればノイズが消えると思うんですけどね。
では、次の項では、これとは別のスケッチを実験してみます。
M5Stack のスピーカーから音を出さない時にノイズが出ないスケッチ例、および破裂音
では、私なりに調べて、M5Stack のスピーカーから音を出していない時にノイズを消すことができるスケッチを紹介します。
これは、Arduino – ESP32 に予め入っているライブラリクラスをインクルードしています。
【ソースコード】 (※無保証 ※PCの場合、ダブルクリックすればコード全体を選択できます)
#include <driver/dac.h> //Arduino-ESP32 driver uint32_t beep_last_time = 0; uint8_t beep_volume = 1; //min 1, max 255 uint32_t beep_total_time = 0; void setup(){ delay(2000); Serial.begin(115200); beep_total_time = millis(); //これを実行すると、M5Stackスピーカーから破裂音が出る dac_output_enable( DAC_CHANNEL_1 ); //DAC channel 1 is GPIO #25 } void loop(){ if( millis() - beep_total_time < 10000 ){ //10秒後にbeep音停止 uint32_t b_period = millis() - beep_last_time; if( b_period < 500 ){ dac_output_voltage(DAC_CHANNEL_1, 0); delay(1); //約500Hz //delayMicroseconds(500); //約1kHz dac_output_voltage(DAC_CHANNEL_1, beep_volume); delay(1); //約500Hz //delayMicroseconds(500); //約1kHz }else if( b_period >= 500 && b_period < 1000 ){ dac_output_voltage(DAC_CHANNEL_1, 0); }else{ beep_last_time = millis(); } }else{ //これを実行するとGPIO #25のDAC outputが無効になり、スピーカーからノイズが出なくなる。 //ただし、次にdac_output_enableを実行する時に破裂音が出る。 dac_output_disable( DAC_CHANNEL_1 ); delay( 10000 ); //10秒無音状態 beep_total_time = millis(); //beep音を鳴らす為にはこれが必要。ただし破裂音が出る。 dac_output_enable( DAC_CHANNEL_1 ); } }
1行目で、Arduino core for the ESP32 のドライバライブラリをインクルードしています。
前項のスケッチと同様、Volume ( 音量 )は最少の 1 にしています。
setup内の
dac_output_enable関数
で、GPIO #25 の DAコンバータ出力有効にセットします。
DAC_CHANNEL_1
にすると、GPIO #25 を選択したことになります。
DAC_CHANNEL_2
は GPIO #26 になります。
そして、dac_output_enable関数を使う時、M5Stack のスピーカーからは必ず「パチッ」という破裂音が出ます。
私がいろいろ試したところ、この関数を使う限りにおいて、この破裂音はどうやっても消せませんでした。
そして、34行目の
dac_output_disable関数
を使うと、GPIO #25 のDAコンバータが無効になります。
すると、スピーカーから出ていたノイズは消えます。
ただし、再び鳴らす時に、dac_output_enable関数を使わなければならない為、その時に破裂音が出ます。
結局のところ、ノイズをヨシとするか、破裂音をヨシとするかの2者択一になってしまいます。
私は、警告音や通知音のように、たまにしか鳴らないものは破裂音があっても良いと思っています。
前項の dacWrite関数のソースを見てみると、ESP32 のレジスタ群で構成されていて、恐らく、初回起動時には dac_output_enable関数と同じ動作をして、それ以降はそのレジスタ指令を生かしたままにしているので、ノイズが出ているものと予想できます。
試しに、このスケッチの setup内のdac_output_enable関数だけそのままにしておいて、34行目と39行目をコメントアウトすると、前項のスケッチと同じ状態になり、初回の破裂音以外は破裂音は無くなります。
しかし、Beep無音時のノイズは出ています。
あとはどちらを使うかは個人の好みということで・・・。
他の処理中に Beep音を出す時の注意点
先の項で紹介したスケッチの場合、delay関数を使っています。
つまり、その間は他の処理が中断します。
幸い、M5Stack の ESP32 はデュアルコアでマルチタスクが可能ですから、別タスクで Beep音を鳴らせば、主タスクを止めずに Beep音を出し続けることができます。
例えば、前回の記事で述べた BME680 のガスセンサの場合、一定の時間間隔で測定し続けなければ、センサの値が狂ってしまいます。
BME680 の測定時間を止めずに Beep音を鳴らし続けるためには、やはりマルチタスクが必要となってくると思います。
シングルタスクでやる場合、かなり頭を使ってプログラムを工夫しなければならないと思います。
まとめ
いかがでしょうか。
あまりキレイなやり方ではありませんが、M5Stack にオーディオアンプIC とスピーカーが内蔵されているおかげで、GPIO からパルスを出力するだけで簡単に Beep音を出すことができました。
あとはノイズと破裂音の問題で、私的にはどちらも大して気にならないので、センサの異常値検知のための Beep音には十分使えて満足しています。
ということで、今回はここまでです。
次回はこの Beep音を使った、BME680グラフを作ってみたいと思います。
・・・っていうか、もう完成しています。
では、また・・・。
Amazon.co.jp 当ブログのおすすめ
コメント
いつも拝見させて頂いております。
今回、新しいリビジョンの末尾が32Dのものを購入し、今までの動作していた液晶ライブラリを入れてみたのですが、画面に何も表示されません。
何かお分かりになることがありましたら教えて頂けますか?
ちなみにmgo-tecさんは32Dを弄った事ってありますか?
ESP好き さん
ブログをご覧いただき、ありがとうございます。
ちょっとよく分からないのですが、M5Stack に32Dというリビジョンが出たのでしょうか?
それとも、ESP-WROOM-32D のことでしょうか?
M5Stack には ESP-WROOM-32D は入らないので、何ですかね???
私はそれは持ち合わせておりませんので、サッパリ分かりません。
そもそも、それは市場で流通していて、Amazonなどで買える状態なのでしょうか?
M5Stackの練習でキッチンタイマーを作っていて、音量を小さくする方法を探していました!
参考にさせていただきます。ありがとうございます。
てるのりさん
記事をご覧いただき、ありがとうございます。
上手く動作するといいですね。
お役に立てれば幸いです。