Yamamoto's Laboratory
 
 
Linux
  設定
  C言語
 
Windows
  設定
 
 

概要

コンパイラーは何を使うか

プロが使うのならともかく,アマチュアーあるいは教育で使うのならできるだけ原始的な開発環境が良いと思っている.原始的なものほど,コンピューターの動作を肌で感じることができるからです.しかし,その分,開発に時間がかかることになるので覚悟が必要です.

ということで,私はgccで開発をすることにします.市販の開発環境に比べて,かなりの手間になりますが,得られるものも大きいと思います.あまり手間をかけないで,H8を使いたい人は市販のものを使うのが良いと思います.

ここでは,gccを使った開発方法について述べます.といっても,私も素人なのでいろいろ調べて分かったことを書きためるつもりです.

全体の流れ

H8をC言語で動かすためには,以下のような作業が必要だ.この中で,4(Sフォーマットへ変換)と5(H8へ転送)の作業が,通常のプログラム開発と異なる.クロスコンパイルを使うことによる.これらの作業を行うためには,さまざまなツールをつかう.開発に使うツールのインストール方法については,環境構築のページに書いているので参考にしてほしい.

1. エディターでプログラム作成. エディター *.c, *.h, *.x, *.S
2. コンパイル h8300-hms-gcc *.o
3. リンク h8300-hms-gcc *.exe
4. モトローラーSフォーマットへ変換 h8300-hms-objcopy *.mot
5. H8へ転送 h8write

H8のプログラム開発は,エディターを使いプログラムソースを書くことからはじまます.必要なソースファイルは,C言語のプログラム(∗.c),ヘッダーファイル(∗.h),リンカースクリプト(∗.x),スタートアップルーチン(∗.S)などです.さらに,Makefileも書く必要があるでしょう.このうち,リンカースクリプトはGNUの開発ツールのスクリプトだし,スタートアップルーチンはアセンブラです.C言語の他にもいろいろ理解する必要があります.

ソースプログラムの役割

先に述べたように,H8のプログラム開発にはC言語のプログラムの他に,ヘッダーファイルやリンカースクリプト,スタートアップルーチンを書かなくてはならない.ヘッダーファイルには,変数やレジスターの定義等を書く.リンカースクリプトには,メモリーへのプログラムの要素(スタートアップルーチンや割り込みベクター,プログラム本体)の配置方法を書く.スタートアップルーチンには,プログラムの本体を実行する前に必要な処理を書く.

メイクファイルについては,説明をするまでもないでしょう.

LEDを数秒点灯させるプログラム

簡単なプログラムをとおして,ソースプログラムの書き方を説明します.ここで使うプログラム例は,スイッチを押すことにより,LEDを数秒間,点灯させるものです.割り込み処理があるできるだけ簡単なプログラムを考えます.そして,ソースファイルも可能な限り関単に記述します.

ソースプログラム

C言語

ふたつのC言語のファイルを使っている.全体の流れを制御するプログラム(led.c)と,初期化などを行うハードウェアーに近い部分を操作するプログラム(h8c.c)である.

led.c

全体を制御するプログラムは次のようになっています.

  • 1行目でレジスターのアドレスを書いているヘッダーファイル(3664.h)をインクルードしている.
  • 2行目で h8c.c の関数のプロトタイプ宣言が書かれたヘッダーファイル(h8c.h)をインクルードしている.
  • 3行目は割り込みの宣言です.この宣言のあとの関数を実行するとレジスターの退避と復元を自動で行います.
  • 4から10行目が割り込みの関数です.大体の動作はコメント文を見れば分かると思います.IRR1とPDR5はレジスターでアドレスは3664.hに書いてあります.また,prohibit_irq()とwait(),permit_irq() はアセンブラ h8s.S に書いています.C言語の関数はコンパイルにより,先頭にアンダースコアがついたラベルに変換されます.したがって,アセンブラ内でアンダースコアーの付いたラベルを書くことにより,C言語からアセンブラを呼び出すことができます.
  • 14から22行目がメイン関数です.初期化するための関数 init_led() と init_irq0() の処理の内容は,h8c.c に書いています.また,スリープモードへ移行する関数 sleep() はアセンブラ(h8s.S)で書いています.
