前回の電子オルゴールプログラムで演奏できるのは一曲だけでした。今回は、プログラムをほんの少しだけ変更して、メモリ容量が許せば何曲でも演奏することができるようにします。

サンプルで掲載する楽譜データ "gakufu.h" には、三曲分のデータが書き込まれていますが、メモリの 60%ほどしか使用していません。まだまだ曲数を増やすことができそうです。

1 使用する回路

前回記事と同じ回路です。

 

 

 

2 プログラム概要

前回作成したプログラムの最終行近くの RESET( ) 命令を削除し、次の曲の頭出しをする命令ステップ (10行) に置き換えます。
曲の停止時にSWを押すと次の曲の演奏が始まります。

3 楽譜データの保存

gakufu.h をに2曲分のデータを追加しました。

曲の終わりは 0xFFFF であらわし、データの最後は、0xFFFF を2つ重ねて示します。

 

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

4 プログラム

main.c 内容を下のリストの様にします。

 

/*************************************
  * File: 電子オルゴール楽曲演奏
  * SWで終了・再スタート
  * NCO で 音階の方形波を作る
  * ccp1 Dutyを徐々に下げて音を小さくする
  * PIC16F18313
  * Created on 2021-12-31
  **************************************/

// 音が減衰する程度  指定値(ms)で1/16減少
#define EnvConst 50

#include <xc.h>
#include "gakufu.h"
#define _XTAL_FREQ 32000000      // delay_ms(x) のための定義

#pragma config FEXTOSC = OFF,RSTOSC = HFINT32
#pragma config CLKOUTEN = OFF,CSWEN = OFF,FCMEN = OFF
#pragma config MCLRE = ON,PWRTE = OFF,WDTE = OFF
#pragma config LPBOREN = OFF,BOREN = OFF,BORV = LOW
#pragma config PPS1WAY = OFF,STVREN = ON,DEBUG = OFF
#pragma config WRT = OFF,LVP = ON,CP = OFF,CPD = OFF

#define LED         LATA4   // LED出力
#define pushSW      RA5     // SW入力
#define newPlay     0       // 制御のステート名
#define getOnp      1
#define startOnp    2
#define waitEnd     3
#define sysSleep    4

// 音の高さを決める定数  for NOC clock 250KHz
const uint16_t dW_tbl[] = {
//  Do,          Re,         Mi,   Fa,         So,         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
};

