SEMB1200A 開発環境の動作確認
SEMB1200A を題材にした、Cygwin 上のクロス開発ツールの使い方と動作確認例です。
インストール方法に関しては Cygwin のインストール を参照してください。
コンテンツ
- Test Program の make
- SEMB1200A への転送と動作確認
- 参考1: SEMB1200A のメモリマップと Boot Sequence
- 参考2: SEMB1200A の開発環境で使用可能な標準関数
Test Program の make
Cygwin 環境上で Test Program を make します。
まず、開発環境のページのソースとコンパイル済みバイナリにある SEMB1200A 用の Test Program ソース testlib_src_A61004.tar.gz を任意のフォルダにダウンロードします。
次に Test Program ソースを展開します。この時、[ダウンロードしたディレクトリ] は Cygwin のインストールの開発環境のバイナリのインストール で説明した Cygwin 環境でのディレクトリになります。
- mkdir [ソースを展開したいディレクトリ]
- cd [ソースを展開したいディレクトリ]
- tar xvzf [ダウンロードしたディレクトリ/testlib_src_A61004.tar.gz]
展開したら make します。
make は Makefile を見て中に書いてあるルールに従って処理を行うコマンドで、コンパイルなどの (通常複数の) 定型処理を何度も行う場合に使用されます。
中身は詳しくは説明しませんが、以下のようになっています。 よく使う構文は2つです。
- 変数の定義
[変数] = [値] - ターゲット(make で行う操作や生成するファイル) の定義と操作方法 ([ターゲット] と [依存リスト] 間および [実行コマンド] の行頭は TAB である必要があります)
[ターゲット]: [依存リスト]
[実行コマンド]
[実行コマンド]
:
Test Program の Makefile は以下のようになっています。# で始まる行はコメントで、説明用に追加しました。なお、"-Wl,--section-start,.text=0x9FC80000" で指定しているアドレスは、プログラムが実行されるアドレスで、後で説明する UX1200E の転送先のアドレスに合わせる必要があります。また、Test Program では 岡田さん 作成の I/O library を使わせてもらっていますので、-lsemb を指定して libsemb.a をリンクしています。
- cat Makefile
# 各種変数の定義 (コマンド名)
AS = mipsel-semb-elf-as
CC = mipsel-semb-elf-gcc
OBJCOPY = mipsel-semb-elf-objcopy
OBJDUMP = mipsel-semb-elf-objdump
# 各種変数の定義 (コンパイルオプション)
CFLAGS = -mips4 -EL -g
ASFLAGS = -mips4 -EL -g
# 各種変数の定義 (リンク時の設定)
LDSCRIPT = /usr/local/mipsel-semb-elf/lib/semb1200a-rom.ld
LDFLAGS = $(CFLAGS) -T$(LDSCRIPT)
LDSTART1 = -Wl,--section-start,.text=0x9FC80000,-Map,check_libsemb_1.map
LDSTART2 = -Wl,--section-start,.text=0x9FD00000,-Map,check_libsemb_2.map
LDSTART3 = -Wl,--section-start,.text=0x9FD80000,-Map,check_libsemb_3.map
# 各種変数の定義 (作成するファイルなど)
TARGET = check_libsemb_1.bin check_libsemb_2.bin check_libsemb_3.bin
OBJS = check_libsemb.o
# all はデフォルトで作成されるターゲット
# "make" は "make all" と同じ
all: $(TARGET)
# check_libsemb_1.bin 〜 check_libsemb_3.bin を作成するためのルール
# $(OBJS) は依存リストで、リストに指定されたターゲットが無いかそのターゲットの
# 依存リストのどれかよりも古い場合にあらかじめ作成される
check_libsemb_1.bin: $(OBJS)
$(CC) -o check_libsemb_1.out $(LDFLAGS) $(LDSTART1) $(OBJS) -lsemb
$(OBJCOPY) -O binary check_libsemb_1.out check_libsemb_1.bin
$(OBJDUMP) -D check_libsemb_1.out > check_libsemb_1.lst
check_libsemb_2.bin: $(OBJS)
$(CC) -o check_libsemb_2.out $(LDFLAGS) $(LDSTART2) $(OBJS) -lsemb
$(OBJCOPY) -O binary check_libsemb_2.out check_libsemb_2.bin
$(OBJDUMP) -D check_libsemb_2.out > check_libsemb_2.lst
check_libsemb_3.bin: $(OBJS)
$(CC) -o check_libsemb_3.out $(LDFLAGS) $(LDSTART3) $(OBJS) -lsemb
$(OBJCOPY) -O binary check_libsemb_3.out check_libsemb_3.bin
$(OBJDUMP) -D check_libsemb_3.out > check_libsemb_3.lst
# make で作成されたファイルを消去する場合によく使用されるターゲット名
# 実行する場合はターゲットを明示して "make clean" とする
clean:
rm -f *.o *.out *.bin *.map *.lst
make を実行して、以下のようなメッセージが表示され、エラーが無く check_libsemb_1.bin, check_libsemb_2.bin, check_libsemb_3.bin ができていれば成功です。
- make
mipsel-semb-elf-gcc -mips4 -EL -g -c -o check_libsemb.o check_libsemb.c
check_libsemb.c: In function `main':
check_libsemb.c:8: warning: return type of 'main' is not `int'
mipsel-semb-elf-gcc
-o check_libsemb_1.out -mips4 -EL -g
-T/usr/local/mipsel-semb-elf/lib/semb1200a-rom.ld
-Wl,--section-start,.text=0x9FC80000,-Map,check_libsemb_1.map
check_libsemb.o -lsemb
mipsel-semb-elf-objcopy -O binary check_libsemb_1.out check_libsemb_1.bin
mipsel-semb-elf-objdump -D check_libsemb_1.out > check_libsemb_1.lst
mipsel-semb-elf-gcc
-o check_libsemb_2.out -mips4 -EL -g
-T/usr/local/mipsel-semb-elf/lib/semb1200a-rom.ld
-Wl,--section-start,.text=0x9FD00000,-Map,check_libsemb_2.map
check_libsemb.o -lsemb
mipsel-semb-elf-objcopy -O binary check_libsemb_2.out check_libsemb_2.bin
mipsel-semb-elf-objdump -D check_libsemb_2.out > check_libsemb_2.lst
mipsel-semb-elf-gcc
-o check_libsemb_3.out -mips4 -EL -g
-T/usr/local/mipsel-semb-elf/lib/semb1200a-rom.ld
-Wl,--section-start,.text=0x9FD80000,-Map,check_libsemb_3.map
check_libsemb.o -lsemb
mipsel-semb-elf-objcopy -O binary check_libsemb_3.out check_libsemb_3.bin
mipsel-semb-elf-objdump -D check_libsemb_3.out > check_libsemb_3.lst - ls -l
total 537
-rwx------ 1 sugi3 default 1286 Oct 4 17:28 Makefile
-rwx------ 1 sugi3 default 262 Oct 4 12:56 check_libsemb.c
-rw-r--r-- 1 sugi3 default 3336 Oct 5 19:41 check_libsemb.o
-rwxr--r-- 1 sugi3 default 3400 Oct 5 19:41 check_libsemb_1.bin
-rw-r--r-- 1 sugi3 default 125125 Oct 5 19:41 check_libsemb_1.lst
-rw-r--r-- 1 sugi3 default 16416 Oct 5 19:41 check_libsemb_1.map
-rwxr--r-- 1 sugi3 default 26503 Oct 5 19:41 check_libsemb_1.out
-rwxr--r-- 1 sugi3 default 3400 Oct 5 19:41 check_libsemb_2.bin
-rw-r--r-- 1 sugi3 default 125063 Oct 5 19:41 check_libsemb_2.lst
-rw-r--r-- 1 sugi3 default 16416 Oct 5 19:41 check_libsemb_2.map
-rwxr--r-- 1 sugi3 default 26503 Oct 5 19:41 check_libsemb_2.out
-rwxr--r-- 1 sugi3 default 3400 Oct 5 19:41 check_libsemb_3.bin
-rw-r--r-- 1 sugi3 default 125125 Oct 5 19:41 check_libsemb_3.lst
-rw-r--r-- 1 sugi3 default 16416 Oct 5 19:41 check_libsemb_3.map
-rwxr--r-- 1 sugi3 default 26503 Oct 5 19:41 check_libsemb_3.out
これでコンパイルの処理は完了です。
SEMB1200A への転送と動作確認
次に作成したバイナリを SEMB1200A に転送して動作確認を行います。
ま
ず、PC と SEMB1200A をマニュアルにしたがって、RS-232C クロスケーブルで接続します。SEMB1200A
には同じコネクタが複数あり、間違えるとボードや PC を破壊する可能性がありますので、マニュアルをよく読んで注意して接続しましょう。
次にターミナルソフトを開き、接続したシリアルポートでコネクションを張ります。ここでは、Windows 標準の「ハイパーターミナル」を使用していますが、他のソフトでも問題ありません。
SEMB1200A に電源を接続すると、bootloader が立ち上がり、コマンド入力待ちになります。メッセージが表示されない場合は、シリアルポートの設定と SEMB1200A の DIP SW の設定がマニュアルの指定と合っているか確認しましょう。
次に、作成したプログラムのオブジェクトを転送します。
"W" そして、"1" を入力します。SEMB1200A が xmodem 受信の待機状態になります。
「ハイパーターミナル」 のメニューから「転送」→「ファイルの送信」を選択します。ファイル名は作成した Test Program のバイナリ (check_libsemb_1.bin)、プロトコルは "Xmodem" を選択します。
[送信 (S)] をクリックすると転送が開始されます。
転
送が終わったら、SEMB1200A が再度コマンド入力待ちになりますので、"R" そして "1" を入力して実行します。下のように
"check_libsemb: Version $Id: version.c,v 1.2 2006/10/04 01:09:07 sugi3
Exp sugi3$"のメッセージ (使用した libsemb.a の version によって異なります)
が出力されれば正常に転送と実行が出来ています。
こ
れでユーザ空間1での転送・実行の確認が終わりましたので、ユーザ空間2および3でも同様に確認します。この場合、転送と実行のアドレス指定で "1"
の代わりに "2" または "3" を、転送するバイナリに check_libsemb_2.bin
または check_libsemb_3.bin を選択します。
アドレスによって、バイナリが異なるの
は SEMB1200A の loader (といっても固定アドレスに飛ぶのみ) がや crt0.o
がアドレスの動的解決をサポートしないためです。OS を搭載しない場合は、loader
を簡略にするためこのようにする場合が多いのですが、代わりに Makefile で指定したような OS
上のアプリケーションのコンパイルでは見慣れない指定が必要になります。実際にはsemb1200a-rom.ld
が主にこのアドレス情報を決定しており、Makefile
での指定は先頭アドレスを変更するためのみに使用されています。check_libsemb_1.map (実際のアドレス情報)
やcheck_libsemb_1.lst (バイナリ逆アセンブル結果)
を見ると、アドレスへの配置状況を見ることが出来ます。また、さらに興味がある方は次の Boot Sequence の説明も読んでみてください。
Test Program の転送と動作確認は、以上で完了です。
参考1: SEMB1200A のメモリマップと Boot Sequence
SEMB1200A のメモリマップは コラム−MIPS のメモリ空間 でも紹介しましたが、以下のようになっています。
MIPS アーキテクチャのプロセッサは論理アドレスの 0xBFC00000 がリセットベクタになりますので、物理アドレスは 0x1FC00000 つまり FlashROM の先頭から実行が開始されます。
UX1200A
ではこの空間はシステム領域となっており、リセット後は bootloader
が実行されるようになっています。ここからの処理の流れを箇条書きにすると以下のようになります。 (bootloader
はソースが非公開なので一部想像 ^_^; を含みます)
- 0xBFC00000 (0x1FC00000) から実行開始
- bootloader が UX1200E をはじめとするデバイスを初期化
- CPU の CP0 レジスタ や Cache (I/D)
- UX1200E の CPU 以外の部分
- その他の I/O など
- bootloader が UART1 にメッセージを出力
- bootloader は DIPSW (の接続されている GPIO) をリード
- リードした値によって直接ユーザプログラムアドレスに分岐
または UART1 からのコマンド待ちで待機 - bootloader がコマンドに応じた処理を実行
bootloader
からユーザプログラムへの制御の引渡しは単にエントリアドレス (0x9FC80000 など)
に飛ぶのみのようです。これ以降はユーザプログラムの領域ですが、単に実行を開始しても C
で作成したプログラムは動作しません。なぜならば、プログラムが書き込まれているのは ROM で、そのままではデータのライトができないためです。
これを処理するのが、crt0 の役目です。したがって、crt0.o は、必ずユーザプログラムの先頭に配置されていないといけません。
SEMB1200A の場合、crt0.o もユーザ作成が前提なので、ここから後はある意味ユーザの自由なのですが、ここではバイナリにも入れさせてもらった岡田さんの crt0.o が必要かつ十分なものなので、この処理を前提にします。
crt0.o は次の処理を行っています。
- bootloader からユーザプログラムの先頭に制御がわたる。
この時、CACHE 可能領域 (0x80000000-0x9FFFFFFF) で呼ばれる - crt0.o は MIPS の ABI で決まられた必要な CPU レジスタの初期化を行う
- crt0.o は RAM 上の初期化が必要な領域の初期化を行う
- crt0.o は FlashROM 上の data section 等 RAM である必要がある領域のデータを
RAM のユーザ空間にコピーする - crt0.o main() に制御を移す
ユー
ザが通常プログラムとして意識するのはこの main() 以降の処理ですが、OS があっても無くても main()
の前にはこれだけの処理が行われています。但し、OS
が搭載されている場合は、プロセスのスケジューリングやメモリ管理などの処理も行われますので、実際にはもっと複雑です。
参考2: SEMB1200A の開発環境で使用可能な標準関数
SEMB1200A など、OS を搭載しない環境では、C の標準関数が全て使用できるわけではありません。これは、これらが通常 OS がサポートするシステムコールを必要としているためです。
も
ちろん OS
を搭載しなくても、必要なシステムコールに相当する処理を別途行えば、全ての関数を使用可能にすることも不可能ではありません。しかし例えばストレージが
無いシステムでファイルシステムをサポートするメリットはありませんので、何らかの制約が付くのが普通です。
SEMB1200A の環境では、nullmon を使用していますので、I/O 関連やメモリ管理に関する標準関数は基本的には使用できないと考えたほうが良いと思います。
I/O 関連の例
標準入出力 = putc(), getc(), puts(), printf(), scanf() など
→ メモリ相手の sprintf() などは使用可能ですが、stdio 族はコードサイズが大きいので注意しましょう
メモリ関連の例
malloc() 関連など
(詳細は後で追加予定です)
Tips: UART への文字列の出力
debug などの用途で UART に文字列を出力する場合下記の2つの書き方ができます。
snprintf(out_str, "hogehohe %s nado\n", get_str());
uart1_puts(out_str);
uart1_puts("hogehoge");
uart1_puts(get_star());
uart1_puts("nado\n");
通
常は、I/O を何度も呼ぶイメージがある下の記述よりも上の記述のほうが綺麗に見えます(よね?)。しかし、SEMB1200A
のような主記憶サイズの制約が大きいシステムの場合、コードサイズを考えると sprintf
は極力使用しないほうが良いでしょう。実際に両方のプログラムを作成してコンパイルしてみるとその差が実感できると思います。
但し、sprintf を一箇所でも使用していれば、何箇所で使用しても増分は同じですので、使用回数を少なくすることには意味はありません。関数の使い方を整理し、必要な関数のみに使用関数種を減らすことがこのような場合には重要です。
な
お、実際には sprintf の処理の大部分は vfprintf が分担しており、例えば sprintf と snprintf
を使用しても2倍になるわけではありません。理解を深めるためには newlib
のソースを読むのが一番ですが、まずはカットアンドトライで感触を掴むのも良いでしょう。
Top
Home
Last Update Oct.06/2006
Copyright (C) sugi3 2006