H8/3052F ITU割り込み処理  
2010/10/10

H8/3052F ITUで時間かせぎ」のページで、ITUタイマー機能を使った時間かせぎのプログラムをご紹介しました。時間かせぎはタイミング調整を行うためにとても重要な機能ですが、先のページでご紹介した機能では時間かせぎを行っている最中、マイコンは時間を計測するのに忙しくて他の仕事を一切できませんでした。ごく短い時間かせぎなら良いのですが、長い期間のインターバルを生成したい場合などは、非常に効率の悪い動きとなってしまいます。そこで割り込み処理の登場です。割り込み処理を用いることで本来の仕事に集中させて、必要な時だけ別の仕事を割り込ませることができるようになります。割り込み処理の発生要因は沢山準備されていますが、今回はITUタイマー機能の割り込み機能を使って、その動作について見ていきましょう。


1.割り込みとは

マイコンのプログラムは、通常記述された命令をひとつずつ順番に実行していきます。しかし、折り込み済みの処理ではなく、どのタイミングで発生するかわからない処理に対応するにはどうしたら良いのでしょう。どのタイミングで発生するかわからないので、ずっと発生するのを待ち続けるというのもひとつの手です。しかしここでは、本来の仕事を処理しながら突然発生した仕事を割り込ませるという方法を使います。それが文字通り「割り込み」です。

割り込みは、よく仕事中にかかってきた電話に例えられます。仕事中はその作業に専念していますが、電話のベルがなると仕事の手を止めて電話に出ます。そして電話が終わったら、再び先ほどの仕事の続きに戻ります。マイコンの世界でも、これと同じことを行います。割り込み要因が発生したら主となるプログラムの処理を一旦中断し、割り込みの発生要因にあわせた分岐先に飛んで必要な処理を行います。そして、その処理が終わったら、再び主となるプログラムの続きに戻ります。つまり、割り込みが発生するまでは、主となるプログラムの実行に専念できるのです。



2.割り込まないプログラム

割り込みを行うプログラムとの違いを分かりやすくするため、今回は先ず割り込まないプログラムを動かして見ましょう。今回のプログラムでは、二つの処理を行います。ひとつは、port2から入力されたBIT情報をそのままport1に出力させます。もうひとつは、port3の出力を1秒ごとに変化させます。port3の出力は正確に1秒ごとに変化させますが、port1の出力はport2の入力が変化したら、それに合わせて即座に変化してほしいですね。 割り込み処理を使わないため、この二つの処理は素直にmain関数内で処理することにします。さて、どういう動きになるでしょう。

【ダウンロード】 int_itu_test0.c 右クリックで「対象をファイルに保存」
【ダウンロード】 myfunc.h 右クリックで「対象をファイルに保存」

/***************************************/
/* C TEST PROGRAM BY RIKIYA 2010/10/10 */
/* for AKI-H8/3052F CPU BOARD          */
/* PROGRAM NAME int_itu_test0.c        */
/***************************************/

#include "iodefine.h"
#include "myfunc.h"

/* メイン関数************************************************/
void main(void){

    int mode;                        // port1 LED点灯パターン

    P1.DDR = 0xFF;                   // port1出力に設定 表示LED
    P2.DDR = 0x00;                   // port2入力に設定 DIPSW
    P2.PCR.BYTE = 0xFF;              // port2プルアップon
    P3.DDR = 0xFF;                   // port3出力に設定 表示LED

    itu0_wait_init();                // 時間かせぎ関数初期化

    mode =0xAA;                      // port1出力LED点灯パターン初期化

    while(1){
        P1.DR.BYTE = P2.DR.BYTE;     // port2 DIPSWをport1 LEDに表示
        switch(mode){
            case 0x55:
                mode =0xAA;          // modeが0x55なら0xAAにする
                break;
            case 0xAA:
                mode = 0x55;         // modeが0xAAなら0x55にする
                break;
            default:
                mode = 0xAA;         // それ以外なら0xAAにする
        }
        P3.DR.BYTE = mode;           // port3のLEDを変化させる
        itu0_wait(1000);             // 1秒のインターバル
    }

}

それでは、プログラムの中身について見てみましょう。

#include "iodefine.h"ではH8/3052Hのハードウェアレジスタを操作するための構造体変数、#include "myfunc.h"では、自作の汎用関数を、それぞれ利用できるようにします。今回はmyfunc.hの中に記述されている時間かせぎ関数を利用します。

