/***************************************************************************************** * metronome_v3 * metronome_v2aプロジェクトの、バージョンアップ版(ハード・ソフト共に更新) * * <更新点> * (1) PIC18F14K50→ PIC16F1938 *   液晶:AQM1602XA-RN-GBW→ SG12864A * (2) GLCDに、振り子動画表示 * * MPU:PIC16F1938 * Condition: * 16MHz 内部クロック→ PLL無し * Fcy=16MHz, Tcy=62.5ns * * 初期作成日:2019/9/10 N.Ishii /*****************************************************************************************/ #include #include "glcd_PIC16F_lib_v2.h" /// コンフィギュレーション1の設定 #pragma config FOSC = INTOSC // 内部クロックを使用する(INTIO) #pragma config WDTE = OFF // ウオッチドッグタイマー無し(OFF) #pragma config PWRTE = ON // 電源ONから64ms後にプログラムを開始する(ON) #pragma config MCLRE = ON // 外部リセット信号使用 #pragma config CP = OFF // プログラムメモリーを保護しない(OFF) #pragma config CPD = OFF // データメモリーを保護しない(OFF) #pragma config BOREN = ON // 電源電圧降下常時監視機能ON(ON) #pragma config CLKOUTEN = OFF // CLKOUTピンをRA6ピンで使用する(OFF) #pragma config IESO = OFF // 外部・内部クロックの切替えでの起動はなし(OFF) #pragma config FCMEN = OFF // 外部クロック監視しない(OFF) /// コンフィギュレーション2の設定 #pragma config WRT = OFF // Flashメモリーを保護しない(OFF) #pragma config VCAPEN = OFF // 低電圧レギュレータ用のキャパシタは使用しない(OFF) #pragma config PLLEN = OFF // 動作クロックを32MHz(4xPLL)では動作させない(OFF) #pragma config STVREN = ON // スタックがオーバフローやアンダーフローしたらリセットをする(ON) #pragma config BORV = HI // 電源電圧降下常時監視電圧(2.5V)設定(HI) #pragma config LVP = OFF // 低電圧プログラミング機能使用しない(OFF) #define LED LATCbits.LATC0 /// Envelope Contorol Block Define #define CK1 LATBbits.LATB4 #define CK1_Lo LATBbits.LATB4 = 0 #define UP1_Hi LATBbits.LATB2 = 1 #define UP1_Lo LATBbits.LATB2 = 0 #define DWN1_Hi LATBbits.LATB1 = 1 #define DWN1_Lo LATBbits.LATB1 = 0 /// SW Define #define Start_Stop_SW PORTBbits.RB5 #define Beat_SW PORTBbits.RB3 /// Rotary ecoder #define RE_A PORTCbits.RC5 #define RE_B PORTCbits.RC4 //==================================================================== char msg1[]= "B:x "; char msg2[]= "xxxbpm"; char msg3[]= "metronome_v3"; char msg4[]= "B:"; //==================================================================== unsigned int T2_count = 0; unsigned int beat_count = 0; //unsigned char BPM; unsigned int BPM; unsigned int Beat_Period; unsigned int T2_count_MAX; char Beat_No; char BeatSwCount; unsigned int Pendulum_count = 0; unsigned int Pendulum_Period; char sq = 0; char i; char start_flag = 0; char sw1_m0 = 0; // Start/Stop SW char sw1_m1 = 0; char sw1_m2 = 0; char sw2_m0 = 0; // Beat Sel SW char sw2_m1 = 0; char sw2_m2 = 0; char re_b_m0 = 0; // ROTARY ENCODER B-OUT:Clock char re_b_m1 = 0; char re_b_m2 = 0; //******************* プロトタイプ *********************************** void Start_Stop_SW_read(void); void Beat_No_Sel_SW_read(void); void Rotary_Encoder_read(void); void ltostring(char digit, unsigned long data, char *buffer); // ********** EEROM に設定する 初期値 *********** __eeprom unsigned char epromvalue[8] = {2,60,0,0,0,0,0,0}; // 0番地:BeatSwCount、1番地:テンポ:BPM /******************************************************************************* * タイマー割込みの処理 T2=1mS周期 *******************************************************************************/ void interrupt InterTimer( void ) { if (TMR2IF == 1) { // Timer2カウントアップ? TMR2IF= 0; // タイマー2割込フラグをリセット ++T2_count; ++Pendulum_count; /// 頭の音だったら、発音=500Hz if (beat_count == 0) { if (T2_count <= 80) CK1 = !CK1; } else { /// 頭以外の音だったら、発音=250Hz if ((T2_count <= 80) && ((T2_count - 1) % 2) == 0) CK1 = !CK1; } /// エンベロープ:ADSR制御 switch (T2_count) { case 1: /// Attack区間:1mS x (11-1)= 10mS(最初のトリガ時のみ、case実行、その後はcase=10までbreak) LED = 1; // LED ON UP1_Hi; // 頭以外の音:UP1=Hi/DWN1=Lo if (beat_count == 0) DWN1_Hi; // 頭の音:UP1=Hi/DWN1=Hi→ アクセントになる。 break; case 11:/// Dacay区間:1mS x ((13-1)-(11-1))= 2mS UP1_Lo; if (beat_count == 0) DWN1_Lo; // 頭のタイミングで、DWN1_Loに戻す。 break; case 13:/// Sustain区間:1mS x ((63-1)-(13-1))= 50mS DWN1_Hi; break; case 63:/// Release:このタイミングで減衰 DWN1_Lo; LED = 0; // LED OFF break; default: break; } /// ビート繰返しの為の、変数初期化 if (T2_count == T2_count_MAX) { T2_count = 0; ++beat_count; if (beat_count == Beat_No) beat_count = 0; } /// 振り子動画処理:割込み内では、メインで処理する描画の実行case番号を取得するのみの処理にする。 if (Pendulum_count == 1) sq = 1; // 往路開始点 左端斜め線(拍の頭の音) else if (Pendulum_count == Pendulum_Period + 1) sq = 2;// 前回描画した線を消去し2本目斜め線描画 else if (Pendulum_count == Pendulum_Period*2 + 1) sq = 3; // 3本目中央直線描画 else if (Pendulum_count == Pendulum_Period*3 + 1) sq = 4; // 4本目斜め線描画 else if (Pendulum_count == Pendulum_Period*4 + 1) sq = 5; // 右端斜め線描画 else if (Pendulum_count == Pendulum_Period*5 + 1) sq = 6;// 帰路1本目斜め線描画 else if (Pendulum_count == Pendulum_Period*6 + 1) sq = 7;// 帰路2本目斜め線描画 else if (Pendulum_count == Pendulum_Period*7 + 1) sq = 8;// 帰路3本目斜め線描画 else if (Pendulum_count == Pendulum_Period*8 + 1) sq = 9;// 帰路3本目斜め線消去 } } /********************************************* * メイン関数 **********************************************/ void main(void) { OSCCON= 0b01111010; // 内部クロックは、16MHzとする ANSELA= 0b00000000; // AN0-AN4は使用しない全てデジタルI/Oとする ANSELB= 0b00000000; // AN8-AN13は使用しない全てデジタルI/Oとする LCD_TRIS= 0b00000000; // ピン(RA)は全て出力に割当てる(0:出力 1:入力) TRISB= 0b11101001; // ピン(RB)は、RB1,2,4(ADSR制御)出力、それ以外は入力に割当てる TRISC= 0b00110000; // ピン(RC)は、RC4,5(ロータリエンコーダ)入力、それ以外は出力に割当てる LED= 0; // LED OFF WPUB= 0b00101000; // RB3,5(START/STOP, BEAT SW)は、内部プルアップに指定 OPTION_REGbits.nWPUEN = 0; //プルアップ有効化 /// Initialize GLCD Control Signal Level LCD_E= 0; LCD_CS1= 1; LCD_CS2= 1; LCD_RW= 1; LCD_DI= 1; lcd_Init(); // GLCD初期化 lcd_Str(0, 0, msg3); // 初期メッセージ __delay_ms(1000); lcd_Clear(0); /// EEPROMから設定値読込み BeatSwCount = eeprom_read(0); BPM = eeprom_read(1); /// 初期 Beat_Noセット if (BeatSwCount < 4) Beat_No = BeatSwCount+1; else Beat_No = BeatSwCount-2; /// その他のパラメータセット Beat_Period = 60000/BPM; T2_count_MAX = Beat_Period; Pendulum_Period= Beat_Period/4; // 振り子の分割時間= T2周期/4→ 最大BPM200の時、200/4= 75mSになる。 /// T2設定:ビート音制御 /// (16M/4)/16/(24+1)/10= 1k(1mS周期) T2CON = 0b01001010; // TMR2プリスケーラ値を1:16、ポストスケーラ値は1:10の設定、T2 OFF PR2 = 24; // タイマーのカウント値を設定 TMR2 = 0; // タイマー2の初期化 TMR2IF = 0; // タイマー2割込フラグを0にする TMR2IE = 1; // タイマー2割込みを許可する PEIE = 1 ; // 周辺装置割り込み有効 GIE = 1 ; // 全割込み処理を許可する //// MAIN LOOP while(1) { /// T2割込みにて、ビート音制御+シーケンス番号を割込み中で取得し、メインで振り子動画処理 /// 振り子動画処理 switch (sq) { case 0: /// break; case 1: lcd_Line(53,5, 8,31, 1); // 往路開始点 左端斜め線(拍の頭の音) sq = 0; break; case 2: lcd_Line(53,5, 8,31, 0); // 前回描画した線を消去 lcd_Line(53,5, 27,50, 1); // 2本目斜め線描画 sq = 0; break; case 3: lcd_Line(53,5, 27,50, 0); // 前回描画した線を消去 lcd_Line(53,5, 53,57, 1); // 3本目中央直線描画 sq = 0; break; case 4: lcd_Line(53,5, 53,57, 0); // 前回描画した線を消去 lcd_Line(53,5, 78,50, 1); // 4本目斜め線描画 sq = 0; break; case 5: lcd_Line(53,5, 78,50, 0); // 前回描画した線を消去 lcd_Line(53,5, 98,31, 1); // 右端斜め線描画 sq = 0; break; case 6: lcd_Line(53,5, 98,31, 0); // 前回描画した線を消去 lcd_Line(53,5, 78,50, 1); // 帰路1本目斜め線描画 sq = 0; break; case 7: lcd_Line(53,5, 78,50, 0); // 前回描画した線を消去 lcd_Line(53,5, 53,57, 1); // 帰路2本目斜め線描画 sq = 0; break; case 8: lcd_Line(53,5, 53,57, 0); // 前回描画した線を消去 lcd_Line(53,5, 27,50, 1); // 帰路3本目斜め線描画 sq = 0; break; case 9: Pendulum_count = 0; lcd_Line(53,5, 27,50, 0);// 帰路3本目斜め線消去 sq=1; break; } Start_Stop_SW_read(); Beat_No_Sel_SW_read(); Rotary_Encoder_read(); if (BeatSwCount < 4) Beat_Period = 60000/BPM; else { if (BeatSwCount == 4) Beat_Period = 60000/(BPM*2); if (BeatSwCount == 5) Beat_Period = 60000/(BPM*3); if (BeatSwCount == 6) Beat_Period = 60000/(BPM*4); } T2_count_MAX = Beat_Period; /// BEAT表示 if (BeatSwCount < 4) { ltostring(1, BeatSwCount+1, msg1+2); lcd_Str(0, 12, msg1); } else { lcd_Str(0, 12, msg4); // PRINT "B:" if (BeatSwCount == 4) for(i= 14; i < 16; ++i) lcd_Char1(0, i, 0xA0); if (BeatSwCount == 5) for(i= 14; i < 17; ++i) lcd_Char1(0, i, 0xA0); if (BeatSwCount == 6) for(i= 14; i < 18; ++i) lcd_Char1(0, i, 0x9B); } /// TEMPO表示 ltostring(3, BPM, msg2); lcd_Str(1, 12, msg2); } } //======================================================================================= /***************************************************** * Start/Stop SW 読込み ******************************************************/ void Start_Stop_SW_read(void) { sw1_m0 = Start_Stop_SW; //New SW1 Data sw1_m2 = sw1_m1 ^ (sw1_m0 & sw1_m1); //Neg_Edge Sence SW1 sw1_m1 = sw1_m0; //Chenge New Data to Old Data m1 if (!start_flag) { if (sw1_m2) { // Start start_flag = 1; T2CONbits.TMR2ON = 1; // T2 ON } } else { if (sw1_m2) { // Stop start_flag = 0; T2CONbits.TMR2ON = 0; // T2 OFF //// パラメータ再セット Beat_Period = 60000/BPM; T2_count_MAX = Beat_Period; Pendulum_Period = Beat_Period/4; T2_count = 0; Pendulum_count = 0; beat_count = 0; sq = 0; CK1_Lo; UP1_Lo; DWN1_Lo; LED = 0; // LED OFF } } } /***************************************************** * Beat SW 読込み ******************************************************/ void Beat_No_Sel_SW_read(void) { sw2_m0 = Beat_SW; //New SW2 Data sw2_m2 = sw2_m1^(sw2_m0 & sw2_m1); //Neg_Edge Sence SW2 sw2_m1 = sw2_m0; //Chenge New Data to Old Data m1 if (sw2_m2) { ++BeatSwCount; if (BeatSwCount == 7) BeatSwCount = 0; if (BeatSwCount < 4) Beat_No = BeatSwCount+1; else Beat_No = BeatSwCount-2; eeprom_write(0, BeatSwCount); // EEPROM 0番地 書込み if (start_flag) { T2CONbits.TMR2ON = 0; // T2 OFF Pendulum_count = 0; T2_count = 0; beat_count = 0; sq = 0; CK1_Lo; UP1_Lo; DWN1_Lo; T2CONbits.TMR2ON = 1; // T2 ON } } } /***************************************************** * Rotary_Encoder 読込み ******************************************************/ void Rotary_Encoder_read(void) { //Read ROTARY ENCODER B_Clock re_b_m0 = RE_B; re_b_m2 = re_b_m1^(re_b_m0 & re_b_m1); //Neg_Edge Sence CLOCK re_b_m1 = re_b_m0; //Chenge New Data to Old Data m1 if (re_b_m2 == 1) { //Neg_Edge Sence B_Clock ? if (RE_A == 0) { // A_Data = 0 ? ++BPM; if (BPM >= 201) BPM = 200; eeprom_write(1, BPM); // EEPROM 1番地 書込み } else { // B_Data = 0 --BPM; if (BPM <= 39) BPM = 40; eeprom_write(1, BPM); // EEPROM 1番地 書込み } if (start_flag) { T2CONbits.TMR2ON = 0; // T2 OFF //// パラメータ再セット Beat_Period = 60000/BPM; T2_count_MAX = Beat_Period; Pendulum_Period = Beat_Period/4; Pendulum_count = 0; T2_count = 0; beat_count = 0; sq = 0; CK1_Lo; UP1_Lo; DWN1_Lo; T2CONbits.TMR2ON = 1; // T2 ON } } } /******************************************************** * Numerical Value-> Ascci Convert *********************************************************/ void ltostring(char digit, unsigned long data, char *buffer) { char i; buffer += digit; // To Last of Strings for(i=digit; i>0; i--) { // From LS. Digit To MS. Digit buffer--; *buffer = (data % 10) + '0'; // This Digit Value-> Ascii Convert ('0'=0x30)-> Store Buffer data = data / 10; // Digit-1 } /// ブランキング処理 i = 0; while((i < digit-1)&&(*buffer == '0')) // 上位桁が0の間 { *buffer = ' '; // ブランクに変換 buffer++; i++; } }