※ 念願のプリント基板を製作する前に。

 【14】リアルタイムなプログラム 2(割り込み)

前回はループを使用してリアルタイムなプログラムを作ってみましたが、次の割り込み機能を利用すると、もう少し楽に、そして正確に行うことができるようになります。


▽ 割り込み △

コンピュータで何らかのものを制御する上で、ぜひ欲しい機能に割り込み処理があります。制御する対象によっては無くても問題ない場合もありますが、あった方が断然有利です。

 どのようなモノかといいますと、字のごとく〝割り込み〟です。公共道徳に反するようなことばですが、コンピュータ制御の世界では逆に重宝されています。

 何が割り込んで来るのかといいますと、いま処理中のプログラムに別のプログラムが割り込んできます。
 古い型や規模の小さいPICにはこの割り込み機能が搭載されてないモノがあります。このようなPICでは前回の説明のように、ループ処理でこなしますが、処理が複雑になってくるとどうしても一部で時間を浪費してしまい、あとの処理が待たされて全体の動きがギクシャクしてきます。
 割り込みが使えると、時間を浪費する処理中でもそれを中断して別の処理ができます。夢のような機能ですが、うまく使わないと、逆にうっとうしいモノにもなりますので注意が必要です。



 プログラムが割り込んでくるといっても、そんなに難しく考える必要はありません。サブルーチンコールという方法を【9】少し高度なプログラムで説明していますが、考え方は同じです。
 サブルーチンコールというのは、プログラムの流れをサブルーチンと呼ばれる別の処理へ移動(コール)させて、そこが終わるとコールした次のアドレスへ自動的に戻ってくる(リターン)という便利なものです。割り込みも同じ感じです。割り込みが掛かると割り込み処理へプログラムの流れが飛んで、終わると、割り込みが掛かった次のアドレスへ戻ってきます。

 サブルーチンコールと大きく異なるのは、処理がそちらへ飛ぶときにプログラマーが意識しているか、していないかの違いがあります。サブルーチンコールの場合はサブルーチンを呼ぶための準備ができます。必要な下拵えをしてからプログラマーがいいと思ったタイミングでサブルーチンをコールできます。ところが割り込みは、いつ発生するか予測が付きません。というか、予測の付かない問題を解決するために割り込み機能があるようなものですので当然かもしれません。

 まず、割り込みが発生するとプログラムの処理先は常に一定のアドレスへ飛ばされます。ミッドレンジPIC(16F84、877あたり)の場合は0004番地です。CPUによっては割り込みの種類別に飛び先を変更して処理を分けることができますが、ミッドレンジPICでは固定されています。でも、どの割り込みが発生したのかが分かるように種類別のフラグが立つようになっていますので、そのフラグを見て処理を分岐させるという方法をとります。

割り込みの正しい概念・・・

 PICに搭載されている割り込みは多くの種類があり、ここですべてを説明するには規模が大きすぎますので、外部割り込みとタイマー0(ゼロ)割り込みについて説明します。この二つの割り込みはほとんどのPICで実装されていますので、割り込み処理の基本になると思います。





▽ 外部割込み △

外部の回路からPICに信号を送って割り込みを発生させる方法です。信号というのは、デジタルですからHかLです。つまり5Vか0VをINTピンと呼ばれるポートへ入力します。PICではそれ専用のポートがRB0に割り当てられています。RB0は通常入出力ポートですが、割り込み信号の入力ポートとしても使用できます。

 割り込みが発生するトリガー(きっかけ)をHからLになったときにするのか、LからHになったときをトリガーとするのかを決めることもできます。
 割り込みをイメージするには外部割込みが一番わかり易いと思います。何らかのメイン処理が進行中、外部からRB0に信号を与えると、その処理を中断して割り込み処理に移る。それが終われば元の処理へ戻る・・・。コレだけのことです。

 この種の割り込みの利点は、ハード的に割り込みが掛かりますので、それまでは何も考えなくてよいというところです。





▽ タイマー0 割込み △