01: #include "3664.h"
02: #include "h8c.h"
03: #pragma interrupt
04: void irq0(void){
05:   prohibit_irq();       /*割り込み禁止*/
06:   IRR1 &= 0xfe;         /*IRQ0 割り込み要求フラグをクリアー*/
07:   PDR5 = 0xff;        /* ポート5の全出力を1に */
08:   wait();               /* 数秒待機 */
09:   PDR5 = 0x00;          /* ポート5の全出力を0に */
10:   permit_irq();         /* 割り込み許可 */
11: }
12: 
13: 
14: int main(void)
15: {
16:   init_led();           /* port5を使うときの初期化 */
17:   init_irq0();          /* irq0割り込みを使うときの初期化 */
18: 
19:   while(1){
20:     sleep();
21:   }
22: }

h8c.c

わざわざ,C言語のプログラムを分割することもないですが,お勉強のためにもうひとつ h8c.c というファイルをつくりました.このファイルの4行目から6行目にかけてグローバル変数を定義して使っていますが,これは後でセクションを学習するためです.

01: #include "3664.h"
02: #include "h8c.h"
03: 
04: unsigned char pmr=0x00;
05: unsigned char pcr=0xff;
06: unsigned char pdr=0x00;
07: //============================================================
08: // port 5の初期化
09: //============================================================
10: void init_led(void){
11:   PMR5 = pmr;           /* 0:汎用入出力 */
12:   PCR5 = pcr;           /* 0:入力 1:出力 */
13:   PDR5 = pdr;           /* Port Data Register  */
14: }
15: 
16: //============================================================
17: // prot 1の割り込み設定
18: //============================================================
19: void init_irq0(void){
20:     permit_irq();      /* 割り込み許可 */
21:     PMR1 |= 0x10;         /* port 1のIRQ0をセット */
22:     IENR1 |= 0x01;        /* enable irq0 */
23: }

アセンブラ

h8s.s

プログラムの一部はアセンブラを使っています.C言語中にアセンブラを埋め込むこともできますが,あまりスマートでは無いので直接書いています.C言語の関数はコンパイルすると,関数名の前にアンダースコアー(_)がつくラベルになるので,アセンブラーにそのラベルを書けばC言語から呼び出すことができます.例えばアセンブラのラベル _sleep はC言語で sleep() として呼出しできます.

01:	.h8300h
02:	.section .text
03:	.global _wait
04:	.global _sleep
05:	.global _permit_irq
06:	.global _prohibit_irq
07:	
08:_wait:
09:	mov.l #0x00500000,er0
10:.L2:
11:	dec.l #1,er0
12:	bne .L2
13:	rts
14:
15:_sleep:
16:	sleep
17:	rts
18:
19:_permit_irq:
20:	andc.b #0x7f,ccr 	; 割り込み許可
21:	rts
22:
23:_prohibit_irq:
24:	orc.b #0x80,ccr		; 割り込み禁止
25:	rts
26:	
27:	.end

ヘッダーファイル

3664.h

H8の内部I/Oレジスターは,ヘッダーファイル3664.hに記述しています.I/Oレジスターの名前&mdash 例えばPDR5など&mdash とアドレスは,ルネサステクノロジー社のマニュアルに書いてあります.他の名前を付けても動きますが,製造元のマニュアルに沿った方が良いでしょう.

ここに書かれている16進数の値は16ビットアドレスです.したがって,C言語では2バイト整数のポインターで表現する&mdashと後で使いやすい.そのためキャストを使って,unsigned char型(2バイト)のポインターに変換しています.volatile修飾子はコンパイラーにこの変数の最適化の処理を禁止する&mdashと宣言するものです.ここではマシン語に近い取扱いをしているので,勝手にコンパイラが最適化して値を変えてしまうと困るからです.そして最後にアスタリスク(*)をつかって,ポインターが示す値にしています.これで代入演算子により,そこのアドレスの値を変えることができます.