main関数では、初めに変数の宣言や、H8/3052FのPIOポートの初期化を行います。今回はport1を出力、port2をプルアップ付きの入力、port3を出力に設定しています。その後、myfunc.hに記述されている時間かせぎ関数を利用するための初期化処理 itu0_wait_init( );を実行し、続いてport3から出力するLED表示パターンを0xAA; (●○●○ ●○●○)に初期化しています。

さて、次の
wile(1){ }のループ処理が、今回のプログラムのメイン部分になります。はじめに、P1.DR.BYTE = P2.DR.BYTE;により、port2のBIT情報をそのままport1に出力させます。次に、port3に出力させるデータを決定する処理をswitch文で行っています。今回、port3への出力は0xAA(●○●○ ●○●○)と0x55(○●○● ○●○●)を交互に出力させます。この出力パターンを変数modeに格納しておき、P3.DR.BYTE = mode;でport3に出力しています。

最後に、port3の出力を1秒ごとに変化させるため、
itu0_wait(1000);を実行していますが... ここでitu0_wait(1000);を実行すると、当然同じwhile(1)ループの中にあるP1.DR.BYTE = P2.DR.BYTE; も1秒ごとに実行されることになります。つまり、port2から入力されたBIT情報は即座にport1の出力に反映されず、port3の出力を変化させるための1秒ごとのタイミングにならないと反映されない、という動きになります。 それでは、とこか他の場所にitu0_wait(1000);を入れてみたらどうでしょう... しかし結果は同じことです。 itu0_wait(1000);で1秒を数えている間は、他のことは何もできないのです。



3.ITU割り込みプログラム

それでは、次にHEW4開発環境の割り込みプログラムでの事例をご紹介します。今回のプログラムでも、先ほどと同じ二つの処理を行います。ひとつは、port2から入力されたBIT情報をそのままport1に出力させます。もうひとつは、port3の出力を1秒ごとに変化させます。今回はITU1のGRAがコンペアマッチした時に発生する割り込みを使って1秒間のインターバルを生成します。 今度はport3の出力を正確に1秒ごとに変化させつつ、port1の出力はport2の入力が変化したら、それに合わせて即座に変化させてみましょう。 

プログラムで利用するITUの基本的な動作とレジスタの説明については「H8/3052F ITUで時間かせぎ」のページを参照して下さい。以下に、ITUを割り込みで利用するためのレジスタについてご紹介します。

■TIER タイマインタラプトイネーブルレジスタ
各ITUチャンネルごとにあるレジスタで、以下の要因で発生する割り込みの許可を設定します。
7 6 5 4 3 2 1 0
- - - - - OVE IMEB IMEA
 1

OVF
TSR タイマーステータスレジスタのOVFビットが1になったときに割り込みを発生させるかどうかを設定します。0なら発生させず、1なら発生させます。TSRのOVFビットは、TCNTタイマカウンタが0xFFFFを超えてオーバーフローしたときに1になります。
IMEB
TSR タイマーステータスレジスタのIMEBビットが1になったときに割り込みを発生させるかどうかを設定します、0なら発生させず、1なら発生させます。TSRのIMEBビットは、TCNTがGRB ゼネラルレジスタBと同じ値になると1になります。
IMEA
TSR タイマーステータスレジスタのIMEAビットが1になったときに割り込みを発生させるかどうかを設定します、0なら発生させず、1なら発生させます。TSRのIMEAビットは、TCNTがGRA ゼネラルレジスタAと同じ値になると1になります。


今回のダウンロードファイルは、HEW4のプロジェクトごとをZIPファイルに圧縮していますので、任意の場所に解凍してからHEW4の「ファイル」→「ワークスペースを開く」から拡張子.hwsのプロジェクトファイルを開いて利用してください。
【ダウンロード】 int_itu_test1.zip 右クリックで「対象をファイルに保存」

/***************************************/
/* C TEST PROGRAM BY RIKIYA 2010/10/10 */
/* for AKI-H8/3052F CPU BOARD          */
/* PROGRAM NAME int_itu_test1.c        */
/***************************************/

#include "iodefine.h"

int cnt;                               //割り込み回数カウンタ
int mode;                              //port1 LED点灯パターン

