第16回 割込み処理(1) 〜割込み処理とは〜

今回から割込み処理を実装していきます。

割込み処理とは

マイコンを使った電子工作の情報をネットで検索していると、「割込み処理」という言葉がよく出てきます。マイコンを使っていく上で、この割込み処理はとても重要な機能ですので、しっかり理解してぜひ自分のものにしてください。

ところで「割込み処理」とは何かを調べてみると「通常のプログラムを動作中、周辺機器などからの要求により強制的に別のプログラムを実行する処理」などと説明されています。

んーっ、、、

って感じになって、結局よくわからないですよね。「別プログラム」って何? って感じです。

そこで、割込み処理が何なのか、その必要性から説明したいと思います。

 

タイマープログラムを改良しよう!

割込み処理とは何か、ということはいったん置いておいて、タイマープログラムの改良を検討してみましょう。

現在のタイマープログラムは、電源を入れ、スタートボタンを押すとLEDの点滅をさせながら時間をカウントして、設定時間になるとブザーを鳴らします。

ところで、タイマーのスタートボタンを押した後、タイマーを再度最初からスタートさせたい、という場合はどうすればよいでしょうか。

例えば、カップラーメンを作ろうとして、タイマーをスタートさせてからすぐにお湯を入れようと思ったら、、、お湯を沸かしてなかったからタイマーをリセットしたい! ってこと、、、ありませんかね。なんかちょっと例が無理矢理なので、タイマーを最初からカウントしたい場合について、各自何かいい例を考えてみてください。

このような要求を満たすために、タイマーのスタートボタンを押してタイマーカウントを開始した後、タイマーカウント中にスタートボタンを押すと最初からタイマーカウントを開始する、という機能を実装したいとします。

実際に今のブログラムにこの機能を実装する場合を考えてみましょう。タイマーのカウント開始後にスイッチの状態を確認すればいいわけですから、LEDを点滅させながら時間計測しているところでスタートボタンの状態を確認すればいいですよね。

スタートボタンの状態を調べるには、RA3レジスタの値を調べればOKです。こんなの簡単だよね、って思いますが、実装しようとすると意外に難しいんです。

現在のプログラムのタイマー時間計測部分は以下のようになっています。

    // タイマー計測開始
    //
    // LED点滅処理
    for(timer=0; timer<timerValue; timer++){
        // LEDを950ms消灯する
        LATA5 = 0;
        __delay_ms(950);
        // LEDを50ms点灯する
        LATA5 = 1;
        __delay_ms(50);            
    }

この部分のどこかでRA3レジスタの値を調べて、スイッチがON(RA3が0)であればスイッチが押された、と判断できます。例えば以下のようにしたらどうでしょうか。

    // タイマー計測開始
    //
    // LED点滅処理
    for(timer=0; timer<timerValue; timer++){
        // LEDを950ms消灯する
        LATA5 = 0;
        __delay_ms(950);
        // LEDを50ms点灯する
        LATA5 = 1;
        __delay_ms(50);
        // スタートスイッチの状態を確認する
        if (RA3 == 0 ) {
            // スタートスイッチが押されたのでタイマーカウントを0にする
            timer = 0;
        }
    }

プログラムを見るとうまく動作しそうですが、この実装は現実的ではありません。というのは、RA3レジスタの値は1秒間に1回しか確認していません。人間がスイッチを押すと、スイッチが押されている時間はせいぜい0.5秒程度でしょうから、上のプログラムでは確実にスタートスイッチが押されたことを確認するのは困難です。

Pic app 16 switch timing

このような感じで、実際のスイッチをONにしたタイミングが、RA3レジスタの確認(スイッチ状態の確認)のタイミングに合わなかった場合、スイッチが押されたという判定はできません。

そこで解決策としては、RA3レジスタの確認をもっと頻繁に行うことが考えられます。1秒間に数十回確認すればスイッチの状態を確認できると思いますので、例えば50ms間隔でRA3の状態を確認するようにプログラムを変更すればOKそうです。

Pic app 16 switch timing revised

このようにスイッチ状態をチェックするには、現在のプログラムを変更する必要があります。

具体的には、現在LEDを950ms消灯するために「__delay_ms(950)」で0.95秒の時間待ちをしています。この間にもスイッチの状態をチェックするために、この部分を例えば以下のように変更する必要があります。以下の例では、スイッチ状態を50msごとにチェックするようにしています。

    for(timer=0; timer<timerValue; timer++){
        // LEDを950ms消灯する
        LATA5 = 0;

        // 950msの消灯中、50msごとにスイッチ状態をチェックする
        // スイッチが押された場合はtimerを0にリセットする
        for( unsigned char i=0; i<19; i++){
            __delay_ms(50);
            if( RA3 == 0 ){
                timer = 0;
            }
        }

        // LEDを50ms点灯する
        LATA5 = 1;
        __delay_ms(50);
        // スタートスイッチの状態を確認する
        if (RA3 == 0 ) {
            // スタートスイッチが押されたのでタイマーカウントを0にする
            timer = 0;
        }
    }

これでLED消灯中の950ms待ちの間もスイッチ状態をチェックできるようになりました。

ところで、このプログラムでLEDの点滅パターンを変更したい場合はどうすればいいでしょうか。現在、950ms消灯、50ms点灯というパターンですが、これを500ms消灯、500ms点灯というパターンにしたい場合、プログラムを色々と変更する必要がありますよね。

そこで、以下のLED点滅処理の部分はこのままで、

    // タイマー計測開始
    //
    // LED点滅処理
    for(timer=0; timer<timerValue; timer++){
        // LEDを950ms消灯する
        LATA5 = 0;
        __delay_ms(950);
        // LEDを50ms点灯する
        LATA5 = 1;
        __delay_ms(50);            
    }

スイッチが押されたら(RA3レジスタが変化したら)、現在行っている処理(LED点滅処理)を強制的に中断して、押された時の処理をする、という手段があるといいですよね。イメージとしてはこんな感じです。

Pic app 16 interrupt

 

割込み処理とは

このように、何かが変化した場合に、現在行っている処理を強制的に中断して、別の処理を行うことを「割込み処理」と呼んでいます。言葉の意味としては、現在行っている処理に「割込んで」別の「処理」を行う、という感じです。

「何かが変化した場合」というのはいろいろなケースがあります。この例で説明したスイッチの状態が変化した時、つまりRAレジスタの状態が変化した時以外にも、「マイコン内部のタイマーが設定時間になった」や「外部とデータ通信している時に、外部からデータが入ってきた」などがあります。例えば外部とデータ通信している時に、いつやってくるかわからないデータを常に監視する処理をプログラムに書くのは大変ですよね。このような、何か変化があった場合に、今実行している処理に割込んで、あらかじめ用意しておいた処理をしてくれると助かります。

割込み処理について、なんとなくわかったような、わからないような感じかもしれません。

そこで、次回は現在作っているタイマープログラムとは別に、割込み処理を確認するために必要最小限のプログラムを作成して、実際の割込み処理の動作確認をします。

 

更新履歴

日付 内容
2017.8.15 新規投稿
2018.12.2 誤記訂正