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

ホーム
12F1822
16F1455
16F1459
18F14K50
18F26J50
dsPIC
その他
オルゴール

PWM を使用してオルゴール調の音色で曲を演奏します。  このページでは、基本的な動作を確認するために1つの音しか発生させていませんが、この発音方法を応用し、4音同時に演奏するプログラムも次のページに掲載しました。 また、8ピンPIC12F1822 でも同じ機能のオルゴールプログラムを作成してあります。このプログラムでは、PICの特徴を生かし、演奏をしていないときはスリープ状態にし消費電力を抑えたため乾電池2本で、電池寿命を忘れるほど長く持ちます。小さな箱に組み込んで使用するのに最適です。

PICのペリフェラルは以下の用途に使用しています。

Timer1
基準となる 1ms のタイミングを作り出します。
Timer2
PWMで使用し、PWM周期 93.75kHzを発生させます。
PWM
PWM2 を使用し、RC6に出力されます。

SW1 、USBコネクタは、ブートローダーで書き込むときのためです。曲の変更をしなければ不要です、また、LEDは動作確認用なのでこれも省略することができます。

PWM出力端子、RC6 に スピーカーまたは、圧電スピーカーを接続します。 圧電スピーカーは直接接続しますが、通常のダイナミックスピーカーを接続するには、RA6 とスピーカーの間に100μF程度の電解コンデンサを介します

楽譜「gakufu.h」の構成

楽譜の情報は、"gakufu.h" に、ある、unsigned int 配列 「p1gkf」に記述されています。

音符の高さ(周波数)の発生法

各音符の音の高さは、PWM 周期 (93.75kHz) 毎に、周波数定数 (incWave) を積算していき、その値 (accWave) が、0xFFFF を越えたときに波形を反転させ発生させます。PIC16F1503 などに搭載されている NUMERICALLY CONTROLLED OSCILLATOR (NCO) MODULE と同じ方法でです。周波数定数 (incWave) は、音符の音の高さ (Freq) 、PWM 周期 (93.75kHz)から次の式で計算します。

たとえば ラ(440Hz)は、615.16 (0x267)
この値を、プログラムに dW_tbl[ ] 配列 として一覧にしています。

音の波形の発生法

オルゴールの音を表現するため、50ms 毎に音の強さを 1/16 ずつ減衰させています。また、音のキレを良くするために、次の音符が始まる前に、無音時間 30ms を設けています。

曲テンポの処理

曲のテンポとして、1分間に演奏される4分音符の数が楽譜の最初に書かれています。この値はプログラムで使用するテンポ単位 (ms) に変換しますが、4分音符は 8テンポ単位、8分音符は 4テンポ単位の演奏時間になります。テンポ単位時間毎に TmpFlg を立ててメインループで処理します。

たとえば 4分音符=120は、テンポ単位は 62.5ms

プログラム

プロジェクトは、曲のデータを持つ「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
};