/* メイン関数************************************************/
void main(void){

    P1.DDR = 0xFF;                     // port1出力に設定 表示LED
    P2.DDR = 0x00;                     // port2入力に設定 DIPSW
    P2.PCR.BYTE = 0xFF;                // port2プルアップon
    P3.DDR = 0xFF;                     // port3出力に設定 表示LED

    ITU1.TCR.BYTE = 0x23;              // GRAコンペアマッチ clock 1/8 TCNTはコンペアマッチでクリア
    ITU1.GRA = 0x0C35;                 // GRAを3124(1mS)に設定
    ITU1.TIER.BYTE = 0xF9;             // ITU1のGRAによるコンペアマッチ割込みを許可
    ITU.TSTR.BIT.STR1 = 0;             // カウント停止状態
    ITU.TSTR.BIT.STR1 = 1;             // ITU1 TCNTカウント開始

    cnt = 0;                           // 割込み発生の回数を0にセット
    mode =0xAA;                        // port1出力LED点灯パターン初期化

    while(1){
        P1.DR.BYTE = P2.DR.BYTE;       // port2 DIPSWをport1 LEDに表示
    }
}

/*ITU1チャンネル GRAコンペアマッチ割り込み処理***************/

void int_imia1(void){

    cnt++;                             // cntの数を1つ増やす

    if(cnt == 1000){                   // 1000(1秒)ならport1のLEDを変化させる
        cnt = 0;                       // カウンタを0に戻す
        switch(mode){
            case 0x55:
                mode = 0xAA;           // 表示が0x55なら0xAAにする
                break;
            case 0xAA:
                mode = 0x55;           // 表示が0xAAなら0x55にする
                break;
            default:
                mode = 0xAA;           // それ以外なら0xAAにする
        }
        P3.DR.BYTE = mode;             // port3のLEDを変化させる
    }
    ITU1.TSR.BIT.IMFA = 0;             // 割込み検知フラグを戻して再開
}

それではプログラムについて見てみましょう。

#include "iodefine.h" をインクルードしてH8/3052Fのハードウェアレジスタ構造体変数を使えるようにします。今回はmyfunc.hは利用しません。また、main関数と割り込み処理先で共通で利用する変数 cntとmodeの宣言をはじめにしておきます。

■main関数

H8/3052FのPIOポートの初期化を行い、port1を出力、port2をプルアップ付きの入力、port3を出力に設定しています。また、割り込みを行わせるITU1の初期設定を行い、1mS(1/1000秒)でGRAコンペアマッチが発生するようにしています。また、
ITU1.TIER.BYTE = 0xF9;でTIERのIMEAビットを1とし、GRAのコンペアマッチによる割り込みを許可しています。その後、while(1){ }ループに入る前に、変数cntとmodeを初期化します。

さて、
while(1){ }ループの中身がmain関数でさせたい処理になりますが、P1.DR.BYTE = P2.DR.BYTE; しかありません。つまり、port2のBIT情報をport1に出力するだけで、port3の出力を変化させる処理は一切記述されていません。その部分は、次の割り込み処理部分に記述しています。

■int_imia1( )関数

この関数が、ITU1のGRAがコンペアマッチしたときに
割り込み処理によって実行される部分になります。先ずcnt変数の値を1だけ増やします。次のif文 if (cnt == 1000){ }でcntの値が1000かどうかを判定し、1000でなければITU1.TSR.BIT.IMFA = 0;を実行し、GRAコンペアマッチが発生したことを示すフラグをクリアして、割り込み処理を終了します。TCNTタイマカウンタは、GRAがコンペアマッチしたタイミングでクリアされて再び0から数え始め、次にGRAコンペアマッチしたタイミングで、再び割り込みが発生します。

もし、cnt変数が1000に達したら、if文の中が実行されます。先ずcnt変数を0にクリアし、port3に出力するためのデータであるmode変数の値を設定します。その後、
P3.DR.BYTE = mode;により、port3の出力が変化します。つまり、1秒(1000mS)ごとにport3の出力が変化することになります。

■割り込み関数を実行させるための処理

さて、先ほどの
int_imia1( )関数ですが、上記のプログラム中ではどこからも呼ばれていません。いったいとこから呼ばれて実行されているのでしょうか。そのための仕掛けが、HEW4プロジェクトの中に含まれるintprg.cの中に隠されています。HEW4からintprg.cを開いてください。

/***********************************************************************/
/*                                                                     */
/* FILE :intprg.c                                                      */
/* DATE :Sun, Sep 19, 2010                                             */
/* DESCRIPTION :Interrupt Program                                      */
/* CPU TYPE :H8/3052F                                                  */
/*                                                                     */
/* This file is generated by Renesas Project Generator (Ver.4.16).     */
/*                                                                     */
/***********************************************************************/

#include <machine.h>
#pragma section IntPRG
// vector 1 Reserved

// vector 2 Reserved

 
〜 途中省略 〜

// vector 24 IMIA0
__interrupt(vect=24) void INT_IMIA0(void) {/* sleep(); */}
// vector 25 IMIB0
__interrupt(vect=25) void INT_IMIB0(void) {/* sleep(); */}
// vector 26 OVI0
__interrupt(vect=26) void INT_OVI0(void) {/* sleep(); */}
// vector 27 Reserved


