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

ホーム
16F18313
16F18325
16F18346
16F1619
Curiosity
---
---
    
12F1822
16F1455
16F1459
18F14K50
18F26J50
dsPIC
その他
4和音オルゴール

既出のPWM オルゴールを改造して4和音のオルゴールにします。 複雑な波形が出力できます。ただし、圧電スピーカーでは、良い音になりません。写真の様な外部スピーカアンプに簡単なアッテネーターとローパスフィルターを取り付けると深みのある音が聞こえてきます。取り扱える音域も6オクターブに拡大してあります。


PWM周期( 93.75kHz)毎に、異なったパートの演奏を行い、結果として4パートの楽譜演奏を行います。各パートの楽譜は、別々の配列に記録します。

Part 1 (主旋律):
p1gkf[] 配列の最初のデータには、テンポ(1分間に演奏する四分音符の数)を記します。
2番目以降は音符です。
最後は 0xFFFF で示します。
Part 2-4 :
p2gkf[], p3gkf[], p4gkf[], 配列の最初のデータには、そのパートの音量を記録します。
音量は、0 - 127 の大きさで127が最大です。なお、Part 1 の音量は常に最大に設定されます。2番目以降のデータはPart 1と同様です。

掲載した楽譜ファイル gakufu.h には、3曲のデータが入っています。それぞれの曲の終わりは、 0xFFFF で示し、3曲目のデータには、 0xFFFF を2つ続けてファイルの終了とにします。

また、4和音になると楽譜をPICが理解するデーター(gakufu.h) に、コーディングするのが大変になったので、Visual C# 2010 で、PC画面上で楽譜を作成し、PIC用データに変換するPCのWindowsアプリケーションを作成しました。プログラムを次ページに紹介します。

プログラム

プロジェクトは、曲のデータを持つ「gakufu.h」 と、曲を演奏するための「main.c」 から構成されています。

プロジェクト B06_orgel_waon.zip

*注意*
 MPLAB X プロジェクト には、PICkit3で書き込む仕様とブートローダで書き込む2通りの仕が設定されています。どちらを利用するかをメインメニューバーのIDE Configuration pull down で選んでください。

MPLAB IPEアプリを使用してPICkit3などで、直接PICにプログラムを書き込む場合には、以下から HEX ファイルをダウンロードして使用してください。

PICkit3_B06_Orgel_waon.zip

main.c

/*  File:   main.c 1
 * 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   Target: PIC116F1459
 *     20210410 rev1 : xc8 ver 2.20 に対応した
 */

// 音が減衰する程度  指定値(ms)で1/16減少
#define EnvelopeConst 50
// 音符と音符の間の無音時間(ms)
#define SilentTime    30

#include "xc.h"
#include "gakufu.h"
#define _XTAL_FREQ 48000000
#define SW2        RA5
#define p1         0
#define p2         1
#define p3         2
#define p4         3
#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 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

