H8/3048の起動と割り込み


Copyright(C) 01Feb2004
Copyright(C) 22Dec2003
coskx CS TNCT 

【1】はじめに

 この文書はH8/3048の起動,メモリ配置,割り込みなどについて記述している。

 

【2】H8/3048の起動
 マイクロコンピュータH8は電源投入直後,あるいはリセット信号入力直後に割り込みベクタ0「0番地から3番地」に格納されている値をアドレスとする場所にあるプログラムの先頭から実行を開始する。最初に実行するプログラムはスタートアップルーチンと呼ばれる。
スタートアップルーチンはスタックポインタの設定を行い,RAM上に配置された静的変数の初期化を行い,ユーザが記述したCプログラムのmain()を呼び出す作業を行う。

 割り込みベクタ0とはアドレス0x00000から0x00003までの4バイトのことである。この場所に通常は0x00100が格納されており,アドレス0x100から始まるプログラム(スタートアップルーチン)が実行される。(割り込みベクタとは,割り込み関数の開始アドレスのことである。ただしベクタ0は特別扱いで,起動時の開始アドレスが格納されている。)

アドレス 内容 備考
0x00000〜0x00003 0x00100 割り込みベクタ0 実行開始アドレス *1
0x00004〜4バイトごと 割り込みベクタテーブル
(割り込み関数の先頭アドレス表)

さまざまな割り込み関数の先頭アドレスが並んでいる
(使用されないベクタもある)
最終は0x000FFまで

0x00100 プログラムの先頭 ここに書いてあるプログラムはスタートアップルーチンと呼ばれる。
次の2つのことを行なう
(1)Cプログラムのmain()を呼び出す前にすべきことを行なう。
(2)Cプログラムのmain()を呼び出す

*1実際にはスタートアップルーチンの開始アドレスが0x100にあるとは限らないが,割り込みベクタ0に書いてあるアドレスはスタートアップルーチンのアドレスである。

なお,H8/3048では,アドレス0x00000-0x1FFFFの範囲はROM領域(実際にはフラッシュメモリ)になっていて,プログラムを格納でき,電源が切れてもプログラムが消えてしまうことは無い。

