電子オルゴール

  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以下の低い周波数は発振できません。
       ここでは、割り込み処理による音の生成法を紹介します。

    2. 音の生成(タイマー1)

        ここでは、タイマー1の割り込み機能を利用して発振を行います。タイマー1は16ビットのカウンタがあります。このカウンタがオーバーフローしたとき、割り込み起こすことができます。タイマーの設定は、まず、
       setup_timer_1(T1_INTERNAL | T1_DIV_BY_4);
      で、設定します。T1_INTERNALはタイマー1のクロックとして、PIC本体のクロック(20MHz)を使用することを指示します。ただし、実際に使用されるクロックはこの1/4の周波数になります。T1_DIV_BY_4 はこれをさらに 1/4 してタイマー1のクロックとすることを指定します(T1_DIV_BY_4 の他に T1_DIV_BY_1、T1_DIV_BY_8 を利用できます)。PICのクロックが20MHzでT1_DIV_BY_4 を指定した場合、タイマ1のクロックは、20/(4*4) MHz となります。これは、0.8マイクロ秒に相当します。
      タイマー1の値は、
       set_timer1(prd);
      で設定できます。 タイマー1はこの値を タイマー1のクロックで計数し、16ビットのカウンタがすべて1になると0に戻ります。このとき、割り込みが起こります。タイマーに設定した値を prd とすると、割り込みが起こるまでの時間は (65536-prd )*0.8 マイクロ秒 になります。

    3. 割り込み

       割り込みは、ハードウエアの要因で現在実行中のプログラムの実行を中断し、指定した関数(割り込み関数といいます)を呼び出す仕組みです。割り込み関数を終了すると、元のプログラムに「戻り」実行を再開します。
       割り込みを利用するには、予め、割り込み許可を出しておく必要があります。下は、タイマー1の割り込みを許可する設定です。enable_interrupts(INT_TIMER1); はタイマー1の割り込みを許可し、 enable_interrupts(GLOBAL); は割り込み機能の利用を許可する関数です。


       enable_interrupts(INT_TIMER1); //タイマ1割込み許可
       enable_interrupts(GLOBAL);

       タイマー1の割り込み関数は、#int_timer1 の直後に定義します。以下の定義で、タイマー1の割り込みが起こると、timer1_isr() の関数が呼び出されます。ここでは、Pin_C1 のピンを反転し、発振信号を生成しています。最後に、指定時間後、再度、割り込みが set_timer1(prd); を呼び出します。
      #int_timer1
      void timer1_isr()
      {
              output_bit(PIN_C1,flag);
              if (flag==1) flag=0; else flag=1;
              set_timer1(prd);
      }

      prd の値は演奏する音階により定めます。割り込みが2回で1周期になりますから、周波数を f にするには、
        (65536-prd )*0.8 *2 *10-6 = 1/f
      となります。0.8 はタイマーのクロック時間です。したがって、
       prd = 65536 - 106/(2*0.8*f)
      となります。これをEXcelで計算します。まず、C3セルの220を入力し、C4セルを C3*2^(1/12)  とし、ラ#の周波数を求めます。これを2オクターブ分コピーして、各音の周波数を求めます。
       C3の周波数に対し、= 65536 - 1000000/(0.8*C3*2) でタイマー設定値を計算します。

       
  4. 回路制作

    1. アンプとスピーカ

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

    2. 回路

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

    3. 回路の動作

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

  5. プログラム(ソフト)

    1. 音階配列

       配列 onkai[30] 低い「ラ」から2オクターブ上の「ラ」までのタイマー設定値記録します。

      unsigned long onkai[30]= {62695,62854,63005, 63147,63281,63407,63527,63639, 63746,
      63847,63941,64031,64115,64195,64270,64341, 64408,64471,64531,64587,64641,64691,64738,
      64783, 64825,64865,64903,64938};

    2. 音符(メロディー)の記述

      ここでは、音符の音の長さはすべて同じとし、音の高さを上の表の番号で表します。 この数字列を、配列 melody[] に指定します。下の例は半音を除く ドレミファソラシドレミファソラシド. を演奏します。最後の255は曲の終わりの音とします。
       int melody[30]={0,2,3,5,7,8,10,12,14,15,17,19,20,22,24,26,27,255};
      プログラムでは、タイマー2の割り込みを設定後、指定時間間隔で melody[i] から音符の番号を読み、それから、onkai[] 配列で音の高さを取り出します。  set_timer1(prd); で、タイマー2を設定します。prd秒後に割り込みが起こり、PIN-C1 の端子を反転します。2回の割り込みで1周期の音を合成します。音符の演奏間隔は delay_ms(500); で指定しています。500mS後経過すると、melody[] 配列から次の音を取り出し演奏をします。

    3. ソース

      #include <16f873a.h>
      //C1にスピーカを接続
      //割込みでonkai[]に指定された音階の信号を生成
      //melody[]に数字で音符を記憶
      
      #fuses HS,NOWDT,NOLVP
      #use delay(CLOCK=20000000) //クロック20MHz
      
      int flag;
      //音階の周期 ラ ラ# シ ド ド# レ レ# ミ ファ ファ# ソ ソ# ラ ラ# シ ド  ド# レ レ#
      //           0     2   3      5      7   8         10     12    14  15     17
      unsigned long onkai[30]=
              {62695,62854,63005, 63147,63281,63407,63527,63639,
              63746,63847,63941,64031,64115,64195,64270,64341,
              64408,64471,64531,64587,64641,64691,64738,64783,
              64825,64865,64903,64938};
      unsigned long prd;
         
      //メロディ 
      int melody[30]={0,2,3,5,7,8,10,12,14,15,17,19,20,22,24,26,27,255};
      int i,oto;
      int num;
      
      //タイマー割り込み関数
      #int_timer1
      void timer1_isr()
      {
          output_bit(PIN_C1,flag);//C1にパルスを発生する
          if (flag==1) flag=0; else flag=1;
          set_timer1(prd);
      }
      
      void main(){
       num=30;//melody[]配列の音符の数     
       setup_timer_1(T1_INTERNAL | T1_DIV_BY_4);
       set_timer1(10);
      
       enable_interrupts(INT_TIMER1); //タイマ0割込み許可
       enable_interrupts(GLOBAL);//タイマー機能利用
      
       while(1){
         for(i=0;i<num;i++){     
           oto=melody[i];
           if(oto==255) break;//1曲終了
           prd=onkai[oto];
           set_timer1(prd);
           delay_ms(500);
         }
         disable_interrupts(INT_TIMER1);//音だしの停止
         delay_ms(500);//次の音符までの時間
         enable_interrupts(INT_TIMER1);//音だし再開
         set_timer1(65000);
       }
      }

    4. 演習

      ●メロディー配列に楽譜を入力してください。最後に255を付加します。途中半音の音が入りますから、番号に注意してください。例えば、ドミソ は 3,7,10 となります。

      ●PICのRAMメモリは少ないので、長いメロディーは記録できません。配列にconst を付加し、
       const int melody[ ]={ , , , };
      とすると、残っているプログラムメモリに記録でき、長い曲が入ります。

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

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

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

    2. アンケート

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