マイクロコンピュータH8/3048におけるタイマ割り込み

01Feb2004
12Nov2002
Copyright(C) TNCT CS Kosaka

【1】 はじめに
この文書ではタイマ割り込みに関して説明している。

【2】一般的なタイマ割り込みの仕組み
 H8CPU内部には「タイマユニット」があり,その中にはシステムクロックをカウントアップするカウンタと,設定された値を保持しているレジスタがあり,この2つの値が一致することによって割り込み要求信号を発生している。割り込み要求信号が発生すると同時にカウンタはリセットされるため,割り込み要求信号は周期的に発生することになる。(発生した割り込み要求信号は割り込み処理手続きの中で消さなければならない。)タイマユニットがこのような動作をするためには事前にこのような動作をするように設定が必要である。
 CPUが割り込み許可状態で,割り込み要求信号を受け取ると,タイマ割り込み処理が始まる。
 タイマ割り込みが生ずると,メモリアドレスの先頭付近に置かれた割り込みベクタテーブル(割り込みが起こったらどのアドレスにジャンプするかのアドレス表)中から,タイマ割り込みの時に参照するベクタ(具体的には割り込み関数の先頭アドレス)を取り出し,そのアドレスにジャンプする。(正確には,CCR[コンディションコードレジスタ]と現在のPC[プログラムカウンタ]を退避させてから,割り込み関数呼び出しする)

割り込み関数は,次の作業を行なわなければならない。

(1)はじめに各レジスタの値をスタックに退避する
(2)タイマユニットの割り込みフラッグをクリアする(割り込み作業が開始したことを知らせ,割り込み要求を取り下げさせる)
(3)必要な作業を行なう
(4)各レジスタにスタックから退避させた値を復帰する
(5)割り込みリターンする(通常の関数では関数リターンだが,CCR[コンディションコードレジスタ]の復帰も行なう)

 次のプログラムはタイマ割り込みを用いてLEDのON-OFFを行う。アセンブリ言語で書かれたものである。
割り込み要因はタイマユニットITU0を用いている。

 通常の電源ONの起動時には0番地に書いてあるアドレスから始まる。すなわち(A)RES_STRから処理が始まる。そのあと変数や必要な設定レジスタを設定し,処理は(B)の無限ループとなる。

割り込みは10msecごとに起動し,次のように動作する。
(1)0x60番地に割り込み関数のアドレスITU_IMIA0が書いてある。割り込みが生じたらITU_IMIA0へ処理が移る。
 この時次の4つのことが自動的に行なわれている。
 a)CCR([コンディションコードレジスタ)をスタック領域にPUSHする
 b)CCRの割り込みマスクをセットし,割り込み禁止になる
 c)現在のPC(プログラムカウンタ)の値をスタック領域にPUSHする。あとで関数から戻るときに使う。
 d)PCにITU_IMIA0のアドレスを書き込む
(2)PUSH 使用レジスタの値をスタックに退避する
(3)タイマユニットの割り込みフラッグをクリアする(割り込み作業が終了したことを知らせ,割り込み要求を取り下げさせる)
(4)必要な作業(LEDのON-OFFなど)を行なう
(5)POP スタックに退避させた値をレジスタに復帰する
(6)RTE 割り込みリターンする(通常の関数では関数リターンRTSが使われるここではRTE)
 この時次のことが行なわれる。
 a)(1)c)で退避したもとのアドレスをスタックからPCに復帰する。割り込み前の作業が継続される
 b)(1)a)で退避したCCR(コンディションコードレジスタ)の復帰。割り込みが許可された状態に戻る

TimerInt.src

 


    .CPU    300HA

;****************************************
;       SYMBOL DEFINITIONS              *
;****************************************
ITU_TSTR    .EQU    H'FFFF60 ;ITU TSTR
ITU0_TCR    .EQU    H'FFFF64 ;ITUch0 TCR
ITU0_TIER   .EQU    H'FFFF66 ;ITUch0 TIER
ITU0_TSR    .EQU    H'FFFF67 ;ITUch0 TSR
ITU0_GRA    .EQU    H'FFFF6A ;ITUch0 GRA
P5_DDR      .EQU    H'FFFFC8
P5_DR       .EQU    H'FFFFCA

