Linux
C言語
Windows
|
H8C言語 (Linux)C言語で LED ピカピカH8 のソフトウェアーを Linux のC言語で開発します. 目次概要コンパイラーは何を使うかプロが使うのならともかく,アマチュアーあるいは教育で使うのならできるだけ原始的な開発環境が良いと思っている.原始的なものほど,コンピューターの動作を肌で感じることができるからです.しかし,その分,開発に時間がかかることになるので覚悟が必要です. ということで,私はgccで開発をすることにします.市販の開発環境に比べて,かなりの手間になりますが,得られるものも大きいと思います.あまり手間をかけないで,H8を使いたい人は市販のものを使うのが良いと思います. ここでは,gccを使った開発方法について述べます.といっても,私も素人なのでいろいろ調べて分かったことを書きためるつもりです. 全体の流れH8をC言語で動かすためには,以下のような作業が必要だ.この中で,4(Sフォーマットへ変換)と5(H8へ転送)の作業が,通常のプログラム開発と異なる.クロスコンパイルを使うことによる.これらの作業を行うためには,さまざまなツールをつかう.開発に使うツールのインストール方法については,環境構築のページに書いているので参考にしてほしい.
H8のプログラム開発は,エディターを使いプログラムソースを書くことからはじまます.必要なソースファイルは,C言語のプログラム(∗.c),ヘッダーファイル(∗.h),リンカースクリプト(∗.x),スタートアップルーチン(∗.S)などです.さらに,Makefileも書く必要があるでしょう.このうち,リンカースクリプトはGNUの開発ツールのスクリプトだし,スタートアップルーチンはアセンブラです.C言語の他にもいろいろ理解する必要があります. ソースプログラムの役割先に述べたように,H8のプログラム開発にはC言語のプログラムの他に,ヘッダーファイルやリンカースクリプト,スタートアップルーチンを書かなくてはならない.ヘッダーファイルには,変数やレジスターの定義等を書く.リンカースクリプトには,メモリーへのプログラムの要素(スタートアップルーチンや割り込みベクター,プログラム本体)の配置方法を書く.スタートアップルーチンには,プログラムの本体を実行する前に必要な処理を書く. メイクファイルについては,説明をするまでもないでしょう. LEDを数秒点灯させるプログラム簡単なプログラムをとおして,ソースプログラムの書き方を説明します.ここで使うプログラム例は,スイッチを押すことにより,LEDを数秒間,点灯させるものです.割り込み処理があるできるだけ簡単なプログラムを考えます.そして,ソースファイルも可能な限り関単に記述します. ソースプログラムC言語ふたつのC言語のファイルを使っている.全体の流れを制御するプログラム(led.c)と,初期化などを行うハードウェアーに近い部分を操作するプログラム(h8c.c)である. 全体を制御するプログラムは次のようになっています.
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: } わざわざ,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: } アセンブラプログラムの一部はアセンブラを使っています.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 ヘッダーファイル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)) 01: void init_led(void); 02: void init_irq0(void); スタートアップルーチン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 リンカースクリプト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:} メイクファイル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について説明染ます.最初の方はコマンドはコマンド名から,その働きはだいたい予想できるでしょう.次の通りです.
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をオプション付きで実行させることにより,途中のファイルを見ることができます.
これらのうちアセンブラ言語のファイルは重宝する.C言語のソースがアセンブラに変換される仕方を見るといろいろ勉強になります. 変数の型について
参考資料
|