第19回 割込み処理(4) 〜タイマープログラムへの実装〜

今までの内容を踏まえて、タイマープログラムに割込み処理を実装します。

動作仕様

それでは今までの内容を元に、タイマープログラムに割込み処理を追加しましょう。

タイマー動作としては以下の仕様とします。

 

プログラム構成

プログラムを作成する前に、プログラム構成といいますか、アルゴリズムをどうするか検討しましょう。

もう一度割込み処理の動作を復習すると、割込み関数はmain関数とは別に用意しておき、該当の割込みが発生したらmain関数の処理を中断して割込み処理を行う、というものでした。

今回、タイマープログラムに実装する処理内容は、割込みが発生した際にタイマーをリセットする、というものです。プログラム動作としては、割込みが発生したら → タイマーのカウントを元に戻す、という処理でうまくいきそうな気がします。でも実際にこのような実装をするにはちょっと工夫が必要です。

タイマー、つまり残り何秒かはmain関数でカウントしています。割込みが発生した場合、割込み処理関数でこのタイマーカウントをリセットすればいいのですが、main関数と割込み処理関数は直接変数(値)のやり取りはできない、という大きな問題があります。

Pic app 19 interrupt function

割込み処理関数からmain関数内の変数を直接操作することができない場合、どうすればいいでしょうか。

このような場合は、両方の関数、つまりmain関数と割込み処理関数の両方から参照・操作できるような変数を用意します。このような変数をグローバル変数と呼んでいます。C言語の入門書に出てきますので、忘れてしまった場合は復習してみてください。

タイマーカウントのリセットは以下のようにmain関数と割込み処理関数の外側でグローバル変数を宣言して使用することにします。

Pic app 19 global variable

 

プログラム動作の検討

次に、実際のプログラム動作を検討してみましょう。

グローバル変数として、現在のタイマー残り時間の変数を用意して、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

// 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 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更新履歴

日付 内容
2017.10.4 新規投稿
2018.12.2 プログラムテンプレートをMPLABX IDE v5.10のものに変更
2019.2.8 割り込み処理関数宣言をXC8コンパイラVersion2.00以降の書式に変更