PICのソフトウエアによる発振機能を用いて、ドレミ.. の音を合成し、簡単な音楽演奏を試みます。
音は物体の機械的振動が空気を媒介して耳で聴取される信号です。音色はその強さを縦軸とし、横軸を時間とした波形で決まります。
任意の波形は複数の周波数の音の重ねた音として合成できます。音の高さは主となる音の周波数で定まります。
音階はドレミで表現しますが、低いドから次のドになると周波数は倍になります。平均率の音階は、この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 |
音符に従った長さと高さ(周波数)で音を生成すれば曲として聞こえます。特定の周波数 (f) の音を出すには、1秒間に f 個のパルスを生成し、その信号でスピーカを鳴らします。ただし、この音の波形は単純ですから、「良い」音にはなりません。きれいな楽器の音を出すには、予め楽器の音をディジタル録音し、その音を指定時間再生する方法が一般的です。
曲のデータは予め4分音符単位の長さで、数字の羅列で音符を表現しておきます。この数字の列にしたがい、数字の音階に対応する音を生成します。2昔前の携帯電話の「着メロ」に近い音になります。
発振を行うには通常PWM機能が利用できます。しかし、PWM機能はタイマー2の発振機能を利用しますが、発振周波数の指定は10ビットしか指定できません。このため、20MHzのクロックを利用すると220Hzの低い周波数は発振できません。そこで、4MHzの発振子か、12F683のようにクロックが変化できるPICを利用します。
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になります。
beat=110;//秒あたりの4分音符の演奏個数 bms = (unsigned long)60000/(beat * 2); intvl = (unsigned long)65536 - (1000 / 8) * bms;
スピーカは磁力を利用して、円筒部にあるコイルを駆動し、コイルに接続した 薄い「幕」を振動し、音波を生成します。小さなスピーカの場合、PICの出力で直接スピーカを鳴らすことができます。大きなスピーカを駆動するためには「アンプ」と呼ばれる電力増幅を行うアナログ回路が必要です。ここでは、簡単なキットによるアンプと小型スピーカを利用します。
ポートCの1ピン(PC1:12ピン)に発振信号が出力されます。これを抵抗100オームとコンデンサを通して小型スピーカと接続します。コンデンサは、直流分を阻止し、交流分のみをスピーカに送ります。下図、左の素子がスピーカのシンボルです。PICの電源、発振子、リセット、などの配線は省略しています。
PICに電源が入ると、自動的にプログラムを実行開始します。音階に対応する音声信号は、C1ピン(12ピン)から出力されます。この信号を アンプ 回路に入れ、電力増幅(パワーアップ)をします。増幅された信号をスピーカに接続します。
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になります。
beat=110;//秒あたりの4分音符の演奏個数 bms = (unsigned long)60000/(beat * 2); intvl = (unsigned long)65536 - (1000 / 8) * bms;
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割込み許可音符は、ポインタ経由で取り出します。最初 pmelody=melody; で、pmelody をmelody配列の先頭に設定し、
enable_interrupts(GLOBAL);
setup_timer_1(T1_INTERNAL | T1_DIV_BY_8);
set_timer1(100);
oto = *pmelody; pmelody++; prd = onkai[oto]; len = *pmelody; pmelody++;で音符の高さと、長さをotoとlenにとりだします。
//音楽演奏 //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){ } }