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

今回から割込み処理を実装していきます。最初は割り込み処理とはどのようなものか解説します。

目次

割込み処理とは?

マイコンを使った電子工作の情報をネットで検索していると、「割込み処理」という言葉がよく出てきます。

マイコンを使っていく上で、この割込み処理はとても重要な機能ですので、ぜひ習得してみてください!

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

う〜ん、、、で何?

って感じになって、結局よくわからないですよね。「要求により強制的に」?「別のプログラム」?って感じです。

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

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

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


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

ところで、タイマーのスタートしたあと、タイマーをキャンセルして最初からスタートさせたい、という場合はどうすればよいか、考えてみます。

このように、タイマー開始後キャンセルしたい、というケースは普通にあると思います。

例えばカップラーメンを作ろうとして、タイマーをスタートさせた後、お湯を入れようと思ったら、、、お湯を沸かしてなかった!なんてうっかりがあるかもしれません。

そのときはスタートしてしまったタイマーをキャンセルして再スタートさせたい、という状況になります。(ちょっと無理矢理感がありますが…)


このような動作を実現するためには、タイマースタート後、タイマーカウント中にスイッチを押すと、最初からタイマーカウントを開始する、という機能の実装が必要になります。

実際に、現在のブログラムにこの機能をどのように実装すればよいか考えてみます。

タイマースタート後にスイッチの状態を確認すればいいわけですから、LEDの点滅処理部分でスイッチ状態を確認すればいいですよね。

スイッチはスタートボタンを使用するとすると、RA3レジスタの値を調べればスイッチ状態がわかります。

こんなの簡単だよね、って思いますが、実装しようとすると意外に難しいんです。

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

// LEDをtimerValue分点滅する
for(uint8_t timer=0; timer<timerValue; timer++) {
    // LEDを950ms消灯する
    LATA5 = 0;
    __delay_ms(950);            
    // LEDを50ms点灯する
    LATA5 = 1;
    __delay_ms(50);
}

この部分のどこかでRA3レジスタの値を調べて、スイッチがON(つまりRA3が0)であればスイッチが押された、と判断できます。

例えば上のプログラムに対して、次のようにしたらどうでしょうか。

10行目から13行目まで、RA3の値を確認して0だったら(スイッチが押されていたら)、タイマー時間を0に戻す、という処理を追加してみました。

// LEDをtimerValue分点滅する
for(uint8_t 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秒程度でしょうから、上のプログラムでは確実にスタートスイッチが押されたことを確認するのは困難です。

プログラムの処理状況を確認すると、次の図のようにLEDの制御は__delay_ms関数を使って時間を計測しています。

__delay_msで時間計測している間は、他の処理は何もできません

例えば、__delay_ms(950)で時間待ちをしている間、スイッチが押された場合、その検知はできないことになります

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秒の時間待ちをしています。

この間にもスイッチの状態をチェックするために、この部分を例えば次のように変更する必要があります。(950msの間、50msごとにスイッチ状態を確認するようにしてみました)

// LEDをtimerValue分点滅する
for(uint8_t timer=0; timer<timerValue; timer++) {
    // LEDを消灯する
    LATA5 = 0;

    // 950msの間は50msごとにスイッチ状態を確認する
    for(uint8_t i=0; i<19; i++) {
        // 50ms待つ
        __delay_ms(50);

       // スイッチ状態を確認する
        if( RA3 == 0 ) {
            timer = 0;
            __delay_ms(1000); // 1秒待ってスイッチが離されるのを待つ
        }

    // LEDを50ms点灯する
    LATA5 = 1;
    __delay_ms(50);

    // ここでもスイッチ状態を確認する
    if (RA3 == 0 ) {
        timer = 0;
        __delay_ms(1000);
    }

}

なんだかプログラムが読みづらくなってきましたが、まずはLED消灯中の950ms待ちの間もスイッチ状態をチェックできるようになりました。


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

正直、このようなプログラミングはかなり大変です。

そこで、このようなときにぴったりなのが「割り込み処理」です。

割り込み処理のイメージ

先ほどのプログラムでは、「スイッチが押されたらタイマー計測時間を0に戻す」という処理を「LEDを点滅する」処理の中に実装していました。

「LEDを点滅する処理」という定期的なタイミングで処理をしているところに、「スイッチが押されたら」という予測できないタイミングで発生する出来事の処理を入れようとしたことで、プログラムの変更が大変になっていました。

そこで、次のような処理ができればいいと思いませんか?

スイッチが押されたら、現在行っているLEDの点滅処理を強制的に中断して、スイッチが押された時の処理をする、という方法です。

プログラム上では、「LEDの点滅処理」と「スイッチが押された時の処理」は独立していて、スイッチが押されたら「LEDの点滅処理」を強制的に中断する、という方法です。

イメージとしては次のイラストのような感じです。

Pic app 16 interrupt

ポイントは、「LEDの点滅処理」(青部分)と「タイマー時間をクリアする処理」(赤部分)の処理は、プログラム上では独立した別々の関数として書くことができる、という点です。

このような実装ができれば、LEDの点滅処理のプログラムに手を加えることなく、スイッチがや押されたときの処理振る舞いを簡単に実現することができそうです。

これが「割り込み処理」の考え方です。

割込み処理とは

このように、何か出来事があった場合に(例えばスイッチ状態の変化があった場合に)、現在行っている処理(例えばLEDの点滅処理)を強制的に中断して、別の処理を行うことを「割込み処理」と呼んでいます

言葉の意味としては、何か出来事があったら、現在行っている処理に「割込んで」別の「処理」を行う、という感じです。

「何か出来事があったら」というのはいろいろなケースがあります。

この例で説明したスイッチの状態が変化したとき(RA3の状態が変化したとき)以外にも、「外部とデータ通信するとき、外部からデータが入ってきたとき」などがあります。

外部とデータ通信している時に、いつやってくるかわからないデータを常に監視する処理をプログラムに書くのは大変ですよね。

このような、何か変化があった場合に、今実行している処理に割込んで、あらかじめ用意しておいた処理をしてくれると助かります。

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

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

更新履歴

日付内容
2017.8.15新規投稿
2018.12.2誤記訂正
2025.5.22説明内容補足
通知の設定
通知タイミング
guest
0 コメント
新しい準
古い順 一番投票が多い
本文中にフィードバック
全てのコメントを見る
目次