H8/3052の起動と割り込み
Copyright(C) 01Feb2004
Copyright(C) 22Dec2003
coskx
CS TNCT
【1】はじめに
この文書はH8/3052の起動,メモリ配置,割り込みなどについて記述している。
【2】H8/3052の起動
マイクロコンピュータH8は電源投入直後,あるいはリセット信号入力直後に割り込みベクタ0に格納されているアドレスにある命令から実行を開始する。
割り込みベクタ0とはアドレス0x00000から0x00003までの4バイトのことである。この場所に通常は0x00100が格納されており,アドレス0x100から始まるプログラムが実行される。(割り込みベクタとは,割り込み関数の開始アドレスのことである。ただし0は特別扱いで,起動時の開始アドレスになっている。)
アドレス | 内容 | 備考 |
0x00000〜0x00003 | 0x00100 | 割り込みベクタ0 実行開始アドレス *1 |
0x00004〜4バイトごと | 割り込みベクタテーブル (割り込み関数の先頭アドレス表) |
さまざまな割り込み関数の先頭アドレスが並んでいる |
0x00100 | プログラムの先頭 | ここに書いてあるプログラムはスタートアップルーチンと呼ばれる。 次の2つのことを行なう (1)Cプログラムのmain()を呼び出す前にすべきことを行なう。 (2)Cプログラムのmain()を呼び出す |
なお,H8/3052では,アドレス0x00000-0x07FFFFの範囲はROM領域(実際にはフラッシュメモリ)になっていて,プログラムを格納でき,電源が切れてもプログラムが消えてしまうことは無い。
割り込みベクタ0はスタートアップルーチンが記述されているアセンブリプログラムファイル中に,次のように記述される。
.SECTION
A,DATA,LOCATE=H'000000 RSTVEC: .DATA.L H'00100 ;リセットベクトル |
実行プログラムコードが0x100から始まるとは限らない。実用では,リンカが実行プログラムコードの起点を決定している。
.SECTION
A,DATA,LOCATE=H'000000 RSTVEC: .DATA.L START ;リセットベクトル |
【3】H8/3052のアドレス
AKI-H8/3052は,H8/300Hアドバンスモードのモード7で動作しているこの時のアドレスとメモリの関係は次のようになっている。
メモリの種類 | アドレス | 備考 |
ROM | 0x00000-0x07FFFF (512kbyte) | 実際にはフラッシュメモリ,書き込みモードでのみ書き込むことが出来る。 電源が切れても,内容は保たれる。 割り込みベクタテーブル,プログラム,定数,変数の初期値の格納場所 |
RAM | 0xFDF10-0xFFF0F ( 8kbyte) | 電源が切れると内容は消滅する。 変数,スタック(オート変数,戻りアドレス,レジスタ退避)の格納場所 |
I/Oレジスタ | 0xFFF1C-0xFFFFF (228byte) | I/Oレジスタ |
プログラム・定数をROM上に,変数をRAM上に配置することは,リンカスクリプトに記述する。
START P,C,D(100),X,B(0FDF10) |
【4】変数のメモリ上の配置
Cプログラミングでの変数の内,グローバル変数あるいはstatic変数で,初期値を持つものがある。
これらの変数はプログラム中で値が変更されるため,RAM領域に置かなければならない。ところがRAM領域では電源を切ると値が失われるため,初期値を持たせることが出来ない。
この矛盾を解決するため,初期値をROM領域に生成し,スタートアップルーチンで,起動時にRAM領域にコピーするからくりを使う。
この作業を行なうには次のようにする必要がある。
(1)リンカスクリプトに,RAM上の変数の初期値をROM上に置くことを指示する。
ROM (D,X) |
;move Section D to Section
X |
【5】H8/3052の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/3052のモード7では20ビットのみ使用) |
CCR | 8bitのコンディションコードレジスタ 割り込みマスクはこのレジスタの第7bit(I) |
スタックポインタはサブルーチンコール時の戻りアドレスなど重要な内容の格納場所を示すレジスタなので,CPU起動時にスタック領域の一番後ろを指すようにしておく。
MOV.L #H'FFF10,ER7 ;スタックポインタ設定 |
【6】 割り込み関数
通常の関数はプログラムから呼び出される。しかし割り込み関数は機能ユニットがある状態になった時に,CPUがある作業をしている最中にその作業を中断して,起動する。関数の作業が終わると,元の作業の続きを行なう。プログラム中から呼び出してはいけない関数である。
【7】 「割り込み関数」の起動
7.1 準備
(1)割り込み要因によってあらかじめ定められた割り込みベクタ番号の位置に,割り込みベクタ(割り込み関数の先頭アドレス)を格納しておく。
(2)割り込みを起こす機能ユニットを初期化設定する。
(3)CPUを割り込み受付可能にする。
7.2 起動
H8/3052の割り込みは次のように起こる。
(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命令を置く。 |
プログラム実装には2つの方法がある。
(1)初心者向けの特殊なからくり(小坂) [h8cc.cmd,(startup.obj)使用]
このからくりはタイマ割り込みのみ実装されている。
割り込み関数を次のように作る。この「タイマ割り込み関数」の名前は「ITU_I」にしてある。
1)前処理
a)割り込み処理で使用する汎用レジスタの退避
2)処理
a)処理本体を記述するための普通の関数を呼び出す。(初心者にはこれが割り込み関数に見える)
b)割り込み要因の解消(割り込みフラッグのクリア)
3)後処理
a)退避したレジスタの復帰
b)関数の終わりにRTE命令を置く。そうすると,見せかけの割り込み関数には次の内容を記述するだけになる。
「見せかけの割り込み関数」の名前は「interrupt_cfunc」にしてある。
a)処理本体
(2)通常の割り込み関数 [h8ccBasic.cmd,(startupBasic.src)使用]
「#pragma interrupt」擬似命令で,関数が割り込み関数であると宣言すると,その関数では次の内容をコンパイラが自動的に生成する
1)前処理
a)割り込み処理で使用する汎用レジスタの退避
3)後処理
a)退避したレジスタの復帰
b)関数の終わりにRTE命令を置く。そこで割り込み関数内に次の内容のみを記述すればよい
2)処理
a)処理本体
b)割り込み要因の解消(割り込みフラッグのクリア)
(3)2つの割り込み記述方法の比較(1)で述べたからくりを用いた割り込み記述は,初心者が「タイマ割り込み」の概念を学ぶためのものである。からくりそのものは面倒なため,自分で「タイマ割り込み」を記述する場合は(2)の通常の方法を用いるのが良い。(2)の方法で記述できるようになれば,「タイマ割り込み」以外の「割り込み」も記述できるようになる。