ミッドレンジPICにはいくつかのタイマーが内蔵されています。タイマーというのは、CPUの中に搭載されている数値をカウントできる特別なメモリのことです。タイマー0(ゼロ)はほとんどのPICに内蔵されているタイマーで、0~255までをカウントできます。255の次は0に戻り、再び255までカウントします。タイマー0が0~255を繰り返してカウントしているときに、255から0に戻る瞬間をトリガーとする割り込みが、タイマー0割り込みです。

 タイマー0のカウントアップはPICのクロックを利用して行うことや外部ポート(T0CKIと呼ばれるピン)のパルスを利用することもできます。一般的にはCPUのクロックを利用します。タイマー0はプログラムで読み書きできる構造になっていますので、カウント開始の初期値を自由に設定できます。たとえば、初期値を254にしてスタートさせると、2カウントした時点で255を超えて、割り込みが発生します。逆に、初期値を0からにすると、256カウントで割り込みが発生します。

 ただそれだけです。ですが、このタイマー0はプリスケーラーと呼ばれるもうひとつのカウンターとペアーで使用すると、細かな時間調整ができるようになり、タイマー0割り込みを自由な時間でトリガーすることができます。

 そこでプリスケーラというカウンターの説明をします。このカウンターは何個のパルスが入ったら、1回のパルスを出すかという働きをします。その何個の・・・部分をプログラムで、2回に1回、4回に1回、8回に1回、16、32、64、128、256回に1回の8種類に設定可能です。

タイマー0割り込みが掛かるまで・・・

 プリスケーラに入ってくるパルスはCPUのクロックパルス4回に1回のパルスです。よってCPUのクロックが10MHzの場合、4分の1ですので、2.5MHz=400nSのパルスが入ります。プリスケーラを最大の256回に1回に設定すると、最終的にタイマー0に入るパルスの速度は102.4μSになりますので、タイマー0の初期値を0にするとさらに256回のパルスで割り込みがトリガーされますので、26.2144mS≒26.2mSが最大となります。

 プリスケーラはウォッチドッグタイマーのプリスケーラにも使用できるような構造になっていますので、ウォッチドッグタイマーに使用した場合はタイマー0には使用できなくなります。ウォッチドッグタイマーについてはこの先で説明します。
 タイマーを利用した割り込みの利点は、規則正しい正確な周期で割り込みが掛かるというところです。





▽ 割り込み処理 △

割り込みが掛かるとPICはどのような行動を取るのか、詳しく見ていきましょう。

 PICに割り込みが掛かると、最初にメインで処理してた命令をとりあえず終わらせます。そして次の命令の入っている番地を戻り番地として、スタックという記憶エリアに書き込みます。次にPIC自身を割り込み禁止に設定してから割り込み処理、0004番地へ飛びます。

 PICが自動的にやってくれるのはコレだけです。
 なぜ割り込みを禁止にしてから割り込み処理へ飛ぶのか、それには理由があります。それは戻り番地を記憶するスタックというメモリの量が限られている(割り込み可能なミッドレンジPICでは8個しかない)からなのと、同じ割り込みを多重に受けても意味が無いからです。高性能なCPUには多重割り込みが可能なタイプもありますが、何重にもなると人間が管理できるものではありませんので、ミッドレンジPICあたりでは多重割り込みはできないようになっています。
 では、割り込みが禁止されるので、割り込みは1回しか使えないのか、という心配は無用です。そのための命令が、割り込み処理を抜け出す直前の命令…。

  〝 retfie 〟

というのを使って割り込み処理を終了させる約束になっています。
〝 retfie (リターン フロム インタラプト)〟という命令は、【8】35個の命令・・・の一覧表にある命令ですので詳しくはそちらをご覧ください。

 〝 retfie (リターン フロム インタラプト)〟は、割り込み処理から復帰する命令ですので、まず割り込みを許可にして、スタックに記憶しておいた戻り番地を読み出してそこへ飛びます。ようするに割り込みが掛かった次の番地に戻ってくることになります。割り込み許可に切り替えていますので、再び割り込みのトリガーが掛かれば、また割り込み処理へ飛びます。

 このように、割り込み処理の入り口と出口の処置はPIC自身が自動的にやってくれますので、何も意識することはありませんが、問題は割り込み処理に入ってからです。そこからはプログラマーが行わなければいけません。

 PIC自身は割り込み処理の中で何をやらされるのかはまったく知りません。PICの頭の中に並んでいるレジスター、SFR(Special Function Register )やユーザーが使用しているメモリなどが割り込み処理に入ってきたからといって、特別に区別されるようなことはありません。なので割り込み処理でそれらを使用すれば、当然内容は変更されます。そして割り込み処理が終わるとそのままメイン処理へ戻って行きます。つまり、メイン処理で使用していた内容が変更されて戻ってきてしまうことになります。

 意識してそのような処理を行うのであれば問題はありませんが、ほとんどの場合はメイン処理が無茶苦茶になります。したがって、割り込み処理内で使用されるポートやSFR、ユーザーメモリはすべてプログラマーが把握しておく必要があります。

 もし、メイン処理で使用しているユーザーメモリを割り込み処理でも使用するなら、メインが使用中は割り込み禁止命令を使用して割り込みが掛からないようにする必要があります。

 もっとも気を使わなくてはいけないのは、SFRやWレジスタが割り込み処理内で変更されることです。SFRの中でも使用しなければ大丈夫なレジスタもありますが、忘れがちなのが、ステータスレジスタとWレジスタです。特にステータスレジスタは意識しなくても命令を処理するだけで変更されてしまうデリケートなレジスタです。Wレジスタも同様に頻繁に使用するレジスタですので、簡単に変更されてしまいます。当然変更されたままメイン処理に戻ると、処理の内容がチグハグになり予期せぬバグが発生します。

 そこで、割り込み処理の最初の段階でそれらのレジスタ類を別のユーザーメモリに退避させておき、割り込み処理終了手前で復帰させるという方法をとります。