;*************** SET VECTORS ***************************
    .SECTION    VEC,DATA,LOCATE=H'000000
    .DATA.L RES_STR
    .ORG    H'000060
    .DATA.L ITU_IMIA0

;*************** RAM ALLOCATION ************************
    .SECTION    B,DATA,LOCATE=H'FFEF10
LOOP:   .RES.W  1
TICK:   .RES.W  1

;*************** MAIN PROGRAM **************************
    .SECTION    P,CODE,LOCATE=H'000100
RES_STR:
    MOV.L   #H'FFFF00,ER7      ;Set Stack Pointer
    MOV.B   #B'00100011,R0L    ;Initialize ITU0_TCR
                               ;GRAcomparematch+Clock/8
    MOV.B   R0L,@ITU0_TCR
    BCLR    #0,@ITU0_TSR       ;Clear IMFA
    MOV.B   #B'00000001,R0L    ;Initialize ITU0_TIER
    MOV.B   R0L,@ITU0_TIER
    MOV.W   #19999,R0          ;Initialize ITU0_GRA
                               ;Period=10msec
    MOV.W   R0,@ITU0_GRA
    MOV.B   #B'00000011,R0L    ;Initialize P5_DDR (initLED)
    MOV.B   R0L,@P5_DDR

    MOV.W   #0,R0              ;Clear variables
    MOV.W   R0,@LOOP           ;    LOOP=0
    MOV.W   R0,@TICK           ;    TICK=0
    BSET    R0L,@ITU_TSTR      ;Start ITU ch0

    ANDC.B  #B'01111111,CCR    ;Clear Interrupt mask
EternalLoop:
    BRA EternalLoop

;*************** IMIA0 INTERRUPT FUNCTION ******************
ITU_IMIA0:
    PUSH.L  ER0
    BCLR    #0,@ITU0_TSR       ;Clear Interrupt Flag
    MOV.W   @TICK,R0           ;    if (TICK==1) {
    CMP.W   #1:16,R0
    BNE     ELSE1
IF1:
        MOV.B   #1:8,R0L       ;        P5.DR=1
        MOV.B   R0L,@P5_DR
        BRA     ENDIF1
ELSE1:                         ;    } else {
        MOV.B   #2:8,R0L       ;        P5.DR=2
        MOV.B   R0L,@P5_DR
ENDIF1:                        ;    }
    MOV.W   @LOOP,R0           ;    LOOP++
    INC.W   #1,R0
    MOV.W   R0,@LOOP
    CMP.W   #50:16,R0          ;    if (LOOP==50) {
    BNE     ENDIF2
IF2:
        MOV.W   #1:16,E0       ;       TICK=1-TICK
        MOV.W   @TICK,R0
        SUB.W   R0,E0
        MOV.W   E0,@TICK
        SUB.W   R0,R0          ;       LOOP=0
        MOV.W   R0,@LOOP
ENDIF2:                        ;    }
    POP.L   ER0
    RTE

    .END


















(1)









(A)



















(B)




(2)
(3)
(4)























(5)
(6)

ここまでの説明をまとめると次のようになる

(1)タイマ割り込みの準備

CPU

  タイマユニット
    MOV.B   #B'00100011,R0L    ;Initialize ITU0_TCR
                               ;GRAcomparematch+Clock/8
    MOV.B   R0L,@ITU0_TCR
    BCLR    #0,@ITU0_TSR       ;Clear IMFA
    MOV.B   #B'00000001,R0L    ;Initialize ITU0_TIER
    MOV.B   R0L,@ITU0_TIER
    MOV.W   #19999,R0          ;Initialize ITU0_GRA
                               ;Period=10msec
    MOV.W   R0,@ITU0_GRA
ITU(タイマユニット)さん,10msecごとに割り込みを入れてね
 了解

    BSET    R0L,@ITU_TSTR      ;Start ITU ch0

ITU(タイマユニット)さん,動作を開始してください
 了解

    ANDC.B  #B'01111111,CCR    ;Clear Interrupt mask

