SH2/7045におけるタイマ割り込み

01Feb2004
12Nov2002
Copyright(C) TNCT CS Kosaka

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

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

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

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

次のプログラムはタイマ割り込みを用いたLEDのON-OFFを行うものである。
割り込み要因はCMT1を用いている。
通常の電源ONの起動時には0番地に書いてあるアドレスから始まる。すなわち(A)_startから処理が始まる。そのあと変数や必要な設定レジスタを設定し,処理は(B)の無限ループとなる。

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

romsh7045.xの一部

        LONG(DEFINED(_int_cmi0)?ABSOLUTE(_int_cmi0):ABSOLUTE(_start))

inttest.c

/**********************************************************
時間割り込みによってLEDのON-OFFを行う
**********************************************************/
#include "7040S.H"           //IOポートアドレス定義
#include "sh_7045.h"         //基本関数群 sh_7045.hの先頭に説明がある

main()
{
    initLed();
    setIntMask(14);                  //レベル15は割り込み許可
    initCMTInt(500000,1,15); /*500000μsec,CMT.ch1使用,割り込みレベル15*/
    startCMT1(); /* コンペアマッチタイマ1スタート */
    while(1);       /*なにもしないループ*/
}

/******************************************************
コンペアマッチタイマ割り込み関数
関数名はリンカスクリプト中で決められている
int_cmi0はコンペアマッチタイマch0の割り込み
int_cmi1はコンペアマッチタイマch1の割り込み
コンペアマッチフラッグのクリアを割り込み関数中で行なうこと
******************************************************/
#pragma interrupt
int_cmi1()
{
    static int tick=0;
    if (tick==1) {
        turnOnLed(0);
        turnOffLed(1);
    } else {
        turnOffLed(0);
        turnOnLed(1);
    }
    tick=1-tick;
    clearCMFlag1(); /* コンペアマッチフラッグのクリア */
}

 

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

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

CPU

  タイマユニット

    setIntMask(14);                  //レベル15は割り込み許可

私が割り込みを受け付けられるようにします
   
    initCMTInt(500000,1,15); /*500000μsec,CMT.ch1使用,割り込みレベル15*/
CMT(タイマユニット)さん,割り込みレベル14で,0.5secごとに割り込みを入れてね
 了解

    startCMT1(); /* コンペアマッチタイマ1スタート */

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

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

CPU

 

タイマユニット

了解

時間ですよ

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

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

 

時間ですよ
を言い続ける

割り込み関数の開始

    Cソースコードには現れません
    PUSH 〜....

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

時間ですよ
を言い続ける

    if (tick==1) {
        turnOnLed(0);
        turnOffLed(1);
    } else {
        turnOffLed(0);
        turnOnLed(1);
    }
    tick=1-tick;

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

時間ですよ
を言い続ける

    clearCMFlag1(); /* コンペアマッチフラッグのクリア */

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

    Cソースコードには現れません
    POP 〜....

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

    Cソースコードには現れません
    RTE

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

割り込み関数の動作終了
   

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

 

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

ここで解説するタイマ割り込みの記述方法は汎用性を持つものであるが,「GCC」の記述規則に依存するものである。

タイマ割り込み関数の名前を 「_int_cmi1」 とする。(この名前はリンカスクリプトで決まっている)

(1)割り込みベクタ 0X250 に 「_int_cmi1」 の先頭アドレスを書いておく。(割り込みベクタは4バイトである。)
この記述はリンカスクリプトに書いてある。

リンカスクリプト
LONG(DEFINED(_int_imia1)?ABSOLUTE(_int_imia1):ABSOLUTE(_start))

 

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

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

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

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

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

割り込み関数の例

#pragma interrupt /*次の名前の関数は割り込みルーチン仕様である*/
                  /*プログラム中から呼び出してはならない*/
void int_cmi1()
{
    static int tick=0;
    if (tick==1) {
        turnOnLed(0);
        turnOffLed(1);
    } else {
        turnOffLed(0);
        turnOnLed(1);
    }
    tick=1-tick;
    clearCMFlag1(); /* コンペアマッチフラッグのクリア */
}


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

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


【4】 タイマ割り込みの起動
プログラムの起動時にsh_7045.h中にある「タイマ割り込み初期化関数」と「CPUの割り込みマスク設定関数」を呼び,「タイマ起動関数」を呼ぶと,「タイマ割り込み初期化関数」で設定した時間間隔でタイマ割り込みが起こり,「割り込み関数int_cmi1() 」が周期的に起動するようになる。
タイマ割り込み初期化関数で設定する割り込みレベル15にしたら,CPUの割り込みマスク値が15より小さければ,この割り込みを受け付けるようになる。

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

void initCMTInt(unsigned long int period,int ch,int intLebel)

意味

ITU1による割り込みタイマーの設定
/* コンペアマッチタイマによるタイマ割り込みの初期化 */
/* 割り込み間隔は引数peiodで単位はμsecである */
/* 0<period<1170104 なので最長1.170104secまで可能 */
/* チャンネルが2つあるのではchは0,1のどちらかを選ぶ */
/* 割り込みレベルintLebelは1〜15にする */

タイマ起動関数

startCMT1()

意味

CMT CH1 スタート

 

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

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

Cソースプログラム

int a=0;

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

void testfunc2()
{
    a++;
}


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

コンパイラが生成したasmソース

    .global _testfunc1
_testfunc1:
    mov.l r1,@-r15
    mov.l r2,@-r15
    mov.l L2,r2
    mov.l @r2,r1
    mov.l r14,@-r15
    add #1,r1
    mov.l r1,@r2
    mov r15,r14
    mov r14,r15
    mov.l @r15+,r14
    mov.l @r15+,r2
    mov.l @r15+,r1
    rte 
    nop
L3:
    .align 2
L2:
    .long _a
    .align 1
    .align 4
    .global _testfunc2
_testfunc2:
    mov.l L5,r2
    mov.l @r2,r1
    mov.l r14,@-r15
    add #1,r1
    mov.l r1,@r2
    mov r15,r14
    mov r14,r15
    rts 
    mov.l @r15+,r14
L6:
    .align 2
L5:
    .long _a



=push r1
=push r2


=push r14


無意味
無意味
=pop r14
=pop r2
=pop r1












=push r14


無意味
無意味

=pop r14