割り込みベクタ0,すなわち,CPU起動直後のプログラム開始アドレスは,スタートアップルーチンが記述されているアセンブリプログラムファイル中に,次のように記述される。
(スタートアップルーチンの開始アドレスがh'100の場合)

    .SECTION A,DATA,LOCATE=H'000000
RSTVEC:
    .DATA.L    H'00100        ;リセットベクトル

実行プログラムコードが0x100から始まるとは限らない。実用では,リンカが実行プログラムコードの起点を決定できるように,100ではなく,ラベルを用いて次のように記述している。(スタートアップルーチンの開始位置のラベルが「START」の場合)ラベルは,リンカによって最終的には具体的なアドレスに変換される。

    .SECTION A,DATA,LOCATE=H'000000
RSTVEC:
    .DATA.L    START        ;リセットベクトル

 

【3】H8/3048のアドレス

AKI-H8/3048は,H8/300Hアドバンスモードのモード7で動作しているこの時のアドレスとメモリの関係は次のようになっている。

メモリの種類 アドレス 備考
ROM 0x00000-0x1FFFF (128kbyte) 実際にはフラッシュメモリ,書き込みモードでのみ書き込むことが出来る。
電源が切れても,内容は保たれる。

割り込みベクタテーブル,プログラム,定数,変数の初期値の格納場所
RAM 0xFEF10-0xFFF0F ( 4kbyte) 電源が切れると内容は消滅する。

変数,スタック(オート変数,戻りアドレス,レジスタ退避)の格納場所
I/Oレジスタ 0xFFF1C-0xFFFFF (228byte) I/Oレジスタ

プログラム・定数をROM上に,変数をRAM上に配置することは,リンカスクリプトに記述する。

START P,C,D(100),X,B(0FEF10)

この記述の意味は
  セクションP,C,Dを100番地(16進)から始まるROM上に展開する
  セクションX,BをFEF10番地(16進)から始まるRAM上に展開する
であり,
  セクションP プログラム領域 (Pの名前はコンパイラが命名)
  セクションC 定数領域(Cの名前はコンパイラが命名)
  セクションB 未初期化データ領域(Bの名前はコンパイラが命名)
  セクションD 初期化データ領域(Dの名前はコンパイラが命名)
  セクションX 初期値を持つデータ領域(実行時に参照)(Xの名前はリンカオプションで作業者が命名)
のように使われる。この説明は,「メモリ割り当て」のところで再度説明する


 

【4】変数のメモリ上の配置

Cプログラミングでの変数の内,グローバル変数あるいはstatic変数で,初期値を持つものがある。
これらの変数はプログラム中で値が変更されるため,RAM領域に置かなければならない。ところがRAM領域では電源を切ると値が失われるため,初期値を持たせることが出来ない。
この矛盾を解決するため,リンカは,初期値をROM領域(セクションD)に生成し,RAM領域(セクションX)に変数が存在するようにふるまう機械語プログラムを生成するようにオプション指定できる。そして,スタートアップルーチン(作業者が作る)で,起動時にROM領域(セクションD)の変数をRAM領域(セクションX)にコピーするからくりを作る必要がある。

この作業を行なうには次のようにする必要がある。
(1)初期値をROM領域(セクションD)に生成し,RAM領域(セクションX)に変数が存在するようにふるまう機械語プログラムを生成するようにリンカに指示するオプション。Xの名前はここで初めて命名される。

ROM (D,X)


(2)スタートアップルーチンでコピー作業を行なう。

    ;move Section D to Section X
    MOV.L @_D_Head, ER0      ;source address to ER0
    MOV.L @_X_Head, ER1      ;destination address to ER1
    MOV.L @_D_Size, ER2      ;size to be copied to ER2
    OR.L  ER2,      ER2      ;(ER2 or ER2) to ER2
    JMP @LOOP_11
LOOP_1:
    MOV.B @ER0+,    R3H      ;source byte to R3H with ER0++
    MOV.B R3H,      @ER1     ;R3H to destination
    ADDS  #1,       ER1      ;increment destination address
    DEC.L #1,       ER2      ;ER2--
LOOP_11:
    BNE LOOP_1

 

【5】H8/3048のCPUレジスタの構成

CPU内の汎用レジスタは8個ある。ただし,ER7はスタックポインタとして使われる。

ER0

32bitレジスタ(E0とR0の各16bitレジスタとしても使え,R0はさらにR0HとR0Lの各8bitレジスタとしても使える)

ER1 32bitレジスタ(E1とR1の各16bitレジスタとしても使え,R1はさらにR1HとR1Lの各8bitレジスタとしても使える)
ER2 32bitレジスタ(E2とR2の各16bitレジスタとしても使え,R2はさらにR2HとR2Lの各8bitレジスタとしても使える)
ER3 32bitレジスタ(E3とR3の各16bitレジスタとしても使え,R3はさらにR3HとR3Lの各8bitレジスタとしても使える)
ER4 32bitレジスタ(E4とR4の各16bitレジスタとしても使え,R4はさらにR4HとR4Lの各8bitレジスタとしても使える)
ER5 32bitレジスタ(E5とR5の各16bitレジスタとしても使え,R5はさらにR5HとR5Lの各8bitレジスタとしても使える)
ER6 32bitレジスタ(E6とR6の各16bitレジスタとしても使え,R6はさらにR6HとR6Lの各8bitレジスタとしても使える)
ER7 32bitレジスタ(E7とR7の各16bitレジスタとしても使え,R7はさらにR7HとR7Lの各8bitレジスタとしても使える)

コントロールレジスタには次の2つがある。
PC 24bitのプログラムカウンタ(H8/3048のモード7では20ビットのみ使用)
CCR 8bitのコンディションコードレジスタ 割り込みマスクはこのレジスタの第7bit(I)

スタックポインタはサブルーチンコール時の戻りアドレスなど重要な内容の格納場所を示すレジスタなので,CPU起動時にスタートアップルーチン中でスタック領域の一番後ろを指すようにしておく。

    MOV.L    #H'FFF10,ER7    ;スタックポインタ設定

 

【6】 割り込み関数

通常の関数はプログラムから呼び出される。しかし割り込み関数は機能ユニットがある状態になった時に,CPUがある作業をしている最中にその作業を中断して,起動する。関数の作業が終わると,元の作業の続きを行なう。プログラム中から呼び出してはいけない関数である。

 

【7】 「割り込み関数」の起動

7.1 準備
(1)割り込み要因によってあらかじめ定められた割り込みベクタ番号の位置に,割り込みベクタ(割り込み関数の先頭アドレス)を格納しておく。
(2)割り込みを起こす機能ユニットを初期化設定する。
(3)CPUを割り込み受付可能にする。

7.2 起動
 H8/3048の割り込みは次のように起こる。
(1)割り込みが起こると自動的にCCRの内容と,戻るべきアドレスがスタックに退避される。
(2)必要な作業を行なう
  1)割り込み処理で使用する汎用レジスタの退避
  2)割り込み要因の解消(割り込みフラッグのクリア)
  3)割り込み処理
  4)退避したレジスタの復帰