01: #define    PDR5   (*(volatile unsigned char*)(0xffd8))
02: #define    PMR1   (*(volatile unsigned char*)(0xffe0))
03: #define    PMR5   (*(volatile unsigned char*)(0xffe1))
04: #define    PCR5   (*(volatile unsigned char*)(0xffe8))
05: #define    IENR1  (*(volatile unsigned char*)(0xfff4))
06: #define    IRR1   (*(volatile unsigned char*)(0xfff6))

h8c.h

01: void init_led(void);
02: void init_irq0(void);

スタートアップルーチン

h8_start.s

01:         ;; H8/3664 Startup routine
02: 
03:         .h8300h
04:         .section .text
05:         .global _start
06: _start:
07:         mov.l #_stack,sp ;; スタックポインターの設定
08: 
09:         ;; ROM領域のデータをRAMへ転送
10: 
11:         mov.l #___dtors_end,er0 ;; .dtorsの終わりのアドレス
12:         mov.l #___data,er1 ;; .dataの開始アドレス
13:         mov.l #_edata,er2 ;; .dataの終わりのアドレス
14: ram_data:
15:         mov.w @er0,r3
16:         mov.w r3,@er1
17:         adds #2,er0
18:         adds #2,er1
19:         cmp.l er2,er1
20:         blo ram_data
21: 
22:         ;; main関数の実行
23: 
24:         jsr @_main
25: sleep:
26:         sleep
27:         bra sleep
28: 
29:         .end

リンカースクリプト

h8_link.x

01:OUTPUT_FORMAT("coff-h8300")
02:OUTPUT_ARCH(h8300h)
03:ENTRY("_start")
04:MEMORY
05:{
06:	vectors : o = 0x0000, l = 0x0034
07:	rom     : o = 0x0034, l = 0x7fcc
08:	ram     : o = 0xf780, l = 0x0400
09:	stack   : o = 0xff80, l = 0x0000
10:}
11:
12:SECTIONS 				
13:{ 					
14:.vectors : {
15:	SHORT(ABSOLUTE(_start))
16:	SHORT(ABSOLUTE(_start))
17:	SHORT(ABSOLUTE(_start))
18:	SHORT(ABSOLUTE(_start))
19:	SHORT(ABSOLUTE(_start))
20:	SHORT(ABSOLUTE(_start))
21:	SHORT(ABSOLUTE(_start))
22:	SHORT(ABSOLUTE(_start))
23:	SHORT(ABSOLUTE(_start))
24:	SHORT(ABSOLUTE(_start))
25:	SHORT(ABSOLUTE(_start))
26:	SHORT(ABSOLUTE(_start))
27:	SHORT(ABSOLUTE(_start))
28:	SHORT(ABSOLUTE(_start))
29:	SHORT(ABSOLUTE(_irq0))
30:	SHORT(ABSOLUTE(_start))
31:	SHORT(ABSOLUTE(_start))
32:	SHORT(ABSOLUTE(_start))
33:	SHORT(ABSOLUTE(_start))
34:	SHORT(ABSOLUTE(_start))
35:	SHORT(ABSOLUTE(_start))
36:	SHORT(ABSOLUTE(_start))
37:	SHORT(ABSOLUTE(_start))
38:	SHORT(ABSOLUTE(_start))
39:	SHORT(ABSOLUTE(_start))
40:	SHORT(ABSOLUTE(_start))
41:        }  > vectors
42:
43:.text 0x0034 : {
44:	*(.text) 				
45:	*(.strings)
46:	*(.rodata) 				
47:   	 _etext = . ; 
48:	} > rom
49:
50:.tors : {
51:	___ctors = . ;
52:	*(.ctors)
53:	___ctors_end = . ;
54:	___dtors = . ;
55:	*(.dtors)
56:	___dtors_end = . ;
57:	}  > rom
58:
59:.data : AT ( ADDR(.tors) + SIZEOF(.tors) ){
60:	___data = . ;
61:	*(.data)
62:	*(.tiny)
63:	 _edata = .;
64:	} > rom
65:
66:.bss : AT ( LOADADDR(.data) + SIZEOF(.data) ) {
67:	 _bss_start = . ;
68:	*(.bss)
69:	*(COMMON)
70:	 _end = . ;  
71:	}  >ram
72:
73:.stack : {
74:	 _stack = . ; 
75:	*(.stack)
76:	}  > stack
77:
78:.stab 0 (NOLOAD) : {
79:	[ .stab ]
80:	}
81:
82:.stabstr 0 (NOLOAD) : {
83:	[ .stabstr ]
84:	}
85:}

