前回の電子オルゴールプログラムで演奏できるのは一曲だけでした。今回は、プログラムをほんの少しだけ変更して、メモリ容量が許せば何曲でも演奏することができるようにします。
サンプルで掲載する楽譜データ "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 次回の予定
次回は、