マイクロコンピュータH8/3048におけるタイマ割り込み
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 |
|
; 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)タイマ割り込みの準備
CPU |
タイマユニット | |||
|
→ | 了解 | ||
|
→ | 了解 | ||
|
(2)タイマ割り込みの動作
CPU |
タイマユニット | |||
了解 |
← |
時間ですよ | ||
割り込みの受け付け(次の動作を自動的に行なう) a)CCR([コンディションコードレジスタ)をスタック領域にPUSHする *1 |
時間ですよ | |||
割り込み関数の開始
|
時間ですよ | |||
|
→ | 了解。黙ります。 | ||
|
||||
|
||||
|
*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による割り込みタイマーの設定 |
タイマ起動関数 |
void startTimer01(void) |
意味 |
Timer CH0 CH1 同時スタート |