私が割り込みを受け付けられるようにします
   

(2)タイマ割り込みの動作

CPU

 

タイマユニット
割り込み要求信号

了解

時間ですよ

割り込みの受け付け(次の動作を自動的に行なう)

a)CCR([コンディションコードレジスタ)をスタック領域にPUSHする *1
b)CCRの割り込みマスクをセットし,割り込み禁止になる
   「私が割り込みを受け付けられない状態になります。」
c)現在のPC(プログラムカウンタ)の値をスタック領域にPUSHする。あとで関数から戻るときに使う。 *2
d)PCにITU_IMIA0のアドレスを書き込む
   「割り込み関数にジャンプすることになります」

 

時間ですよ
を言い続ける

割り込み関数の開始

    PUSH.L  ER0

割り込み関数中で使用するレジスタをスタックに退避します。
 

時間ですよ
を言い続ける

    BCLR    #0,@ITU0_TSR       ;Clear Interrupt Flag

ITU(タイマユニット)さん,「時間ですよ」を了解しました。もう言わなくてもOKです。
 *3
了解。黙ります。

    MOV.W   @TICK,R0           ;    if (TICK==1) {
    CMP.W   #1:16,R0
    BNE     ELSE1
IF1:
        MOV.B   #1:8,R0L       ;        P5.DR=1
        MOV.B   R0L,@P5_DR
        BRA     ENDIF1
ELSE1:                         ;    } else {
        MOV.B   #2:8,R0L       ;        P5.DR=2
        MOV.B   R0L,@P5_DR
ENDIF1:                        ;    }
    MOV.W   @LOOP,R0           ;    LOOP++
    INC.W   #1,R0
    MOV.W   R0,@LOOP
    CMP.W   #50:16,R0          ;    if (LOOP==50) {
    BNE     ENDIF2
IF2:
        MOV.W   #1:16,E0       ;       TICK=1-TICK
        MOV.W   @TICK,R0
        SUB.W   R0,E0
        MOV.W   E0,@TICK
        SUB.W   R0,R0          ;       LOOP=0
        MOV.W   R0,@LOOP
ENDIF2:                        ;    }

P5.DRを操作してLEDのON-OFFの作業を行ないます。
ここが割り込みルーチンの作業本体
   

    POP.L  ER0

スタックに退避していたレジスタ値をレジスタに復帰します。
   

    RTE

*2で退避したもとのアドレスをスタックからPCに復帰する。割り込み前の作業に戻る。
*1で退避したCCR(コンディションコードレジスタ)の復帰。割り込みが許可された状態に戻る。
    「私は次の割り込みを受け付けることが出来ます」

割り込み関数の動作終了
   

*3 タイマユニットに対して割り込み要求を取り下げさせる。この作業を忘れると割り込み関数が終了直後に,再び割り込みを受け付けて,割り込み関数が起動してしまう。

 

【3】 タイマ割り込みの仕掛け

ここで解説するタイマ割り込みの記述方法は汎用性を持つものであるが,「日立評価版コンパイラVer2」の記述規則に依存するものである。

タイマ割り込み関数の名前を 「TimerIntFunc」 とする。

(1)割り込みベクタ 0X70 に 「TimerIntFunc」 の先頭アドレスを書いておく。(割り込みベクタは4バイトである。)
この記述はCソースファイル中にインラインアセンブリを用いる。

日立評価版コンパイラVer2の場合
#pragma asm
    .SECTION    MYVEC, DATA, LOCATE=H'000070
    .ORG        H'000070  ;IMIA1
    .DATA.L     _TimerIntFunc
    .SECTION    P,CODE,ALIGN=2 ;これを忘れてはいけない
#pragma endasm

 

(2)割り込み関数であることを示すために「#pragma interrupt」宣言を行なうと,コンパイラが自動的に次の処理を付け加えてくれる。

前処理
   1)各レジスタの値をスタックに退避する

後処理
   6)各レジスタにスタックから退避させた値を復帰する
   7)割り込みリターンする

(3)残されたことは,割り込み関数 「TimerIntFunc」 には次のことを記述すればよい。

