リンカスクリプトとは、プログラムやデータを物理的なメモリに配置する方法を 記したスクリプトです。PCでアプリケーションプログラムを作成する分にはメモ リの何番地にプログラムがロードされるかなんてことは全然気にしなくていいの ですが、組み込み用プログラムを書く上ではこれが非常に重要になってきます。
リンカスクリプトは、対象となるCPUボードのメモリ量やメモリ配置によって書 き直す必要があります。ただし、H8に外部メモリを接続せずに内部のROMとRAMだ け使うのであれば、サンプルのリンカスクリプトで十分です。でも、これが何を やっているのかをきちんと把握するのはいいことだと思います。
リンカスクリプトでは、次のことがらが指定されます。
これから、次のことを順に説明していきます。
H8では、メモリの配置についていくつかのオプションがあります。
これらのオプションを組み合わせた7つの動作モードが存在し、CPUのモードピン の状態をHigh/Lowにすることでハードウェア的に選択されます。
これからは、一番良く使うであろうモード7(外部バス無効モード)について解説 していきたいと思います。
このモードのメモリマップを以下に示します。
H'00000 +-------------+ |Vector | +-------------+ |On-chip ROM | H'1FFFF +-------------+ H'FEF10 +-------------+ |On-chip RAM | H'FFF0F +-------------+ H'FFF1C +-------------+ |I/O Registers| H'FFFFF +-------------+
H8ブートモードは、シリアルI/Oを通してプログラムをCPUにロードさせるモード です。外部メモリの無いボードでは、これがターゲットCPUにプログラムをロー ドさせる唯一の方法です。普通はフラッシュROM書き込み用のプログラムをこの モードで送り、その後でプログラム本体を書き込むという二段構えの方法を取り ます。
H8ブートモードでは、プログラムは内部RAMに読み込まれます。このため、ROMに
書き込む普通のプログラムとH8ブートモード用のプログラムとでは違うリンカス
クリプトを使用しなくてはなりません。
H8では、割り込みベクタはメモリの先頭(H'00000)に置かれています。この場所 は内部ROMに当たることに注意しなくてはなりません。つまり、割り込みベクタ はプログラム実行中に書換えることができないのです。そのため、割り込みベク タはリンカスクリプトによって事前に与えなくてはなりません。
H8はメモリマップドI/O方式であり独立のI/O空間はないため、I/Oレジスタはメ モリに割り付けられています。このアドレスをリンカスクリプトで指定すること もできますが、次のようなある意味汚い方法でアクセスしてしまうのが一般的で す。
aValue = *(char *)0xfffd6;
次に、グローバル変数の初期値についてです。PCではグローバル変数はOSによっ て初期化されRAMに格納されますが、組み込み機器ではグローバル変数の初期化 ルーチンを自分で用意しなくてはなりません。一番簡単な初期化方法はmain関数 の中で初期化することですが、Cプログラムに手を加えずにすむ次の方法をとる のが一般的です。
これを実行するために、リンカスクリプトとブートストラップルーチンを書き換
える必要があります。
ここでは、h8rom.x
というファイル名のリンカスクリプトの内容を
順に見ていきます。詳しい説明はld
のマニュアルを見て下さい。
OUTPUT_FORMAT("coff-h8300") OUTPUT_ARCH(h8300h)
スクリプトの頭では、デフォルトオプションを指定します。ここでは、
しています。GCCのオプション-mhを付けるとH8/300H用のコードを出力 します。
ENTRY("_start")
次に、プログラムの開始場所を指定します。ここではラベル_startか ら始まることを指定しています。このラベルはCのブートアップルーチンにあり ます。
この指定は形式的に付けていますが、最終的なROMイメージを作る時には意味を なしません。この指定と無関係に、リセットベクタに書いてある番地からプログ ラムが実行されます。
MEMORY { vectors(r) : o = 0x0000, l = 0xff rom(rx) : o = 0x100, l = 0x1ff00 ram(rwx) : o = 0xfef10, l = 0x1000 /* The top of main RAM, which the stack starts. */ topram(rw) : o = 0xffefc, l = 0x4 }
MEMORYコマンドは物理メモリのマップを定義します。ここで、割り込 みベクタ、内部ROM、内部RAM、RAMの最終番地とサイズをそれぞれ定義します。 oが番地で、lがサイズです。
SECTIONS { .vectors : { LONG(ABSOLUTE(_start)) /* 0 Reset vector */ LONG(ABSOLUTE(_start)) /* Reserved */ ...... FILL(0xff) } > vectors
次に、SECTIONSコマンドでデータをどのように物理メモリに割り当て るかを指定します。この例では割り込みベクタを指定しています。 例えば一番目の項目は「_startの絶対番地をメモリに書け」という指 定です。
.text : { *(.text) *(.strings) *(.rodata) _etext = . ; } > rom .tors : { ... }
ここではROMに割り付けられるデータを指定しています。text、
string、rodataはそれぞれプログラムコード、文字列、定数
を表わすセクション名で、GCC
がつけます。*(XXX)とい
う記法は、「XXXの中身をこの場所に書け」という指定です。
そして、_etextというラベルを.rodataの最後に定義してい ます。このラベルはプログラム中で使用できます。
.torsではC++のコンストラクタとデストラクタを配置しています。
.data : AT ( ADDR(.tors) + SIZEOF(.tors) ){ ___data = . ; *(.data) *(.tiny) _edata = .; } > ram
.dataセクションでは初期値のあるグローバル変数を配置しています。 前にも述べたように、これはROMに書き込まれ、RAMに配置されます。 AT命令は書き込むときのアドレス、> ramはRAMに配置する ことを示しています。
.bss : { _bss_start = . ; *(.bss) *(COMMON) _end = . ; } >ram .stack : { _stack = . ; *(.stack) } > topram
bssと.stackは初期化されないデータです。これらは最初か
らRAMに格納されます。.stackは後から前へ使っていきますので、
RAMの最後の番地に設定します。
kp9m-iwt@asahi-net.or.jp