PIC16F18325の基本動作から応用プログラムまでを学びます。

ホーム
12F1822
16F1455
16F1459
18F14K50
18F26J50
dsPIC
その他
    
16F18313
16F18325
16F18346
16F1619
Curiosity
---
---

和音オルゴール

2021-04-11

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,
};