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以下の低い周波数は発振できません。
ここでは、割り込み処理による音の生成法を紹介します。
ここでは、タイマー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 マイクロ秒 になります。
割り込みは、ハードウエアの要因で現在実行中のプログラムの実行を中断し、指定した関数(割り込み関数といいます)を呼び出す仕組みです。割り込み関数を終了すると、元のプログラムに「戻り」実行を再開します。
割り込みを利用するには、予め、割り込み許可を出しておく必要があります。下は、タイマー1の割り込みを許可する設定です。enable_interrupts(INT_TIMER1);
はタイマー1の割り込みを許可し、 enable_interrupts(GLOBAL); は割り込み機能の利用を許可する関数です。
enable_interrupts(INT_TIMER1); //タイマ1割込み許可 enable_interrupts(GLOBAL);
#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) でタイマー設定値を計算します。
スピーカは磁力を利用して、円筒部にあるコイルを駆動し、コイルに接続した 薄い「幕」を振動し、音波を生成します。小さなスピーカの場合、PICの出力で直接スピーカを鳴らすことができます。大きなスピーカを駆動するためには「アンプ」と呼ばれる電力増幅を行うアナログ回路が必要です。ここでは、簡単なキットによるアンプと小型スピーカを利用します。
ポートCの1ピン(PC1:12ピン)に発振信号が出力されます。これを抵抗100オームとコンデンサ(C)を通して小型スピーカと接続します。コンデンサは直流分を阻止し、交流分(音声のように変化する信号)のみをスピーカに送ります。下図、左の素子がスピーカのシンボルです。PICの電源、発振子、リセット、などの配線は省略しています。コンデンサは長いほうが+端子で、これを抵抗側に接続します。
PICに電源が入ると、自動的にプログラムを実行開始します。音階に対応する音声信号は、C1ピン(12ピン)から出力されます。この信号を アンプ 回路に入れ、電力増幅(パワーアップ)をします。増幅された信号をスピーカに接続します。
配列 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};
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[] 配列から次の音を取り出し演奏をします。
#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); } }
●メロディー配列に楽譜を入力してください。最後に255を付加します。途中半音の音が入りますから、番号に注意してください。例えば、ドミソ は 3,7,10
となります。
●PICのRAMメモリは少ないので、長いメロディーは記録できません。配列にconst を付加し、
const int melody[ ]={ , , , };
とすると、残っているプログラムメモリに記録でき、長い曲が入ります。