(注意)このブログは本家のほうの文章部分のみの転載です.ソースコードの配布,画像などについては本家のほうを参照してください.文章中のリンク先は面倒なのですべて本家のほうに変換してしまっているのでご注意ください.

ちょっと別件の作業をしていたので滞っていたけど,昨日ようやくそっちがなんとか片付いたので,H8移植をまた進めよう.

今回は,割り込み処理の実装だ.とりあえずタイマ割り込みを実装したい.

で,いろいろちょっと調べたのだけど,今回利用しているH8ボードでは,割り込みベクタがROM上にあるのでソフトウエアから起動後に書き換えるのは無理っぽい.なので,割り込みベクタの管理はブートローダーにまかせるしかない.ていうかブートローダーのROMへの書き込み時に,割り込みベクタもいっしょに書き込まれてしまい,OS起動後に設定しなおすようなことができない.

で,とりあえず割り込みの入口はブートローダーにやってもらって,ソフトウエア的に独自に割り込みベクタみたいなのをRAM上に作っておいて,ブートローダー側ではそのソフトウエア・割り込みベクタを見て適切なアドレスに飛ぶ,というよくありがちな2段構成にしてみよう.これならソフトウエア・割り込みベクタをOS側で書き換えることで,割り込みハンドラを自由に登録できるようになる.

ソフトウエア・割り込みベクタの場所なのだけど,RAMの先頭領域である0xffbf20 から256バイトを割り当てよう.現状でRAMの先頭付近からOSをロードして使うようになっているので,OSのロード先は256バイト後ろにずらして調整する.

ということで実装したのが以下のような感じ.まずブートローダー側の修正なのだけど,割り込みの入口として intr.S を新規追加してある.ちょっとベタな書き方になってしまっていて,ほんとはマクロとか使って短くしたいところなんだけど,なんかうまくアセンブルできなかったので面倒なのでベタで書いてしまった.

ちなみにファイル名が *.s だとプリプロセッサを通さないが,*.S だとプリプロセッサを通して前処理が行われるようだ(man gcc にそう書いてあった).アセンブラのソースで #define とか使いたいときは要注意.なお intr.S は内部ではひとまず #define とかは使ってないので現状ではプリプロセッサを通す必要はないのだけど,将来的に #define でまとめたいので,intr.S というファイル名にしている.

vector.c では割り込みハンドラとしてとりあえずスタートアップを全部に登録していたが,intr.Sで定義してある各割り込みに応じたハンドラを登録するように修正した.

割り込みの入口では,スタックにレジスタを退避して,割り込みベクタ番号を引数にして interrupt() という関数を呼んでいる.interrupt() の本体はinterrupt.c で定義してあって,ソフトウエア・割り込みベクタ(VECTORS[])を参照して,登録してあるハンドラを呼び出すような作りになっている.OSが起動したらここにハンドラを登録しておけば,割り込みが入ったときに適切にハンドラを呼び出してくれるわけだ.割り込み関連の定義は interrupt.h に書いておいた.

あと main.c からソフトウエア・割り込みベクタの初期化用関数であるinterrupt_init()を呼んで,最初はNULLクリアするようにしている.

ブートローダーの修正はこんなもんかな.次にOS側の修正.

まず,RAMの先頭である 0xffbf20 番地はソフトウエア・割り込みベクタを配置するので,プログラムのロード先を256バイト後ろにずらす.このためにリンカスクリプトに以下の修正を入れている.

--- hello/ld.scr Wed Sep 2 23:32:57 2009
+++ timer/ld.scr Mon Sep 14 22:13:00 2009
@@ -4,8 +4,11 @@

MEMORY
{
- /* reserve 256bytes space for ELF header */
- ram(rwx) : o = 0xffbf20 + 0x100, l = 0x004000 - 0x100 /* 16kb */
+ /*
+ * reserve 256bytes space for software interrupt vector
+ * and 256bytes space for ELF header.
+ */
+ ram(rwx) : o = 0xffbf20 + 0x200, l = 0x004000 - 0x200 /* 16kb */
stack(rw) : o = 0xffff00, l = 0x000010 /* end of RAM */
}

まあ,単にずらしただけ.

あとタイマ関連の処理のために timer.[ch] を追加.これらはH8の持つタイマの各種レジスタを操作して,タイマ起動とあと割り込み時の処理を行うもの.ボード付属のCD-ROMについてたH8のマニュアルとか,あと本とか読んでてきとうに書いてみた.とりあえず,1/50秒周期でタイマをかけてカウントして,1秒おきに「I」という文字を出力するようにしてみた.

あと割り込み処理用にブートローダーの interrupt.[ch] を流用して,ソフトウエア・割り込みベクタに割り込みハンドラを登録できるようにした.これは実際には timer.c のタイマ初期化関数である timer_init() で,

int timer_init()
{
...
interrupt_sethandler(VECTYPE_IMIA0, timer_interrupt);
...

のようにして,timer_interrupt() をベクタ番号24(IMIA0)に登録している.

main.c では timer_init() を呼んでタイマの初期化をして,あと ENABLE_INTR を呼んで割り込み有効にしている.

では,実際に実行してみよう.今回はブートローダーに割り込み処理の機能追加がされているので,まずはブートローダーをビルドしてフラッシュROMに上書きする.

teapot# make write
../../h8write/h8write -3069 -f20 kzload.mot
H8/3069F is ready! 2002/5/20 Yukio Mituiwa.
writing
WARNING:This Line dosen't start with"S".
Address Size seems wrong
WARNING:This Line dosen't start with"S".
Address Size seems wrong
.........................................................................
EEPROM Writing is successed.
teapot#

気のせいか,書き込み時の「.」の数が増えたような...intr.S がベタで書いてあるのが効いているのかしら.

次にブートローダーを起動して,OSをダウンロードして実行する.

teapot# cu -l /dev/cuad0
Connected
Hello World!
> load
~CLocal command? lsx hello
Sending hello, 20 blocks: Give your local XMODEM receive command now.
Bytes Sent: 2688 BPS:665

Transfer complete
eceive succeeded.
> run
starting from entry point.boot succeed!
os> IIItest
test
os> IIIsampleI
sample
os> III

なんと驚くことに一発で動いてしまって,作った本人がビックリ.

いやホントに一発で動いたのよ.今回は割り込み周りのコーディングだし,H8のアセンブラってろくに書いたことないし,もっとハマることを覚悟していたのだけど,コーディングしただけであっさり動いてホント,びっくり.

なんつーか,H8は使うのが簡単でとても楽.H8だとシリアルもタイマもちょろっと設定しただけであっさり動くのですげー楽.PowerPCだとこうはいかないよなあ.

とりあえず割り込みも受けられるようになって,なんか随分前進した感じ.次はシリアルの受信割り込みかなあ.