(H8移植編その2第1回)タイマを動かしてみよう

2010/04/03

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

組み込みOS本の執筆でしばらくの間KOZOSの開発が止まっていたのだけど, 本の原稿のほうがなんとかなったということで,そろそろまた開発を進めたいと思う.

ブログほうではarduinoに移植するっつー ことを書いたのだけど,なんとなく気が向いたので,KOZOS/H8を機能拡張してみる.

KOZOS/H8に関しては,もともと「移植編その2:KOZOSをH8に移植する」で開発を 進めて,「H8移植編第14回 DRAM上で動かそう」でDRAMを使うとこまでいっているの だけど,書籍用にソースコードをきれいに整理しなおしたので,今後はそっちを ベースにして開発を進めよう.

ということで今回は,「番外編:組込みOS自作本用にKOZOSを改良する」の (OS自作本編第3回)Ubuntu,Fedora,Cygwinで動作確認した(2010/02/28) のソースコードをベースにしてタイマ機能を追加して,動作確認を行ってみた.

H8/3069Fのマニュアル(秋月のH8/3069Fマイコンボードの付属CD-ROMに添付されて います)を見たところ,H8は8ビットと16ビットのタイマを持っているようだ. で,8ビットタイマの方がなんか扱いが楽そうなのと,クロックを8192分割して さらにタイマをカスケード接続して16ビットタイマとすることで長い時間も 測れるようなので,そっちを使うことにする.(そのかわりカスケード接続での 動作なので16ビット利用したときの精度は低い.そのような場合は16ビットタイマの 方を利用するということなのだろう,たぶん)

まあタイマの使いかたはあんまし難しくなくてマニュアルのとおりにレジスタ設定 するだけ.以下,修正したソース.

まずブートローダーの修正点について.

ブートローダーにはソフトウエア・割り込みベクタの処理を実装してあるが, 割り込みとしてタイマ割り込みを受け付けるように拡張する.

まずは intr.S の修正.

diff -ruN osbook/osbook_03/12/bootload/intr.S h8_2/h8_01/bootload/intr.S
--- osbook/osbook_03/12/bootload/intr.S	Sun Feb 28 23:23:47 2010
+++ h8_2/h8_01/bootload/intr.S	Sat Apr  3 13:36:17 2010
@@ -79,3 +79,29 @@
 	mov.l	@er7+,er5
 	mov.l	@er7+,er6
 	rte
+
+	.global	_intr_timintr
+#	.type	_intr_timintr,@function
+_intr_timintr:
+	mov.l	er6,@-er7
+	mov.l	er5,@-er7
+	mov.l	er4,@-er7
+	mov.l	er3,@-er7
+	mov.l	er2,@-er7
+	mov.l	er1,@-er7
+	mov.l	er0,@-er7
+	mov.l	er7,er1
+	mov.l	#_intrstack,sp
+	mov.l	er1,@-er7
+	mov.w	#SOFTVEC_TYPE_TIMINTR,r0
+	jsr	@_interrupt
+	mov.l	@er7+,er1
+	mov.l	er1,er7
+	mov.l	@er7+,er0
+	mov.l	@er7+,er1
+	mov.l	@er7+,er2
+	mov.l	@er7+,er3
+	mov.l	@er7+,er4
+	mov.l	@er7+,er5
+	mov.l	@er7+,er6
+	rte
割り込みハンドラとして _intr_timintr を追加する.内容は他のハンドラを コピーしただけ.

さらに intr.h の修正.

diff -ruN osbook/osbook_03/12/bootload/intr.h h8_2/h8_01/bootload/intr.h
--- osbook/osbook_03/12/bootload/intr.h	Sun Feb 28 23:23:47 2010
+++ h8_2/h8_01/bootload/intr.h	Sat Apr  3 13:36:17 2010
@@ -3,10 +3,11 @@
 
 /* ソフトウエア・割込みベクタの定義 */
 
-#define SOFTVEC_TYPE_NUM     3
+#define SOFTVEC_TYPE_NUM     4
 
 #define SOFTVEC_TYPE_SOFTERR 0
 #define SOFTVEC_TYPE_SYSCALL 1
 #define SOFTVEC_TYPE_SERINTR 2
+#define SOFTVEC_TYPE_TIMINTR 3
 
 #endif
こちらは割り込み種別として SOFTVEC_TYPE_TIMINTR を追加.

