マイクロコンピュータH8/3048foneにおけるタイマ割り込み
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)の無限ループとなる。
割り込みは6.4msec(25MHzの時は6.4msec)ごとに起動し,次のように動作する。
(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=6.4msec 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】 タイマ割り込みの仕掛け
ここで解説するタイマ割り込みの記述方法は汎用性を持つものであるが,「日立評価版コンパイラ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である 値は20971以下でなければならない。20.971msecまで設定可能 割り込み要因はIMIA1信号である |
タイマ起動関数 |
void startTimer1(void) |
意味 |
Timer CH1 スタート |
(2)長周期割り込み(ITU0とITU1の両方を使用した割り込み)
タイマ割り込み初期化関数 |
void initTimer01Int(unsigned short int period) |
意味 |
ITU0とITU1による割り込みタイマーの設定 |
タイマ起動関数 |
void startTimer01(void) |
意味 |
Timer CH0 CH1 同時スタート |
【5】 「#pragma
interrupt」の役割の検証
「#pragma
interrupt」の役割をテストプログラムで見てみよう。
このCソースプログラムで関数「testfunc1()」と関数「testfunc2()」は作業は同じだが,関数「testfunc1()」は割り込み関数であると宣言されている。
Cソースプログラム |
int a=0; |
コンパイラが生成した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 |