趣味 電子工作

Arduino+圧電スピーカー(パッシブブザー)でマリオとゼルダを奏でてみました

エンジニア歴20年以上、フリーランスでソフトウェアエンジニア・ゲームクリエイター・シナリオライターをやっております『NaruTaku』と申します。

今回はArduinoと100円の圧電スピーカーでマリオ(一機アップ)とゼルダ(謎解き)を奏でてみましたのでその内容を紹介したいと思います。

Arduinoでメロディを奏でる

今回は赤外線モジュールも使い、リモコン受信信号によって鳴らすメロディーを変える仕様としました。手持ちのDVDレコーダーのリモコンで、「1」ボタンはマリオの一機アップ、「2」ボタンはマリオの一機アップを1オクターブ上げる。「3」ならゼルダの謎解き、「4」ならゼルダの謎解きを1オクターブあげる。としました。

完成はこんな感じになります。

ラジコン作っていてその基板使ったのでタイヤとかついてますが、音にはまったく関係ありません^^;

使用部材はこちらです。

1.Arduino(互換機) UNO R3

Arduinoは互換機ですが、なんの問題もありません。

2.圧電スピーカー(パッシブブザー)

それぞれ、アマゾン、楽天でも購入可能です。



赤外線モジュールについては、こちらの記事で詳しく紹介しております。

こちらもCHECK

Arduinoで赤外線受信モジュールを使いリモコン信号受信してみました

エンジニア歴20年以上、フリーランスでソフトウェアエンジニア・ゲームクリエイター・シナリオライターをやっております『NaruTaku』と申します。 今回はArduinoで赤外線受信モジュールを使って遊 ...

続きを見る

使用部材はこれだけとなります。

配線

Arduinoと赤外線受信モジュール、圧電スピーカーは以下のように接続しました。

・赤外線受信モジュールの+(5V)⇒Arduinoの5V
・赤外線受信モジュールの-(GND)⇒ArduinoのGND
・赤外線受信モジュールのS⇒Arduinoの12ピン
・圧電スピーカーの+⇒Arduinoの3ピン
・圧電スピーカーの-S⇒ArduinoのGND

今回は赤外線受信モジュールのS、圧電スピーカーの+をそれぞれArduinoの12ピン,3ピンに接続しましたが、デジタルIOピンならどこでもよいです。ArduinoへはPCからUSB給電となります。

ハードは以上となります。

ソフトウェア作成

ソースコードは以下になります。


#include <IRremote.h>
#include <NewTone.h>

#define IR_RECEIVE_PIN  12  //受信ピン指定

IRrecv rcvdata(IR_RECEIVE_PIN); // 受信オブジェクトを作成 
decode_results dec_results;  // 受信データの格納先

//未受信カウンター
static int no_rcvcnt;

//音階
const int mario[] = {330, 392, 659, 523, 587, 784, 0}; //マリオ(ミ ソ ミ ド レ ソ)
const int zerda[] = {784, 740, 622, 440, 415, 659, 784, 1046,0}; //ゼルダ(ソ #ファ #レ ラ #ソ ミ #ソ ド)
int *p_melo; //メロディ
int *p_top; //先頭ポインタ

//ブザー用
int BZ_bk = 3;

//音楽用
bool rcv_flg = false; //受信チェック
int octv = 1; //オクターブ
int delay_tim; //wait時間(ミリ秒)

void setup() {
  // put your setup code here, to run once:
   rcvdata.enableIRIn();     // 赤外線受信開始

  //初期化
   no_rcvcnt = 0;
   rcv_flg = false;
   octv = 1; 
   delay_tim = 100; 
}

