電子オルゴール

  1. 実験の目的

     PICのソフトウエアによる発振機能を用いて、ドレミ.. の音を合成し、簡単な音楽演奏を試みます。

  2. 音の合成

    1. 音の生成

       音は物体の機械的振動が空気を媒介して耳で聴取される信号です。音色はその強さを縦軸とし、横軸を時間とした波形で決まります。

      任意の波形は複数の周波数の音の重ねた音として合成できます。音の高さは主となる音の周波数で定まります。

    2. 音階

      音階はドレミで表現しますが、低いドから次のドになると周波数は倍になります。平均率の音階は、この1オクターブを周波数の比が一定になるよう12等分します。従って、前の音の周波数を1にすると、次の音の周波数は、前の音の約 2^(1/12) = 1.06倍になります(^はべき乗で、Excelの演算子です)。ミとファおよび、シとド以外には半音が入ります。これはピアノ鍵盤の黒鍵に相当します。ド# はドの半音上の音で、これは レ♭の音と同じです。

       五線譜のラの音の周波数はちょうど440ヘルツになりますから、次1オクターブ低いラの音までの周波数は次の表のようになります。 A,B,C..はドイツ語の音階表記です。A(ラ)の音はきれいな整数値なので、よく楽器の音あわせに利用されます。440Hzは時報の低い音に相当します。

      ドイツ音階 A A# B C C# D D# E F F# G G# A
      ラ# ド# レ# ファ ファ# ソ#
      周波数 220.0 233.1 246.9 261.6 277.2 293.7 311.1 329.6 349.2 369.9 391.9 415.3 440.0
      周波数比 1.1 1.1 1.1 1.1 1.1 1.1 1.1 1.1 1.1 1.1 1.1 1.1
      周期(mS) 4.5 4.3 4.1 3.8 3.6 3.4 3.2 3.0 2.9 2.7 2.6 2.4 2.3
    3. 音階の合成

      音符に従った長さと高さ(周波数)で音を生成すれば曲として聞こえます。特定の周波数 (f) の音を出すには、1秒間に f 個のパルスを生成し、その信号でスピーカを鳴らします。ただし、この音の波形は単純ですから、「良い」音にはなりません。きれいな楽器の音を出すには、予め楽器の音をディジタル録音し、その音を指定時間再生する方法が一般的です。

    4. 音の合成実験

       曲のデータは予め4分音符単位の長さで、数字の羅列で音符を表現しておきます。この数字の列にしたがい、数字の音階に対応する音を生成します。2昔前の携帯電話の「着メロ」に近い音になります。

  3. 割り込み回路

    1. PWM回路

       発振を行うには通常PWM機能が利用できます。しかし、PWM機能はタイマー2の発振機能を利用しますが、発振周波数の指定は10ビットしか指定できません。このため、20MHzのクロックを利用すると220Hzの低い周波数は発振できません。そこで、4MHzの発振子か、12F683のようにクロックが変化できるPICを利用します。

    2. ハードpwm

      PICは回路による PWM 信号の生成機能があります。まず、
          setup_ccp1(CCP_PWM);
      で、ハードPWMの利用を設定します。PWMの周期は
       setup_timer_2(T2_DIV_BY_16,prd,1);
      で指定します。これで、クロックの 1/4 をさらに1/16 とした周期のprd倍の周期でPWM信号が生成されます。PWMの幅:duty は
       set_pwm1_duty(duty);
      で指定します。これで、ポートPC2から、指定した周期と幅の信号が繰り返し出力されます。ソフトPWMに比較してCPUの負担は減りますが、1チャンネルしか利用できませんし、固定のポートを利用する必要があります。また、発振周波数を設定するビット数が8ビットですから、低い周波数の設定ができません。そこで、CPUへのクロックを4MHzに変更します。onkai[] はこの場合の、oto の高さに対するクロック数を指定します。最大値は255になります。

    3. 音の長さの指定

       ここでは、メロディーに音の高さだけでなく、長さを指定します。長さは8分音符を1とし、4分音符を2、2部音符を4、全音符を8、とします。演奏の速度は、通常の楽譜のテンポと同様、1分あたりに演奏する4分音符の数で変数beatに指定します。ゆっくりした音楽は beat=80、普通の速度は beat=120、速い演奏の場合 beat=160 程度になります。
       プログラムでは、beat から8分音符あたりの演奏時間をbmsに算出します。これから、タイマー1が割り込みクロック数をintvl に求めます。
        beat=110;//秒あたりの4分音符の演奏個数
        bms = (unsigned long)60000/(beat * 2);
        intvl = (unsigned long)65536 - (1000 / 8) * bms;

  4. 回路制作

    1. アンプとスピーカ

       スピーカは磁力を利用して、円筒部にあるコイルを駆動し、コイルに接続した 薄い「幕」を振動し、音波を生成します。小さなスピーカの場合、PICの出力で直接スピーカを鳴らすことができます。大きなスピーカを駆動するためには「アンプ」と呼ばれる電力増幅を行うアナログ回路が必要です。ここでは、簡単なキットによるアンプと小型スピーカを利用します。

    2. 回路

       ポートCの1ピン(PC1:12ピン)に発振信号が出力されます。これを抵抗100オームとコンデンサを通して小型スピーカと接続します。コンデンサは、直流分を阻止し、交流分のみをスピーカに送ります。下図、左の素子がスピーカのシンボルです。PICの電源、発振子、リセット、などの配線は省略しています。

    3. 回路の動作

       PICに電源が入ると、自動的にプログラムを実行開始します。音階に対応する音声信号は、C1ピン(12ピン)から出力されます。この信号を アンプ 回路に入れ、電力増幅(パワーアップ)をします。増幅された信号をスピーカに接続します。

  5. プログラム(ハードPWM)


    1. ハードpwm

      PICは回路による PWM 信号の生成機能があります。まず、
          setup_ccp1(CCP_PWM);
      で、ハードPWMの利用を設定します。PWMの周期は
       setup_timer_2(T2_DIV_BY_16,prd,1);
      で指定します。これで、クロックの 1/4 をさらに1/16 とした周期のprd倍の周期でPWM信号が生成されます。PWMの幅:duty は
       set_pwm1_duty(duty);
      で指定します。これで、ポートPC2から、指定した周期と幅の信号が繰り返し出力されます。ソフトPWMに比較してCPUの負担は減りますが、1チャンネルしか利用できませんし、固定のポートを利用する必要があります。また、発振周波数を設定するビット数が8ビットですから、低い周波数の設定ができません。そこで、CPUへのクロックを4MHzに変更します。onkai[] はこの場合の、oto の高さに対するクロック数を指定します。最大値は255になります。

    2. 音の長さの指定

       ここでは、メロディーに音の高さだけでなく、長さを指定します。長さは8分音符を1とし、4分音符を2、2部音符を4、全音符を8、とします。演奏の速度は、通常の楽譜のテンポと同様、1分あたりに演奏する4分音符の数で変数beatに指定します。ゆっくりした音楽は beat=80、普通の速度は beat=120、速い演奏の場合 beat=160 程度になります。
       プログラムでは、beat から8分音符あたりの演奏時間をbmsに算出します。これから、タイマー1が割り込みクロック数をintvl に求めます。
        beat=110;//秒あたりの4分音符の演奏個数
        bms = (unsigned long)60000/(beat * 2);
        intvl = (unsigned long)65536 - (1000 / 8) * bms;

    3. 割り込みによる演奏

       音符単位の進行にはタイマー1の割り込みを利用します。enable_interupts() でタイマー1の割り込みの許可を出します。以下で、タイマー1のクロックは内部クロックを利用し、それを 1/8 にして使用することを指定します。次に、タイマー1の値を指定します。
        setup_timer_1(T1_INTERNAL | T1_DIV_BY_8);
        set_timer1(intvl);
       タイマー1は上で設定したクロックで計数をおこない、オーバーフローする(計数値が0xffffを超える)と割り込みを生成します。割り込みが発生すると、void timer1_isr() を呼び出します。呼び出しは8分音符間隔ですから、len が0なら次の音符の PWM の設定を変更します。len は音符の長さで、割り込む度に1減らします。
      enable_interrupts(INT_TIMER1); //タイマ1割込み許可
      enable_interrupts(GLOBAL);
      setup_timer_1(T1_INTERNAL | T1_DIV_BY_8);
      set_timer1(100);
       音符は、ポインタ経由で取り出します。最初 pmelody=melody; で、pmelody をmelody配列の先頭に設定し、
          oto = *pmelody;
          pmelody++;
          prd = onkai[oto];
          len = *pmelody;
          pmelody++;
       で音符の高さと、長さをotoとlenにとりだします。

    4. プログラム

       注意!!!
      このプログラムは、クロックを4MHzに設定する必要があります(20MHzでは、低い周波数の音を出すことができません。)このため、セラロックを4MHzに、PICのブートローダー(ソフト)を4MHz用に交換する必要があります。
       波形の生成にはPWM、演奏の制御にはタイマー1を利用します。main() での処理は不要になりますから、別の処理が可能になります。
      //音楽演奏
      //PWMを PC2 に出力
      //発振は4MHzを使用する
      
      #include <16f873A.h>
      
      #fuses HS,NOWDT,NOLVP,NOPROTECT//外部クロック、WDT,LVPなし
      #use delay(CLOCK=4000000) //クロック4MHz
      
      //音階の周期 ソソ#ララ#シドド#レレ#ミファ  ファ#ソ  ソ#ララ#シド  ド#レレ#ミ
      //             レ レ#  ミ ファ ファ# ソ    ソ#  ラ    ラ#  シ  ド  ド# 
      int onkai[]={0,253,239,225,213, 201,  190, 179, 169,  159, 150,142,134,
      //             レ  レ#   ミ  ファ  ファ# ソ  ソ#  ラ  ラ#  シ  ド  ド#
                     127,119, 113, 106,  100,  95, 89,  84, 79,  75, 71, 67,
      //             レ  レ#   ミ  ファ  ファ# ソ  ソ#  ラ  ラ#  シ  ド  ド#
                     63, 60,  56  ,53,    50,  47,  44, 42, 40,  38,  35 };
      
      //レ:1 レ#:2  ミ:3 ファ:4 ファ#:5 ソ:6  ソ#:7  ラ:8   ラ#:9  シ:10  ド:11 ド#:12
      //レ:13 レ#:14  ミ:15 ファ:16 ファ#:17 ソ:18  ソ#:19  ラ:20   ラ#:21  シ:22 ド:23 ド#:24
      //レ:25 レ#:26  ミ:27 ファ:28 ファ#:29 ソ:30  ソ#:31  ラ:32   ラ#:33  シ:34 ド:35
      
      //メロディー
      //              ミ     ソ  ラ     ド      ミ
      int melody[]={ 20,1,22,1,23,3,22,1,23,2,27,2,22,4,//あの地平線
                      15,2,20,3,18,1,20,2,23,2,18,4,//かがやくのは
                     15,1,15,1,16,3,16,1,16,1,23,3,15,4,//どこかにきみを
                     23,1,23,1,23,1,22,3,17,1,16,2,22,2,22,4,//かくしているから
                     0,10 };
         
      int len,oto;
      int *pmelody;
      int prd;
      int beat;
      unsigned long bms,intvl;
      
      #int_timer1
      void timer1_isr()
      {
        if(len==0){
          oto = *pmelody;
          pmelody++;
          prd = onkai[oto];
          len = *pmelody;
          pmelody++;
          
          if(oto == 0) {
            pmelody = melody;
            set_pwm1_duty(0);
          }
          else set_pwm1_duty(prd/2);  //パルス幅は周期の半分  
      
           //タイマー2(PWM)の周期を設定      
           setup_timer_2(T2_DIV_BY_16,prd,1);
        } 
        len--;
        set_timer1(intvl);//4分音符=80 の速度
      }
      
      
      void main(){
           
       //RC2にPWM信号生成
        setup_ccp1(CCP_PWM);
        pmelody=melody;
      
        beat=110;//秒あたりの4分音符の演奏個数
        bms = (unsigned long)60000/(beat * 2);
        intvl = (unsigned long)65536 - (1000 / 8) * bms;
        len=0;
      
        enable_interrupts(INT_TIMER1); //タイマ1割込み許可
        enable_interrupts(GLOBAL);
      
        setup_timer_1(T1_INTERNAL | T1_DIV_BY_8);
        set_timer1(intvl);
      
       while(1){
      
       }
      
      }

  6. 発展・課題・アンケート

     
    1. 楽譜の作成、音符の長さ

      演奏する楽譜を作成してください。また、ソフトPWM演奏に音符に長さをつけてください。8(16)分音符を単位の長さ1とし、各音符の長さを整数で指定します。指定回数の割り込みが発生したら、次の音符の処理を行います。

    2. アンケート

      1:演奏の原理は理解できましたか? 1:できた 2:だいたい 3:まだ
      2:実行できましたか? 1:できた 2:まだ
      3:曲は作成できました? 1:できた 2:まだ
      4:プログラムの送信はできますか? 1:できる 2:できない