さらに割り込みベクタの設定を修正.

diff -ruN osbook/osbook_03/12/bootload/vector.c h8_2/h8_01/bootload/vector.c
--- osbook/osbook_03/12/bootload/vector.c	Sun Feb 28 23:23:47 2010
+++ h8_2/h8_01/bootload/vector.c	Sat Apr  3 13:36:17 2010
@@ -4,6 +4,7 @@
 extern void intr_softerr(void); /* ソフトウエア・エラー */
 extern void intr_syscall(void); /* システム・コール */
 extern void intr_serintr(void); /* シリアル割込み */
+extern void intr_timintr(void); /* タイマ割込み */
 
 /*
  * 割込みベクタの設定.
@@ -15,7 +16,8 @@
   NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
   NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
   NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
-  NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+  intr_timintr, intr_timintr, intr_timintr, intr_timintr,
+  intr_timintr, intr_timintr, intr_timintr, intr_timintr,
   NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
   intr_serintr, intr_serintr, intr_serintr, intr_serintr,
   intr_serintr, intr_serintr, intr_serintr, intr_serintr,
8ビットタイマの割り込みのハンドラに intr_timintr を設定する.

ブートローダーの修正はここまで.なにをやっているのかちょっとわかりにくいかも しれないが,まあ割り込み周りの詳細は5月に出版予定の組み込みOS本のほうで 説明してあるので,そちらを待ってちょうだい.

次にOS側の修正.

まずはタイマ用のデバイスドライバとして timer.h,timer.cを作成する. 以下,timer.h の内容.

#ifndef _TIMER_H_INCLUDED_
#define _TIMER_H_INCLUDED_

int timer_start(int index, int msec); /* タイマ開始 */
int timer_is_expired(int index);      /* タイマ満了したか? */
int timer_expire(int index);          /* タイマ満了処理 */
int timer_cancel(int index);          /* タイマキャンセル */

#endif
timer.h ではいくつかのサービス関数を定義してある.それぞれの意味は コメントの通り.

次に timer.c の内容.

#include "defines.h"
#include "timer.h"

#define TIMER_TMR_NUM 2

#define H8_3069F_TMR01 ((volatile struct h8_3069f_tmr *)0xffff80)
#define H8_3069F_TMR23 ((volatile struct h8_3069f_tmr *)0xffff90)

struct h8_3069f_tmr {
  volatile uint8 tcr0;
  volatile uint8 tcr1;
  volatile uint8 tcsr0;
  volatile uint8 tcsr1;
  volatile uint8 tcora0;
  volatile uint8 tcora1;
  volatile uint8 tcorb0;
  volatile uint8 tcorb1;
  volatile uint16 tcnt;
};

#define H8_3069F_TMR_TCR_DISCLK       (0<<0)
#define H8_3069F_TMR_TCR_CLK8         (1<<0)
#define H8_3069F_TMR_TCR_CLK64        (2<<0)
#define H8_3069F_TMR_TCR_CLK8192      (3<<0)
#define H8_3069F_TMR_TCR_OVF          (4<<0)
#define H8_3069F_TMR_TCR_CMFA         (4<<0)
#define H8_3069F_TMR_TCR_CLKUP        (5<<0)
#define H8_3069F_TMR_TCR_CLKDOWN      (6<<0)
#define H8_3069F_TMR_TCR_CLKBOTH      (7<<0)

#define H8_3069F_TMR_TCR_CCLR_DISCLR  (0<<3)
#define H8_3069F_TMR_TCR_CCLR_CLRCMFA (1<<3)
#define H8_3069F_TMR_TCR_CCLR_CLRCMFB (2<<3)
#define H8_3069F_TMR_TCR_CCLR_DISINPB (3<<3)

#define H8_3069F_TMR_TCR_OVIE         (1<<5)
#define H8_3069F_TMR_TCR_CMIEA        (1<<6)
#define H8_3069F_TMR_TCR_CMIEB        (1<<7)

#define H8_3069F_TMR_TCSR_OS_NOACT    (0<<0)
#define H8_3069F_TMR_TCSR_OIS_NOACT   (0<<2)
#define H8_3069F_TMR_TCSR_ADTE        (1<<4)
#define H8_3069F_TMR_TCSR_ICE         (1<<4)
#define H8_3069F_TMR_TCSR_OVF         (1<<5)
#define H8_3069F_TMR_TCSR_CMFA        (1<<6)
#define H8_3069F_TMR_TCSR_CMFB        (1<<7)

