AKI-SH2/7045の起動と割り込み


Copyright(C) 20Feb2004
coskx 

【1】はじめに

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

 

【2】SH8/7045の起動
 マイクロコンピュータSH2は電源投入直後,あるいはリセット信号入力直後に割り込みベクタ0に格納されているアドレスにある命令から実行を開始する。

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

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

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

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

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

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

GCCでは割り込みベクタ0はリンカスクリプトに書かれている。
「start」はスタートアップルーチンの先頭アドレス(リセットベクトル)を示している。

LONG(ABSOLUTE(_start))

 

【3】SH2/7045のアドレス

AKI-SH2/7045は,モード2で動作しているこの時のアドレスとメモリの関係は次のようになっている。

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

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

0x00400000〜変数領域
0xFFFFF000スタック領域(オート変数,戻りアドレス,レジスタ退避)の格納場所
I/Oレジスタ 0xFFFF8000〜0xFFFF87FF (2kbyte) I/Oレジスタ

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

    .text : {
        CREATE_OBJECT_SYMBOLS
        *(.text)                     /*すべてのソースファイルの.textセクションをここに*/
        _dataROM_begin = .;          /*変数_dataROM_beginにカレントのアドレスを代入*/
    }  > rom                         /*ここまでをrom領域に割り付ける*/
    .data : AT (_dataROM_begin) {    /*AT 初期値データを_dataROM_beginに書き込む*/
        _dataRAM_begin = .;          /*変数_dataRAM_beginにカレントのアドレスを代入*/
        *(.data)                     /*すべてのソースファイルの.dataセクションをここに*/
        _dataRAM_end = .;            /*変数_dataRAM_endにカレントのアドレスを代入*/
    } > ram2                         /*ここまでをram2領域に割り付ける*/
    .bss : {
        _bss_begin = .;              /*変数_bss_beginにカレントのアドレスを代入*/
        *(.bss)                      /*すべてのソースファイルの.bssセクションをここに*/
        _bss_end = .;                /*変数_bss_endにカレントのアドレスを代入*/
    }  >ram2                         /*ここまでをram2領域に割り付ける*/

「.text」,「.data」,「.bss」はセクションの名前である。
また,「.data」セクションはram領域におかれるが,「AT (_dataROM_begin)」によって初期値が「_dataROM_begin」のアドレスに書き込まれる。「_dataROM_begin」のアドレスは「_dataROM_begin = . ;」によって「*(.text) 」の直後ということになっている。
 

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

Cプログラミングでの変数の内,グローバル変数あるいはstatic変数で,初期値を持つものがある。
これらの変数はプログラム中で値が変更されるため,RAM領域に置かなければならない。ところがRAM領域では電源を切ると値が失われるため,初期値を持たせることが出来ない。
この矛盾を解決するため,初期値をROM領域に生成し,スタートアップルーチンで,起動時にRAM領域にコピーするからくりを使う。

この作業を行なうには次のようにする必要がある。
(1)リンカスクリプトに,RAM上の変数の初期値をROM上に置くことを指示する。
    .data : AT (_dataROM_begin) {
        _dataRAM_begin = . ;
        *(.data)
         _dataRAM_end = . ;
    }  > ram /*ram領域に配置*/

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

!-----------------------------------------------------------------------------
! ROMに格納されているデータ(変数など)をRAM領域にコピーする
!   RAM_BGN  : データを格納するRAM領域の開始アドレス
!   RAM_END : データを格納するRAM領域の終了アドレス
!   ROM_BGN : 実際にデータが書き込まれているROM領域の開始アドレス
!-----------------------------------------------------------------------------
    MOV.L       RAM_BGN,  r0        ! RAM_BGN -> r0
    MOV.L       RAM_END,  r1        ! RAM_END -> r1
    MOV.L       ROM_BGN,  r2        ! ROM_BGN -> r2
    BRA         LOOP11
    NOP
LOOP1:
    MOV.B       @r2,    r3          ! ROM領域からデータを取得
    MOV.B       r3,     @r0         ! RAM領域へデータをコピー
    ADD         #1,     r0          ! RAM領域を指すアドレスをインクリメント
    ADD         #1,     r2          ! ROM領域を指すアドレスをインクリメント
LOOP11:
    CMP/eq      r0,     r1          ! RAM_BGN == RAM_END ならば T=1
    BF          LOOP1               ! T!=1 ならば LOOP1へ
    NOP
END_LOOP1:

 

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

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

R0

32bit汎用レジスタ

R1

32bit汎用レジスタ

R2

32bit汎用レジスタ

R3

32bit汎用レジスタ

R4

32bit汎用レジスタ

R5

32bit汎用レジスタ

R6

32bit汎用レジスタ

R7

32bit汎用レジスタ

R8

32bit汎用レジスタ

R9

32bit汎用レジスタ

R10

32bit汎用レジスタ

R11

32bit汎用レジスタ

R12

32bit汎用レジスタ

R13

32bit汎用レジスタ

R14

32bit汎用レジスタ

R15 32bitレジスタ(スタックポインタ)

コントロールレジスタには次の2つがある。
SR

ステータスレジスタ 32bitのレジスタ 10ビットのみ使われている
Tビット(コンペア命令の結果True(1)False(0)が格納される)
MASKビット(割り込みマスク4ビット)

GBR グローバルベースレジスタ 32bitのレジスタ
間接アドレッシング
VBR ベクタベースレジスタ 32bitのレジスタ
割り込みベクタのオフセット
通常は0

システムレジスタ
MAC 積和演算のアキュムレータ
PR プロシージャレジスタ 32bit サブルーチン(関数)call時の戻りアドレス格納用
PC プログラムカウンタ 32bit

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

_start:                                 ! プログラム開始位置
!-----------------------------------------------------------------------------
! スタックポインタ設定
!
! 汎用レジスタR0〜R15のうちR15がハードウェアスタックポインタに指定されている
! アドレスは 0xFFFFF000〜0xFFFFFFFF の内臓RAMを使用するため、
! STACK_ROOTは0x0を入れておく
!-----------------------------------------------------------------------------
    MOV.L    STACK_ROOT,    r15

 

【6】 割り込み関数

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

 

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

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

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

【8】 「割り込み関数」の起動例(コンペアマッチタイマ割り込み)

コンペアマッチタイマ割り込みは,CMT(コンペアマッチタイマCompare Match Timer Unit)があらかじめ設定されている時間間隔で引き起こす割り込みである。

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

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

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

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

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

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

    initCMTInt(500000,1,15); /*500000μsec,CMT.ch1使用,割り込みレベル15*/
    startCMT1(); /* コンペアマッチタイマ1スタート */

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

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

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

! void setIntMask(int mask)   r4:mask r2:work r1:srreg
    .align  4
    .global _setIntMask
_setIntMask:
    stc      sr,r1          ! srreg = __sr__
    mov.l    MASKVALUER,r2
    and      r2,r1          ! srreg &= 0xffffff0f
    shll2    r4             ! mask <<= 2
    shll2    r4             ! mask <<= 2
    mov.l    MASKVALUE,r2
    and      r2,r4          ! mask &= 0x00f0
    or       r4,r1          ! srreg |= mask
    ldc      r1,sr
    rts 
    nop

! この関数はIntMaskを返します。
! int getIntMask(void)
    .align  4
    .global _getIntMask
_getIntMask:
    stc      sr,r0
    mov.l    MASKVALUE,r2
    and      r2,r0
    shlr2    r0
    shlr2    r0
    rts 
    nop

 

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

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

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


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

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


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

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

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