次のプログラムは、私がよくやる退避方法です。使用PICは16F877Aです。


;/////////////////////////////////////////////////////////////////////////////
;// 割り込み処理先頭 org 0x0004
INT_ENT:

;//  退避する(PUSH処理)

         movwf   WregBuf     ;Wregをユーザーメモリ、WregBuf へ退避
                                 ;コレ以降Wレジスタは使いたい放題…。
         swapf   STSreg,to_W     ;STSレジスタを変化させないようにWレジスタへ
                                 ;swapして退避させる。復帰時もswapするので、
                                 ;結局元に戻る。

         clrf    STSreg     ;割り込み進入時はbankが不定なので、bank-0に
                 ;設定した退避メモリ、STSregBufが正しくアクセス
                  ;できるようにbank0に設定する。

         movwf   STSregBuf       ;WレジスタにいれたSTSregの内容を退避
         movf    PCLATH,to_W
         movwf   PC_Buf          ;PCLATHを退避

         movf    MVC1,to_W       ;MVC1という汎用のユーザーメモリを退避
         movwf   MVC1BUF         ;退避先へ格納

         clrf    PCLATH          ;割り込み処理はPage-0で行うように指定

         movf    FSreg,to_W
         movwf   FSregBuf        ;FSregを退避


;/////////////////////////////////////////////////////////////////////////////
;// 
;//	ここから割り込み処理を書く・・・。
;// 
;/////////////////////////////////////////////////////////////////////////////


;// もとに戻す(POP処理)

         clrf    STSreg          ;割り込み処理内でバンクが変更されているかも
                                 ;知れないのでBank-0に

         movf    MVC1BUF,to_W    ;退避していたMVC1を復帰
         movwf   MVC1

         movf    FSregBuf,to_W
         movwf   FSreg           ;FSreg復帰

         movf    PC_Buf,to_W
         movwf   PCLATH          ;PCLATH復帰

         swapf   STSregBuf,to_W  ;swapして退避していた内容をswapして
         movwf   STSreg          ;STSregに復帰する

         swapf   WregBuf,to_F    ;一度Wregバッファー内の上下ニブルを入れ替え
         swapf   WregBuf,to_W    ;さらに上下ニブルを入れ替えてWregに入れる。
                                 ;理由はmovfでWregに入れると
                                 ;STSregのZesroフラグが変化するから。
         retfie                  ;割り込み許可にして終了。


コレだけの退避処理を行っておけば無事に割り込み処理から戻ってきます。

 この中で私は、汎用のユーザーメモリをひとつ退避していますが、これは特に特別な用途は無いが、Wレジスタのように何にでも汎用的に使用できるメモリをユーザーメモリのひとつを使って、MVC1という名前で定義しています。そしてメイン処理でも割り込み処理でも使えるように割り込み処理の頭で退避しています。



補足1:swapの意味

割り込み処理の先頭やラストで、なぜ 〝swapf〟 命令を使ってレジスタの内容を移動しているか謎だと思いませんか?
その理由は、〝ステータスレジスタ〟を変更させないようにしなければいけないからです。割り込み処理に入ってきた直後のステータスレジスタと同じ内容に戻してメイン処理へ帰さないと、予期せぬバグが起きるからです。

 ミッドレンジPICには、【8】35個の命令・・・の一覧表にある命令しかありません。その中でステータスレジスタを変更させずに任意のレジスタの内容をユーザーメモリへ移動させる命令は、 〝swapf〟 しかありません。通常の〝movf〟を使うと、ステータスレジスタの〝Zフラグ〟が影響を受けます。
_______________________________________________________



次回は 割り込み処理あれこれです・・・。

2010.09.04