static struct {
  volatile struct h8_3069f_tmr *tmr;
} regs[TIMER_TMR_NUM] = {
  { H8_3069F_TMR01 }, 
  { H8_3069F_TMR23 }, 
};

/* タイマ開始 */
int timer_start(int index, int msec)
{
  volatile struct h8_3069f_tmr *tmr = regs[index].tmr;
  int count;

  tmr->tcr0 = H8_3069F_TMR_TCR_OVF | H8_3069F_TMR_TCR_CCLR_CLRCMFA;
  tmr->tcr1 = H8_3069F_TMR_TCR_CLK8192 | H8_3069F_TMR_TCR_CCLR_DISCLR;

  tmr->tcsr0 = 0;
  tmr->tcsr1 = 0;

  count = msec / 105; /* 20MHz: (msec * 20000000 / 8192 / 256 / 1000) */

  tmr->tcnt = 0;
  tmr->tcora0 = count;
  tmr->tcr0 |= H8_3069F_TMR_TCR_CMIEA; /* 割込み有効化 */

  return 0;
}

/* タイマ満了したか? */
int timer_is_expired(int index)
{
  volatile struct h8_3069f_tmr *tmr = regs[index].tmr;
  return (tmr->tcsr0 & H8_3069F_TMR_TCSR_CMFA) ? 1 : 0;
}

/* タイマ満了処理 */
int timer_expire(int index)
{
  volatile struct h8_3069f_tmr *tmr = regs[index].tmr;

  tmr->tcsr0 &= ~H8_3069F_TMR_TCSR_CMFA;

  return 0;
}

/* タイマキャンセル */
int timer_cancel(int index)
{
  volatile struct h8_3069f_tmr *tmr = regs[index].tmr;

  timer_expire(index);

  tmr->tcr0 = 0;
  tmr->tcr1 = 0;

  tmr->tcr0 &= ~H8_3069F_TMR_TCR_CMIEA; /* 割込み無効化 */

  return 0;
}
まあそれぞれの関数ではタイマ関連のレジスタを設定しているだけなので, 詳細はH8/3069Fのマニュアルを参照してちょうだい. H8/3069Fは8ビットタイマを4チャネル持っているのだけど, それぞれを2つずつカスケード接続で組み合わせて16ビットタイマ2本として 利用できる.ここでは秒単位の長い時間を測定できるようにするため, 16ビットタイマ×2本として利用している.

次に,intr.h の修正.これはブートローダー側で入れた修正をそのままOS側にも 反映しただけ.

diff -ruN osbook/osbook_03/12/os/intr.h h8_2/h8_01/os/intr.h
--- osbook/osbook_03/12/os/intr.h	Sun Feb 28 23:23:47 2010
+++ h8_2/h8_01/os/intr.h	Sat Apr  3 13:36:17 2010
@@ -3,10 +3,11 @@
 
 /* ソフトウエア・割込みベクタの定義 */
 
-#define SOFTVEC_TYPE_NUM     3
+#define SOFTVEC_TYPE_NUM     4
 
 #define SOFTVEC_TYPE_SOFTERR 0
 #define SOFTVEC_TYPE_SYSCALL 1
 #define SOFTVEC_TYPE_SERINTR 2
+#define SOFTVEC_TYPE_TIMINTR 3
 
 #endif

次にcommand.cの修正.

diff -ruN osbook/osbook_03/12/os/command.c h8_2/h8_01/os/command.c
--- osbook/osbook_03/12/os/command.c	Sun Feb 28 23:23:47 2010
+++ h8_2/h8_01/os/command.c	Sat Apr  3 13:36:17 2010
@@ -3,6 +3,11 @@
 #include "consdrv.h"
 #include "lib.h"
 
+#if 1
+#include "timer.h"
+#include "intr.h"
+#endif
+
 /* コンソール・ドライバの使用開始をコンソール・ドライバに依頼する */
 static void send_use(int index)
 {
@@ -27,6 +32,18 @@
   kz_send(MSGBOX_ID_CONSOUTPUT, len + 2, p);
 }
 
