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バイトごと | 割り込みベクタテーブル (割り込み関数の先頭アドレス表) |
さまざまな割り込み関数の先頭アドレスが並んでいる |
0x00100 | プログラムの先頭 | ここに書いてあるプログラムはスタートアップルーチンと呼ばれる。 次の2つのことを行なう (1)Cプログラムのmain()を呼び出す前にすべきことを行なう。 (2)Cプログラムのmain()を呼び出す |
なお,H8/3048では,アドレス0x00000-0x1FFFFの範囲はROM領域(実際にはフラッシュメモリ)になっていて,プログラムを格納でき,電源が切れてもプログラムが消えてしまうことは無い。
GCCでは割り込みベクタ0,すなわち,CPU起動直後のプログラム開始アドレスは,リンカスクリプトに書かれている。
「start」はスタートアップルーチンの先頭アドレス(リセットベクトル)を示しているラベルである。
ラベルは,リンカによって最終的には具体的なアドレスに変換される。
LONG(ABSOLUTE(_start)) /*
vector#0 Reset vector
*/ : : |
【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上に配置することは,リンカスクリプトに記述する。
/*プログラム領域&参照のみのデータ領域*/ .text : { *(.text) /*プログラム領域*/ *(.rodata) /*コンスタント領域(read only)*/ _dataROM_begin = . ; } > rom /*rom領域に配置*/ /*初期値を持つグローバル変数,スタティック変数領域*/ .data : AT (_dataROM_begin) { _dataRAM_begin = . ; *(.data) _dataRAM_end = . ; } > ram /*ram領域に配置*/
/*初期値を持たないグローバル変数,スタティック変数領域*/ |
「.text」,「.data」,「.bss」はセクションの名前である。
また,「.data」セクションはram領域におかれるが,「AT (_dataROM_begin)」によって初期値が「_dataROM_begin」のアドレスに書き込まれる。「_dataROM_begin」のアドレスは「_dataROM_begin = . ;」によって「*(.rodata) /*コンスタント領域(read
only)*/」の直後ということになっている。
【4】変数のメモリ上の配置
Cプログラミングでの変数の内,グローバル変数あるいはstatic変数で,初期値を持つものがある。
これらの変数はプログラム中で値が変更されるため,RAM領域に置かなければならない。ところがRAM領域では電源を切ると値が失われるため,初期値を持たせることが出来ない。
この矛盾を解決するため,初期値をROM領域に生成し,スタートアップルーチンで,起動時にRAM領域にコピーするからくりを使う。
この作業を行なうには次のようにする必要がある。
(1)リンカスクリプトに,RAM上の変数の初期値をROM上に置くことを指示する。
.data : AT
(_dataROM_begin) { _dataRAM_begin = . ; *(.data) _dataRAM_end = . ; } > ram /*ram領域に配置*/ |
/*初期値のある書き換えデータ領域のコピー。ROM->RAM。*/ |
【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 #_initial_stack_point,sp
【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に割り込み関数の先頭アドレスを書いておく必要がある。
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)割り込み要因の解消(割り込みフラッグのクリア)