(H8移植編第6回)割り込み処理を実装する

2009/09/14

あなたは 人目のお客様です.

ちょっと別件の作業をしていたので滞っていたけど,昨日ようやくそっちがなんとか 片付いたので,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だとこうはいかないよなあ.

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


メールは kozos(アットマーク)kozos.jp まで