(3)関数の終わりにRTE命令が置かれており,これを実行するとCCRをもとに戻し,もとの実行アドレスへ戻る。

【8】 「割り込み関数」の起動例(タイマ割り込み)

タイマ割り込みは,ITU(インテグレーテッドタイマユニット Integrated Timer Unit)があらかじめ設定されている時間間隔で引き起こす割り込みである。

この割り込み動作のためには次の内容の準備が必要である。

(1)あらかじめ定められた割り込みベクタ番号の位置に割り込みベクタ(割り込み関数の先頭アドレス)を格納しておく。
例えばITU1のコンペアマッチ割り込みは,割り込みベクタ番号28でアドレス0x0070〜0x0073に割り込み関数の先頭アドレスを書いておく必要がある。

この記述はスタートアップルーチンの書いてあるアセンブリソースファイル中に記述するか,別のアセンブリソースファイルに記述する必要がある。

日立評価版コンパイラVer1,Ver2の場合
「ITU_I」というのは割り込み関数の先頭アドレスの一例
    .ORG    H'000070  ;IMIA1
    .DATA.L    ITU_I

あるいはインラインアセンブリ記述を行なう。

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

GCCの場合はリンカスクリプトに記述する。

GCCのリンカスクリプト場合は3項演算子を使って記述する
もしint_imia1という関数が使われていたらその先頭アドレスを登録,そうでなかったらstartを登録
        LONG(DEFINED(_int_imia1)?ABSOLUTE(_int_imia1):ABSOLUTE(_start))

(2)割り込み要因が割り込み要求を発生するように初期化する。
例えばITU1のコンペアマッチ割り込みを行なうためにinitTimer1INT()のような関数で初期化し,startTimer1()で起動する。

この記述はCソースファイル中かあるいはインクルードファイル中に書く

    initTimer01Int(500); /*時間割り込み500msec ch0,ch1使用*/
    E_INT();        /*CPU割り込み許可*/
    startTimer01();  /*時間割り込みタイマスタートch0,ch1*/

(3)割り込みを受け付けるためには,CPUの割り込みマスクがクリアされている必要がある。(CCRのIビット)
E_INT()の関数を使う。この作業はC言語では記述できないため,アセンブリ言語で書かれた関数になる。

この記述はスタートアップルーチンの書いてあるアセンブリソースファイル中に記述しておけばよい。

    .SECTION P,CODE,ALIGN = 2;
_E_INT:
    ANDC.B   #B'01111111,CCR    ;Clear Interrupt mask = Enable Interrupt
    RTS
    .SECTION P,CODE,ALIGN = 2;
_D_INT:
    ORC.B    #B'10000000,CCR    ;Set Interrupt mask = Disable Interrupt
    RTS

 

【9】 「割り込み関数」の実装例

割り込み関数は次の作業を行なう

  1)前処理
     a)割り込み処理で使用する汎用レジスタの退避
  2)処理
     a)処理本体
     b)割り込み要因の解消(割り込みフラッグのクリア)
  3)後処理
     a)退避したレジスタの復帰
     b)関数の終わりにRTE命令を置く。


#pragma interrupt」擬似命令で,関数が割り込み関数であると宣言すると,その関数では次の内容をコンパイラが自動的に生成する

  1)前処理
     a)割り込み処理で使用する汎用レジスタの退避


  3)後処理
     a)退避したレジスタの復帰
     b)関数の終わりにRTE命令を置く。

そこで割り込み関数内に次の内容のみを記述すればよい

  2)処理
     a)処理本体
     b)割り込み要因の解消(割り込みフラッグのクリア)