PWM を使用してオルゴール調の音色で曲を演奏します。 このページでは、基本的な動作を確認するために1つの音しか発生させていませんが、この発音方法を応用し、4音同時に演奏するプログラムも次のページに掲載しました。 また、8ピンPIC12F1822 でも同じ機能のオルゴールプログラムを作成してあります。このプログラムでは、PICの特徴を生かし、演奏をしていないときはスリープ状態にし消費電力を抑えたため乾電池2本で、電池寿命を忘れるほど長く持ちます。小さな箱に組み込んで使用するのに最適です。
PICのペリフェラルは以下の用途に使用しています。
SW1 、USBコネクタは、ブートローダーで書き込むときのためです。曲の変更をしなければ不要です、また、LEDは動作確認用なのでこれも省略することができます。
PWM出力端子、RC6 に スピーカーまたは、圧電スピーカーを接続します。 圧電スピーカーは直接接続しますが、通常のダイナミックスピーカーを接続するには、RA6 とスピーカーの間に100μF程度の電解コンデンサを介します
楽譜の情報は、"gakufu.h" に、ある、unsigned int 配列 「p1gkf」に記述されています。
各音符の音の高さは、PWM 周期 (93.75kHz) 毎に、周波数定数 (incWave) を積算していき、その値 (accWave) が、0xFFFF を越えたときに波形を反転させ発生させます。PIC16F1503 などに搭載されている NUMERICALLY CONTROLLED OSCILLATOR (NCO) MODULE と同じ方法でです。周波数定数 (incWave) は、音符の音の高さ (Freq) 、PWM 周期 (93.75kHz)から次の式で計算します。
incWave = Freq x 2 x 65536 93750
たとえば ラ(440Hz)は、615.16 (0x267)
この値を、プログラムに dW_tbl[ ] 配列 として一覧にしています。
オルゴールの音を表現するため、50ms 毎に音の強さを 1/16 ずつ減衰させています。また、音のキレを良くするために、次の音符が始まる前に、無音時間 30ms を設けています。
曲のテンポとして、1分間に演奏される4分音符の数が楽譜の最初に書かれています。この値はプログラムで使用するテンポ単位 (ms) に変換しますが、4分音符は 8テンポ単位、8分音符は 4テンポ単位の演奏時間になります。テンポ単位時間毎に TmpFlg を立ててメインループで処理します。
テンポ単位(ms) = 60 x 1000 8 x テンポ
たとえば 4分音符=120 は、テンポ単位は 62.5 ms
プロジェクトは、曲のデータを持つ「gakufu.h」 と、曲を演奏するための「main.c」 から構成されています。
プロジェクト B05_orgel.zip |
*注意*
MPLAB X プロジェクト には、PICkit3で書き込む仕様とブートローダで書き込む2通りの仕が設定されています。どちらを利用するかをメインメニューバーのIDE Configuration pull down で選んでください。
main.c
/* File: main.c 1
* gakufu は、16 bit 単位。
* 最初にテンポ(1分間に演奏する四分音符の数)が記録されている。
* それ以降は音符 MSB 8 bitが音の高さ。 半音単位で、中央のドが 60(0x3C)
* LSB 8 bitが音の長さ
* 2分音符:10、 4分音符:08、 8分音符:04、 16分音符:02
* 付点2分音符:18、付点4分音符:0C、付点8分音符:06、付点16分音符:03
* | 7 6 5 4 3 2 1 0| 7 6 5 4 3 2 1 0 |
* | 高さ | 長さ |
* Language: MPLABX XC8
* Target: PIC116F1459
*/
// 音が減衰する程度 指定値(ms)で1/16減少
#define EnvelopeConst 50
// 音符と音符の間の無音時間(ms)
#define Silent 30
#include "xc.h"
#include "gakufu.h"
#define _XTAL_FREQ 48000000
#define SW2 RA5
#define SPK LATC6
#define TRIS_SPK TRISC6
#define TRIS_LED3 TRISC2
#define LED3 LATC2
#define waitStart 0
#define getGakufu 1
#define getOnpu 2
#define onpuStart 3
#define onpuPlay 4
#define onpuEnd 5
#define gkfEnd 6
#pragma config FOSC = INTOSC, WDTE = OFF, PWRTE = OFF, MCLRE = OFF, CP = OFF
#pragma config BOREN = ON, CLKOUTEN = OFF, IESO = OFF, FCMEN = OFF
#pragma config WRT = OFF, CPUDIV = NOCLKDIV, USBLSCLK = 48MHz, PLLMULT = 3x
#pragma config PLLEN = ENABLED, STVREN = ON, BORV = LO, LPBOR = OFF, LVP = OFF
// for 48MHz Fosc PWM Freq 93.75kHz
const unsigned int dW_tbl[] = {
// Do, Do#, Re, Re#, Mi, Fa, Fa#, So, So#, Ra, Ra#, Si,
0,
0x05B,0x061,0x067,0x06D,0x073,0x07A,0x081,0x089,0x091,0x09A,0x0A3,0x0AD,
0x0B7,0x0C2,0x0CD,0x0D9,0x0E6,0x0F4,0x103,0x112,0x122,0x134,0x146,0x159,
0x16E,0x184,0x19B,0x1B3,0x1CD,0x1E8,0x205,0x224,0x245,0x267,0x28C,0x2B2,
0x2DC,0x307,0x335,0x366,0x39A,0x3D1,0x40B,0x448,0x489,0x4CE,0x517,0x565
};
unsigned int accWave,incWave;
unsigned char amp,wave;
void main(void) {
char SWdown=0; // SW が押下のフラグ
char SWnotRdy=0; // SW チャタリング防止
char state = 0; // 制御ステート
unsigned int pOnp; // 音符位置
unsigned int Onp; // 音符
unsigned char tempo; // テンポ
unsigned char tPlay; // 演奏長さ
char AmpCnt = 25; // 振幅調整
char TmpCnt = 65; // テンポ調整
char TmpFlg = 0; // テンポフラグ
char SltCnt = 20; // 無音時間
char SltFlg = 0; // 無音フラグ
char ampNext; // 次音符音量
OSCCON = 0b11111100; // SPLLEN = ON, 16MHz(x3=48MHz)
TRIS_SPK = 0; // SPK RA2 音声出力
SPK = 0; // SPK 出力「0]
TRIS_LED3 = 0; // LED3 RC2 出力
LED3 = 0; // LED3 消灯
nWPUEN = 0; // 弱プルアップ 有効
// Timer1 の設定 -------------------------
T1CON = 0b00000001; // Fosc/4 ps 1/1 TMR1ON
TMR1 = 0xD100; // +12032 (1ms) で Carry
TMR1IF = 0; // Timer1フラグクリア
// PWM の設定 -------------------------
PWM2DCL = 0;
PR2 = 0x7F; // Freq_PWM = 93.75kHz
T2CON = 0b00000000; // Fosc/4 ps 1/1 T2OFF
TMR2IF = 0; // Timer2フラグクリア
TMR2IE = 1; // Timer2割込み有効
PEIE = 1; // ペリフェラル割込み有効
GIE = 1; // 割込み有効
while(1){
while(!TMR1IF); // 1ms で Carry
TMR1H = 0xD1; // 次の 1ms を設定
TMR1IF = 0;
//------- SWチャタリング防止 1msループ内 ----------------
if(SWnotRdy){ // SWupの確認中なら
if(SW2)SWnotRdy--; // SWupの確認をー1
else SWnotRdy=5; // SWupの確認初期値
}else if(!SW2){ // SWupの確認済みでDownなら
SWdown=1; // SWが押されたマーク
SWnotRdy=5; // SWupの確認待ちに
}
//------- 振幅調整 50ms -----------------------------------
if(AmpCnt-- == 1){
AmpCnt = EnvelopeConst; // 音量を 50ms毎に
amp = (int)amp * 15/16; // 音量を -6% する
}
//------- テンポ調整 -----------------------------------
if(TmpCnt-- == 1){
TmpCnt = tempo; // テンポ毎に
TmpFlg = 1; // テンポフラグをセット
}
//------- Silent時間 -----------------------------------
if(SltCnt-- == 1){ // 消音時間になれば
SltFlg = 1; // 消音フラグをセット
}
//---------------------------------------------------------
switch(state){
case waitStart: // ====== 演奏開始待ち ======
if(SWdown){ // SW 押されるのを待つ
SWdown=0; // SW 処理済み明示
state = getGakufu;
}
break;
case getGakufu: // ====== 楽譜の設定 ======
tempo = 7500/p1gkf[0]; // 楽譜のテンポ取得
pOnp = 1; // 音符の開始ポイント
PWM2CON = 0b11000000; // PWM ON
TMR2ON = 1; // Timer2 ON
TmpCnt = tempo; // テンポセット
TmpFlg = 0; // フラグりセット
state = getOnpu;
break;
case getOnpu: // ====== 音符の設定 ======
Onp = p1gkf[pOnp++]; // 音符を取得
if(Onp == 0xFFFF){ // 曲の終了なら
state = gkfEnd; // 抜ける
break;
}
tPlay = Onp & 0x000F; // 音符の長さ取得
Onp = Onp >> 8; // 音高を取り出す
if(Onp){ // 音符なら
incWave = dW_tbl[Onp - 35]; // 音高を繰返し時間に変換
ampNext = 0x7F; // 音量最大
}else{ // 休符なら
incWave = 0; // 繰返し なし
ampNext = 0; // 音量OFF
}
state = onpuStart;
break;
case onpuStart: // ====== 音符の演奏開始 ======
if(TmpFlg){ // テンポ待ち
TmpFlg = 0; // テンポフラグリセット
amp = ampNext; // 音量最大
if(amp)LED3=1; // 休符でなければLED点灯
AmpCnt = EnvelopeConst; // 音量減少リセット
state = onpuPlay;
}
break;
case onpuPlay: // ====== 音符の演奏継続 ======
if(TmpFlg){ // テンポ待ち
TmpFlg = 0; // テンポフラグリセット
if(tPlay-- == 2){ // 音符終了なら
SltCnt = tempo - Silent; // 消音時間をセットし
SltFlg = 0; // 消音フラグをリセット
LED3=0; // LED消灯
state = onpuEnd;
}
if(SWdown){ // SWが押されたなら
SWdown=0; // SW 処理済み明示
state = gkfEnd; // 終了処理へ
}
}
break;
case onpuEnd: // ====== 音符の演奏終了 ======
if(SltFlg){ // 時間を待つ
amp = 0; // 音量OFF
state = getOnpu; // 次の音符取得へ
}
break;
case gkfEnd: // ====== 楽譜の演奏終了 ======
PWM2CON = 0; // PWM OFF
TMR2ON = 0; // Timer2 OFF
LED3=0; // LED消灯
state = waitStart;
break;
}
} // while
}
// 割込み処置 -----------------------------------------------------
void interrupt isr(void){
if(TMR2IF){ // Timer2 割込みを確認
TMR2IF = 0; // Timer2 フラグ リセット
accWave += incWave; // 周期計算
if(STATUSbits.CARRY)wave ^= 1; // キャリが出れば、波形反転
if(wave)PWM2DCH = amp; // 波形ONなら、 出力あり
else PWM2DCH = 0; // OFFなら、出力なし
}
}
gakufu.h
// 夕焼け小焼け
const unsigned int p1gkf[] = {
84,
0x4304,0x4304,0x4304,0x4504,0x4304,0x4304,0x4304,0x4004,
0x3C04,0x3C04,0x3E04,0x4004,0x3E0C,0x0004,0x4008,0x4004,
0x4304,0x4504,0x4804,0x4804,0x4504,0x4304,0x4304,0x4504,
0x4304,0x480C,0x0004,0x4806,0x4A02,0x4804,0x4504,0x4804,
0x4804,0x4304,0x4304,0x4504,0x4304,0x4504,0x4304,0x400C,
0x0004,0x4304,0x4004,0x3E04,0x3C04,0x3E04,0x3E04,0x3C04,
0x3E04,0x4004,0x4304,0x4504,0x4304,0x480C,0x0004,
0xFFFF,
0xFFFF
};