今までの内容を踏まえて、タイマープログラムに割込み処理を実装します。
動作仕様
それでは今までの内容を元に、タイマープログラムに割込み処理を追加しましょう。
タイマー動作としては以下の仕様とします。
- スタートボタン(RA3ピン)が押されてタイマー動作開始後、タイマー動作中にスタートボタン(RA3ピン)が押されたら、タイマーカウントをリセットする(最初からタイマー計測をやり直す)
- タイマーカウントをリセットする際、タイマー時間をLEDの点滅数で表現する
プログラム構成
プログラムを作成する前に、プログラム構成といいますか、アルゴリズムをどうするか検討しましょう。
もう一度割込み処理の動作を復習すると、割込み関数はmain関数とは別に用意しておき、該当の割込みが発生したらmain関数の処理を中断して割込み処理を行う、というものでした。
今回、タイマープログラムに実装する処理内容は、割込みが発生した際にタイマーをリセットする、というものです。プログラム動作としては、割込みが発生したら → タイマーのカウントを元に戻す、という処理でうまくいきそうな気がします。でも実際にこのような実装をするにはちょっと工夫が必要です。
タイマー、つまり残り何秒かはmain関数でカウントしています。割込みが発生した場合、割込み処理関数でこのタイマーカウントをリセットすればいいのですが、main関数と割込み処理関数は直接変数(値)のやり取りはできない、という大きな問題があります。
割込み処理関数からmain関数内の変数を直接操作することができない場合、どうすればいいでしょうか。
このような場合は、両方の関数、つまりmain関数と割込み処理関数の両方から参照・操作できるような変数を用意します。このような変数をグローバル変数と呼んでいます。C言語の入門書に出てきますので、忘れてしまった場合は復習してみてください。
タイマーカウントのリセットは以下のようにmain関数と割込み処理関数の外側でグローバル変数を宣言して使用することにします。
プログラム動作の検討
次に、実際のプログラム動作を検討してみましょう。
グローバル変数として、現在のタイマー残り時間の変数を用意して、main関数でそのタイマー残り時間を1秒ずつ減らして、残り時間がゼロになったらブザーを鳴らせばよさそうです。
割込み処理側は、割込みが発生して呼ばれたら、そのグローバル変数のタイマー残り時間を元の値に戻せばよさそうです。ところで、どの値に戻せばいいでしょうか。元に戻す値は、EEPROMに書いてあるので、その値を読めばいいですが、EEPROMから読み出すのはちょっと時間がかかりますので、タイマー設定時間もグローバル変数にしておきたいと思います。
このような考え方で割込み処理によるタイマーリセットプログラムを実装してみます。
プログラムの実装
これまでの考え方でプログラムを実装してみます。
グローバル変数として、「timerValue」をタイマー設定値として、「timerCount」を現在のタイマー残り時間として使用します。
main関数はtimerCountをtimerValueから1秒に1ずつ減らしながらカウントします。割込み処理関数は呼ばれたらtimerValueをtimerCountに戻します。
なお、このような処理に変更しますので、main関数のタイマーカウントはfor文からdo-while文に変更しています。
なお、XC8コンパイラVersion2.00以降では、割り込み関数の書き方が変わっています。以下のプログラムはXC8コンパイラVersion2.00以降のものです。Version2.00より前のバージョンをお使いの場合は以下の変更をお願いいたします。
void __interrupt() isr(void)
を
void interrupt isr(void)
に変更。
以下、完成した割込み処理プログラムです。
/* * File: main.c * 変更履歴 * 2016.11.20: スイッチ制御部分を追加 * 2016.12.05: 時間計測とブザー制御部分を追加 * 2017.01.15: 設定時間が来たらLEDをPWM制御に変更 * 2017.07.29: 時間設定ボタン処理を追加 * 2017.07.30: EEPROMにタイマー時間の保存、読み出し機能を追加 * 2017.10.03: 割り込み処理追加(タイマー開始後にスイッチを押すと最初からカウントする) */ #include <xc.h> // PIC12F1822 Configuration Bit Settings // CONFIG1 #pragma config FOSC = INTOSC // Oscillator Selection (INTOSC oscillator: I/O function on CLKIN pin) #pragma config WDTE = OFF // Watchdog Timer Enable (WDT disabled) #pragma config PWRTE = OFF // Power-up Timer Enable (PWRT disabled) #pragma config MCLRE = OFF // MCLR Pin Function Select (MCLR/VPP pin function is digital input) #pragma config CP = OFF // Flash Program Memory Code Protection (Program memory code protection is disabled) #pragma config CPD = OFF // Data Memory Code Protection (Data memory code protection is disabled) #pragma config BOREN = ON // Brown-out Reset Enable (Brown-out Reset enabled) #pragma config CLKOUTEN = OFF // Clock Out Enable (CLKOUT function is disabled. I/O or oscillator function on the CLKOUT pin) #pragma config IESO = OFF // Internal/External Switchover (Internal/External Switchover mode is disabled) #pragma config FCMEN = OFF // Fail-Safe Clock Monitor Enable (Fail-Safe Clock Monitor is disabled) // CONFIG2 #pragma config WRT = OFF // Flash Memory Self-Write Protection (Write protection off) #pragma config PLLEN = OFF // PLL Enable (4x PLL disabled) #pragma config STVREN = OFF // Stack Overflow/Underflow Reset Enable (Stack Overflow or Underflow will not cause a Reset) #pragma config BORV = LO // Brown-out Reset Voltage Selection (Brown-out Reset Voltage (Vbor), low trip point selected.) #pragma config LVP = OFF // Low-Voltage Programming Enable (High-voltage on MCLR/VPP must be used for programming) // クロック周波数指定 // __delay_ms()関数が使用する #define _XTAL_FREQ 1000000 // 割り込み関数のプロトタイプ宣言 void __interrupt() isr(void); // EEPROM初期データ定義 // アドレス0はタイマー設定値の初期値、残りはダミーデータ __EEPROM_DATA (0x05, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF); // グローバル変数 unsigned char timerValue; // タイマー設定値(main関数の最初でEEPROMから設定値を読み出し、この変数に入れておく) unsigned char timerCount; // 現在のタイマー残り時間(main関数ではこの値を減らしながらタイマーカウント。割込み処理関数では呼ばれたらtimerValueに戻す) // // メイン関数 // void main(void) { // PICマイコン設定 OSCCON = 0b01011010; // 内部クロック周波数を1MHzに設定 ANSELA = 0b00000100; // RA2ピンをアナログ、それ以外のピンはデジタルに設定 TRISA = 0b00001100; // RA2とRA3を入力、それ以外は出力に設定 // ADコンバータ設定 ADCON0 = 0b00001001; // RA2(AN2)をADコンバータピンに設定し、ADコンバータ機能をEbableにする ADCON1 = 0b10000000; // 結果数値は右寄せ、ADコンバータクロックはFOSC/2、基準電圧はVDD // 変数宣言 unsigned short timer; // 時間計測 unsigned short duty; // PWMのデューティーサイクル unsigned short i; // for文で使う変数 // LEDを点灯する LATA5 = 1; // ブザーをOFFにする LATA4 = 0; // タイマー時間変数 // EEPROMに保存してある値を使用する // timerValueは1バイトにしています timerValue = eeprom_read(0); // 設定時間を表現するために現在の設定時間を // LEDの点滅回数で表現する // // ちょっと待ち時間を置いて、 __delay_ms(800); // いったんLEDを消して、 RA5 = 0; __delay_ms(500); // 設定時間分LEDを点滅して、 for(i=0; i<timerValue; i++) { RA5 = 0; __delay_ms(200); RA5 = 1; __delay_ms(200); } // ちょっとの間LEDを消して、 RA5 = 0; __delay_ms(800); // LEDを点灯する RA5 = 1; // スイッチが押されたらタイマースタートする // タイマースタート前にタイマー時間設定ボタンが押されたらその処理を行う while(RA3){ // ADコンバータ読み取り開始 GO = 1; // 読み取り完了待ち while(GO); // 読み取りが終わると結果の数値はADRESレジスタに入っている // ADRESの数値に応じてタイマー時間の増減を行う // // タイマー時間を減らすスイッチの処理 // (ADコンバート値が250未満の場合) if( ADRES < 250 ) { // タイマー時間を減らす timerValue--; // タイマー時間をLEDの点滅回数で表現する // // いったんLEDを消して、 RA5 = 0; __delay_ms(500); // 設定時間分LEDを点滅して、 for(i=0; i<timerValue; i++) { RA5 = 0; __delay_ms(200); RA5 = 1; __delay_ms(200); } // ちょっとの間LEDを消して、 RA5 = 0; __delay_ms(800); // LEDを点灯する RA5 = 1; } else { // タイマー時間を増やすスイッチの処理 // (ADコンバート値が250以上750未満) if( 250 <= ADRES && ADRES < 750 ) { // タイマー時間を増やす timerValue++; // タイマー時間をLEDの点滅回数で表現する // 点滅制御方法は減らす場合と同じ RA5 = 0; __delay_ms(800); for(i=0; i<timerValue; i++) { RA5 = 0; __delay_ms(200); RA5 = 1; __delay_ms(200); } RA5 = 0; __delay_ms(800); RA5 = 1; } } } // タイマー設定値をEEPROMに書き込む eeprom_write(0, timerValue); // RA3割り込み設定 IOCIE = 1; IOCAN3 = 1; GIE = 1; // 点滅開始 timerCount = timerValue; do { timerCount--; LATA5 = 0; __delay_ms(950); LATA5 = 1; __delay_ms(50); } while( timerCount ); // 割り込み禁止 GIE = 0; IOCIE = 0; IOCAN3 = 0; // ブザーをONにする LATA4 = 1; // LEDをPWM制御してスムーズな点滅制御をする // PWM機能のピン割り当て設定 APFCONbits.CCP1SEL = 1; // PWM機能をRA5ピンに設定 CCP1CONbits.CCP1M = 0b1100; // PWM機能を有効、active-highに設定 CCP1CONbits.P1M = 0b00; // RA2ピンはGPIOに設定 // 周期(1ms)とデューティーサイクル(0.5ms)の設定 T2CONbits.T2CKPS = 0b00; // プリスケーラを1:1に設定 PR2 = 249; // 周期を1msに設定 (249 + 1) x 4 x 1us = 1000us = 1ms CCPR1L = 500/4; // デューティーサイクルを0.5msに設定 CCP1CONbits.DC1B = 500; // PWM制御スタート T2CONbits.TMR2ON = 1; // LEDをPWM制御 // デューテー比は10%〜100%の制御にする while(1) { // 5% -> 100%の制御 for(duty=50; duty<=1000; duty++) { CCPR1L = duty / 4; // 上位8ビット CCP1CONbits.DC1B = duty; // 下位2ビット __delay_ms(1); } // 100% -> 5%の制御 for(duty=1000; duty>=50; duty--) { CCPR1L = duty / 4; // 上位8ビット CCP1CONbits.DC1B = duty; // 下位2ビット __delay_ms(1); } } // 以下の命令は実行されない return; } // 割り込み関数 void __interrupt() isr(void) { // 設定時間を表現するために現在の設定時間を // LEDの点滅回数で表現する __delay_ms(800); RA5 = 0; __delay_ms(500); for( unsigned char i=0; i<timerValue; i++) { RA5 = 0; __delay_ms(200); RA5 = 1; __delay_ms(200); } RA5 = 0; __delay_ms(800); RA5 = 1; // timerを最初から開始する timerCount = timerValue; // 割り込みフラグをクリアする IOCAF3 = 0; }
更新履歴
日付 | 内容 |
---|---|
2017.10.4 | 新規投稿 |
2018.12.2 | プログラムテンプレートをMPLABX IDE v5.10のものに変更 |
2019.2.8 | 割り込み処理関数宣言をXC8コンパイラVersion2.00以降の書式に変更 |