/****************************************************************** * ECG_Scope_V4 プログラム * * 2.4インチ・カラーQVGA液晶モジュール(aitendo): UL024TF互換品 * * Condition: * 8MHz External X'tal Oscillator, 10x PLL (8MHzx10= 80MHz) * Fcy=80MHz/2=40MHz, Tcy=25ns * * MPU: dsPIC33FJ64GP802 * * N.Ishii 2017.8.5 *********************************************************************/ #include "p33FJ64GP802.h" #include "colorlcd_libdsPICVH.h" /// コンフィギュレーション設定 80MH _FOSCSEL(FNOSC_PRIPLL & IESO_OFF); // Prim OSC _FOSC(FCKSM_CSDCMD & IOL1WAY_OFF & OSCIOFNC_OFF & POSCMD_XT); _FWDT(FWDTEN_OFF); _FPOR(ALTI2C_OFF & FPWRT_PWR32); _FICD(JTAGEN_OFF & ICS_PGD1); #define LED LATBbits.LATB4 #define HOLD_SW PORTBbits.RB3 #define READY_SW PORTBbits.RB2 #define TRIG_MODE_SW PORTAbits.RA1 #define TRIG_LEVEL_SW PORTBbits.RB1 #define SAMPLE_TIME_SW PORTBbits.RB0 #define nFIFO 32 /// メッセージ・テーブル char MsgData1[] = "xxxbpm"; char MsgData2[] = "n= xxxx"; char MsgData3[] = "x= xxx"; char MsgData4[] = "p= xxx"; /// 移動平均フィルタ関係 char cnt, ptr; int ave, Din, sum, FIFO[nFIFO]; unsigned int WAVbuffer[512]; int POT; int Index; char EndFlag; char HOLD_ON_flag = 0; char hold_status = 0; char READY_flag = 0; char ready_status = 0; char sample_cnt; /// Function Prottypes void Oscillo(void); void AxisDraw(void); void DispMeas(void); void Single_trigger_mode(void); void Oscillo_single_trigger(void); void itostring(char digit, unsigned int data, char *buffer); // 170729 /************************************************ * スイッチ割り込み処理 ************************************************/ void __attribute__((interrupt, no_auto_psv)) _CNInterrupt(void) { IFS1bits.CNIF = 0; // 割り込みフラグクリア delay_ms(20); // スイッチ安定待ち→ アクティブ時 if (!hold_status) { // hold_status -> 0: RUN if (!HOLD_SW) { HOLD_ON_flag = 1; } } else { // hold_status -> 1: HOLD_ON Loop if (!HOLD_SW) { HOLD_ON_flag = 0; } } if (ready_status) { if (!READY_SW) READY_flag = 1; } delay_ms(10); // スイッチ安定待ち→ リリース時 } /******************************************************************** * T3同期のADC割込み関数 * 0.625mS周期:移動平均フィルタ処理+10mS周期:波形BUFへの格納処理 *********************************************************************/ void __attribute__((interrupt, auto_psv)) _ADC1Interrupt(void) { /// --- A/D is 12bits, filtering is 12bits while(!AD1CON1bits.DONE); // AD変換終了待ち Din= ADC1BUF0 >> 2; // 10bitの値に変換して格納 /// 移動平均フィルタ処理:0.625mS周期 sum= sum - FIFO[ptr] + Din; // 変化成分だけを使って移動平均を計算する。 ave= sum/nFIFO; FIFO[ptr]= Din; // FIFOに、データを格納する。 ptr++; // FIFOポインタ更新 if (ptr == nFIFO) ptr=0; // FIFOの深さ(32ワード)を超えたら、ポインタをクリア cnt++; if (!SAMPLE_TIME_SW) sample_cnt= 8; // 250mS/D時:0.625*8=5mS周期:波形BUFへの格納処理 else sample_cnt= 16; // 500mS/D時:0.625*16=10mS周期:波形BUFへの格納処理 if (cnt == sample_cnt) { cnt= 0; WAVbuffer[Index++] = ave; if (Index >= 512) { // Buffer Full ? T3CONbits.TON= 0; // T3 OFF EndFlag = 1; } } IFS0bits.AD1IF = 0; // clear A/D interuppt flag } /************************************************ * Function Main ************************************************/ void main(void) { char i; /// Initialize Clock CLKDIVbits.PLLPRE= 0; // PLLPRE=1/2 to 4MHz PLLFBDbits.PLLDIV= 40; // PLLDIV=1/40 to 160MHz CLKDIVbits.PLLPOST= 0; // PLLPOST=1/2 to 80MHz then 40MIPS /// Initialize Clock OSCCON= 0x00300; // PRIPLL指定 CLKDIV= 0x0100; // クロックの分周1:2に設定 PLLFBD= 0x0026; // 40倍 8MHz÷2×40÷2 = 80MHz /// Initialize Port AD1PCFGL= 0xFFFE; // AN0 are Analog(他は、デジタルピンとして使用) TRISA= 0x000F; // RAO=AN0 IN, RA1=SW IN, RA2,3= X'TAL IN, RA4=RST OUT TRISB= 0x000F; // RB4=LED OUT, RB5-15=QVGA OUT(RSTはRA4), RB0-3=SW IN LED= 0; // LED OFF /// 状態変化割込み設定 CNPU1= 0x00F8; // CN3-7(RA1,RB0-3) 内部プルアップに指定 CNEN1= 0x00C0; // CN6-7(RB2,3) 状態変化割り込み有効 // IEC1bits.CNIE = 1; // 状態変化割り込み許可→ ここでは未だ禁止 /// Initialize GLCD lcd_Init(); lcd_Clear(BLACK); // クリア lcd_Str(0, 0, "ECG Scope V4", CYAN, BLACK); delay_ms(1000); /// 移動平均フィルタ初期化 cnt= ptr= sum= 0; for(i= 0; i < nFIFO; i++) FIFO[i]= 0; /// ADC設定 AD1CON1bits.ADON= 0; // ADC OFF AD1CHS0= 0x0000; // AN0ピンを、CH0のサンプルホールドに接続 AD1CON1= 0x8444; // AD ON, 12bitADC, 整数, T3同期, 自動サンプルON AD1CON2= 0x0000; // 常にCH0を変換しアドレス0から書込み AD1CON3= 0x0210; // Tad= 16Tcy, Tsamp= 2Tad AD1CSSL= 0x0000; // 入力スキャンしない /// T3設定 /// T3= Tcy x PS<1:0> x (PR3+1)= 0.025u x 1 x 25000= 0.625mS周期(50Hzの1周期を、32サンプル)これが、ADサンプル周波数になる。 T3CON= 0x0000; // T3_OFF, T3_GATE_OFF, T3_PS_1_1, T3_SOURCE_INT PR3= 24999; // タイマーのカウント値を設定 lcd_Clear(BLACK); // 最初だけ、全画面クリアを行う /// enable interrupt IEC0bits.AD1IE= 1; // Enable ADC INT // T3CONbits.TON= 1; // T3ON-> ADC Start→ ここでは未だ開始しない /// Main Loop while(1){ /// Draw a Graph of Input Wave Data Index= 0; EndFlag= 0; // Clear End of Convertion Flag if (TRIG_MODE_SW) { // Trigger is AUTO Mode if (TRIG_LEVEL_SW) POT= 820; // 2.4V相当(但し、Vref= 3V計算) else POT= 512; // 1.5V相当 LED= 0; // Ready LED OFF READY_flag= 0; ready_status= 0; IEC1bits.CNIE= 0; // AD変換中は、SW割込み禁止 T3CONbits.TON= 1; // T3ON-> ADC Start while(!EndFlag); // EOC待ち // バッファリング終了で抜ける(この時、T3OFFにしてる) IEC1bits.CNIE= 1; // 変換終了後に、SW割込み許可→ こうしないと、バッファフルの途中でSW割込みが入り // 正しい波形をホールド出来ない場合がある。 Oscillo(); // Disp. Oscillo EndFlag = 0; Index = 0; } else Single_trigger_mode(); } } /****************************************** * Drawing Coordinate Axis *******************************************/ void AxisDraw(void){ int i; /// Singleモードの時のみ、以下を実行:シングル時に、前の波形が残る多重ホールド波形表示をさせない対策 if (!TRIG_MODE_SW) lcd_Clear(BLACK); lcd_Line(0, 0, 0, 231, BROWN); // Y axis:左端ライン for(i=50; i<319; i+=50) lcd_Line(i, 13, i, 231, BROWN); // Y axis:残6本分のライン for(i=0; i<239; i+=58) lcd_Line(0, i, 319, i, BROWN); // X axis } /******************************************* * 計測条件表示関数 ********************************************/ void DispMeas(void){ if (!SAMPLE_TIME_SW) lcd_Str(19,16,"250mS/D", CYAN, BLACK); else lcd_Str(19,16,"500mS/D", CYAN, BLACK); } /******************************************************* * Disp. Oscillo (Disp. Save Buffer Data) * Detect Trigger-> Disp. 320Data from this Position * Not Detect Trigger-> Disp. 320Data from Top Position * +心拍数表示 ********************************************************/ void Oscillo(void){ int i,x, Point; unsigned int bpm, n; char non_det_flag; AxisDraw(); // 座標表示(この中では、全画面クリアはしない) DispMeas(); // 計測条件読込み表示処理 Point = -1; // 最初のデータ /// トリガチェック(今回は、立上りトリガのみ) for(x=0; x<190; x++){ if((WAVbuffer[x] < POT) && (WAVbuffer[x+5] >= POT)){ Point = x; continue; } } /// トリガ成立、その位置から波形表示 if(Point != -1){ for(i=1; i<=320; i++) { lcd_Line(i, WAVbuffer[i+Point-1]*5/22, i, WAVbuffer[i+Point]*5/22, MAGENTA); } n= 1; // 最初のトリガ検知の、サンプル位置初期化(1回目) /// 最初のトリガ検知以降、次のトリガ点検知を実施(2回目のトリガ検知で抜け出す) x= Point+1; // 次の比較元ポインタにするためインクリメント non_det_flag= 0; // 未検知フラグ・リセット while(!((WAVbuffer[x] < POT) && (WAVbuffer[x+5] >= POT))){ x++; if(x == 380) { // x= 189maxで最初のトリガが検知された場合は、ここから2回目のトリガ検知回数を190とするため380とする。 non_det_flag= 1; // 最大トライ回数実施しても未検知の場合、フラグ・セット break; } else n++; } /// 次のトリガ点を検知したら、心拍数を表示(整数計算) if (!SAMPLE_TIME_SW) bpm= 60000/(5 * n); // 60秒x1000/(0.005秒x1000) x n else bpm= 6000/n; // 60秒x100/(0.01秒x100) x n // 2回目の検知成立かつ、2回目検知が、1サンプル分の時間で起きなかった場合のみ心拍数表示 // 通常は、1サンプル分の時間で検知されることはないが、ノイズの影響で起きることがあったので // (この時6000 bpm表示が正解だが、3桁表示としているので、0bpmとなる)これを避けるために条件に追加した if((!non_det_flag) && (x != 190)) { itostring(3, bpm, MsgData1); lcd_Char(1, 16, 0x8F, RED, BLACK); // ハートキャラクタ表示 lcd_Str(2, 16, MsgData1, WHITE, BLACK); // 心拍数表示 } else lcd_Str(1, 16, " ", BLACK, BLACK); // トリガ不成立の場合は、心拍数表示領域をクリア /// 指定色で、サンプルデータを0.05秒間表示後、波形のみをクリア(黒で描画) delay_ms(50); // 波形表示中に、Hold SWが押されたら、画面をホールド→ もう一度、Hold SWを押すと解除 while (HOLD_ON_flag) { hold_status = 1; } hold_status = 0; // 波形のみをクリア(黒で描画) for(i=1; i<=320; i++) { lcd_Line(i, WAVbuffer[i+Point-1]*5/22, i, WAVbuffer[i+Point]*5/22, BLACK); } // 波形のみをクリアすると、波形線と座標線が交差した座標線の一部が途切れるので、再度座標描画する。 AxisDraw(); } /// トリガ不成立、最初から表示する else { for(i=1; i<=320; i++){ lcd_Line(i, WAVbuffer[i-1]*5/22, i, WAVbuffer[i]*5/22, MAGENTA); } lcd_Str(1, 16, " ", BLACK, BLACK); // トリガ不成立の場合は、心拍数表示領域をクリア /// 指定色で、サンプルデータを0.05秒間表示後、波形のみをクリア(黒で描画) delay_ms(50); while (HOLD_ON_flag) { hold_status = 1; } hold_status = 0; for(i=1; i<=320; i++){ lcd_Line(i, WAVbuffer[i-1]*5/22, i, WAVbuffer[i]*5/22, BLACK); } AxisDraw(); } } /****************************************************** * Disp. Oscillo and Cheack trigger for Single mode * +心拍数表示 *******************************************************/ void Oscillo_single_trigger(void){ int i,x, Point; unsigned char bpm, n; char non_det_flag; Point = -1; // 最初のデータ /// トリガチェック(今回は、立上りトリガのみ) for(x=0; x<190; x++){ if((WAVbuffer[x] < POT) && (WAVbuffer[x+5] >= POT)){ Point = x; continue; } } /// トリガ成立、その位置から波形表示 if(Point != -1){ LED = 0; // Ready LED OFF AxisDraw(); for(i=1; i<=320; i++) { lcd_Line(i, WAVbuffer[i+Point-1]*5/22, i, WAVbuffer[i+Point]*5/22, MAGENTA); } READY_flag = 0; n= 1; // 最初のトリガ検知の、サンプル位置初期化(1回目) /// 最初のトリガ検知以降、次のトリガ点検知を実施(2回目のトリガ検知で抜け出す) x= Point+1; // 次の比較元ポインタにするためインクリメント non_det_flag= 0; // 未検知フラグ・リセット while(!((WAVbuffer[x] < POT) && (WAVbuffer[x+5] >= POT))){ x++; if(x == 380) { // x= 189maxで最初のトリガが検知された場合は、ここから2回目のトリガ検知回数を190とするため380とする。 non_det_flag= 1; // 最大トライ回数実施しても未検知の場合、フラグ・セット break; } else n++; } /// 次のトリガ点を検知したら、心拍数を表示(整数計算) if (!SAMPLE_TIME_SW) bpm= 60000/(5 * n); // 60秒x1000/(0.005秒x1000) x n else bpm= 6000/n; // 60秒x100/(0.01秒x100) x n // 2回目の検知成立かつ、2回目検知が、1サンプル分の時間で起きなかった場合のみ心拍数表示 // 通常は、1サンプル分の時間で検知されることはないが、ノイズの影響で起きることがあったので // (この時6000 bpm表示が正解だが、3桁表示としているので、0bpmとなる)これを避けるために条件に追加した if((!non_det_flag) && (x != 190)) { itostring(3, bpm, MsgData1); lcd_Char(1, 16, 0x8F, RED, BLACK); // ハートキャラクタ表示 lcd_Str(2, 16, MsgData1, WHITE, BLACK); // 心拍数表示 } else lcd_Str(1, 16, " ", BLACK, BLACK); // トリガ不成立の場合は、心拍数表示領域をクリア } /// トリガ不成立、Retry sample } /********************************************* * Single_trigger_mode **********************************************/ void Single_trigger_mode(void) { IEC1bits.CNIE= 1; // SW割込み許可 // T3CONbits.TON= 1; // T3ON-> ADC Start DispMeas(); // 計測条件読込み表示処理 /// READY SW ON 待ちループ while ((!READY_flag) && (!TRIG_MODE_SW)) { ready_status = 1; } delay_ms(50); // SINGLEから、AUTOトリガに切替えた瞬間のチャッタ防止のための待ち(チャッタで誤判断し // 1度、信号入力状態(READY LED点灯→ADC開始)になるのを防ぐための待ち) // BNC入力に信号が有る状態で、誤動作すると、AUTOに戻った時に、ホールドされた波形が変に残ることがあった。 if (!TRIG_MODE_SW) { ready_status = 0; LED = 1; // Ready LED ON T3CONbits.TON= 1; // T3ON-> ADC Start while(!EndFlag); Oscillo_single_trigger(); } else lcd_Clear(BLACK); } /***************************************** * 数値から文字列に変換 *****************************************/ void itostring(char digit, unsigned int data, char *buffer) { char i; buffer += digit; //文字列の最後 for(i=digit; i>0; i--) { //最下位桁から上位へ buffer--; //ポインター1 *buffer = (data % 10) + '0'; //その桁数値を文字にして格納 data = data / 10; //桁-1 } /// ブランキング処理 i = 0; while((i < digit-1)&&(*buffer == '0')) // 上位桁が0の間 { *buffer = ' '; // ブランクに変換 buffer++; i++; } }