1)実行したい内容
2)タイマユニットの割り込みフラッグをクリア

割り込み関数の例

#pragma interrupt(TimerIntFunc)/*この名前の関数は割り込みルーチン仕様である*/
                            /*プログラム中から呼び出してはならない*/
void TimerIntFunc()
{
    static int tick=0;
    if (tick==1) {
        turnOnLed(0);
        turnOffLed(1);
    } else {
        turnOffLed(0);
        turnOnLed(1);
    }
    tick=1-tick;
    ITU1.TSR.BIT.IMFA=0; /*Clear IMFA*/
}


なおタイマ割り込みが起こった時の処理は次のようになる。

時間の経過 割り込みベクタ 割り込み関数
TimerIntFunc
  0x70にTimerIntFunc  
 ↓   レジスタ退避(この部分はコンパイラが作ってくれる)
 ↓   タイマ割り込みで実行したい内容
割り込み要因解除
 ↓   レジスタ復帰(この部分はコンパイラが作ってくれる)
割り込みルーチンリターン(この部分はコンパイラが作ってくれる)


【4】 タイマ割り込みの起動
プログラムの起動時にh8-01.h中にある「タイマ割り込み初期化関数」と「CPUの割り込み許可関数E_INT()」を呼び,「タイマ起動関数」を呼ぶと,「タイマ割り込み初期化関数」で設定した時間間隔でタイマ割り込みが起こり,「割り込み関数timer1INT() 」が周期的に起動するようになる。

なお「タイマ割り込み初期化関数」と「タイマ起動関数」は2組あり,組み合わせを崩してはならない。
(1)短周期割り込み(ITU1による割り込み)

タイマ割り込み初期化関数

void initTimer1Int(unsigned short int period)

意味

ITU1による割り込みタイマーの設定
 割り込み間隔は引数peiodで単位はμsecである
 値は32767以下でなければならない。32.767msecまで設定可能
割り込み要因はIMIA1信号である

タイマ起動関数

void startTimer1(void)

意味

Timer CH1 スタート

(2)長周期割り込み(ITU0とITU1の両方を使用した割り込み)

タイマ割り込み初期化関数

void initTimer01Int(unsigned short int period)

意味

ITU0とITU1による割り込みタイマーの設定
(ITU0のオーバーフローを利用したITU1の割り込み)
 割り込み間隔は引数peiodで単位はmsecである
 値は65535以下でなければならない。65.535secまで設定可能
 ただしポートPAの第3ビットが使用できなくなるので注意
割り込み要因はIMIA1信号である

タイマ起動関数

void startTimer01(void)

意味

Timer CH0 CH1 同時スタート

 

【5】 「#pragma interrupt」の役割の検証

「#pragma interrupt」の役割をテストプログラムで見てみよう。
このCソースプログラムで関数「testfunc1()」と関数「testfunc2()」は作業は同じだが,関数「testfunc1()」は割り込み関数であると宣言されている。

Cソースプログラム

int a=0;

#pragma interrupt (testfunc1)
void testfunc1()
{
    a++;
}

void testfunc2()
{
    a++;
}


コンパイラが生成したasmソースでみると,関数「testfunc1()」では,関数の初めに利用しているレジスタの退避コードがあり,関数の終わりにレジスタの復帰コードが生成されている。さらに関数の終わりは,関数「testfunc1()」では,「RTE」(ReTurn from Exception)で終了し,関数「testfunc2()」では,「RTS」(ReTurn from Subroutine)で終了しているのが分る。

コンパイラが生成したasmソース
_testfunc1:
      PUSH.W      R1
      PUSH.L      ER0

;
      MOV.L       #_a:32,ER0   ;a++;
      MOV.W       @ER0,R1      ; :
      INC.W       #1,R1        ; :
      MOV.W       R1,@ER0      ; :
;
      POP.L       ER0
      POP.W       R1

      RTE
;
_testfunc2:
      MOV.L       #_a:32,ER0   ;a++;
      MOV.W       @ER0,R1      ; :
      INC.W       #1,R1        ; :
      MOV.W       R1,@ER0      ; :
;
      RTS