const unsigned int dW_tbl[] = {     // for 48MHz  PWM Freq 93.75kHzx4
//  Do,   Do#,  Re,   Re#,  Mi,   Fa,   Fa#,  So,   So#,  Ra,   Ra#,  Si,
    0,
    0x0B7,0x0C2,0x0CD,0x0D9,0x0E6,0x0F4,0x103,0x112,0x122,0x134,0x146,0x159, //12
    0x16E,0x184,0x19B,0x1B3,0x1CD,0x1E8,0x205,0x224,0x245,0x267,0x28C,0x2B2, //24
    0x2DC,0x307,0x335,0x366,0x39A,0x3D0,0x40B,0x448,0x489,0x4CE,0x517,0x565, //36
    0x5B7,0x60E,0x66A,0x6CC,0x733,0x7A1,0x815,0x890,0x913,0x99D,0xA2F,0xACA, //48
    0xB6E, 0xC1C, 0xCD5, 0xD98, 0xE67, 0xF42, 0x102A,0x1120,0x1225,0x1339,0x145E,0x1594, //60
    0x16DC,0x1838,0x19A9,0x1B30,0x1CCE,0x1E84,0x2055,0x2241,0x244A,0x2673,0x28BC,0x2B28, //72
};  // 0x5B7(index 37) が中心のド(60 0x3E) ,0x99D はラ (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};
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) {
    OSCCON = 0b11111100;     // SPLLEN = ON, 16MHz(x3=48MHz)
    TRISC = 0b10110000;      // PWM2,LEDs 出力設定
    LATC = 0;                // LEDs 消灯
    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){
        if(TMR1IF){                        // 1ms で Carry
            TMR1H = 0xD1;                  // 次の 1ms を設定
            TMR1IF = 0;
            SWcheck();                     // SW ONの確認
            EnvelopeCnt(p1);               // 振幅調整 50ms
            EnvelopeCnt(p2);               // 振幅調整 50ms
            TempoCnt();                    // テンポ調整
            SilentCnt();                   // 無音時間
        }
        Control(p1);                       // Part1 出力制御
        Control(p2);                       // Part2 出力制御
        Control(p3);                       // Part1 出力制御
        Control(p4);                       // Part2 出力制御
    }   //  while
}
// 割込み処置 --------------------------------------------
void __interrupt() isr(void){        // xc8 ver 2.x 用
    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])PWM2DCH = amp[ch];      // 波形 Hi ならamp
        else PWM2DCH = 0;                   // 波形 Low ならゼロを
    }                                       // 次サイクルで出力
}
//------- SWチャタリング防止 1msループ内 ----------------
void SWcheck(void){
    static char SWnotRdy = 10;              // SWupの確認待ち初期化
    if(SWnotRdy){                           // SWupの確認中なら
        if(SW2)SWnotRdy--;                  //   SWupの確認をー1
        else SWnotRdy = 10;                 //   SWupの確認初期値
    }else if(!SW2){                         // 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 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){         // 楽譜終端なら
                for(i=0;i<=3;i++){               //   全パートの楽譜を
                    pOnp[i] = 0;                 //   先頭にリセット
                }
            }
            tempo = 7500/p1gkf[pOnp[p1]];        // 楽譜のテンポ取得
            TmpCnt = tempo;                      // テンポセット
            PWM2CON = 0b11000000;                // PWM 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;              // 演奏時間に変換
            Onp = Onp >> 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(amp[pt])LATC |= 1 << pt;      // 音符なら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:             // ====== 音符の無音時間設定 ======
            LATC &= ‾(1 << pt);                  // 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 終了なら
                LATC &= 0xF0;                    //     LED消灯
                PWM2CON = 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,
    92, // フランスの古い歌
    0x3E04,0x4304,0x4504,0x4604,0x4804,0x4A0C,0x4A04,0x4804,
    0x4A04,0x4B04,0x4804,0x4A0C,0x4A04,0x4804,0x4A04,0x4B04,
    0x4804,0x4A04,0x4B02,0x4A02,0x4804,0x4604,0x450E,0x4302,
    0x430C,0x3E04,0x4304,0x4504,0x4604,0x4804,0x4A0C,0x4A04,
    0x4804,0x4A04,0x4B04,0x4804,0x4A0C,0x4A04,0x4804,0x4A04,
    0x4B04,0x4804,0x4A04,0x4B02,0x4A02,0x4804,0x4604,0x450E,
    0x4302,0x4310,0x4308,0x4304,0x4504,0x460C,0x4604,0x4808,
    0x4808,0x450C,0x4504,0x4A0C,0x4A04,0x4B04,0x4D02,0x4B02,
    0x4A04,0x4804,0x4608,0x4504,0x4304,0x450C,0x3E04,0x4304,
    0x4504,0x4604,0x4804,0x4A0C,0x4A04,0x4804,0x4A04,0x4B04,
    0x4804,0x4A0C,0x4A04,0x4804,0x4A04,0x4B04,0x4804,0x4A04,
    0x4B02,0x4A02,0x4804,0x4604,0x450E,0x4302,0x4310,
    0xFFFF,
    92, // かたつむり
    0x4506,0x4502,0x4504,0x4204,0x3E06,0x3E02,0x3E04,0x4004,
    0x4206,0x4202,0x4004,0x3E04,0x4008,0x0008,0x4206,0x4302,
    0x4504,0x4704,0x4506,0x4502,0x4504,0x4204,0x4006,0x4002,
    0x3E04,0x4004,0x4208,0x0008,0x4504,0x4A04,0x4A04,0x4504,
    0x4204,0x4504,0x4504,0x4204,0x3E04,0x4204,0x4206,0x4002,
    0x3E08,0x0008,
    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,
    100, // フランスの古い歌
    0x0004,0x0010,0x0010,0x0010,0x0010,0x0010,0x0010,0x0010,
    0x0010,0x0010,0x0010,0x0010,0x0010,0x0010,0x0010,0x0010,
    0x0010,0x0010,0x0010,0x0010,0x0010,0x0010,0x3F10,0x0010,
    0x410C,0x0004,0x0010,0x0010,0x0010,0x0010,0x0010,0x0010,
    0x3F08,0x3E08,0x3A10,
    0xFFFF,
    100, // かたつむり
    0x4206,0x4202,0x4204,0x3E04,0x3E06,0x3E02,0x3E04,0x3D04,
    0x3E06,0x3E02,0x3D04,0x3E04,0x3D08,0x0008,0x3E06,0x4002,
    0x4204,0x4304,0x4206,0x4202,0x4204,0x3E04,0x3D06,0x3D02,
    0x3E04,0x3D04,0x3E08,0x0008,0x4204,0x4204,0x4204,0x4204,
    0x3E04,0x3E04,0x3E04,0x3E04,0x3E04,0x3E04,0x3E06,0x3D02,
    0x3E08,0x0008,
    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,
    100, // フランスの古い歌
    0x0004,0x3A04,0x3C04,0x3E04,0x3C04,0x3A10,0x3F08,0x3C08,
    0x3A08,0x3708,0x3F08,0x3C08,0x3A08,0x3708,0x3C10,0x3A08,
    0x2B08,0x3A04,0x3C04,0x3E04,0x3C04,0x3A10,0x3F08,0x3C08,
    0x3A08,0x3708,0x3F08,0x3C08,0x3A08,0x3708,0x3C10,0x3A10,
    0x2804,0x3704,0x3A04,0x3F04,0x3004,0x3704,0x3C04,0x3F04,
    0x2804,0x3704,0x3A04,0x3F04,0x3204,0x3A04,0x3C04,0x4204,
    0x3A04,0x3E04,0x4304,0x0004,0x3F10,0x4308,0x0008,0x3E08,
    0x3208,0x3A04,0x3C04,0x3E04,0x3C04,0x3A10,0x3F08,0x3C08,
    0x3A08,0x3708,0x3F08,0x3C08,0x3A08,0x3708,0x3708,0x3604,
    0x3210,
    0xFFFF,
    80, // かたつむり
    0x3204,0x3904,0x3204,0x3904,0x3604,0x3904,0x3604,0x3904,
    0x3204,0x3904,0x3704,0x3604,0x3904,0x2D04,0x3904,0x0004,
    0x3204,0x2604,0x3204,0x2604,0x3204,0x2604,0x3204,0x2604,
    0x2D04,0x3904,0x2D04,0x3904,0x3204,0x3904,0x3E04,0x0004,
    0x3204,0x3604,0x3904,0x3E04,0x3204,0x3604,0x3904,0x3E04,
    0x3604,0x3204,0x3904,0x2D04,0x3204,0x3604,0x3204,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,
    80, // フランスの古い歌
    0x0004,0x3710,0x3708,0x3708,0x3710,0x3708,0x3708,0x3710,
    0x3708,0x3708,0x3608,0x3208,0x3708,0x2B08,0x3710,0x3708,
    0x3708,0x3710,0x3708,0x3708,0x3710,0x3708,0x3708,0x3508,
    0x3208,0x3710,0x0010,0x0010,0x0010,0x0010,0x3704,0x000C,
    0x3C10,0x3E08,0x0008,0x0010,0x3710,0x3708,0x3708,0x3710,
    0x3708,0x3708,0x3710,0x3708,0x3708,0x3008,0x3208,0x2B10,
    0xFFFF,
    0,0xFFFF, // かたつむり
    0xFFFF,
};