メイクファイル

Makefile

01:TARGET		=	h8exp.mot
02:
03:CFLAGS		=	-O -mh -g -mrelax -mint32 -DH8_3664
04:TOOL_PREFIX	=	h8300-hms-
05:CC		=	$(TOOL_PREFIX)gcc
06:AS		=	$(TOOL_PREFIX)as
07:
08:LDSCRIPT	=	h8_link.x
09:CRT0		=	h8_start.s
10:SRCS		=	led.c
11:FUNC		=	h8c.c
12:ASM		=	h8s.s
13:
14:all : $(TARGET)
15:
16:
17:$(TARGET): $(TARGET:.mot=.exe)
18:	$(TOOL_PREFIX)objcopy -O srec $(TARGET:.mot=.exe) $@
19:
20:
21:$(TARGET:.mot=.exe): Makefile $(LDSCRIPT) $(SRCS:.c=.o) $(FUNC:.c=.o)\
22:			$(H8:.c=.o) $(ASM:.s=.o) $(CRT0) $(ASRCS) $(LIBS)
23:	$(CC) $(CFLAGS) -T $(LDSCRIPT) -nostdlib $(CRT0) $(ASRCS)\
24:		$(SRCS:.c=.o) $(FUNC:.c=.o) $(ASM:.s=.o) -o $@ $(LIBS) -lc -lgcc
25:
26:write:
27:	h8write -3664 $(TARGET) /dev/ttyS0
28:
29:clean :
30:	rm -f $(TARGET)
31:	rm -f $(TARGET:.mot=.exe)
32:	rm -f $(SRCS:.c=.o)
33:	rm -f $(FUNC:.c=.o)
34:	rm -f $(ASM:.s=.o)

リンカースクリプトの書き方

統合開発環境をつかうとメモリーの配置はほとんど気にする必要はありません.上手に開発環境がメモリーを割り当ててくれます.それでは,あまり面白くないのでリンカースクリプトを書いて,メモリーの割り当てまで考えてみましょう.コンピューターへの理解が深まると思います.

リンカーとリンカスクリプト

リンカーは,オブジェクトファイルを実行ファイル(機械語)に変換します.このときリンカーは,オブジェクトファイルのセクションを単位として機械語に変換します.この機械語がコンピューターのメモリーにロードされるときにも,このセクション単位でアドレスが決められます(マッピング).したがって,メモリーへのマッピングの仕方は,リンカーがセクションを単位として決めることになります.

リンカーはどのようにしてマッピングを行うのでしょうか? リンカーは,リンカースクリプトにしたがいメモリーのマップのマッピングを行います.プログラマーはリンカースクリプトを書くことによって,H8の好きな場所に機械語を格納することができます.

先に述べたように,リンカースクリプトは,メモリー上の機械語(プログラムやデータ)のアドレスを決めます.H8のプログラムを実行させるためには,次のようなビットパターンを

  • 割り込みベクター:割り込み処理のアドレス
  • スタートアップルーチン:ユーザーが作成したプログラムに先立って実行するルーチン
  • プログラム本体:ユーザーが作成したプログラム本体

をメモリーに適切に配置しなくてはなりません.以降は,これらの配置を決めるリンカースクリプトについて述べます.

セクション

まずは,セクションです.これを理解しないとリンカースクリプトは絶対に書くことができません.

役割

メモリーに格納するプログラムやデータは,セクションという単位で管理します.以下に示すセクションがよく使われます.