void loop() {
  // put your main code here, to run repeatedly:
   if (rcvdata.decode(&dec_results)) {      // 受信確認
      if (dec_results.value == 0xA23D807F){ // 1ボタンを押すと前進
            p_melo = mario;
            p_top =  mario;
            octv = 2;
            rcv_flg = true;
            delay_tim = 120;
            
       }else if(dec_results.value == 0xA23D40BF){ // 2ボタンを押すと後進
            p_melo = mario;
            p_top =  mario;
            octv = 4; 
            rcv_flg = true;
            delay_tim = 120;
       }else if(dec_results.value == 0xA23DC03F){ // 3ボタンを押すと左回転
            p_melo = zerda;
            p_top = zerda;
            octv = 1;
            rcv_flg = true;
            delay_tim = 150;
       }
       else if(dec_results.value == 0xA23D20DF){ // 4ボタンを押すと右回転
            p_melo = zerda;
            p_top = zerda;
            octv = 2;
            rcv_flg = true;
            delay_tim = 150;
       }
       rcvdata.resume();                    //リセット
       no_rcvcnt = 0;
   }else{
      //受信していないときは停止(2回で確定にする)
      if(no_rcvcnt >0){
          noNewTone(BZ_bk); //音を止める
          rcv_flg = false;
          delay_tim = 100;
        }else{
          no_rcvcnt++;
        }
   }

   if(rcv_flg == true){
     if(*p_melo == 0){
        noNewTone(BZ_bk); //音を止める
        p_melo = p_top; //先頭の音に戻す
      }else{
        NewTone(BZ_bk, *p_melo * octv); //音を鳴らす
        p_melo++; //次の音にすすめる
      }
   }
   delay(delay_tim); //指定時間待つ
}

では、簡単ですがソースコードの説明をします。

今回はリモコンの受信信号によって鳴らす音を変えているのですが、受信信号で処理を振り分けるところの説明はラジコンを作った際にやってますので、今回はメロディを鳴らすところのみ説明したいと思います。受信信号での処理振り分けはこちらで説明しております。

こちらもCHECK

Arduino+家にあるリモコン(赤外線)でラジコンを作ってみました

エンジニア歴20年以上、フリーランスでソフトウェアエンジニア・ゲームクリエイター・シナリオライターをやっております『NaruTaku』と申します。 今回はArduino+家にあるDVDレコーダーのリモ ...

続きを見る

まず、#include <NewTone.h>ですが、これはメロディーを鳴らすのに使っているNewTone関数や音を止めるnoNewTone関数を使用するために必要なライブラリになります。

実はメロディーを使うだけなら、「Tone」ライブラリのTone関数を使うのが一般的だと思います。(インクルードしなくても動いた気がします)

ではなぜ「NewTone」ライブラリを使っているのかというと、赤外線受信に使う「IRremote」ライブラリと「Tone」ライブラリは一緒に使えないからです。それぞれライブラリの中でタイマーを使っているのですが、使っているタイマーが「Timer2」でかぶってしまっていて、同時に使うと挙動がおかしくなります。

コンパイルエラーになることもあるようなのですが、僕の場合はコンパイルは通りました。しかし、音を鳴らした後、赤外線の受信コードがめちゃくちゃになりました。なので、使うタイマーを変える必要があり、[Timer1]を使っている「NewTone」ライブラリを使っています。

このブログは経験の浅い方を対象としておりますので少し難しいかもしれませんが、とりあえず、「IRremote」ライブラリと「Tone」ライブラリは一緒に使えないということだけ覚えていただければと思います。「NewTone」ライブラリのインストールについては、以下の記事で紹介しております。

こちらもCHECK

Arduinoでメロディーを鳴らす際の注意点!IRremote使用時はNewToneを使おう!

エンジニア歴20年以上、フリーランスでソフトウェアエンジニア・ゲームクリエイター・シナリオライターをやっております『NaruTaku』と申します。 今回はArduinoで圧電スピーカーを用いてメロディ ...

続きを見る

ではメロディーを鳴らす部分を説明していきます。まず、鳴らすメロディーですが、以下で定義しています。

const int mario[] = {330, 392, 659, 523, 587, 784, 0}; //マリオ(ミ ソ ミ ド レ ソ)

const int zerda[] = {784, 740, 622, 440, 415, 659, 784, 1046,0}; //ゼルダ(ソ #ファ #レ ラ #ソ ミ #ソ ド)

音階(ドやレなど)の周波数は決まっていますので、メロディにあった周波数を配列で定義しているだけです。基本的にはこの配列に定義した周波数をNewTone関数で鳴らしてやるだけになります。配列の最後を0としているのは、一度フレーズが終わった後に音を切りたいためです。

そして、鳴らすメロディーの振り分けを受信した信号のところでやっています。以下の部分です。

p_melo = mario;
p_top = mario;
octv = 2;
rcv_flg = true;
delay_tim = 120;