uint8_t  EnvCnt   = EnvConst;   // 音量減衰時間
uint8_t  amp      = 0;          // 波形振幅
uint16_t remnTime = 0;          // 音符演奏残り時間
uint8_t  ptrOnp   = 0;          // 音符データポインタ
uint8_t  state    = newPlay;    // 制御ステート
uint16_t tempo;                 // テンポ時間 ms
uint16_t onpWord;               // 音符データ
uint8_t  onpLEN;                // 音符 長さ
uint8_t  onpMIDI;               // 音符MIDI番号
uint16_t nextRemnTime;          // 演奏時間残り ms
uint16_t nextIncWave;           // 波形周期計算 増加値
uint8_t  nextAmp;               // 次の波形振幅


 void main(void) {
     LATA   = 0;            // LED Off
     TRISA  = 0b101011;     // A2,A4出力、A5入力に設定
     ANSELA = 0;            // 全てのpinをデジタルに設定
     WPUA   = 0b111111;     // 入力に弱プルアップ

    // Timer0 設定 ***************************************
    T0CON1 = 0b01000101;    // Fosc/4 Sync PreS 1/32
    T0CON0 = 0b10000000;    // T0 ON 8bit  Post 1/1
    TMR0H  = 249;           // 32MHz/4/32/250=1KHz (1ms)
    // PWM 設定 ******************************************
    T2CON  = 0b00000100;    // Timer 2 PS1/1設定
    PR2    = 0x1F;          // Timer2 周期 250KHz設定
    CCP1CON= 0b10001111;    // Duty 下位 PWMモード

    // CLC1 設定 ***************************************
    // T2_matchをNCO Clockに接続する
    CLC1POL  = 0x02;        // LC1G2POL 反転
    CLC1SEL0 = 0x1A;        // LC1D1S T2_match;
    CLC1SEL1 = 0x00;        // LC1D2S 接続なし
    CLC1SEL2 = 0x00;        // LC1D3S 接続なし
    CLC1SEL3 = 0x00;        // LC1D4S 接続なし
    CLC1GLS0 = 0x02;        // LC1G1D1T enabled
    CLC1GLS1 = 0x00;        // LC1G2 全入力なし
    CLC1GLS2 = 0x00;        // LC1G3 全入力なし
    CLC1GLS3 = 0x00;        // LC1G4 全入力なし
    CLC1CON  = 0x80;        // LC1EN 割り込みなし AND-OR;
    // NCO 設定 ************************************
    NCO1CLK = 0x02;         // LC1OUTがクロック
    NCO1CON = 0x80;         // NCO ON
    // DSM 設定 ************************************
    MDCON  = 0x80;          // 情報信号で搬送波『On/Off』
    MDSRC  = 0x0B;          // 情報信号 正論理非同期 NOC1
    MDCARH = 0x04;          // 搬送波H 正論理非同期 CCP1
    MDCARL = 0;             // 搬送波L VSS
    RA2PPS = 31;            // RC3にDSM出力
    
    while(!pushSW);         // SW upを確認する
    __delay_ms(10);         // チャタリング防止

     while(1){
        // 1ms毎に TimeUp (タイミング) ##########
        if(TMR0IF){                 // 1ms毎に TimeUp
            TMR0IF = 0;             // TimeUp flag クリア
            // 音量減衰 -------------------------------------
            if(EnvCnt-- == 0){          // 30ms毎に
                EnvCnt = EnvConst;      //
                amp = (int)amp * 15/16; // 音量を -6% する
                CCPR1H = 0;
                CCPR1L = amp;
                if(amp > 100)LED = 1;   // 音量大で LED On
                    else LED = 0;       // 音量小で LED Off
            }
            // 音符演奏 残 ----------------------------
            if(remnTime) remnTime--;    // 残り時間を1ms減少
        } // end if(TMR0IF)
        
        // Main controls (ステート制御) #####################
        switch(state){
            case newPlay:           // 新しく演奏準備 -----
                tempo  = 7500/p1gkf[ptrOnp++];  //Tempをmsに変換
                state = getOnp;
                break;
            case getOnp:            // 音符情報を取得 -----
                onpWord = p1gkf[ptrOnp++];      // 音符情報を取得
                onpLEN  = onpWord & 0xFF;       // 長さ取得
                nextRemnTime = onpLEN * tempo;  // 演奏時間ms計算
                onpMIDI = onpWord >> 8;         // MIDI番号取得
                if(onpMIDI < 24) onpMIDI = 0;   // 演奏不可音符は
                if(onpMIDI > 95) onpMIDI = 0;   //  休符に変換
                if(onpMIDI){                    // 音符なら
                    // 波形加算値を取得
                    nextIncWave = dW_tbl[onpMIDI-23];  
                    nextAmp = 0x7F;             // 音量最大
                }else{                          // 休符なら
                    nextAmp = 0;                //    音量ゼロ
                }
                if(onpLEN <= 32)                // 音符休符なら
                    state = startOnp;           //   演奏する
                 else                           // 曲終わりなら
                    state = sysSleep;           //  先頭に戻る
                break;
            case startOnp:      // 新たな音符休符の情報をセット-----
                NCO1CON = 0;                    // NCO OFF
                NCO1INCU = 0;                   // 次の音を
                NCO1INCH = nextIncWave >> 8;    // NCOにセット
                NCO1INCL = nextIncWave & 0xFF;  //
                NCO1CON  = 0x80;                // NCO ON
                amp      = nextAmp;         // 音量を
                CCPR1L   = amp;             // PWMに設定
                EnvCnt   = EnvConst;        // 音量減衰を初期化
                remnTime = nextRemnTime;    // 演奏時間を設定
                state = waitEnd;
                break;
            case waitEnd:       // 前の音符演奏終了をを待つ-----
                if(pushSW == 0){        // SW 押下なら                   
                    state = sysSleep;   //  スリープへ
                }
                if(remnTime == 0){      // 音符演奏終了なら  
                    state = getOnp;     //  次の音符取得へ
                } 
                break;
            case sysSleep:     // SWが押下まで、スリープ -----
                CCP1CON = 0;            // CCP1 OFF
                LATA = 0;               // 全出力を 0 にする
                __delay_ms(10);         // チャタリング防止
                while(!pushSW);         // SW upを確認する
                __delay_ms(10);         // チャタリング防止
                IOCAN5 = 1;             // SW ONを待つ
                IOCAF = 0;              // SW フラグリセット
                IOCIE = 1;              // SW SLEEP再起動設定
                GIE = 0;                // 割込み禁止
                NOP();       // ----------------------------
                SLEEP();     //        この間 スリープ
                NOP();       // ----------------------------
                __delay_ms(10);         // チャタリング防止
                while(!pushSW);         // SW upを確認する
                __delay_ms(10);         // チャタリング防止
                // 曲終了まで音符位置を進める
                // 楽譜終了なら音符位置先頭に
                ptrOnp--;                          
                while(p1gkf[ptrOnp++] != 0xFFFF);
                if(p1gkf[ptrOnp] == 0xFFFF) ptrOnp = 0; 
                CCP1CON = 0b10001111;    // CCP1 ON
                state = newPlay;        // 次の曲へ
                break;
        }   // end swtich =====
    }
}
/** End of File **/

 

5 動作の確認

書き込み終了後に、夕焼け小焼けがスピーカーから聞こえるでしょう。

曲の途中でSWを押すとその時点で演奏が終了します。終了後に再びSWを押すと次の楽曲が演奏されます。

 

6 次回の予定

次回は、