.text セクション  プログラムの実行コードのセクションです.通常,ROM領域—H8ではフラッシュメモリー—に格納します.プログラムの命令の部分は実行時に書き換える必要がないので,RAMに入れる必要はありません.電源をOFFにしてもプログラムが消えないので,再ロードしなくても実行ができます.
.data セクション  初期化しているデータのセクションです.これは,ROM領域に書き込まれて,実行時にRAM領域にコピーします.ユーザーが作成したプログラム実行に先だって,スタートアップルーチンにより,ROM領域の .dataセクションのデータをRAM領域にコピーします.このようにすることにより,電源をOFFにしてもデータは残り,実行開始時にはRAMにあるので書き換えが可能になります.
.bss セクション  初期化していないデータのセクションです.これは,RAM領域に格納します.初期化されていないので,プログラム実行開始時,どのような値でもよいからです.加えて,書き換えが可能でなくてはならないからである.
.rodata セクション  値が変わらないデータのセクションです.「文字列定数」や「constで修飾された変数の値」などです.値を書き換える必要が無いので,ROM領域&mdashフラッシュメモリー&mdashに格納します.
.stack セクション  スタック領域&mdash RAMの後ろの方&mdashのセクションです.おもに,ローカル変数の値を格納します.

それでは,作成したプログラムのセクションはどうなっているのでしょうか? アセンブラ言語であれば,ソースにセクションを記述するので分かります.C言語の場合コンパイラーが決めてくれます.コンパイラーが決めたセクションは,その出力であるアセンブラ言語の見ることにより分かります.「h8300-hms-gcc -S ファイル名」でコンパイルすると,アセンブラ言語を得ることができます.一度見ると良いでしょう.

実際にここでの例でセクションを見ましょう.-Sオプション付きで led.c をコンパイルすると,led.sというアセンブラのプログラムができます.これを見ると分かるように,.textというセクションのみで構成されていることが分かります.同様にコンパイルすると,h8c.c は h8c.sになり,.dataセクションと.textセクションからできていることが分かります.次に述べるコマンド objdump を使うと,もうちょっとちゃんとセクションを調べることができます.

セクションを調べる

プログラム中のセクションは,「h8300-hms-objdump -h オブジェクトファイル名」で調べることができます.つぎのようにします.

$ h8300-hms-gcc -c led.c
$ h8300-hms-objdump -h led.o

すると,以下のように表示されるでしょう.

led.o:     ファイル形式 coff-h8300

セクション:
索引名          サイズ      VMA       LMA       File off  Algn
  0 .text         0000004c  00000000  00000000  0000008c  2**1
                  CONTENTS, ALLOC, LOAD, RELOC, CODE
  1 .data         00000000  0000004c  0000004c  00000000  2**1
                  ALLOC, LOAD, DATA
  2 .bss          00000000  0000004c  0000004c  00000000  2**1
                  ALLOC, NEVER_LOAD

三つのセクション(.txt, .data, .bss)の記述がありますが,.textセクション以外はサイズがゼロバイトです.したがって,lec.o は.textセクションのみからできていることが分かります.オブジェクトファイル led.o の中のテキストセクションについては,別のページに簡単に記述します.他のソースプログラムについては,ここのページに示しておきます.

リンカースクリプトの文法

ここの例のリンカースクリプトの内容について述べます.ここでの説明が不足と感じると,を読むとよいでしょう.

もろもろのコマンド

SECTIONコマンド以外のコマンド&mdash リンカスクリプト例の前半部分 &mdashについて説明染ます.最初の方はコマンドはコマンド名から,その働きはだいたい予想できるでしょう.次の通りです.

  • コマンドOUTPUT_FORMATは,リンカーの出力のフォーマットを指定します.
  • コマンドOUTPUT_ARCHは,ターゲットマシンのアーキテクチャーを指定します.
  • コマンドENTRYは,エントリーポイントを指定します.エントリーポイントは実行開始アドレスで,例で示したプログラムはスタートアップルーチンから実行するので,エントリーポイントはそのラベルとなる.
  • コマンドMEMORYは,ターゲットアーキテクチャーのメモリのアドレスと大きさをリンカーに指示します.メモリー領域の名前とメモリー領域のスタートアドレスと,サイズ(バイト)を記述します.

