PWM を使用してオルゴール調の音色で曲を演奏します。 4音同時に演奏するため迫力のある音を楽しめます。 ただし、圧電スピーカーでは、良い音になりません。外部スピーカアンプに簡単なアッテネーターとローパスフィルターを取り付けると深みのある音が聞こえてきます。このプログラムは、PIC16F1459の同じ機能のプログラムをP16F18325用に変更したものです。
*Note*
このプログラムで使用する楽譜データは、 [オルゴールデーター作成 Windows アプリ] を利用して、作ることができます。
オルガン プログラム B04_Orgel_18325.zip |
<プログラム> main.c
/* File: main.c 20210410 * gakufu は、16 bit 単位 * パート1は、最初にテンポ(1分間に演奏する四分音符の数)が記録さる。 * その他のパートは そのパートの音量(0-127)が記録さる。 * パート1の音量は常に最大(127) * それ以降は音符 MSB 8 bitが音の高さ。 半音単位で、中央のドが 60(0x3C) * LSB 7 bitが音の長さ * 二分音符:10、 四分音符:08、 八分音符:04、 十六分音符:02 * 付点二分音符:18、付点四分音符:0C、付点八分音符:06、付点十六分音符:03 * | 7 6 5 4 3 2 1 0| 7 6 5 4 3 2 1 0 | * | 高さ | | 長さ | * * Language: MPLABX XC8 v2.20 * PIC16F18325 on Curiosity */ // 音が減衰する程度 指定値(ms)で1/16減少 #define EnvelopeConst 50 // 音符と音符の間の無音時間(ms) #define SilentTime 30 #include <xc.h> #include "gakufu.h" #define _XTAL_FREQ 32000000 #define SW RC4 // PushSWは、RC4 #define SPK LATC5 // スピーカは、RC5 #define LED LATA5 // LEDは、 RA5 #define TRIS_SW TRISC4 // 上記ポートのTRIS #define TRIS_SPK TRISC5 // #define TRIS_LED TRISA5 // #define p1 0 // Part1(主旋律)の略称 #define p2 1 // Part2 の略称 #define p3 2 // Part3 の略称 #define p4 3 // Part4 の略称 #define waitStart 0 // ステート制御用の名称 #define getGakufu 1 // 同上 #define getOnpu 2 // 同上 #define onpuStart 3 // 同上 #define onpuPlay 4 // 同上 #define onpuLast 5 // 同上 #define onpuEnd 6 // 同上 #define gkfEnd 7 // 同上 #pragma config FEXTOSC = OFF,RSTOSC = HFINT32 // HFINTOSC (32MHz) #pragma config CLKOUTEN = OFF,CSWEN = OFF,FCMEN = OFF #pragma config MCLRE = ON,PWRTE = OFF,WDTE = OFF,LPBOREN = OFF #pragma config BOREN = OFF,BORV = LOW,PPS1WAY = OFF,STVREN = ON #pragma config DEBUG = OFF #pragma config WRT = OFF,LVP = ON,CP = OFF,CPD = OFF // 音の高さを決める定数 for 32MHz Fosc PWM Freq 15.625kHz const unsigned int dW_tbl[] = { // Do, Do#, Re, Re#, Mi, Fa, Fa#, So, So#, Ra, Ra#, Si, 0,0x112,0x122,0x133,0x146,0x159,0x16E,0x183,0x19B,0x1B3,0x1CD,0x1E8,0x205, //12 0x224,0x245,0x267,0x28C,0x2B3,0x2DC,0x307,0x336,0x366,0x39A,0x3D1,0x40B, 0x449,0x48A,0x4CF,0x518,0x566,0x5B8,0x60F,0x66C,0x6CD,0x735,0x7A3,0x817, 0x892,0x915,0x99F,0xA31,0xACD,0xB71,0xC1F,0xCD8,0xD9B,0xE6A,0xF46,0x102E, 0x1125,0x122A,0x133E,0x1463,0x159A,0x16E3,0x183F,0x19B0,0x1B37,0x1CD5,0x1E8C,0x205D, 0x224A,0x2454,0x267D,0x28C7,0x2B34,0x2DC6,0x307E,0x3361,0x366F,0x39AB,0x3D19,0x40BB }; // 0x892(index 37) が中心のド(60 0x3E) ,0xD9B はラ (440Hz) unsigned int accWave[] = {0,0,0,0}; // 周波数決定用の積算値 unsigned int incWave[] = {0,0,0,0}; // 周波数決定用の加算値 unsigned char amp[] = {0,0,0,0}; // 現在の音量 unsigned char wave[] = {0,0,0,0}; // 現在の波高 (1 or 0) char SWdown=0; // SW 押下フラグ char TmpCnt; // テンポ時間カウント char SltCnt; // 消音時間カウント unsigned char tempo; // 16分音符の時間 ms char TmpFlg[] = {0,0,0,0}; // テンポ フラグ char SltFlg[] = {0,0,0,0}; // 消音 フラグ char EnvCnt[] = {1,1,1,1}; // 振幅調整時間カウント // プロトタイプ void SWcheck(void); void EnvelopeCnt(char); void TempoCnt(void); void SilentCnt(void); void Control(char); unsigned int ReadOnp(char,unsigned int); void main(void) { char pt; ANSELC = 0; // Port C 全てデジタルIO TRIS_SPK = 0; // SPK 音声出力 SPK = 0; // SPK 出力「0] TRIS_LED = 0; // LED RC2 出力 LED = 0; // LED 消灯 TRIS_SW = 1; // SW 入力 WPUC4 = 1; // SW 入弱プルアップ 有効 // Timer1 の設定 ------------------------------------ T1CON = 0b00000001; // Fosc/4 ps 1/1 TMR1ON TMR1 = 0xE100; // +7936 (0.992ms) で Carry TMR1IF = 0; // Timer1フラグクリア // Timer2 の設定 ------------------------------------ T2CON = 0b00000100; // PreS 1/1 PostS 1/1 PR2 = 0x7F; // 繰返し周波数 15.625KHz X 4 TMR2IF = 0; // Timer2フラグクリア TMR2IE = 1; // Timer2割込み有効 // PWM5 設定--------------------------------- PWM5CON = 0; // 正論理出力 PWM5DCH = 0; // ヂューティは、後ほど設定 PWM5DCL = 0; // ヂューティの下位2bitは使用しない RC5PPS = 2; // RC5にPWM5を出力 PEIE = 1; // ペリフェラル割込み有効 GIE = 1; // 割込み有効 while(1){ if(TMR1IF){ // 1ms毎に TimeUp TMR1H = 0xE1; // 次の 1ms を設定 TMR1IF = 0; // TimeUp flag クリア SWcheck(); // SW ONの確認 EnvelopeCnt(p1); // 振幅調整 50ms EnvelopeCnt(p2); // 振幅調整 50ms EnvelopeCnt(p3); // 振幅調整 50ms EnvelopeCnt(p4); // 振幅調整 50ms TempoCnt(); // テンポ調整 SilentCnt(); // 無音時間 } Control(p1); // Part1 出力制御 Control(p2); // Part2 出力制御 Control(p3); // Part3 出力制御 Control(p4); // Part4 出力制御 } // while } // 割込み処置 -------------------------------------------- void __interrupt() isr(void){ // xc8 ver 2.xx 用 static char ch; if(TMR2IF){ // Timer2 割込みを確認 TMR2IF = 0; // Timer2 フラグ リセット ch++; // 出力するPart番号を進める ch &= 3; // 番号は0〜3まで accWave[ch] += incWave[ch]; // part繰返し波形更新 if(STATUSbits.CARRY)wave[ch] ^= 1; // キャリが出れば反転 if(wave[ch])PWM5DCH = amp[ch]; // 波形 Hi ならamp else PWM5DCH = 0; // 波形 Low ならゼロを } // 次サイクルで出力される } //------- SWチャタリング防止 1msループ内 ---------------- void SWcheck(void){ static char SWnotRdy = 10; // SWupの確認待ち初期化 if(SWnotRdy){ // SWupの確認中なら if(SW)SWnotRdy--; // SWupの確認をー1 else SWnotRdy = 10; // SWupの確認初期値 }else if(!SW){ // SWupの確認済みでDownなら SWdown = 1; // SWが押されたマーク SWnotRdy = 10; // SWupの確認待ちに } } //------- 振幅調整 50ms ----------------------------------- void EnvelopeCnt(char pt){ if(EnvCnt[pt]-- == 1){ EnvCnt[pt] = EnvelopeConst; // 50ms毎に amp[pt] = (int)amp[pt] * 15/16; // 音量を -6% する } } //------- テンポ調整 ----------------------------------- void TempoCnt(void){ if(TmpCnt-- == 1){ TmpCnt = tempo; // テンポ毎に TmpFlg[p1] = 1; // テンポフラグをセット TmpFlg[p2] = 1; TmpFlg[p3] = 1; TmpFlg[p4] = 1; } } //------- 無音時間 ----------------------------------- void SilentCnt(void){ if(SltCnt-- == 1){ // 消音時間になれば SltFlg[p1] = 1; // 消音フラグをセット SltFlg[p2] = 1; SltFlg[p3] = 1; SltFlg[p4] = 1; } } //------- 主制御 ----------------------------------- void Control(char pt){ static char state[] = {0,0,0,0}; static unsigned int pOnp[] = {0,0,0,0}; // 音符位置 static unsigned char iAmp[4]; // パートの音量 static unsigned char tPlay[4]; // 演奏長さ static unsigned int incNext[4]; // 次音符周期 static unsigned char ampNext[4]; // 次音符音量 unsigned int Onp; // 音符 char i; //--------------------------------------------------------- switch(state[pt]){ case waitStart: // ====== 演奏開始待ち ====== if(SWdown){ // SW 押されるのを待つ if(pt == p1){ // Part1だけ処理 SWdown=0; // SW 処理済み明示 state[p1] = getGakufu; // 演奏開始 } } break; case getGakufu: // ====== 楽譜の設定 ====== if(p1gkf[pOnp[p1]]==0xFFFF){ // 楽譜終端なら pOnp[p1] = 0; // 全パートの楽譜を pOnp[p2] = 0; // 先頭にリセット pOnp[p3] = 0; pOnp[p4] = 0; } tempo = 7500/p1gkf[pOnp[p1]]; // 楽譜のテンポ取得 TmpCnt = tempo; // テンポセット PWM5EN = 1; // PWM5 ON TMR2ON = 1; // Timer2 ON for(i=0;i<=3;i++){ // 全パートの iAmp[i] = ReadOnp(i, pOnp[i]++); // 音量取得 TmpFlg[i] = 0; // テンポフラグりセット state[i] = getOnpu; // 音符設定へ } iAmp[p1] = 0x7F; // Part1 音量は常に最大 break; case getOnpu: // ====== 音符の設定 ====== Onp = ReadOnp(pt, pOnp[pt]++); // 該当楽譜から音符取得 if(Onp == 0xFFFF){ // Partの終りなら state[pt] = gkfEnd; // 曲終了処理へ break; } tPlay[pt] = Onp & 0x7F; // 下位8ビットの演奏時間に変換 Onp = Onp >> 8; // 上位8ビットの音の高さを取り出す if((Onp>23)&(Onp<96)){ // 音符なら incNext[pt] = dW_tbl[Onp - 23]; // 音高を繰返し時間に変換 ampNext[pt] = iAmp[pt]; // 最大音量 }else{ // 休符なら incNext[pt] = 0; // 繰返し なし ampNext[pt] = 0; // 音量OFF } state[pt] = onpuStart; // 音符演奏開始へ break; case onpuStart: // ====== 音符の演奏開始 ====== if(TmpFlg[pt]){ // テンポを待って TmpFlg[pt] = 0; // 音符の演奏情報を更新 incWave[pt] = incNext[pt]; // 繰返し単位セット accWave[pt] = 0; // 波形繰返しリセット wave[pt] = 0; // 波形 OFF EnvCnt[pt] = EnvelopeConst; // 音量減衰タイマリセット amp[pt] = ampNext[pt]; // 音量セット if(pt==p1){ // 主パートの if(amp[0])LED=1; // 音符ならLED点灯 else LED=0; // 休符ならLED消灯 } state[pt] = onpuPlay; // 音符演奏継続へ } break; case onpuPlay: // ====== 音符の演奏継続 ====== if(TmpFlg[pt]){ // テンポを待って TmpFlg[pt] = 0; // テンポフラグリセット if(tPlay[pt]-- == 2) // 音符の演奏終了なら state[pt] = onpuLast; // 音符の無音設定へ if(SWdown){ // SWが押されれば state[p1] = gkfEnd; // 曲の終了処理へ SWdown=0; // SW 処理済み明示 } } break; case onpuLast: // ====== 音符の無音時間設定 ====== if(pt==p1)LED = 0; // LED OFF SltCnt = tempo - SilentTime; // 無音時間をセット SltFlg[pt] = 0; // 無音フラグをリセット state[pt] = onpuEnd; // 音符の時間終了へ break; case onpuEnd: // ====== 音符の演奏終了 ====== if(SltFlg[pt]){ // 演奏終時間を待つ amp[pt] = 0; // 音量OFF state[pt] = getOnpu; // 次の音符取得へ } break; case gkfEnd: // ====== 楽譜の演奏終了 ====== if(pt == p1){ // Part 1 終了なら LED = 0; // LED消灯 PWM5EN = 0; // PWM OFF TMR2ON = 0; // Timer2 OFF for(i=0;i<=3;i++){ // 全Partともに pOnp[i]--; // 曲終了まで進め while(ReadOnp(i, pOnp[i]++) != 0xFFFF); state[i] = waitStart; // 演奏開始待ちに } }else{ // Part 1 以外なら amp[pt] = 0; // 該当音量OFF incWave[pt] = 0; // 繰返し単位 0 state[pt] = waitStart; // 次曲演奏待ちへ } break; } } // 指定 part の楽譜から指定 index の音符取得 unsigned int ReadOnp(char part, unsigned int index){ switch(part){ case p1:return p1gkf[index]; case p2:return p2gkf[index]; case p3:return p3gkf[index]; case p4:return p4gkf[index]; } return 0; }
<プログラム> 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, }; const unsigned int p2gkf[] = { 100, // 夕焼け小焼け 0x4008,0x4004,0x4104,0x4008,0x4304,0x4004,0x3C08,0x3B04, 0x3E04,0x3B0C,0x0004,0x3C08,0x3C04,0x4304,0x3C08,0x3C08, 0x4008,0x4108,0x400C,0x0004,0x4504,0x3C04,0x4504,0x3C04, 0x4304,0x3C04,0x4004,0x3C04,0x4104,0x4004,0x4104,0x4004, 0x400C,0x0004,0x4304,0x4004,0x3E04,0x3C04,0x3E08,0x3C04, 0x3E04,0x4004,0x4304,0x4504,0x4304,0x400C,0x0004, 0xFFFF, 0xFFFF, }; const unsigned int p3gkf[] = { 100, // 夕焼け小焼け 0x0004,0x3704,0x3704,0x3704,0x3704,0x3704,0x3704,0x3704, 0x3404,0x3404,0x3204,0x3004,0x0004,0x3504,0x3404,0x3204, 0x0004,0x3704,0x3704,0x3704,0x0004,0x3504,0x3504,0x3504, 0x0004,0x3704,0x3704,0x3704,0x2D04,0x3704,0x2D04,0x0004, 0x2908,0x3504,0x0004,0x2808,0x3408,0x3008,0x3708,0x3008, 0x3708,0x3404,0x3704,0x3504,0x3404,0x3204,0x3504,0x3404, 0x3204,0x3004,0x3404,0x3504,0x3704,0x300C,0x0004, 0xFFFF, 0xFFFF, }; const unsigned int p4gkf[] = { 80, // 夕焼け小焼け 0x3010,0x3010,0x3404,0x3404,0x3204,0x3004,0x2B10,0x3010, 0x3010,0x2B10,0x0010,0x4806,0x4A02,0x4804,0x4504,0x4804, 0x4804,0x4304,0x4304,0x0010,0x0010,0x4304,0x4004,0x3E04, 0x3C04,0x3E04,0x3E04,0x3C04,0x3E04,0x0010,0x480C,0x0004, 0xFFFF, 0xFFFF, };