読み始めた「12ステップで作る組込みOS自作入門」の本ですが、gccだと書籍の内容そのままで楽しくないので、H8の製造元であるルネサスが出しているIDEのHEWを使用して同じものを作ってみる事にします。
ターゲットのマイコンも,書籍ではH8/3069Fで説明していますが、H8/3694Fをターゲットに作成します。
このチップを使用する理由は、”たまたま手元にあったから”というだけです。
3069FはフラッシュROMが512Kbteなのに対して、3694Fのほうは32kByteしかないので途中で容量不足になりそうな気もしますが、出来るところまで作ってみて足りなくなったらその時に考える事にします。
また、プログラムは書籍に書かれている内容を参考にしつつ、基本的には自分で考えて作ることにします。
ですので一部引数の型が違っていたり、関数仕様は同じだけど中身のロジックが異なる場合も有ります。
というわけで、早速1stステップのプログラムを作成します。
読書メモのほうにも書きましたが、1stで作るのは以下の8ファイルです。
[読書メモ]12ステップで作る組込みOS自作入門
main.c hello worldの本体 startup.s スタートアップルーチン/ラベル_startが存在する アセンブラで記述されており、ここからmain()がコールされる vector.c 割り込みベクタ 今は、start()しかなく、これはstartup.sの_startラベルに対応している lib.h/lib.c 標準ライブラリ関数を入れるところ 今はputs,putcしかない。 putsはputcを使って実装されている (なので、今のところputcだけがHW依存になっている) putcはserial.cの関数を使っている serial.h/serial.c シリアルデバイスドライバ 0xffffb0,ffffb8,ffffc0のアドレスを直指定で、データのr/wを行っている。 (シリアルは3つあるらしくプログラムでは0xffffb8から8byte分を使っている) クロックを元に9600bpsを算出してるっぽい。 defines.h NULLとかuint8,uint16などのdefine ld.scr リンカスクリプト ここで、各処理が何番地にマッピングされるかを決めているらしい。 割り込みベクタはCPUの仕様として0番地から始まるので、開始アドレスを決めている。 ここは、まだ良く分からないけど3章で説明されるらしい... Makefile 普通のmakefile |
HEWを使っていると自動生成してくれるモノもありますが、その辺りも含めてどんなコードになったかを順に見ていきます。
main.c
main関数の中身は同じです。
#include "serial.h" #include "lib.h" void main(void); #ifdef __cplusplus extern "C" { void abort(void); } #endif void main(void) { serial_init(); puts( "hello world\n" ); // 無限ループ while( 1 ) ; return; } #ifdef __cplusplus void abort(void) { } #endif |
書籍の内容と比べると、abort()関数が追加されていますが、これはHEWがプロジェクト作成時に自動生成したものです。
これはHEWのコンパイラがC++も対応しているのが理由で、#ifdef __cplusplusで囲まれているのを見ても分かりますが、C++コンパイラ用のコードです。今回はC言語で書くので無視して良いです。
startup.s
書籍ではスタートアップルーチンを自作してますが、HEWでは完全に自動生成なので、何もする必要がありません。HEWではresetprg.cというファイルに記載されています。自動生成されたコードはC++コンパイラ対策のifdefなどが大量にあり見づらいので、C言語の場合に有効となるコードだけを抜粋します。
/***********************************************************************/ /* FILE :resetprg.c */ /* DATE :Tue, Sep 11, 2012 */ /* DESCRIPTION :Reset Program */ /* CPU TYPE :H8/3694F */ /* This file is generated by Renesas Project Generator (Ver.4.16). */ /***********************************************************************/ #include <machine.h> #include <_h_c_lib.h> #include "typedefine.h" #include "stacksct.h" extern void main(void); __entry(vect=0) void PowerON_Reset(void); #pragma section ResetPRG __entry(vect=0) void PowerON_Reset(void) { set_imask_ccr((_UBYTE)1); _INITSCT(); HardwareSetup(); // Use Hardware Setup set_imask_ccr((_UBYTE)0); main(); sleep(); } |
まずプログラムがアセンブラではなく、C言語になっています。
関数名も_startではなくPowerON_Reset()と分かり易いです。
set_imask_ccrは割り込みの無効化処理で、割り込みを一時的に無効にして2つの関数をコールしています。
_INITSCT()はアセンブラで実装されているらしいのですが、まだ良く分かってないので一旦無視します。
HardwareSetup()はプログラマが中身を作る関数です。今回は空っぽのままなので何もしません。
その後main()を呼び、もしmainが終了した場合はCPUがスリープ状態に入ります。組み込みの場合main()の最後に無限ループを入れるのがセオリーですが、万が一戻ってきた場合でも対応出来る様になってます。関数定義の頭にある__entry(vect=0)というのは拡張構文で、これによって割り込みベクタへの登録も行っているようです。
__entryの意味は、ルネサス提供のコンパイラマニュアルで説明があります。
(b) ベクタテーブル生成機能 #pragma interrupt, #pragma indirect, #pragma entry および__interrupt, __indirect, __entry のvect 指定を用いて、自動的に関数のベクタテーブルを生成できます。 |
vector.c (改めintprg.c)
割り込みベクタもHEWが自動生成してくれています。名前はintprg.cとなっていましたが、多分INTerrupt PRoGramの略でしょう。#include <machine.h> #pragma section IntPRG // vector 1 Reserved // vector 2 Reserved // vector 3 Reserved // vector 4 Reserved // vector 5 Reserved // vector 6 Reserved // vector 7 NMI __interrupt(vect=7) void INT_NMI(void) {/* sleep(); */} // vector 8 TRAP #0 __interrupt(vect=8) void INT_TRAP0(void) {/* sleep(); */} // vector 9 TRAP #1 __interrupt(vect=9) void INT_TRAP1(void) {/* sleep(); */} // vector 10 TRAP #2 __interrupt(vect=10) void INT_TRAP2(void) {/* sleep(); */} // vector 11 TRAP #3 __interrupt(vect=11) void INT_TRAP3(void) {/* sleep(); */} // vector 12 Address break __interrupt(vect=12) void INT_ABRK(void) {/* sleep(); */} // vector 13 SLEEP __interrupt(vect=13) void INT_SLEEP(void) {/* sleep(); */} // vector 14 IRQ0 __interrupt(vect=14) void INT_IRQ0(void) {/* sleep(); */} // vector 15 IRQ1 __interrupt(vect=15) void INT_IRQ1(void) {/* sleep(); */} // vector 16 IRQ2 __interrupt(vect=16) void INT_IRQ2(void) {/* sleep(); */} // vector 17 IRQ3 __interrupt(vect=17) void INT_IRQ3(void) {/* sleep(); */} // vector 18 WKP __interrupt(vect=18) void INT_WKP(void) {/* sleep(); */} // vector 19 Timer A Overflow __interrupt(vect=19) void INT_TimerA(void) {/* sleep(); */} // vector 20 Reserved // vector 21 Timer W __interrupt(vect=21) void INT_TimerW(void) {/* sleep(); */} // vector 22 Timer V __interrupt(vect=22) void INT_TimerV(void) {/* sleep(); */} // vector 23 SCI3 __interrupt(vect=23) void INT_SCI3(void) {/* sleep(); */} // vector 24 IIC2 __interrupt(vect=24) void INT_IIC2(void) {/* sleep(); */} // vector 25 ADI __interrupt(vect=25) void INT_ADI(void) {/* sleep(); */} |
__interruptで諸々の割り込みハンドラの関数を登録しています。
全て何もしない関数になってます。
lib.h/lib.c
lib.h、lib.cの中身は書籍と同じです。特に書くべきことが無いので省略します。serial.h/serial.c
serial.hも書籍と同じです。serial.cはHEWがI/Oレジスタの定義用変数を用意してくれているので、中身が大きく変わります。
(といっても、行っている内容は同じですが…)
#include "serial.h" #include "iodefine.h" #define SCI_9600_BPS (64) //*************************************************************************** // Function : serial_init //*************************************************************************** int serial_init( void ) { int i; IO.PMR1.BIT.TXD = 1; // port1の役割を決める(シリアルとして使用) SCI3.SCR3.BYTE = 0; SCI3.SMR.BYTE = 0; // all0で 8bit, N, 1になる SCI3.BRR = SCI_9600_BPS; // 20MHzの場合は、64を指定で9600bpsになる // ちょっとスリープする for ( i = 0; i < 10000; i++ ) /* nop */; SCI3.SCR3.BYTE = 0x30; // RxEnable and TxEnable SCI3.SSR.BYTE = 0x00; } //*************************************************************************** // Function : serial_is_send_enable //*************************************************************************** int serial_is_send_enable( void ) { if ( SCI3.SSR.BIT.TDRE == 1 ) { // Txがempty -> 送信可能 return 0; } // 送信NG return 1; } //*************************************************************************** // Function : serial_send_byte //*************************************************************************** int serial_send_byte( unsigned char c ) { // 送信可能になるまで待つ while( 1 ) { // 送信可能かチェック if ( serial_is_send_enable() == 0 ) { break; } } // 1byte送信する SCI3.TDR = c; // TRREのビットを落として(1:not empty)、送信を要求する SCI3.SSR.BIT.TDRE = 1; } |
HEWが用意したiodefine.hというヘッダを使用しているため、書籍ではソースの先頭にあった膨大な定義は全て省略できました。コード中で使用しているSCI3構造体はiodefine.hで定義されているのですが、この説明をここで行うと長くなるので後で別記事に書きました。
HEWが自動生成したiodefine.hの読み方
あと、書籍で使っているH8/3069FはSCIが3つありますが、3694Fにはシリアルが1個しかないので、引数のindexを削除しました。indexはポートを指定するのに使われていたものです。
defines.h
深い理由は有りませんが,deines.hは作ってません。(後で作るかも)
ld.scr
リンカスクリプトですが、これもHEWが自動生成するので何もすることはありません。HEWの場合はファイルではなく、コマンドライン引数で指定するらしいです。
これを確認するためには、以下の手順で操作します。
メニューより、ビルド->H8S,H8/300 Standard Toolchainを選択します。