上は「1」受信時なのですが、やっていることは他の信号受信時でも同じです。p_melo = mario;p_top = mario;で鳴らすメロディー配列の先頭ポインタを入れています。なぜ違う変数に同じポインタを入れているかというと「p_melo」は音を鳴らす用、「p_top」は最後の音を鳴らしたあと、先頭に戻る用としているためです。

あと、配列をインデックス指定なしで配列名だけ書くとその配列の先頭ポインタを返します。この辺りはC言語と同じ仕様です。なので、p_melo = &mario[0]と書いても同じ挙動になります。単純に音を鳴らす用にもう一つ配列を作ってそこにコピーしてもよかったのですが、ポインタを使った方が簡潔に書けます。

ただ、経験の浅い方はポインタが苦手かもしれませんので、また別途ポインタについて別記事にてわかりやすく説明したいと思います。

octv = 2;についてですが、これは鳴らす音のオクターブ調整用です。周波数を倍にしてやると1オクターブ、さらに倍にしてやると2オクターブと上がっていくのですが、それぞの周波数を定義した配列を持つのは面倒なので、配列は一つにし、鳴らすときにこの「octv」を掛けてやっています。なのでoctv=2なら、配列で定義している周波数から見て1オクターブ、octv=4なら2オクターブ上ということになります。

rcv_flg = true;はボタンが押されているときだけメロディーを鳴らしたいので、ボタンが押されている判定用です。

delay_tim = 120;ですが、これは音と音の間隔です。マリオとゼルダのメロディーでテンポが違うので、そのテンポに合うように音と音の間隔を調整してます。音の間隔は僕の感覚で調整したので、適当です^^;

最後に以下です。

if(rcv_flg == true){
if(*p_melo == 0){
noNewTone(BZ_bk); //音を止める
p_melo = p_top; //先頭の音に戻す
}else{
NewTone(BZ_bk, *p_melo * octv); //音を鳴らす
p_melo++; //次の音にすすめる
}
}
delay(delay_tim); //指定時間待つ

if(rcv_flg == true)で、ボタンが押されているかの判定をしており、押されている間だけメロディーを鳴らします。

if(*p_melo == 0){で配列の最後か(値が0か)?のチェックをしていて、最後でなければNewTone(BZ_bk, *p_melo * octv);で音を鳴らしています。NewTone関数は第一引数でスピーカーをつないだポート(今回は3(BZ_bkは3で定義))を指定、第二引数で鳴らす音(周波数)の指定です。第三引数で鳴らす時間を指定できるのですが、今回は使用してません。その後p_melo++;で次の音(配列の次の値)に進めています。

配列の最後(値が0)の場合はnoNewTone関数で音を止めています。周波数0でNewTone関数を実行すると音が止まるのかなと思ったのですが、雑音がなったのでこうしました。その後p_melo = p_top;で最初の音(配列の先頭)に戻しています。

音の処理をした後にdelay(delay_tim);で指定した時間待ち、以後、このサイクルを繰り返します。

ちなみに、ボタンが押されていない判定をしたときもnoNewTone関数で音を止めています。if(rcv_flg == true)のelse側で音を止めてもよかったのですが、こうするボタンが押されていないときはずっとnoNewTone関数が走ってしまうことになり、それは嫌だったのでこうしました。

ソースの説明は以上となります。

以上、今回はArduinoと100円の圧電スピーカーでマリオ(一機アップ)とゼルダ(謎解き)を奏でてみましたのでその内容を紹介させていただきました。

まとめ

今回はArduinoで簡単なメロディーの鳴らし方を紹介してみました。

説明の中でポインタの使い方など不十分だったと思いますので、ポインタにつきましてはまた別途別の記事で紹介したいと思います。

最後まで読んでくださりありがとうございました。

  • この記事を書いた人
  • 最新記事

NaruTaku

40代のフリーランスエンジニア・シナリオライター。 20代前半から20年間エンジニアとしてキャリアを積みフリーランスへ転身。ソフトウェア開発とゲーム開発、シナリオ執筆をメインに、プログラミング講師・Webライターとしても活動。また、フリーランスになり見た目の重要性に今さら気づき、ヒゲ脱毛を実施中。

-趣味, 電子工作