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

12Nov2002
Copyright(C) TNCT CS Kosaka

【1】 はじめに
小坂の初心者向け開発環境では,タイマ割り込みに関してからくりが仕込んである。この文書はこのからくりについて説明している。

【2】は一般的な内容であり,【3】【4】は初心者向け環境に特有な仕掛けの内容である。

【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'FFDF10
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】 小坂のタイマ割り込みの仕掛け

ここで小坂の仕掛けでは,多少時間は浪費するが以下のようにスタートアップファイル中に記述している。
割り込み要因はITU0のIMIAである。

(1)割り込みベクタ 0X70 に ITU_Iのアドレスを書いておく(関数interrupt_cfunc() のアドレスではない)
(割り込みベクタは4バイトである。)
(2)アドレスITU_Iのところには次の内容を書いておく
   1)各レジスタの値をスタックに退避する
   2)タイマユニットの割り込みを禁止にする
   3)タイマユニットの割り込みフラッグをクリアする
   4)関数interrupt_cfunc() を呼び出す
   5)タイマユニットの割り込みを許可する
   6)各レジスタにスタックから退避させた値を復帰する
   7)割り込みリターンする

具体的には次のように書いてある。

ベクタテーブル部
    .ORG    H'000070  ;IMIA1

    .DATA.L    ITU_I

割り込み関数部
TIER1   .EQU    H'FFFF70
TSR1    .EQU    H'FFFF71
ITU_I:
    PUSH.L    ER0
    PUSH.L    ER1
    PUSH.L    ER2
    PUSH.L    ER3
    PUSH.L    ER4
    PUSH.L    ER5
    PUSH.L    ER6

    BCLR    #0,@TIER1    ;割り込み停止
    BCLR    #0,@TSR1    ;Clear IMFA

    JSR @_interrupt_cfunc

    BSET    #0,@TIER1    ;割り込み再開

    POP.L    ER6
    POP.L    ER5
    POP.L    ER4
    POP.L    ER3
    POP.L    ER2
    POP.L    ER1
    POP.L    ER0
    RTE


この結果,小坂の仕掛けを使用する立場から見ると,タイマ割り込みが生ずると関数interrupt_cfunc() が呼ばれているように見える。

また関数interrupt_cfunc() 内で次の作業は行なう必要がない。

(1)はじめに各レジスタの値をスタックに退避する
(2)タイマユニットの割り込みを禁止にする
(3)タイマユニットの割り込みフラッグをクリアする(割り込み作業が終了したことを知らせ,次の割り込みの準備をする)
(5)タイマユニットの割り込みを許可する
(6)各レジスタにスタックから退避させた値を復帰する
(7)割り込みリターンする(通常の関数では関数リターン)

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

時間の経過 割り込みベクタ 割り込み関数
ITU_I
Cプログラムで見える割り込み関数
interrupt_cfunc()
  0x70にITU_I    
 ↓   レジスタ退避
ITU_I割り込み禁止
割り込み要因解除
 
 ↓     タイマ割り込みで実行したい内容
通常のサブルーチンリターン
 ↓   ITU_I割り込み許可
レジスタ復帰
割り込みルーチンリターン
 


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

なお「タイマ割り込み初期化関数」と「タイマ起動関数」は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 同時スタート