SECTION コマンド

リンカースクリプトのもっとも重要なコマンドは,メモリーの配置を決める SECTION コマンドです.これは少し長くなるので,他のコマンドとは別に説明しておきます.

SECTIONコマンドはリンカーの出力ファイル&mdash通常は実行ファイル&mdashのメモリーでのレイアウトを記述します.オブジェクトファイルのセクションをメモリーのどのアドレスに配置するか&mdashを決めます.ここの例で示したリンカースクリプトのSECTIONコマンドの動作を説明します.

はじめに,割り込みが発生したときのアドレス&madash割り込みベクター&mdashのアドレスを決めています.

.vectors : {
	SHORT(ABSOLUTE(_start))
	SHORT(ABSOLUTE(_start))
	SHORT(ABSOLUTE(_start))

  (長いので省略)

	SHORT(ABSOLUTE(_start))
        }  > vectors

.vectorはセクション名で.後述するロケーションカウンターを設定していませんので,これら割り込みベクターのアドレスは0番地より順番にメモリーに書かれることになります._staartはスタートアップルーチン(h8_start.s)のラベルで,プログラムの開始アドレスです.この例では,irq0を除いて,割り込みが発生したときには_startを動作させるようになっています.

最後のにより,コマンドMEMORYで指定したメモリーのvector領域に配置せよ&mdashと言っている.これがあるため,データ&mdahsここでは割り込みベクター(アドレス)&mdashが指定の領域を越える場合,エラーメッセージを出力します.

SECTIONコマンドでの次のメモリー配置の指定を見ましょう

ドットはロケーションカウンターで,メモリーのアドレスを表します.メモリーにデータを記述すると,自動的にロケーションカウンターは増えます.ここでは,ROM領域の先頭アドレスをロケーションカウンターに代入しています.ロケーションカウンターの値からメモリーに.TEXTセクションの内容が書かれます.

C言語注意事項

h8300-hms-gccのオプション

開発を進めているとき,ソースファイルがどのようにして機械語に変換される課程を調べたいことがある.そのようなとき,h8300-hms-gccをオプション付きで実行させることにより,途中のファイルを見ることができます.

h8300-hms-gcc -E プリプロセッサーでの処理の結果を標準出力に
h8300-hms-gcc -S アセンブラ言語のファイル
h8300-hms-gcc -c オブジェクトファイル

これらのうちアセンブラ言語のファイルは重宝する.C言語のソースがアセンブラに変換される仕方を見るといろいろ勉強になります.

変数の型について

変数の型と大きさ
バイト数 最小値 最大値
char 1 -128 127
unsigned short int 2 0 65535
int 4 -2147483647 2147483647
float 4
double 4

参考資料

  1. gccによるH8マイコン開発には詳しく gcc による開発方法が書かれており,いろいろと参考になった.とくに,コンパイルページはコンパイルオプションについて詳しいので,便利.
  2. GCC:スタートアップルーチンを,スタートアップルーチンの参考にしました.
  3. aKI-H8/3664用スタートアップルーチン「startup3664.src」も,スタートアップルーチンの参考になります.
  4. かなり参考になる.H8 Tinyの部屋
  5. iwatamさんのLinuxでH8のフリー開発環境を!.とくに,「章 3 リンカスクリプト」は,リンカスクリプトの理解に役立った.
  6. リンカの使い方およびスクリプトの書き方は,(株)SRa 先端技術研究所さんのWingnutの中にあるGNU リンカ LD の使い方が大変参考になりました.
  7. 米田聡,ゼロから挑戦!はじめる組込みLinux,ソフトバンククリエイティブ(株),ISBN978-4-7973-3719-9,2007年4月
  8. リンカスクリプト.非常に詳しく説明している.
  9. 西田亙,GNU Development Tools,ISBN4-903708-01-2,2006年8月.
  10. わかばやしたかゆきさんのページ—「infoを読め」と言われたら...&mdashセクションについて詳しく説明している.


no counter