最適化リンカタブの一番下に有るオプションに記載されています。

-noprelink -rom=D=R -nomessage -list="$(CONFIGDIR)\$(PROJECTNAME).map" -nooptimize -start=PResetPRG,PIntPRG/0400,P,C,C$DSEC,C$BSEC,D/0800,B,R/0FB80,S/0FE80 -nologo -output="$(CONFIGDIR)\$(PROJECTNAME).abs" -end -input="$(CONFIGDIR)\$(PROJECTNAME).abs" -form=stype -output="$(CONFIGDIR)\$(PROJECTNAME).mot" -exit |
色々なオプションが指定されていますが、-startオプションで各領域の先頭アドレスが記載されています。一旦*.absというバイナリを生成した上で、モトローラSフォーレコードフォーマットの*.motファイルを作ってるようです。
*.absというのは聞いたことがない拡張子ですが、エディタで中身を見たらELFフォーマットのバイナリでした。ELFは書籍の5stステップで説明があります。
Makefile
これもIDEによる自動生成です。リンカについては、前述のリンカオプションでだいたい説明しました。
ビルドオプションのコンパイラタブでは、以下のオプションが定義されていました。
-cpu=300HN -object="$(CONFIGDIR)\$(FILELEAF).obj" -outcode=sjis -debug -nolist -chgincpath -nologo |
gccでは-Wallオプションですべての警告を表示させていますが、HEWでは表示されない警告も有ります。HEWでも-Wallと同等のことを行うことも可能ですが、方法は改めて説明します。
-> [HEW]コンパイル時,gcc -Wallのように全ての警告メッセージを表示させる方法
あと、H8に詳しい人から教えてもらったのですが、H8マイコンについて知りたい場合は、
以下の本が良いらしいです。

C言語でH8マイコンを使いこなす
著者が、H8の製造元であるルネサスの方で、しかも講師をしている人なので内容も分かりやすく深いところまで書かれています。読む上でC言語の知識は必要ですが、本記事を見ている(OS自作に興味がある)人はその辺は問題ないでしょうから、手元に置いておく事をお勧めします。
ここまで作った分のソース(HEWのプロジェクトファイル)です。
ダウンロード: kozos_1st.zip
関連記事
コメントを残す