+void timer_intr(softvec_type_t type, unsigned long sp)
+{
+  if (timer_is_expired(0)) {
+    puts("timer expired 0.\n");
+    timer_expire(0);
+  }
+  if (timer_is_expired(1)) {
+    puts("timer expired 1.\n");
+    timer_cancel(1);
+  }
+}
+
 int command_main(int argc, char *argv[])
 {
   char *p;
@@ -44,6 +61,14 @@
     if (!strncmp(p, "echo", 4)) { /* echoコマンド */
       send_write(p + 4); /* echoに続く文字列を出力する */
       send_write("\n");
+    } else if (!strncmp(p, "timer", 5)) { /* タイマ起動コマンド */
+      softvec_setintr(SOFTVEC_TYPE_TIMINTR, timer_intr);
+      puts("start timer.\n");
+      timer_start(0, 3000);
+      timer_start(1, 4500);
+    } else if (!strncmp(p, "cancel", 6)) { /* タイマキャンセル */
+      puts("cancel timer.\n");
+      timer_cancel(0);
     } else {
       send_write("unknown.\n");
     }
プロンプトから「timer」というコマンドを実行すると2本のタイマが動作開始する. 片方(チャネル0)は3000ミリ秒,つまり3秒で満了する.もう片方(チャネル1)は 4.5秒でタイマ満了する.

タイマ満了するとtimer_intr()が呼ばれる.チャネル0の場合はtimer_expire()が 呼ばれることで割り込みがクリアされ,そのままタイマ動作続行する. よってチャネル0は3秒ごとに「timer expired 0.」のメッセージを出力する.

これに対してチャネル1はtimer_intr()が呼ばれると, timer_cancel()によってタイマ停止する. よってチャネル1は4.5秒で「timer expired 1.」のメッセージを出力し, それで動作停止する.

最後にMakefileの修正.これは新しく作成した timer.c を追加しただけ.

diff -ruN osbook/osbook_03/12/os/Makefile h8_2/h8_01/os/Makefile
--- osbook/osbook_03/12/os/Makefile	Sun Feb 28 23:23:47 2010
+++ h8_2/h8_01/os/Makefile	Sat Apr  3 13:36:17 2010
@@ -14,7 +14,7 @@
 STRIP   = $(BINDIR)/$(ADDNAME)strip
 
 OBJS  = startup.o main.o interrupt.o
-OBJS += lib.o serial.o
+OBJS += lib.o serial.o timer.o
 
 # sources of kozos
 OBJS += kozos.o syscall.o memory.o consdrv.o command.o
ここまでで修正は終了.動作させてみよう.

今回はブートローダーに手を入れているため,h8write でブートローダーを フラッシュROMに書き込む必要がある.書き込んだらいつもどおりの手順で OSをロードして実行する.以下,実行結果.

kzload> run
starting from entry point: ffc020
kozos boot succeed!
command> echo sample
 sample
command> timer
start timer.
command> timer expired 0.
timer expired 1.
timer expired 0.
timer expired 0.
timer expired 0.
timer expired 0.
timer expired 0.
cancel
cancel timer.
command> 
おー,ちゃんと動いている.とくに問題無し.わりとあっさり動いた.

さて,ここでちょっとリアルタイム処理について考えてみよう.

先述したようにH8はタイマを何チャネルか持っている.これらを使えば 正確な時間経過で割り込みを上げて,タイマサービスを提供することができる.

しかしタイマの本数には限りがある.なのでタイマが必要なアプリごとに タイマを提供していたら,あっと言う間に足りなくなってしまう.

ということでアプリが要求するタイマサービスをキューで管理して, 一番近い時間のものをタイマにセットし,タイマ満了したら次に近い時間のものを セットし直す,という処理が考えられる. これならば1本のタイマを利用するだけで,複数のアプリ向けにタイマサービスを 提供することができる.

しかしこのような処理はキュー検索が必要になるため,リアルタイム処理には 向かない.また繰り返しタイマ満了することで誤差が蓄積していくため, 精度の高いタイマサービスは行えない.

ということで,高い精度が必要なリアルタイム処理などはCPUの持つタイマを そのまま利用して,アプリが利用する精度のいらないタイマにはキュー管理の サービスを提供するといった使い分けが必要になってくる.前者は精度は高いが タイマ資源の数に上限があるので,どのタイマはどのサービスに使うなどといった 仕様決定が必要になる.後者は誰でも使っていいが,精度は低いというわけだ.

まあ詳しいことはまたそのうち説明しよう.とりあえず今回はタイマが動いたので よかったよかった.


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