<PICで音を創る(基本編)>
PICのPWMを使いスピーカーから音を出してみます。↓回路図

大まかな設定手順は、
①イニシャライズ:
・クロック設定:16MHz、プリスケーラx4(Fosc/4)
・ペリフェラルインターラプト設定:PWM周期を作るTimer2のカウンタでインターラプト
・タイマー分周の設定:Timer2プリスケーラx1で再生周波数(Fosc/4/PR2=16KHz)、ポストスケーラx2でサンプル周波数(8KHz)
・PWM設定:標準モード、周期(振幅諧調)カウンタセット(PR2=250)
・I/Oポート設定:出力pin(RA2)
②メイン関数:音データをReadしPWM幅へ代入、SETフラグ(サンプリング周期)を待ち次のデータへ繰り返し
③インターラプト関数:Timer2インターラプトでSETフラグを立てる

まずは1音、1KHzの正弦波です。
C言語にて正弦波形はsin()関数で簡単に作れますが、PICは浮動小数点を扱えないので、SINの整数データを作っておくのが簡単です。 6bit8KHzサンプリングのSINデータを配列にしメモリ(SRAM)に置き、10bit16KHzで再生するプログラムを作ります。 何故、6bit8KHzデータで10bit16KHz再生を使うかは、メモリ容量とクロック周波数(PWM周期)のバランスで汎用性と実用性を備えプログラムし易いところだからです。 詳しくは前ページをご覧ください。 6bitの整数でSINを表現するには振幅を0~63の値で表し、8KHzのサンプリングにて1KHzの音程ですのでサンプル数は8個の配列です。
unsigned char sin08[]={32,54,63,54,32,9,0,9};
もっと高い精度の波形データを使いたいとお思いになるかもしれませんが、音としては十分に聴こえるだけでなく、 この音をベースに波形合成(Synthesizer)することを考えると、PICにとっては精度限界に近いデータです。 (もちろん目的が単音を正確に出すことでしたら10bitPWMを持つPICではClock32MHzにて10bit32KHzサンプリング/再生まで上げられます。)
↓プログラム

PIC再生音
↓1KHzSIN波(再生波形)

綺麗な正弦波ではないため高調波が出てしまいますが、2倍の2KHzで-30dB下がっていますので充分でしょう。 この簡単なプログラムにて、sin08[]の代わりに任意の音源データを使えば、その音を再生できるわけです。


次に音程を変えてみます。周波数を変えるには2つの方法が考えられます。SINデータの長さを変える方法と再生周波数を変える方法です。 SINデータは8個で1KHzでしたので、9個にすると1KHz*8/9=875Hz、7個にすると1.125KHzというように変わります。 再生周波数は16KHz再生で1KHzでしたので、32KHz再生すれば2KHz、8KHz再生すれば500Hzというように、これはPWMタイマー(Timer2)のポストスケーラ(若しくはプリスケーラ)で変更できます。 そして音程の微調整にはPWM周期変数PR2=250(16KHz再生)を変えることになります。PR2=251では1KHz*250/251=996Hz、PR2=249では1.004KHzのように変わるのですが、 この変数は振幅階調のダイナミックレンジでもあるため、大きく(周波数を低く)すれば音量は下がり、小さく(周波数を高く)すれば音量が上がります。SINデータがオーバーレンジとなる 振幅階調まで下げてしまうと音にならなくなるため、PWM周期にリンクして振幅値を変更しないとなりません。
従って音階を作るためには、1オクターブをPWM周期(PR2と同時に振幅調整)の変化で作り、音域の切り替えに再生周波数を変更するという考え方が必要になります。
unsigned char freq[]={239,213,190,179,159,142,127}; //ド,レ,ミ,ファ,ソ,ラ,シ
↓プログラム

PIC再生音


さて音には音程(周波数)の他に、大きさ(強弱)という情報があります。強弱が一定であれば「ピーーーー」 ですが、同じ音程でも強弱を付けると「ピンッ」とか「ピューイ」のように表現ができるのです。
この強弱を示すエンベロープデータをSINデータに乗じ(変調を掛ける)れば色々な音を創れるのですが、 元のSINデータが6bitですので、10bitPWMで表現できるエンベロープは残り4bit分(16階調)になります。そして踏切警報音「カン、カン」のエンベロープを考えてみると、1回の長さが0.45秒ありますから 0.45secを何分割すれば「カン」を表現できるかを考えなければなりません。細かく分割すればより忠実な再現が可能ですが、メモリ容量の制約もありますし、 強弱が16階調(0~15)ですので、まずは時間も16分割とし16個のデータで表現してみます。
unsigned char env[]={15,12,8,8,7,6,6,5,3,2,2,2,2,1,1,1};
↓プログラム

PIC再生音
↓1KHzで0.45secエンベロープ(再生波形)


それでは2つの音程を和音にしてエンベロープを掛けるにはどうするか、いろいろな方法があり少し迷います。
①音源(SINデータ)を和音データにしてエンベロープを掛ける
②2つの音程のSINデータを持ち、合成してエンベロープを掛け、1つのPWMで再生する
③2つのPWMを使い2つの音程を作りそれぞれエンベロープを掛け、出力をスピーカーで合成する(BTL)
などなど他にもありそうですが、ポイントは2つの音程の差分の音(うねり)を再生することです。2つの周波数が近ければ近いほど差は小さく低い周波数のうねりとなります。 方法①はうねり周波数を再現できるデータの長さが必要になります。 方法③のBTLドライブは高性能で汎用性が高いメリットがありますのでいずれ試してみたいと思いますが、今回は2つの基音をもつ踏切警報音を作ることを前提にある程度決まった音程で良いとして、 方法②を考えます。sin09[]データ(875Hz)を作りsin08[](1kHz)に加算するとかなり実音に近づくことが判ります。
unsigned char sin09[]={32,52,63,60,43,21,4,0,11};
↓プログラム

PIC再生音
これで和音の原理も出来上がりました。あとは以上の理解のもとで、実際の音程に合わせて調整すれば簡易シンセサイザーの出来上がりです。

戻る