// ITU1チャンネル GRAコンペアマッチ割り込み処理を追記
// vector 28 IMIA1
void int_imia1(void);
__interrupt(vect=28) void INT_IMIA1(void) { /* sleep(); */ 
int_imia1();}


// vector 29 IMIB1
__interrupt(vect=29) void INT_IMIB1(void) {/* sleep(); */}
// vector 30 OVI1
__interrupt(vect=30) void INT_OVI1(void) {/* sleep(); */}
// vector 31 Reserved

 
〜 以下省略 〜


このintprg.cは、HEW4でプロジェクトを作成した時に自動で生成されるソースです。H8/3052Fで扱うことができる全ての割り込み要因のベクタテーブルがあらかじめ記述されており、それぞれに
/* sleep(); */ と記述されている通り、無処理扱いとしてあります。したがって、利用したい割り込み要因に対して、実行させたい割り込み関数を記述してあげることで、割り込み処理を実行させることが出来るようになります。

今回の事例では、ITU1のGRAがコンペアマッチしたときに発生する割り込みを利用したいので、
IMIA1のベクタテーブル部分を利用します。割り込みが発生したときに実行させる割り込み関数の関数名は何でもかまいません。今回、intprg.cに手を加えた部分は、上記プログラムの青文字部分のみになります。

実は、このほかにもH8/3052Fマイコンの基本的な動作を決定している
CCR コントロールレジスタというものがあり、そこで割り込みマスクを行うなどの大本の設定があります。HEW4のプロジェクトでは、resetprg.cの中でその設定が記述されていますが、プロジェクトが生成された段階で割り込み許可となっているため、ここでは特に意識する必要はありません。



4.プログラムの実行

■割り込まないプログラム int_itu_test0.c
【MOVIE】 ダブルクリックで再生されます。
それでは、先ずはじめに割り込まないプログラムint_itu_test0.cをビルドして実行してみましょう。画面上方にある8BITのLEDがport3の出力で、1秒間隔で●○●○ ●○●○と、○●○● ○●○●の表示を繰り返しています。一方、画面下方右のDIPスイッチがport2の入力で、そのBIT情報が画面下方左の8BITのLEDのport1に出力されます。
動きを良く見てみますと、DIPスイッチを押した瞬間にport1のLEDが変化するのではなく、port3のLEDが変化するタイミングに合わせて変化する様子が分かります。つまり、割り込まない時間かせぎの処理に引っ張られてしまっているということになります。


■ITU割り込みプログラム int_itu_test1.c
【MOVIE】 ダブルクリックで再生されます。
次に割り込むプログラムint_itu_test1.cをビルドして実行してみましょう。各LEDとDIPスイッチの役割は先ほどの割り込まないプログラムと同じですが、今度はDIPスイッチを操作すると瞬時にport1のLEDが変化する様子が分かります。

つまり、主となる動作が、時間かせぎの処理に引っ張られることなく、瞬時に処理されているということになります。


今回は、割り込み処理の考え方とHEW4の環境下での割り込みプログラムの事例を見てみました。マイコンプログラムにおいて割り込み処理は非常に重要なもので、色々な場面で使われます。難しそうだということで敬遠しがちかもしれませんが、少しずつ慣れていくことも必要でしょう。ここでひとつ。「2.割り込まないプログラム」でご紹介したプログラムの事例ですが、while(1){ }ループの中でitu0_wait(1000);によって丸々1秒を待たず、割り込みプログラムの事例でご紹介した手法と同じくitu0_wait(1);で1ms(1/1000秒)ごとにwhile(1){ }ループを回せば、見た目にport1の出力はport2の変化に即座に対応して変化させることが可能です。それなら割り込みなど使わなくてもいいじゃんということになりますが、そんなことはありません。見た目に瞬時に対応していても、プログラムの処理としては1mSの待機時間が発生していることに変わりはありません。また、割り込みプログラムにより、main関数の記述は主な処理に専念した分かりやすいプログラムにすることができますし、main関数のwhile(1){ }ループ内に時間がかかる処理が含まれている場合、itu0_wait(1);が実行される間隔自体が長くなり、正確な1秒間隔を得ることが出来なくなりますね。割り込み要因には内部機能から呼ばれる場合や、周辺デバイスから呼ばれる場合など、様々あります。それらの割り込み要求に即座に答えて迅速に処理する、という必要性は理解して頂けると思います。

先頭に戻る

 

【表紙に戻る】