2008年12月アーカイブ




久々に木工しました。電子部品が四散して収集つかないことになっているので
整理棚を作ります。まずは試作。材料は安い杉板。これで150円くらい。



痛恨の失敗をしてしまった。外枠のホゾを反対に掘ってしまったのだ...。 E-24系列を一段ごとにまとめていこうかなという計画。なのでこれから量産で す。10,100,1K,10K,100K,1Mと、コンデンサ用に二段くらいと、雑多なもの三段 で、あと10段くらい欲しいとこだけど、ちょっと大変だな。

ファイバの続きで、継続された元にも戻れるようにしてみたのだけど、たった 十数行のプログラムですら、動きが読めなくなった。これはだめだ。こういう のは言語で縛らないと、まさに自分の足を撃ち抜くことになるね。


昨日のスレッドの実装は思慮が足りなかった。スピンから割りこまれて、その
ハンドラからスケジューラが呼ばれて、もう一度ここに来た時の対応。
  // No thread in ready queue.
  if (!tc)
    {
      if (saved_thread)
	{
	  assert (saved_thread == current_thread);
	  return 0;	// Spin again.
	}
昨日はここで二重は無理とあきらめていたけれど、ここにきた時のcurrent_threadの
コンテキストは、下のwhile(1);で割りこまれた時のものだ。なら、このままcurrent_
threadを変更しないで抜けてしまえば、また下のwhile(1);に戻る。

      saved_thread = current_thread;
      memcpy (&saved_regs, ¤t_thread->regs, sizeof (struct reg));

      DPRINTF ("\t\t\t\t\t%s: spin %d\n", __FUNCTION__, current_thread->id);
      intr_enable ();
      while (/*CONSTCOND*/1)
	; ←ここで割りこまれる。
      assert (0);
      /*NOTREACHED*/
    }
これでTSSのようなスケジューリングでも問題ない。 あと、コンテキストスイッチで余計なところを廃止しました。RESTORE_CCR_PCの部分。
	/* void thread_switch (int schedule_policy) */
	.globl _thread_switch
_thread_switch:
	; disable interrupt
	stc.b	ccr, r2l
	orc	#0x80, ccr

	; save context.
	mov.l	@_current_thread, er3

	mov.l	@sp, er1	; return address
#ifdef RESTORE_CCR_PC
	mov.l	er1, @(0x20, er3) ; pc
	mov.b	r2l, @(0x20, er3) ; ccr    |ccr(8)|   pc(24)  |
#endif
	mov.l	sp,  @(0x1c, er3) ; sp
	mov.l	er6, @(0x18, er3) ; callee saved
	mov.l	er5, @(0x14, er3) ; callee saved
	mov.l	er4, @(0x10, er3) ; callee saved
	; no need to store caller saved and ccr.

	jsr	@_thread_context_switch

	mov.l	@_current_thread, er0

	mov.l	@(0x1c, er0), sp  ; sp
#ifdef RESTORE_CCR_PC
	mov.l	@(0x20, er0), er2 ; pc
	mov.l	er2, @sp	  ; set return address and ccr
#endif
	mov.l	@(0x18, er0), er6 ; callee saved
	mov.l	@(0x14, er0), er5 ; callee saved
	mov.l	@(0x10, er0), er4 ; callee saved
	mov.l	@(0x0c, er0), er3 ; caller saved
	mov.l	@(0x08, er0), er2 ; caller saved
	mov.l	@(0x04, er0), er1 ; caller saved
	mov.l	@(0x00, er0), er0 ; caller saved
	; resume interrupt. and return
	rte
ここは手で復帰しなくてもSPが復帰されればそこに入っているので。(同じの上書きしてるだけ)
次はDRO基板の製作です。ちょっと秋葉原に行って、旋盤用DROのパーツを買っ てきました。ついついまた余計なものまで買ってしまった。
ちょっとお遊びを。ファイバの続きです。
	/* void fiber_switch_arg (fiber_t from, fiber_t to, uint32_t ret) */
                                    「er0」       「er1」       「er2」
	.globl _fiber_switch
_fiber_switch:
	; save current context.
	; no need to store caller saved and ccr.
	mov.l	sp,	@(0x0c, er0) ; sp (contain return address)
	mov.l	er6,	@(0x08, er0) ; callee saved
	mov.l	er5,	@(0x04, er0) ; callee saved
	mov.l	er4,	@(0x00, er0) ; callee saved
	; load next context.
	mov.l	@(0x0c, er1), sp  ; sp
	mov.l	@(0x08, er1), er6 ; callee saved
	mov.l	@(0x04, er1), er5 ; callee saved
	mov.l	@(0x00, er1), er4 ; callee saved
	mov.l	er2,	er0	  ; subroutine return value / 1st arg.
	rts

これはファイバのスイッチルーチン。ファイバは明示的なサブルーチンコールでしか
スイッチしないので、とても軽いです。最後のmov.l er2, er0は最初のファイバ
呼び出しの際には第一引数になり、通常のコールの際は返り値になります。


struct fiber_reg
{
  uint32_t er4;		// 0x00
  uint32_t er5;		// 0x04
  uint32_t er6;		// 0x08
  uint32_t sp;		// 0x0c (er7)
} __attribute__((packed)); //16byte

struct fiber;
typedef struct fiber *fiber_t;

struct fiber
{
  struct fiber_reg regs;
  fiber_t parent;    便宜上、ファイバの作成をしたファイバを親にします。
  fiber_t continuation; このファイバの継続先です。
  int32_t id;
  uint8_t stack[0];
} __attribute__((packed, aligned (4)));

__BEGIN_DECLS
fiber_t fiber_init (fiber_t);
スレッドがファイバを使う際に自分をファイバとして扱えるようにします。

fiber_t fiber_create (fiber_t, uint8_t *, size_t, void (*)(fiber_t));
新しくファイバを作ります。第一引数は自分です。

uint32_t fiber_switch (fiber_t, fiber_t, uint32_t);
ファイバを切り変えます。次の継続のリターン値はuint32_tにつめます。

fiber_t fiber_twist (fiber_t, int, ...);
ファイバの実行順序を設定します。第一引数は自分、第二引数はファイバの数、
その後には実行順にファイバを並べます。
__END_DECLS


static int __fiber_id;

// make myself (thread) as fiber.
fiber_t
fiber_init (fiber_t f)
{
ファイバIDを設定するくらいです。

  f->id = ++__fiber_id;
  f->parent = NULL;

  return f;
}

// create new fiber.
fiber_t
fiber_create (fiber_t parent, uint8_t *fiber_local_store, size_t size, void (*start)(fiber_t))
{
  fiber_t f = (fiber_t)fiber_local_store;
  size_t stack_size = size - sizeof (struct fiber);

  f->parent = parent;
  f->id = ++__fiber_id;

  f->regs.sp = (addr_t)f->stack + stack_size - 4;
  // install return address for 'rts'
  *(uint32_t *)f->regs.sp = (addr_t)start;

スタックの設定をして、'rts'命令用にジャンプ位置を設定しておきます。CCR
は設定してないので、確実に割り込みは開く。これは問題にはならないとは思
うけど、割り込み禁止の状態からファイバをまわすなら、この時点のCCRを読ん
で設定するようにする。
  printf ("%x %x %x\n", f->parent, f->regs.sp,*(uint32_t *)f->regs.sp);

  return f;
}

// link continuation.
fiber_t
fiber_twist (fiber_t myself, int n, ...)
{
  va_list ap;
  fiber_t f, pre_f, start_f;
  int i;

引数の順番にそれぞれのファイバの継続先を設定していきます。最後は自分に
戻ってくるように設定します。返り値はスタートすべきファイバです。

  f = pre_f = start_f = NULL;
  va_start(ap, n);
  for (i = 0; i < n; i++)
    {
      f = va_arg (ap, fiber_t);
      if (pre_f)
	{
	  pre_f->continuation = f;
	  printf ("%s: %d->%d\n", __FUNCTION__, pre_f->id, f->id);
	}
      else
	{
	  start_f = f;
	}

      pre_f = f;
    }
  va_end (ap);
  f->continuation = myself;
  printf ("%s: %d->%d\n", __FUNCTION__, f->id, myself->id);

  return start_f;
}

さて、ここまで用意した。シリアルコンソールはバッファリングされているので今は
  thread_priority (current_thread, PRI_LOW);	// lowest.
  while (/*CONSTCOND*/1)
    {
       sci_putc (sci_getc ());
    }

こんな感じで一番低い優先度でまわしておくだけで、echoはできる。シリアル
コンソールを叩くと、受信割りこみがかかって、受信スレッドを起こす。受信
スレッドは受信リングバッファに値を入れる。値待ちで寝ていたsci_getc()を
呼んでいるスレッドがおこされて値を取得、それはsci_putcにわたされて、送
信リングバッファに入れる。送信リングバッファで待っていた送信スレッドが
SCIをたたいて、エコーバックです。

これをファイバにしてみた。受信ファイバ、送信ファイバ、おまけにフィルタ
ファイバです。
  thread_priority (current_thread, PRI_LOW);	// lowest.
  while (/*CONSTCOND*/1)
    {
      fiber_switch (&myself, start_fiber, 0);
    }
受信ファイバ→フィルタファイバ→送信ファイバと継続して、ここに戻ってくる。



#define	FLS_SIZE	256
uint8_t fls[3][FLS_SIZE] __attribute__ ((aligned (4)));
ファイバのコントロールブロック+局所記憶です。ちょっと大いような気がする
かもしれないけれど、割り込まれた時の割り込みスタックがあるので、ある程
度のマージンが必要。割り込みスタックを別に用意してもいいんだけど...

void prompt (void);
void fiber_1 (fiber_t);
void fiber_2 (fiber_t);
void fiber_3 (fiber_t);

void
prompt ()
{
  struct fiber myself; スレッド自分自身をファイバとして使うための領域です。
  fiber_t input_fiber, output_fiber, filter_fiber;
  fiber_t start_fiber;

  // Fiber test.
  fiber_init (&myself);
  input_fiber = fiber_create (&myself, fls[0], FLS_SIZE, fiber_1);
  output_fiber = fiber_create (&myself, fls[1], FLS_SIZE, fiber_2);
  filter_fiber = fiber_create (&myself, fls[2], FLS_SIZE, fiber_3);

  fiber_switch (&myself, input_fiber, (uint32_t)input_fiber);
  fiber_switch (&myself, output_fiber, (uint32_t)output_fiber);
  fiber_switch (&myself, filter_fiber, (uint32_t)filter_fiber);
ここのスイッチはそれぞれのファイバに最初の引数を渡して、待機状態にまで
もっていかせるためのものです。それ用にファイバも書かれている必要があります。

#if 1
  start_fiber = fiber_twist (&myself, 3,
			     input_fiber, filter_fiber, output_fiber);
  入力→フィルタ→出力でまわります。
#else
  start_fiber = fiber_twist (&myself, 2, input_fiber, output_fiber);
  入力→出力でまわります。
#endif

  printf ("fiber return.\n");

  intr_enable ();

  thread_priority (current_thread, PRI_LOW);	// lowest.
  while (/*CONSTCOND*/1)
    {
      if (fiber_switch (&myself, start_fiber, 0) != 0)
	{
	  printf ("hello world\n");
	}
    }
}

void
fiber_1 (fiber_t myself)
{
  fiber_switch (myself, myself->parent, 0);
最初の呼ばれる時はコンテキスト設定用ということになっているので、すぐに
呼び出し元に帰ります。つぎにスイッチした時はwhile (...からはじまります。

  while (/*CONSTCOND*/1)
    {
      int8_t c = sci_getc ();
      if (c == 'q')
	fiber_switch (myself, myself->parent, 1);
'q'が押されたら、継続せずに呼び出しもとに戻ります。try-catchのようなジャンプ。

      fiber_switch (myself, myself->continuation, (uint32_t)c);
次の継続に、この値を渡します。
    }
}

void
fiber_2 (fiber_t myself)
{
  fiber_switch (myself, myself->parent, 0);

  while (/*CONSTCOND*/1)
    {
      int8_t c = (int8_t)fiber_switch (myself, myself->continuation, 0);
      sci_putc (c);
前の継続から渡された値をsci_putcに投げます。
    }
}

void
fiber_3 (fiber_t myself)
{
  fiber_switch (myself, myself->parent, 0);

  while (/*CONSTCOND*/1)
    {
      int8_t c = (int8_t)fiber_switch (myself, myself->continuation, c);
      c = c < 'a' ? c : c - 32;
前の継続から来た値を(アルファベットとして)、大文字にして次の継続に渡します。
    }
}



なんでこんなことをしているかというと、今日買ってきた本にインスパイアを 受けてます。魅力的なパラダイムだけれど局所記憶の無駄が多すぎ。スタック をなんとかできればな...


スレッドシステムを、レディキューに一つもスレッドがない状態でスピンするように
変更しました。全てのスレッドが寝た時の割り込み応答性のために。

int
thread_context_switch (int rotate_ready_queue)
{
  // already interrupt disabled.
  struct thread_control *tc;
  struct tc_queue *q;
  int i;

  // Rotate ready queue request.
  if (current_thread->state == THR_RUN && rotate_ready_queue)
    {
      current_thread->state = THR_READY;
      q = __ready_queue (current_thread);
      SIMPLEQ_REMOVE_HEAD (q, tc_link);
      SIMPLEQ_INSERT_TAIL (q, current_thread, tc_link);
    }

  // Find next thread.
  for (i = 0, tc = NULL; i < THREAD_PRIORITY_MAX; i++)
    {
      q = thread_ready_queue + i;
      if ((tc = SIMPLEQ_FIRST (q)) != NULL)
	break;
    }

ここまでに変更はありません。レディキューから次に走らせるスレッドがみつかれば
tcにセットされています。

  // If saved thread is exists, restore it.
  if (tc && saved_thread)
    {
次のスレッドはあって、スピンから割り込みが入って、その割り込みハンドラの
中でwakeup_threadが呼ばれて、その呼ばれたスレッドが選ばれた状況です。

      DPRINTF ("\t\t\t\t\t%s: spin thread %d restored.\n", __FUNCTION__,
	       saved_thread->id);
      memcpy (&saved_thread->regs, &saved_regs, sizeof (struct reg));
      saved_thread = NULL;
スピンから割り込みに入られたスレッドはレジスタセットがスピンしている状
態(下のwhile(1);の部分)を記憶させられてしまっているので、本来のレジスタ
セットに復帰します。(しないと、saved_threadはスケジュールされても延々と
while(1);の部分を走ることになってしまう。)
    }

  // No thread in ready queue.
  if (!tc)
    {
レディキューが空の時です。
      assert (!saved_thread);
二重には無理。二重に呼ばれるとしたら、スピンから割りこみが入って、割りこみ
ハンドラはどのスレッドもwakeupさせずにこのスケジューラを呼んだ状況。
それは十分に考えられるのだけれど、それはなしにします。どれもwakeupしないな
らスケジューラは呼ばない決まりにします。TSSはできないということ。

      saved_thread = current_thread;
      memcpy (&saved_regs, ¤t_thread->regs, sizeof (struct reg));
割り込みを開けるために本来の復帰すべき状態を退避しておきます。
      DPRINTF ("\t\t\t\t\t%s: spin %d\n", __FUNCTION__, current_thread->id);
      intr_enable ();
      while (/*CONSTCOND*/1)
	;      ここで割り込みを待ちます。なんにもしてないので応答性は最高。
sleep命令を入れると応答性が下がるので、スピンです。
      assert (0);
      /*NOTREACHED*/

このスピンで割りこみを待ちます。スケジューラを呼ばない割り込みハンドラ
であれば、割り込み終了後はここにもどってくるので、ずっとスピンです。ス
ケジューラを呼ぶ割り込みハンドラはは、もう一度、この関数に入ってきて、
上の(tc && saved_thread)処理で退避しておいたレジスタセットに戻るので、
ここでスピンしたスレッドはきちんと元にもどれます。
    }

  current_thread = tc;
  current_thread->state = THR_RUN;

  DPRINTF ("switch %d: sp=%x\n", current_thread->id, current_thread->regs.sp);

  return 0;
}
要はこのくらいなのだけど、丸一日格闘しました。最初はスピンの中で割り込 み終了を判定してレディキューを探しに行こうとして、そのフラグにCCRのユー ザビットを割りあててみたのだけど、割り込みハンドラの中でCCRのビットいじっ てもrteでスタックに退避されたCCRを上書きされてしまうので、だめです。し かたないので、スタックの一番上にフラグを起くスペースを作ってみたのだけ ど、それでもどうもうまくいかない。デバッグしながら考え直してやっとこれ。 これがいいかどうかもわからないけれど、ノギスのデータ取りのプログラムで は納得の応答性が得られたので、よしとします。
デバッグプリントをオンにした時の様子を確認しておきます。


thread_context_switch:                                  thread_context_switch: spin 1
ここでスピンに入った。
thread_context_switch:                                  thread_context_switch: spin thread 1 restored.
割り込みハンドラからのスケジューラ呼び出しで、スピンしていたスレッドの状態は元に戻った。
thread_context_switch: switch 2: sp=847a0
「2」スレッドが割り込みハンドラで起こされた。
                                monitor_enter:lock by 2
そのスレッドはモニタに入った。
monitor_signal:[2] signal-->1
「1」スレッドのイベント待ち条件がクリアされたので「1」を起こすことにした。
monitor_exit: [2] wakeup------>1
「1」がモニタを出る時に実際に起こされます。
thread_priority: [1] pri 3->0 state = 2
イベント待ちが入口待ちより先に実行されるために、monitor_exitの中で「1」の
優先度は低い3から、「1」の優先度0にまで上げられました。「1」はこの状態では
WAITですが、このあとでthread_wakeupされのでREADYになります。
                monitor_exit: unlock by 2
ここでthread_rotate_ready_queueが呼ばれて、
thread_context_switch: switch 1: sp=84500
「1」に制御が移ります。
monitor_wait: lock by 1
イベント待ちで寝ていた「1」が起きてロックを取得しました。
monitor_exit: inherit priority [1] (0->3)
thread_priority: [1] pri 0->3 state = 0
処理が終了したので、継承していた優先度を元に戻しました。
thread_context_switch: switch 2: sp=8476c
「1」のmonitor_exitの最後、優先度継承していたら、thread_priorityを呼ぶので、
「2」に制御が戻ります。(まだ「1」は抜けてない)
thread_context_switch: switch 1: sp=844e4
「2」はなにもすることがなかったので「1」に戻ってきた。
                monitor_exit: unlock by 1
ここで「1」がモニタから抜けます。
thread_context_switch: switch 1: sp=8450c
monitor_exit最後のrotate_ready_queueでも自分に戻ってきた。このrotateは
イベントで寝ていたのを起こすためのものなのだけど、誰もいなかったので。
                                monitor_enter:lock by 1
「1」はまたモニタに入った。
monitor_signal:[1] signal-->3
「3」がイベント待ちなので、3を起こした。
monitor_exit: [1] wakeup------>3
thread_priority: [3] pri 1->3 state = 2
                monitor_exit: unlock by 1
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXここ間違い。低い優先度を継承してるXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
これは、優先度継承の時の優先度をカレントスレッドの優先度にしているから。
固定で割り込みスレッドの優先度にしてた時のコードのままだ。どっちに
するか決めておかないと。

thread_context_switch: switch 3: sp=84970
monitor_wait: lock by 3
monitor_exit: inherit priority [3] (3->1)
thread_priority: [3] pri 3->1 state = 0
                monitor_exit: unlock by 3
thread_context_switch: switch 3: sp=8497c
                                monitor_enter:lock by 3
monitor_wait: unlock by 3
thread_context_switch: switch 1: sp=844e8
thread_context_switch: switch 1: sp=84548
---Ready Queue---
<0>: 
<1>: 
<2>: 
<3>: 1 
---Thread Status---(remain/total)
[4] W 0 (0/384) app0
[3] W 1 (0/384) sci send
[2] W 0 (0/384) sci recv
[1] R 3 (0/384) root
CCR_PC=0
---Monitor---
W ringbuffer
        lock : 
        event: 3(sci send) 
U ringbuffer
        lock : 
        event: 
                                monitor_enter:lock by 1
monitor_wait: unlock by 1
thread_context_switch:                                  thread_context_switch: spin 1
thread_context_switch:                                  thread_context_switch: spin thread 1 restored.
thread_context_switch: switch 4: sp=84bcc
thread_context_switch:                                  thread_context_switch: spin 4
thread_context_switch:                                  thread_context_switch: spin thread 4 restored.
thread_context_switch: switch 2: sp=847a0
                                monitor_enter:lock by 2
monitor_signal:[2] signal-->1
monitor_exit: [2] wakeup------>1
ちょっと問題をみつけてしまいました。とはいえソフト的にはなんとか満足で きるとこまでもってこれた。


イサギダプロダクツから購入したデジタルスケールは、SHANのノギスとまった
く同じ信号でした。SHANのプログラムでそのままデータがとれた。写真はまだ
補正(fv = (val - (val /100)/1.27)/100;)をしてないのでちょっとずれていま
す。



今日はノギスデータの取り込みからはじめました。昨日作ったインターバルタ イマによるサンプリングではせいぜい10KHzがいいとこ。ノギスの信号は90KHz 近くです。
限界までサンプリングするためにアセンブラで書き直しました。
	.h8300h
	.section .text
	/* void port9_polling (int32_t loop, int8_t bit_position) */
	.global _port9_polling
_port9_polling:
	mov.l	er4,	@-sp
	mov.l	er5,	@-sp
	mov.l	er6,	@-sp

	mov.b	r1l,	r4h
ターゲットのビットが引数r1lに入っているのでr4hに置いておきます。
	mov.l	#_la_high,	er5
	mov.l	#_la_low,	er6
サンプリングしたデータをとっておく配列の先頭アドレスです。
	mov.l	er0,	er3
サンプリング回数は引数er0に入っているのでer3に置いておきます。
	shll	er3
	shll	er3
データは4byteなので、二回シフトして4倍しておきます。

	add.l	er5,	er3	; er3 max loop
サンプリングデータの終了地点をer3としておきます。

	sub.l	er0,	er0
	sub.l	er1,	er1
er0 = 0
er1 = 0

ph_loop: adds	#1,	er0	; high count
	mov.b	@0xd2:8,r4l	; r = *P9_DR
	btst	r4h,	r4l	; z = !(r & 0x10)
	bne	ph_loop		;

do {er0++;} while (*P9_DR & r4l)
ここが抜けた所でLOWです。

	mov.l	er1, @er6	; 1st data of e[] is bogus.
	mov.l	er0, @er5
ここでer0の他にまだ計測していないer1までセーブしているのはHIGH区間の
計測と、LOW区間の計測の間の時間を均等にしたいため。この区間は結構暇。
	adds	#4,  er5
	adds	#4,  er6
次のデータに向けてインクリメント(4byte)
	sub.l	er0,	er0
	sub.l	er1,	er1
 次の計測に向けて。
er0 = 0
er1 = 0

pl_loop: adds	#1,	er1	; low count
	mov.b	@0xd2:8,r4l	; r = *P9_DR
	btst	r4h,	r4l	; z = !(r & 0x10)
	beq	pl_loop

do {er1++;} while (!(*P9_DR & r4l));

	cmp.l	er5, er3
	bne	ph_loop
サンプリングの終了判定。ここが結構時間かかるので、er1のセーブを上でしている。
	mov.l	@sp+,	er6
	mov.l	@sp+,	er5
	mov.l	@sp+,	er4
	rts

これでクロック信号をサンプリングすると、
拡張RAM バス幅8bit 3ステートだと
ExtRAM 8bit 3state.

47, 4 3
48, 4 3
49, 241783 52
50, 4 38
51, 4 3
52, 4 3
53, 4 3
54, 4 3
55, 4 3
56, 4 4
57, 4 3
58, 4 3
59, 4 3
60, 4 3
61, 4 3
62, 4 3
63, 4 4
64, 4 3
65, 4 3
66, 4 3
67, 4 3
68, 4 3
69, 4 3
70, 3 4
71, 4 4
72, 4 3
73, 4 3
74, 4 82
75, 4 3
76, 4 3
77, 4 3
78, 4 4
79, 4 3
80, 4 3
81, 4 3
82, 4 3
83, 4 3
84, 4 3
85, 3 4
86, 4 4
87, 4 3
88, 4 3
89, 4 3
90, 4 3
91, 4 3
92, 4 3
93, 4 4
94, 4 3
95, 4 3
96, 4 3
97, 4 3
98, 241784 52
99, 4 37
100, 4 3

拡張ラム バス幅8bit 2ステートだと、
Ext RAM 8bit 2state.

48, 6 5
49, 346907 76
50, 6 55
51, 6 6
52, 6 5
53, 6 6
54, 6 5
55, 6 6
56, 6 5
57, 6 6
58, 6 5
59, 6 6
60, 5 6
61, 6 6
62, 6 6
63, 6 5
64, 6 6
65, 6 5
66, 6 6
67, 6 5
68, 6 6
69, 6 5
70, 6 6
71, 6 5
72, 6 6
73, 6 6
74, 6 118
75, 6 6
76, 6 5
77, 6 6
78, 6 5
79, 6 6
80, 6 6
81, 6 5
82, 6 6
83, 6 5
84, 6 6
85, 6 5
86, 6 6
87, 6 5
88, 6 6
89, 6 5
90, 6 6
91, 6 6
92, 6 5
93, 6 6
94, 6 5
95, 6 6
96, 6 5
97, 6 6
98, 346908 75
99, 6 54
100, 6 6

内蔵ロム バス幅16bit 2ステートだと
ROM 16bit 2state

47, 11 11
48, 11 12
49, 613759 136
50, 11 99
51, 11 11
52, 11 12
53, 11 12
54, 11 12
55, 11 11
56, 11 12
57, 11 12
58, 11 12
59, 11 11
60, 11 12
61, 11 12
62, 11 11
63, 11 12
64, 11 12
65, 11 12
66, 11 11
67, 11 12
68, 11 12
69, 11 12
70, 11 11
71, 11 12
72, 11 12
73, 11 11
74, 11 213
75, 11 11
76, 11 12
77, 11 12
78, 11 11
79, 11 12
80, 11 12
81, 11 12
82, 11 11
83, 11 12
84, 11 12
85, 11 12
86, 11 11
87, 11 12
88, 11 12
89, 11 12
90, 11 11
91, 11 12
92, 11 12
93, 10 12
94, 11 12
95, 11 12
96, 11 12
97, 11 11
98, 613760 135
99, 11 99
100, 11 12

になる。ここでもメモリアクセスのスピードがそのまま結果に出た。バス幅が
半分になって速度半分、2ステートから3ステートになってさらに1.5倍時間がか
かっている。さすがにこの信号を8bit 3stateではとりきれないので、スタート
アップでメモリチェックをすること前提に8bit 2stateにしました。これなら
なんとかギリギリいける。たまにCS5につながってるRAMが失敗する。
あと、gccのコンパイルオプションで-mint32(intを32bitとする、指定しなけれ ば16bit)を外すと、実行速度があがる。-mint32にすると例えば構造体の配列の アクセスに32bitの乗算が無条件に呼ばれてしまう(mulsi3とか)のでだめ。int 16bitでも、コーディングに気をつけないとすぐ呼ばれてしまう。m[i] みたい のはだめで、最初にp = m; p++形式にしないといいコードを吐いてくれない。
以前のCで書いた信号取り込みルーチンは内蔵RAM/ROM上では大丈夫なのだけど、 拡張RAM上ではほとんど取りこめない。アセンブラに。いずれここはキュンキュ ンにチューンしたいと思ってたとこだし。
アセンブラの際はできるだけ、バイト相手にしておくと命令長を小さくできる。 結構、レジスタを使わずに処理できるのでよく命令のデータシートを読む。実 はCISCのアセンブラを書くのは初めてで驚きが多いです。「これがレジスタ一 つも使わずに一命令かよ!...」みたいな。
#define	CLK_PORT	@0xd2:8
#define	CLK_BIT		#4
#define	DATA_PORT	@0xd2:8
#define	DATA_BIT	#5
#define	FUNC_NAME	_irq4
#define	HI_LIMIT	#20

	.h8300h
	.section .text

	.global FUNC_NAME
FUNC_NAME:	;;  LOW
	mov.l	er0,	@-sp
	mov.l	er1,	@-sp
	mov.l	er2,	@-sp
	mov.l	er3,	@-sp
	sub.l	er2,	er2	; caliper data
	mov.l	#1,	er3	; bit
立ち下がりで割り込みが入る。ここからクロックがはじまるまでに50usecあるので
このくらい用意しておいても大丈夫。

wait_rising_edge0:
	mov.b	CLK_PORT, r0l
	btst	CLK_BIT,  r0l
	beq	wait_rising_edge0
count_high_level:
	;;  HIGH
	mov.b	HI_LIMIT r0h	; limit count.
wait_falling_edge0:
	add.b	#-1,	r0h
	beq	error_return_0		; too long high region.
	mov.b	CLK_PORT, r0l
	btst	CLK_BIT,  r0l
	bne	wait_falling_edge0
クロックがはじまって、立ち下がりがいつまでたってもこなければ、間違えた
ところでエッジをとってしまったので、次のターンを待ちます。

	;;  LOW
	mov.b	#20	r0h
wait_rising_edge1:
	add.b	#-1,	r0h
	beq	sampling_start	; data phase start.
	mov.b	CLK_PORT, r0l
	btst	CLK_BIT,	r0l
	beq	wait_rising_edge1
	bra	count_high_level

最初の24クロックは、本来とるべきデータの相補な値を供給するのだけれど、
ここはタイミングとりだけに使ってます。後半までに110usecのLOW区間があるので
それを検出して、データ取りにうつります。

sampling_start:
	;; still LOW.
	mov.b	#23,	r1l	; r1l counter.
24クロックとります。カウンタが8bitから16bitになると命令長が倍になるので、
できるだけ小さく。(レジスタを使う量は増えるけれど...)

wait_rising_edge2:
	mov.b	CLK_PORT, r0l	; waiting for rising edge.
	btst	CLK_BIT,  r0l
	beq	wait_rising_edge2
	;;
	;; data phase
	;;
	;; HIGH
sampling_loop:			; wait for falling edge.
	mov.b	HI_LIMIT r0h	; limit count.
wait_falling_edge1:
	add.b	#-1,	r0h
	beq	error_return_1		; too long high region. return.
	mov.b	CLK_PORT, r0l
	btst	CLK_BIT,  r0l
	bne	wait_falling_edge1

なんらかの失敗でHIGH区間が続いてしまった場合は、もうデータはとれないので
次のターンを待ちます。

	;; LOW
	mov.b	DATA_PORT, r0l	; sample data bit.
	btst	DATA_BIT,  r0l
	beq	data_bit_not_set
	or.l	er3, er2	; set data.
data_bit_not_set:	; wait for rising edge

立ち下がりの後にデータを取ります。

	mov.b	CLK_PORT, r0l
	btst	CLK_BIT,  r0l
	beq	data_bit_not_set
	;; HIGH
	shll.l	er3	; next bit.
ループに一回ずつシフトすることで、1bitシフトしかないのをカバー。

	;; 24clock?
	add.b	#-1,	r1l
	bpl	sampling_loop
;;; sampling done.

	mov.l	#_shan_data, er0
	mov.l	er2,	@(0x0, er0) 	; copy data.
うまくいったので、データを所定のメモリに置きます。
	mov.l	#_shan_data_updated, er0
	bset	#0,	@er0		; update flag.
データがアップデートされたことを知らせます。
	bra	success_return
error_return_0:
	mov.l	#_shan_error, er0
	bset	#0,	@er0
	bra	success_return
error_return_1:
	mov.l	#_shan_error, er0
	mov.b	r1l, @_shan_error_cause,
	bset	#1,	@er0
失敗したので、失敗したことを知らせ、その原因もとっておきます。

success_return:
	mov.l	@sp+,	er3
	mov.l	@sp+,	er2
	mov.l	@sp+,	er1
return:
	mov.l	@_shan_thread, er0
	jsr	@_thread_wakeup
割り込みスレッドを起して残りの処理をまかせます。
	mov.l	@sp+,	er0
	bclr	#4,	@0xf6:8	; ISR clear flag during data sampling.
この割り込み処理中に立ち下がりエッジは何度もでてきているので、割り込み待ち
状態になっている。それはいらない待ちなので、クリアして次を待ちます。
	rte

void
shan_main ()
{
  shan_thread = current_thread;
  iprintf ("shan_thread=%x\n", shan_thread);
  thread_priority (current_thread, PRI_HIGH); // interrupt thread.

  *P9_DDR = ~0x30;	// 4, 5 Input.
  *INTC_ISCR = 0x10;	// Falling edge. IRQ4
  *INTC_IER = 0x10;

  intr_enable ();
  while (/*CONSTCOND*/1)
    {
      int32_t d = 0;
      thread_sleep (current_thread);
割り込みハンドラでデータが更新された、あるいはエラーが起きたら、起こされます。

      int s = intr_suspend ();
      if (shan_error)
	{
	  iprintf ("error=%d cause=%d\n", shan_error, shan_error_cause);
	  shan_error = 0;
	}
      if (shan_data_updated)
	{
	  d = shan_data[0];
	  shan_data_updated = 0;
	}
      intr_resume (s);
割り込みハンドラと共有するデータなので、割り込みを禁止して取りこみます。
      shan_print (d);
後は呑気に表示します。
    }
}

void
shan_print (int32_t a)
{
  if (a & 0x800000)
    a |= 0xff000000;
  a >>= 3;

  LCD_PRINTF ("%d, %d\n", a, *INTC_ISR & 0x10);
}
こんな感じに実装したくて、延々とRTOS書いてた。やっぱりレディキューから 全て抜かれた時にコンテキストスイッチのルーチンでスピンループして割り込 みを待つようにしないと、データの落としがひどいです。
あと、たまにデータがおかしくなる。それは
$ bit 1388
.....................|.|.||.||.. 10 8 6 5 3 2 [0x0000056c] 1388
$ bit 1516
.....................|.||||.||.. 10 8 7 6 5 3 2 [0x000005ec] 1516
取りこめてない時がある様子。



そろそろニンジンの収穫の季節です。なんと一個、こんなニンジンっぽいのが
収穫できた! これはうれしい。



他に収穫したのは、ちょっと残念な育ちです。一番手前のなんて3cmもない。ニ ンジンとしての自覚をもって欲しい。この差はいったいどういうことなんだろ う。一番良くできたのは、種がうっかり外にまかれて、畝の端に育ってたやつ なんだよね。日当たりもよくて、他に周りもいなかったのがよかったのかも。
さっそく夕飯で食べました。味が濃厚でニンジンの味に溢れてます。

開発機の方はロジアナっぽいものにしてみました。これはLCDのクロックをとり こんだものです。DRAMを使ってないのでリフレッシュコントローラをインター バルタイマに使って、20usecごとにサンプリングしたものです。これから計測 すると、2.0KHzでした。モニタからの実行で割り込みベクタはモニタから間接 に呼び出しになるというのと、拡張RAMが3ステートアクセスということもあっ て割り込み突入までに最大13usはかかるのだ。これではせいぜい10KHzが限界か な。
ノギスのクロックは90KHzくらいあるので、割り込みでサンプリングは無理。
ここまで作るのに丸一日かかってしまった。というのも自作のRTOSの不具合で。 レディキューから全てなくなってしまった時にどうするか。今迄はなくなる前 のスレッドをそのまま走らせていたのだけど、モニタでスリープに入る時に割 込みを禁止した状態を覚えてしまって、割りこみが入らない。これはスイッチ する時にCCRまで記憶させているからなのだけど、割り込みOKの時からの時も とっておきたいので、しかたない。
とりうる選択肢としては、アイドリングスレッドを作っておいて、レディキューに ひとつもスレッドがない状態がないようにする。か、コンテキストスイッチ中に 割り込みを受けつけるようにスリープするか。
コンテキスト中に割り込みを受けつけるようにするとすると、割りこみハンド ラはカレントのスレッドのストレージに状態を記憶する。そうすると、本来の 状態が上書きされてしまうので、そのスレッドは呼び戻される時にはコンテキ ストスイッチ中にジャンプしてしまう(完全に戻るべき場所をなくしてしまう)。 なので、割り込みを受けつける前に特別の場所に、カレントスレッドの状態を 保存しておいて、そのスレッドにはフラグをつけておく、そして、いつかその スレッドが復帰する時には、フラグによって特別の場所の置いておいた状態を 復帰する。
それは無用な複雑さを招きそうなので、アイドリングスレッドにしておいたの だけど、これはこれで問題があった。
これはアイドリングスレッドとスケジューラとの間で延々とやりとりを繰り返 すことになる。そうなるとその最中に割り込みを禁止して作業することも多々 あるので、外部割り込みの応答性がとても悪くなってしまうのだ。
外部割り込みの応答性を保持しながら、何もしないには明示的にスピンするしか ない。
ここまでに気付くのに一日かかったのだ。

ここでやっとノギスの登場。これは以前使ったSHANのノギスです。これは確認 のためのはずだったのだけど、ここでまたはまった。どうも状態をとれない。 オシロあててみると、コンパレータの出力をそのままオシロにあてると、きっ ちりフルスイングするのだけど、H8の端子に接続した状態だと半分くらいまで にしかスイングしないのだ。これで、延々、あれやこれや配線外したりつけた りして格闘したのだけど、そういうものだった。H8の電源入れるときっちりフ ルスイングした。
しかし、ここでもRAMの問題が。RAMの遅さで3倍遅くなっているので、信号を とれるかとれないかのギリギリなんだ。

旋盤用に買っておいたアゴなしノギスです。アゴなしだとかなり安いです。フ ライスの時もこれが欲しかったのだけど、入手先がみつからなくてしかたなく ノギスから改造した。 http://www7a.biglobe.ne.jp/~isagida/ から、かなり安く購入できました。
これもボディがGNDじゃなく+なので、絶縁して取りつけないとだめだ。
クロックをとってみたところ、SHANと同じっぽい。
49, 241813 38
50, 0 3
51, 0 2
52, 0 2
53, 0 3
54, 0 2
55, 0 3
56, 0 2
57, 0 2
58, 0 3
59, 0 2
60, 0 3
61, 0 2
62, 0 2
63, 0 3
64, 0 2
65, 0 3
66, 0 2
67, 0 3
68, 0 2
69, 0 2
70, 0 3
71, 0 2
72, 0 3
73, 0 81
74, 0 2
75, 0 3
76, 0 2
77, 0 3
78, 0 2
79, 0 2
80, 0 3
81, 0 2
82, 0 3
83, 0 2
84, 0 2
85, 0 3
86, 0 2
87, 0 3
88, 0 2
89, 0 2
90, 0 3
91, 0 2
92, 0 3
93, 0 2
94, 0 3
95, 0 2
96, 0 2
97, 0 49
98, 241812 38
RAMの遅さがかなりきついです。16bit2ステートアクセスなら
49, 613698 98
50, 7 13
51, 7 12
52, 8 12
53, 7 13
54, 7 12
55, 8 12
56, 7 13
57, 7 12
58, 8 12
59, 7 13
60, 7 12
61, 8 12
62, 7 13
63, 7 12
64, 8 12
このくらいなのに。



割り込みの入口と出口のアセンブラのマクロを作ろうとしたのだれど、ステートメント
のセパレータが必要だ。h8300-hms-binutils/work/binutils-2.12.1/gas/doc/c-h8300.texiによると
@samp{$} can be used instead of a newline to separate statements.
となっているので$を使ってみてもだめ。 h8300-hms-binutils/work/binutils-2.12.1/gas/config/tc-8300.cを見ると
const char comment_chars[] = ";";
const char line_comment_chars[] = "#";
const char line_separator_chars[] = "";
セパレータないの!? .....マクロ組めない。仕方ないので、該当部分を #include することにしました。
	.h8300h
	.section .text
	.globl _sci1_rxi
_sci1_rxi:
#include <asm/intr_enter.s>
	mov.l	@_sci_recv_asm_p, er0
	mov.b	@0xbd:8, r1l
	mov.b	r1l, @(0x04, er0) ; sci_recv_asm_p->data = *SCI1_RDR;
	bclr	#6,@0xbc:8	; *SCI1_SSR &= ~SSR_RDRF

	mov.l	@(0x00, er0), er0 ; r0l = sci_recv_asm_p->th
	jsr	_thread_wakeup	; (id)

	sub.l	er0, er0	; dont' rotate ready queue.
	jsr	@_thread_context_switch
#include <asm/intr_exit.s>
こんな感じで。intr_enter.s, inter_exit.sはこのOSのスレッドシステムのカ レントスレッドのレジスタ領域にレジスタを退避して、exitではスケジューリ ングし直します。このルーチンはSCI1のホストからの入力をとるところです。 データを保存して、このデータを処理するスレッドを起こします。処理するス レッドは割り込み後処理用の一番優先度の高いキューにつながれるので、割り 込み終了後はまず先にそれが動いて、寝た所でもう一度スケジューリングされ ます。(そこまでに割りこみからスケジュールの変更がなければ、ここで割り込 まれたスレッドが動きはじめます)
インラインアセンブラの場合はセパレータは'\n'でいけます。
  // Jump to main with changing stack to thread local storage.
  __asm volatile ("mov.l %0, sp \n jmp @%1"::
		  "r"(current_thread->stack_bottom), "r"(board_main));
これはスタートアップが終了して、スタックをスレッドローカルストレージに 変更して、アプリのメインにジャンプするところです。
今日はシリアルコンソールを書きあげました。効率の面と、リソースの消費量 では?だけれど、これはスレッドシステムのデバッグを兼ねてます。
#define	SCI_FIFO_SIZE	16
FIFOサイズはちょっと小さめ(SCI自体にFIFOがないし)。これでリングバッファ
をいじめます。

#define	SCI_STACK_SIZE	THREAD_STACK_DEFAULT
THREAD_STACK_DEFAULTは256。256Byteあれば今のとこ十分。

STATIC struct sci_thread
{
  uint8_t tls[THREAD_STACK_SIZE (SCI_STACK_SIZE)];
  uint8_t buf[RINGBUFFER_SIZE (SCI_FIFO_SIZE)];
  ringbuffer_t rb;
  thread_t th;
  bool started;
}
  sci_recv __attribute ((aligned (4))),
  sci_send __attribute ((aligned (4)));

スレッドローカルストレージ(thread local storage tls)は、スレッドを登録
する側が確保することにしました。スレッド毎にスタックの大きさを変えたい
こともあるし。tlsの先頭は4byteアラインである必要があるので__attribute
((aligned(4)))で確定させます。

受信、送信で同時に待ちに入ることがあるので別スレッドです。(この時点でか
なり富豪主義的です)

STATIC struct sci_recv_asm
{
  thread_t th;		// 0x00
  uint8_t data;		// 0x04
} sci_recv_asm;
struct sci_recv_asm *sci_recv_asm_p = &sci_recv_asm;	// global for asm.

割りこみハンドラはアセンブラで書くので、そこからアクセスする変数はアク
セスしやすいようにまとめておきました。別にしておけば本体のストラクチャ
が変更されたら、アセンブラ側でオフセットを変更しないといけないことから
逃れられるので

STATIC void sci_recv_thread (uint32_t);
STATIC void sci_send_thread (uint32_t);
STATIC void sci_thread_start (struct sci_thread *, void (*)(uint32_t),
			      const char *);
STATIC void sci_buf_putc (int8_t);
STATIC uint8_t sci_buf_getc (void);
void (*sci_putc)(int8_t);
uint8_t (*sci_getc)(void);

void
sci_boot_console_init ()
{
これはブートして一番最初に呼ばれて、printfを有効にするルーチン。
  //  console for booting. don't buffered.

  /* 115200bps 8N1 */
  *SCI1_SCR = 0;	/* CKS1=0 CKS=0 : 1/1 clock (n = 0)*/
  *SCI1_SMR = 0;	/* 8N1 */
  //  *SCI1_BRR = 0xd;	/* 25*1000000/(64*2^(2*0 - 1)*57600) - 1 = 12.56 ->0xd*/
  *SCI1_BRR = 6;	/* 25*1000000/(64*2^(2*0 - 1)*115200) - 1 = 5.78 */
  //  udelay (18);		/* wait 1bit cycle 1/57600*1000000=17.36us */
  udelay (9);		/* wait 1bit cycle 1/115200*1000000=8.68us */
  *SCI1_SCR |= (SCR_TE | SCR_RE);

H8/3052 25MHzなら115200bpsでいけます。

  sci_putc = isci_putc;
  sci_getc = isci_getc;

まだスレッドは使えないので、直にレジスタをポーリングするルーチンを
printfに供出します。スレッドが使えるようになっても割り込みコンテキスト
からは待ちに入ることはできないので、そこからはこのisci_putc, isci_getc
を使ってprintfします。
}

extern inline void
sci_putc1 (int8_t c)
{
ポーリングによるシンプルな書きこみです。

  while ((*SCI1_SSR & SSR_TDRE) == 0)
    ;
  *SCI1_TDR = c;
  *SCI1_SSR &= ~SSR_TDRE;
}


void
isci_putc (int8_t c)
{

CRLFの変換をいれます。このあたりのラインディシプリンもまた泥沼の入口。
でもここには絶対に足を踏み入れません。踏み入れたが最後、通信するのにま
ずは呪文を延々と唱えないといけなくなる。
  cfsetispeed(&t, B57600);
  cfsetospeed(&t, B57600);
  t.c_cflag &= ~(CSIZE|PARENB);
  t.c_cflag |= CS8;
  t.c_iflag &= ~(ISTRIP|ICRNL);
  t.c_oflag &= ~OPOST;
  t.c_lflag &= ~(ICANON|ISIG|IEXTEN|ECHO);
  t.c_cc[VMIN] = 1;
  t.c_cc[VTIME] = 0;
こんなような...

  if (c == '\n')
    sci_putc1 ('\r');
  sci_putc1 (c);
}

uint8_t
isci_getc ()
{
  uint8_t c;

これも素直なポーリングによるホストからの入力を受けとるルーチンです。こ
れはホストからの入力を延々とポーリングしているので効率悪いけれど、これ
が必要な時も少なからずあります。

  while (((c = *SCI1_SSR) & (SSR_RDRF | SSR_ERR_BITS)) == 0)
    ;
  if (c & SSR_ERR_BITS)
    {
      *SCI1_SSR &= ~(SSR_RDRF | SSR_ERR_BITS);
      return 0;
    }

  c = *SCI1_RDR;
  *SCI1_SSR &= ~SSR_RDRF;

  return c;
}

void
sci_console_init ()
{
  // buffered console. need threading support.
スレッドシステムが起動したら、バッファリングするputc, getcにしてみます。

  sci_thread_start (&sci_recv, sci_recv_thread, "sci recv");
  sci_thread_start (&sci_send, sci_send_thread, "sci send");
  sci_recv_asm_p->th = sci_recv.th;
  thread_priority (sci_recv.th, PRI_HIGH); // this is interrupt thread.
ホストからの入力は割り込みでとるので、それを処理するスレッドは割り込み
スレッドとして、一番高い優先度とします。

  // Install buffered putc/getc.
  sci_putc = sci_buf_putc;
  sci_getc = sci_buf_getc;

  // Enable 'Receive data full' and 'Receiver error' interrrupt.
  *SCI1_SCR |= SCR_RIE;
受信データがあふれた時(と受信に失敗した時)の割り込みだけとります。

  //  *SCI1_SCR |= SCR_TIE | SCR_TEIE;
}

void
sci_thread_start (struct sci_thread *st, void (*func)(uint32_t),
		  const char *name)
{
  memset (st, 0, sizeof (struct sci_thread));

  st->rb = ringbuffer_init (st->buf, SCI_FIFO_SIZE);
受信、送信それぞれのリングバッファを用意します。

  st->th = thread_create (st->tls, SCI_STACK_SIZE, name, func, (uint32_t)st);
  thread_start (st->th);
スレッドを作ってスタートします。

  // make sure that ringbuffer_read is waiting.
  while (!st->started)
    thread_rotate_ready_queue (); // XXX wrong -uch

モニタは先にWAIT、次にSIGNALなので、WAITになるまで待ちます。ここで
rotate_ready_queueなのは、このスレッドと、作ったスレッドの優先度が同じ
であることによっています。優先度が同じなら同じレディキューなので、まわ
すことでいつかはターゲットのスレッドに制御が移って、そこでWAITで寝るこ
とで、ここに戻ってくることを期待しています。
 セマフォで待てばいいんだけどね。やっぱりセマフォは必要かな。

}

void
sci_recv_thread (uint32_t arg)
{
  struct sci_thread *st = (struct sci_thread *)arg;
  int s;
  uint8_t c;
  int8_t cr = '\n';

  IPRINTF ("RECV%x\n", arg);
  st->started = TRUE;
  while (/*CONSTCOND*/1)
    {
      thread_sleep (st->th);
      s = intr_suspend ();
      c = sci_recv_asm_p->data;
      intr_resume (s);

割り込みハンドラでストアされたデータをコピーします。割り込みハンドラと
共有データなので割り込みでロックします。本当は、割り込みハンドラからこ
こにくるまでにもう一回割り込みがくるかもしれないので、ここで次の割りこ
みをイネーブルしないといけないのだけど、それはそうしないといけないよう
になったら。

      ringbuffer_write (st->rb, &c, 1);
      if (c == '\r')
	ringbuffer_write (st->rb, &cr, 1);

リングバッファにつっこみます。フルだったらここでずっと寝る。割りこみ
ハンドラから起床されても起きないので、データは落ちます。

    }
}

void
sci_send_thread (uint32_t arg)
{
  struct sci_thread *st = (struct sci_thread *)arg;
  uint8_t buf[SCI_FIFO_SIZE];
  int i;

  IPRINTF ("SEND%x\n", arg);
  st->started = TRUE;
  while (/*CONSTCOND*/1)
    {
      size_t sz = ringbuffer_read (st->rb, buf, SCI_FIFO_SIZE);
      for (i = 0; i < sz; i++)
	sci_putc1 (buf[i]);
    }

ここはリングバッファに入ってたら起きるので、それを書くだけです。
ringbufferにwaterlinとflushを入れたい...。
}

void
sci_buf_putc (int8_t c)
{
  int8_t cr = '\r';

  if (c == '\n')
    ringbuffer_write (sci_send.rb, &cr, 1);
  ringbuffer_write (sci_send.rb, &c, 1);
}

uint8_t
sci_buf_getc ()
{
  uint8_t c;

  if (ringbuffer_read (sci_recv.rb, &c, 1) == 1)
    return c;

  return 0;
}
バッファ版のputcとgetcはリングバッファのアクセスのみです。

スレッドシステムまわりも全体的にいじりました。スレッド、モニタは呼び出 し側からリソースを設定することにしたので、数の限界はなくなりました。最 初そうしなかったのは、動的に登録だとそれぞれをリストでとっておかないと いけないのが最初は辛いかなと思ったから。
トグルスイッチの割り込みで、スレッド、モニタの状況もわかるようにしました。
---Ready Queue---
<0>: 
<1>: 
<2>: 
<3>: 7 
---Thread Status---(remain/total)
[7] R 3 (88/256) button polling
[6] W 1 (104/256) button 2
[5] W 1 (100/256) button 1
[4] W 1 (100/256) button 0
[3] W 1 (144/256) sci send
[2] W 0 (164/256) sci recv
[1] W 3 (52/256) root
CCR_PC=82b6a
---Monitor---
W button
        lock : 
        event: 4(button 0) 
U ringbuffer
        lock : 
        event: 
W ringbuffer
        lock : 
        event: 3(sci send) 
W ringbuffer
        lock : 
        event: 1(root) 7(button polling) 
スレッドスタックをどこまで使っているかの見積もりは、スレッド作成時にス タックには0xaaを書きこんでおいて、時折りスレッドトップから0xaaがどこま で続いてるかをカウントして調べています。
これでダンマリ状況の状態を探れます。
なんとかこれでいいかな。

ということで次の段階に。ケースの中から伸びてきている線が端子につながって いるのは、LCD用のクロック。これが何KHzなのかを調べます。この端子からは コンパレータ経由で3.3Vになって、IRQ5につながれています。
ここで基板の設計ミスにぶちあたりました。外部電源を入れてコンパレータから 信号が出力されている状態だと、シリアルの信号をじゃまして、プログラムが ロードできなかったりします。
 H8/3052 Advanced Mode Monitor Ver. 3.0A
 Copyright (C) 2003 Renesas Technology Corp.

: l
~>Local file name? test.mot
1910 lines transferred in 4 seconds 
!
  ********  Check Sum Error  ********  
: g

プログラムをロードする前に外部電源を切って、ロードし終わったら外部電源 を入れて、それから実行しないとだめなのだ....。電源別のスイッチにしてお いてよかったよ。
この信号は
uint16_t la_t0;

void
la_test ()
{
  *ITU_TSTR = 0;	// All timer stop.
  *ITU0_TCR = ITU__TCR_ICLK1;
  *ITU0_TIOR = 0; // compare match. don't output.
  *ITU0_TIER = ITU__TIER_OVIE;
  *ITU0_TCNT = 0;
  *ITU_TSTR |= ITU_TSTR_STR0;	// timer 0 start
  *ISCR |= 0x30;		// Falling edge.
  *IER |= 0x30;
  intr_enable ();

  while (1)
    {
      IPRINTF ("%d\n", (uint32_t)la_t0);
    }
}

	.globl _irq5
_irq5:
	mov.l	er0, @-sp
	mov.w	@0xff68:16, r0
	mov.w	r0, @_la_t0	; la_t0 = *ITU0_TCNT
	sub.w	r0, r0
	mov.w	r0, @0xff68:16	; *ITU0_TCNT = 0
	bclr	#2, @0xfff67:8	; *ITU0_TSR &= ~ITU__TSR_OVF
	mov.l	@sp+,er0
	rte
これで計測。結果は
13875
13883
13844
13869
13887
13881
13852
13861
13851
13878
13863
13900
13866
13869
13869
13875
13871
13852
13869
13869
13869
13882
13844
カウンタはシステムクロックでまわしているので、25MHz. 13850カウントで554usec.→1.8KHz。まぁいいとこだ。このLCDは1KHz-3KHzだから。
ちょこっと作るつもりだったのが結局全力投球で書いてしまったRTOSは これ


シリアルコンソールの出力をリングバッファ経由にしてみました。これはかな
りのスレッド周りのデバッグになる。わらわらと至らない点がでてきました。

#define	RINGBUFFER_SIZE(x)	(sizeof (struct ringbuffer) + (x))

#define	FIFO_SIZE	8

uint8_t rbuf[RINGBUFFER_SIZE (FIFO_SIZE)];
uint8_t sbuf[RINGBUFFER_SIZE (FIFO_SIZE)];

void send_thread (uint32_t);
ringbuffer_t rb_send

void __sci_putc (int8_t);

void
sci_console (uint32_t arg)
{
  int id;
  rb_send = ringbuffer_init (sbuf, FIFO_SIZE);

  thread_create (send_thread, &id, (uint32_t)rb_send);
  thread_start (id);
  thread_switch (); // make sure to send_thread ring_buffer_read is waiting.

モニタのセマンティクスは、先にWAITで次にシグナル。先に口を開けるように
しておかないで書きはじめると、「バッファがフルだから、抜いて!」というシ
グナルを送ってもその受け手がいないのでデッドロックする。シグナルを送る
時点で受け手がいなければ捨てられてしまうはモニタの定義。

  sci_putc = __sci_putc;

  return;
}

void
send_thread (uint32_t arg)
{
  ringbuffer_t rb = rb_send;
  uint8_t buf[FIFO_SIZE];
  int i;

  while (/*CONSTCOND*/1)
    {
      size_t sz = ringbuffer_read (rb, buf, FIFO_SIZE);
      for (i = 0; i < sz; i++)
	{
	  while ((SCI1->SSR & SSR_TDRE) == 0)
	    ;
	  SCI1->TDR = buf[i];
	  SCI1->SSR &= ~SSR_TDRE;
 リングバッファが空でなければringbuffer_readのブロックが落ちるので
その分を書きこみます。
	}
    }
}

void
__sci_putc (int8_t c)
{
  int8_t cr = '\r';

  if (c == '\n')
    {
      ringbuffer_write (rb_send, &cr, 1);
    }
  ringbuffer_write (rb_send, &c, 1);
}

こんな感じで動かしてみたところ、デッドロックしまくり。

monitor_enter:lock by 2              書き込みに入った。
W< i(0) o(0) f(8) s(8) remain=1
====>1 8 1
monitor_signal:[2] signal-->1        空で待っているの(1番)がいたので起こします。
monitor_exit: [2] wakeup------>1     exitでthread_wakeupしました。
monitor_exit: unlock by 2
monitor_enter:lock by 3              !しかしここで(1番)の前に他が入った!
W< i(1) o(0) f(7) s(8) remain=1
====>1 7 1
monitor_signal:[3] signal-->1        さっきのシグナルで起きてないのでまた起こし。
thread_sleep: wakeup: 1       いきなり(1番)が起きた。タイマのスケジューリングで
monitor_wait: lock by 1     waitはロックをとれることが保証されてるのでロック。
monitor_wait: [1] steeled lock from [3]  !!!!ここでレース!!!!
XXXXXXXXXX Asssertion failed. kernel/monitor.c, 153 XXXXXXXXXX
                                R:filled! ==>2 2 8 6
                                R> i(2) o(2) f(8) s(8)
monitor_exit: unlock by 1
XXXXXXXXXX Asssertion failed. kernel/monitor.c, 66 XXXXXXXXXX
monitor_exit: unlock by 3
ttmonitor_enter:lock by 1
                                R< i(2) o(2) f(8) s(8)
                                R:empty!
monitor_wait: unlock by 1
thread_sleep: sleep: 1
W> i(2) o(2) f(8) s(8)
monitor_enter:lock by 2
W< i(2) o(2) f(8) s(8) rema

つまり、monitor_exitでthread_wakeupしても、それはレディキューにつないだ
だけ。exitの時点で既にenter待ちよりは先に実行されるけれど、exitしてから
ラウンドロビンした先が(この時点ではまだenterしていない)、enterしたらそ
れが入ってしまうのでレース状況が発生してしまう。exitが終了する所で、イ
ベント待ちのは全て処理されないといけないんだ。

ということで、monitor_exitを変更

void
monitor_exit (monitor_t m)
{
  struct thread_control *tc;
  int s = intr_suspend ();

  assert (m->lock_thread == current_thread);
  // wakeup scheduled thread if any.
  // monitor assures wakeuped thread can handle resource.
  SIMPLEQ_FOREACH (tc, &m->event_queue, tc_monitor_event)
    {
      if (!tc->monitor_wakeup_request)
	continue;
      DPRINTF ("%s: [%d] wakeup------>%d\n", __FUNCTION__, current_thread->id,
	       tc->id);
イベント待ちのスレッドの優先度を最高レベルに上げてmonitor_enterに入るスレッド
より先に処理することを確定させます。

      if (!tc->inherit_priority)
	tc->native_priority = tc->priority;
      tc->inherit_priority = TRUE;

      thread_priority (tc->id, 0);
イベント待ちのスレッドはTHR_WAIT状態なので、コントロールブロックの値が
変更されるだけで、switchは行なわれません。(したところで、レディキューに
登録されてないので実行されることはないので)

      thread_wakeup (tc->id);
ここで、レディキューにつながれます。つながれるだけでスイッチはしません。
    }

  while ((tc = SIMPLEQ_FIRST(&m->lock_queue)) != NULL)
    {
      SIMPLEQ_REMOVE_HEAD(&m->lock_queue, tc_monitor_lock);
      thread_wakeup (tc->id);
    }
ここでロック待ちのスレッドがレディキューにつながれます。

  m->state = MON_UNLOCKED;
  m->lock_thread = NULL;
  current_thread->monitor_event = 0;
  // Inherit priority.
  if (current_thread->inherit_priority)
    {
      SCI_PRINTF_BOOT ("%s: inherit priority [%d] (%d->%d)\n",
		       __FUNCTION__, current_thread->id,
		       current_thread->priority,
		       current_thread->native_priority);
      current_thread->inherit_priority = FALSE;
      thread_priority (current_thread->id, current_thread->native_priority);
もし、ロック待ち、あるいは、イベント待ちで優先度継承されていたら元に戻します。
    }
  DPRINTF ("%s: unlock by %d\n", __FUNCTION__, current_thread->id);

  thread_switch (); //make sure to run thread that wakeuped by event.
ここでスイッチすることで、確実にイベント待ちのスレッドがmonitor_exitを
抜けた後に実行されることを確定させます。

  intr_resume (s);
}

結果は。

fiber 1
fiber 2
fiber return.
monitor_exit: inherit priority [1] (0->1)
tttthhhhrrrreeeeaaaadddd    2345((((rrrseeeecccneeediiievvvreee)rrr:))) :::a   raaagrrr=ggg8===82488
変な出力ですが、一文字putcして、そこでモニタのスケジューリングが入ってそれぞれの
スレッドがディスパッチされて一つのリングバッファに入るのでこうなります。


monitor_exit: inherit priority [1] (0->1)
monitor_exit: inherit priority [4] (0->1)

monitor_exit: inherit priority [1] (0->1)
thread 4: RECV 8
ohayodesyo1234

なんとかなったかな。



昨日はNSR50を整備して散髪に行って、今日朝、秋ヶ瀬に行く準備をしていたの
ですが、起きてみたらあまりに寒いのでパスしました。なんか去年、今年と寒
過ぎないか?とデータをみてみると2004年の秋ヶ瀬は12/8 20.7℃、12/17
13.6℃、12/24 14.4℃、1/14 13.6℃。いずれも昼過ぎ。2004-2006まで暖かかっ
た。

やっぱりもうちょっとH8/3052開発機ソフトを進めます。ノリノリだし。
シリアルコンソールの部分がおざなりなので、まずはそれのためにリングバッファを 用意しました。バッファリングはリングバッファ一個で済ませる予定。

struct ringbuffer
{
  struct monitor *mon;
  uint8_t *buffer;

  size_t input;
  size_t output;
  size_t size;	// must be power of 2
  size_t free;
} ringbuffer;

#define	RINGBUFFER_FULL		0x1
#define	RINGBUFFER_EMPTY	0x2

ringbuffer_t
ringbuffer_init (uint8_t *buf, size_t size)
{

  ringbuffer.mon = monitor_allocate ();

  ringbuffer.buffer = buf;
  ringbuffer.input = 0;
  ringbuffer.output = 0;
  ringbuffer.size = size;
  ringbuffer.free = size;

  return &ringbuffer;
}

void
ringbuffer_write (ringbuffer_t ctx, uint8_t *buf, size_t size)
{
  struct ringbuffer *rb = ctx;
  size_t i, j, sz;

  while (size > 0)
    {
      monitor_enter (rb->mon);
      if (rb->free == 0)
	monitor_wait (rb->mon, RINGBUFFER_FULL);

条件変数の場合はここでロックをとり直すけれど、モニタの場合は既にロックを
握っているのでいらない。

しかし、この前実装したモニタには間違いがあった。シグナルで起こされたイ
ベント待ちのスレッドを全て起しているのだけど、モニタとしては全てではな
く、一つを起こして、monitor_waitが確実にイベントをとれるようにしないと
いけない。僕の実装だと一つのイベントに対して二つ以上が待っていた場合に
レースになる。

このリングバッファの場合、二つの書き手がいた場合、バッファが空いて最初
の書き手はこのままmonitor_exitまで進んでモニタを開放、次の書き手がここ
からはじまるのだけど、既に前の書き手によってrb->free == 0になってしまっ
ているかもしれない。

ここを
      while(rb->free == 0)
	monitor_wait (rb->mon, RINGBUFFER_FULL);
としてもいいかも。waitでthread_sleepに入るので、レディキューから抜かれて、
monitor_exitでロック取得待ちのがモニタに入ってこれるはず。
イベントに対してリソースを取り合いがない(参照するだけ)場合は、今の仕様
の方が扱いやすい。

もうちょっと考えます。

      sz = rb->free < size ? rb->free : size;

      for (i = 0; i < sz; i++)
	{
	  j = (rb->input + i) & (rb->size -1);
	  rb->buffer[j] = *buf++;
	}

      rb->input += sz;
      rb->input &= (rb->size - 1);
      rb->free -= sz;
      size -= sz;
      monitor_signal (rb->mon, RINGBUFFER_EMPTY);
      monitor_exit (rb->mon);
    }

  SCI_PRINTF ("W%d %d %d\n", rb->input, rb->output, rb->free);
}

size_t
ringbuffer_read (ringbuffer_t ctx, uint8_t *buf, size_t size)
{
  struct ringbuffer *rb = ctx;
  size_t buffered, sz, i, j;

  monitor_enter (rb->mon);
  if ((rb->size - rb->free) == 0)
    monitor_wait (rb->mon, RINGBUFFER_EMPTY);

  buffered = rb->size - rb->free;

  sz = buffered > size ? size : buffered;

  for (i = 0; i < sz; i++)
    {
      j = (rb->output + i) & (rb->size -1);
      buf[i] = rb->buffer[j];
    }

  rb->output += sz;
  rb->output &= (rb->size - 1);
  rb->free += sz;

  SCI_PRINTF ("R%d %d %d\n", rb->input, rb->output, rb->free);

  monitor_signal (rb->mon, RINGBUFFER_FULL);
  monitor_exit (rb->mon);

  return sz;
}

モニタを使って書いてみると、ほとんど条件変数を使った場合とコーディング
は変わらない。このくらいならむしろセマフォ二つでロックとイベントをまか
せた方が素直。条件変数と同じように書けるけれど、条件変数の場合、
monitor_enterとmonitor_exitにあたる部分は単なるmutexなので、ただのロッ
クにもそのmutexを使える。しかしモニタの場合monitor_exitがwaitで起こされ
るスレッドの処理までするのでちょっとオーバーヘッドがある。いろいろ考え
ると、セマフォだけ実装するのが一番いいかもしれない。でも今回はモニタだ
けでやります。モニタの良し悪しを見極めたい。

リングバッファも最初はこのくらいでいいのだけれど、「待たないでやれる分
だけやりたい」とか、「待ちに入っちゃったのをキャンセルして終わりたい」
とかいろんな要望で、泥沼に入っていく。スレッドは、終了させましょうとい
う時に、待ちを解除してリソースを開放させるのがとてもつらい。

そしてスレッド脳なうちに、もうちょっと実装しました。ファイバ(fiber)。こ
れは継続(continuation)とかコルーチン(coroutine)とか呼ばれているもののマ
イクロソフトの呼称だと思う(ちょっとわからない)。要はスケジューラのない
スレッドかな。スケジューラがないので、それぞれのファイバは明示的に次の
ファイバを指定することで制御を移行します。ファイバは局所記憶をそれぞれ
が持つのでスレッドに近い。


それぞれのファイバはサブルーチンコールだけによって順々に実行されるので、
ファイバ間で共有するリソースにロックは必要ない。

スケジューラがないのでsleep/wakeupはない。明示的にfiber_switchでつなぐか
つながないかだけ。

ただスレッドシステムの場合、スケジューラに属さないと動きようがないので、
ファイバはスレッドに属することになる。なかなかいいネーミングです。

実装してみました。

#ifndef _FIBER_H_
#define	_FIBER_H_
typedef struct fiber
{
  uint32_t er4;		// 0x00
  uint32_t er5;		// 0x04
  uint32_t er6;		// 0x08
  uint32_t sp;		// 0x0c (er7)
  uint32_t pc;		// 0x10
  uint32_t pad;		// for alignment adjustment.
  uint8_t stack[0];
} *fiber_t __attribute__((packed)); //24byte

明示的にサブルーチンコールでしかスイッチしないのでcallee savedと、スタッ
ク、次に実行するPC(jsr fiber_switchの次の行)をとっておけばいい。パディ
ングがあるのは、fiber_init用にsizeof (struct fiber)をとったけれども、
4byteアラインじゃなかった時に4byteアラインにラウンドしても全体のサイズ
がとれるように。

stack[0]はこのファイバ用のスタック領域の先頭。実際にはこのストラクチャ
とスタックを合わせた領域をとって、これにキャストする。


__BEGIN_DECLS
void fiber_switch (fiber_t, fiber_t);
fiber_t fiber_create (uint8_t *, size_t, void (*)(void));
fiber_t fiber_init (fiber_t);

__END_DECLS

#endif

ファイバ切り替えルーチン。

	.h8300h
	.section .text

	/* void fiber_switch (fiber_t(er0), fiber_t(er1)) */
	.globl _fiber_switch
_fiber_switch:
	; save current context.
	; no need to store caller saved and ccr.
	mov.l	@sp, er2	; return address
	mov.l	er2, @(0x10, er0) ; pc
	mov.l	sp,  @(0x0c, er0) ; sp
	mov.l	er6, @(0x08, er0) ; callee saved
	mov.l	er5, @(0x04, er0) ; callee saved
	mov.l	er4, @(0x00, er0) ; callee saved
	; load next context.
	mov.l	@(0x0c, er1), sp  ; sp
	mov.l	@(0x10, er1), er2 ; pc
	mov.l	er2, @sp	  ; set return address and ccr
	mov.l	@(0x08, er1), er6 ; callee saved
	mov.l	@(0x04, er1), er5 ; callee saved
	mov.l	@(0x00, er1), er4 ; callee saved
	rts

現在のファイバのコンテキストが第一引数(er0)、次のファイバのコンテキストが
第二引数er1に入る。ファイバは割り込みから状態を変更されることはないので
割り込みを禁止する必要はない。スイッチングの速さはいい。とはいえサブルーチン
コールにくらべればずっと重いです。

スレッドの場合、割り込みからも状態が変更されるので割り込みを禁止し、
割りこみからスイッチされたコンテキストのために、caller savedまで復帰しない
といけない。次のスレッドを決めるにはthread_context_switchを呼んで、それは
優先度ごとのレディキューをまわって、次のスレッドを探している。

	.globl _thread_switch
_thread_switch:
	; disable interrupt
	stc.b	ccr, r2l
	orc	#0x80, ccr
	; save context.
	mov.l	@_current_thread, er0
	mov.l	@sp, er1	; return address
	mov.l	er1, @(0x20, er0) ; pc
	mov.b	r2l, @(0x20, er0) ; ccr    |ccr(8)|   pc(24)  |
	mov.l	sp,  @(0x1c, er0) ; sp
	mov.l	er6, @(0x18, er0) ; callee saved
	mov.l	er5, @(0x14, er0) ; callee saved
	mov.l	er4, @(0x10, er0) ; callee saved
	; no need to store caller saved and ccr.
	jsr	@_thread_context_switch
	mov.l	@_current_thread, er0
	mov.l	@(0x1c, er0), sp  ; sp
	mov.l	@(0x20, er0), er2 ; pc
	mov.l	er2, @sp	  ; set return address and ccr
	mov.l	@(0x18, er0), er6 ; callee saved
	mov.l	@(0x14, er0), er5 ; callee saved
	mov.l	@(0x10, er0), er4 ; callee saved
	mov.l	@(0x0c, er0), er3 ; caller saved
	mov.l	@(0x08, er0), er2 ; caller saved
	mov.l	@(0x04, er0), er1 ; caller saved
	mov.l	@(0x00, er0), er0 ; caller saved
	; resume interrupt. and return
	rte



static void *round_buffer_4 (uint8_t *, size_t *);

// Return own fiber control block.
fiber_t
fiber_init (fiber_t fiber_local_store)
{
  size_t sz; // dummy.

  return round_buffer_4 ((uint8_t *)fiber_local_store, &sz);
}

ファイバを作成しようとするスレッドが、自分をファイバとして登録する領域を
とっておきます。4byteアラインにするだけです。自分のファイバのスタックは
現在実行時点でのスタックをそのまま使います。

// create new fiber.
fiber_t
fiber_create (uint8_t *fiber_local_store, size_t size, void (*start)(void))
{
  fiber_t f = round_buffer_4 (fiber_local_store, &size);
  size_t stack_size = size - sizeof (struct fiber);

  f->sp = (addr_t)f->stack + stack_size - 4;
  f->pc = (addr_t)start;
  // install return address for 'rts'
  *(uint32_t *)f->sp = f->pc;

  return f;
}

新しいファイバを作ります。ファイバのコントロールブロックとスタックを合わせた
ものは呼び出し元から設定されたのを使います。
|            fiber_local_store (size byte)        |
| struct fiber |           stack               |PC|

PCの領域にこのファイバがはじめるべき関数のアドレスを入れておき、SPに設定。
fiber_switchの最後の'rte'で、そこにジャンプしてくれます。

void *
round_buffer_4 (uint8_t *b, size_t *sz)
{
 // aligned to 4byte.
  addr_t f = ROUND ((addr_t)b, 4);
  *sz = TRUNC ((addr_t)b + *sz, 4) - f;

  return (void *)f;
}

fiber用の領域を__aligned (4)で設定してもいいのだけど、一応。


テストプログラム。やる事に対してこれは、メモリのかなりの無駄だし、遅い。
あくまでテスト用です。このくらいならファイバなんてやらずにswitch文です。
そう考えると、この先使う事があるのかどうか微妙ではある。

void thread_test (void);
void button_0 (void);
void button_1 (void);
void button_2 (void);
void button_put_thread (int);

uint8_t rbuf[16];
ringbuffer_t rb;
struct
{
  struct monitor *mon;
  int data;
} button;
void button_put (int, int);
int button_get (int);
int button_data;

fiber_t fiber[4]; ファイバコンテキストの参照用です。
uint8_t fls[3][128]; ファイバコンテキストです。 ファイバ0はスレッドスタックを
そのまま使うので、残りの3つしか用意しません。

void
thread_test ()
{
  int id;
  struct fiber myself;

  fiber[0] = fiber_init (&myself);
ファイバを使うにあたって、自分用のファイバコンテキストブロックを設定します。

  fiber[1] = fiber_create (fls[0], 128, button_0);
  fiber[2] = fiber_create (fls[1], 128, button_1);
  fiber[3] = fiber_create (fls[2], 128, button_2);
ボタンそれぞれの分に三つ作ります。

  rb = ringbuffer_init (rbuf, 16);
メッセージ伝達テスト用のリングバッファーです。

  button.mon = monitor_allocate ();
  thread_create (button_put_thread, &id, 0);
  thread_start (id);
ボタンを監視して、入力に変化があったらリングバッファーにつっこむスレッドです。

#if 1
  *ITU_TSTR = 0;	// All timer stop.
  *ITU0_TCR = ITU__TCR_ICLK8;	// .08us (CPU 25MHz) ->OVF 5.242 msec
  *ITU0_TIOR = 0; // compare match. don't output.
  *ITU0_TIER = ITU__TIER_OVIE;
  *ITU0_TCNT = 0;
  *ITU_TSTR |= ITU_TSTR_STR0;	// timer 0 start

  *ISCR |= 0x30;		// Falling edge.
  *IER |= 0x30;
  intr_enable ();
#endif
 タイマ割り込みでラウンドロビンさせます。

  while (/*CONSTCOND*/1)
    {
      button_data = button_get (PS_0 | PS_1 | PS_2);
      button_getは入力がなければブロックします。
      ボタンの入力があったところで起きます。

      SCI_PRINTF ("fiber dispatch\n");
ボタンそれぞれのファイバにスイッチしていきます。
      fiber_switch (fiber[0], fiber[1]);
全てのファイバがスイッチされるとここに戻ってきます。

      SCI_PRINTF ("fiber dispatch done.\n");

      thread_debug_stack_check ();
    }
}

void
button_put (int data, int channel)
{

  monitor_enter (button.mon);
  button.data = data;
  monitor_signal (button.mon, channel);
  monitor_exit (button.mon);
}

int
button_get (int channel)
{
  int data;

  monitor_enter (button.mon);
  monitor_wait (button.mon, channel);
  data = button.data;
  monitor_exit (button.mon);

  return data;
}

void
button_put_thread (int arg)
{
  int id;

  id = thread_id ();
  SCI_PRINTF ("thread %d(sender): arg=%d\n", id, arg);
  while (/*CONSTCOND*/1)
    {
      int button;
      if ((button = push_switch ()) != 0)
	{
	  SCI_PRINTF ("thread %d: SEND %d channel %d\n", id, button, button);
	  button_put (button, button);
	  //	  thread_switch ();
      ボタンに変化があったら、ボタン状態を送ります。
	}
    }
}

char write_msg[] = "/usr/pkg/bin/h8300-hms-gcc -mh -fomit-frame-pointer -mquickcall -O -mrelax -Wall -Werror -DDEBUG -I/home/uch/h8/w -I/home/uch/h8/w/h8/3052 -I/home/uch/h8/w/include -Wp,-MD,.deps/thread_test.P -c -o thread_test.o ud01/thread_test.chello ohayo";

void
button_0 ()
{
  int id;
  char *p = write_msg;

  id = thread_id ();

  SCI_PRINTF ("thread %d fiber 1 (receiver):\n", id);
  while (/*CONSTCOND*/1)
    {
      if (button_data & PS_0)
	{
	  SCI_PRINTF ("thread %d fiber 1: RECV %d\n", id, button_data);
	  lcd_clear (0x00);
	  led (0, TRUE);
	  led (1, FALSE);
	  SCI_PRINTF ("write: %c\n", *p);
	  ringbuffer_write (rb, p, 8);
      !!!ここで寝る可能性があります。
	  p += 8;
      ボタン0が押されていたら、メッセージを8byte送ります。

	}
      fiber_switch (fiber[1], fiber[2]);
      次のファイバに移ります。
    }
}

char read_msg[1024];

void
button_1 ()
{
  int id;
  char *p = read_msg;

  id = thread_id ();

  SCI_PRINTF ("thread %d fiber 2 (receiver):\n", id);
  while (/*CONSTCOND*/1)
    {
      if (button_data & PS_1)
	{
	  SCI_PRINTF ("thread %d fiber 2: RECV %d\n", id, button_data);
	  lcd_home ();
	  led (0, FALSE);
	  led (1, TRUE);
	  size_t sz = ringbuffer_read (rb, p, 64);
      !!!ここで寝る可能性があります。
	  SCI_PRINTF ("read %d:%s\n",sz, read_msg);
	  p += sz;
      ボタン1が押されていたら、メッセージを64byteまで読みます。
	}
      fiber_switch (fiber[2], fiber[3]);
      次のファイバに移ります。
    }
}

void
button_2 ()
{
  int id;

  id = thread_id ();

  SCI_PRINTF ("thread %d fiber 3 (receiver):\n", id);
  while (/*CONSTCOND*/1)
    {
      if (button_data & PS_2)
	{
	  SCI_PRINTF ("thread %d fiber 3: RECV %d\n", id, button_data);
	  lcd_test ();
      ボタン2が押されていたら、LCDのテストをしてみます。
	}
      fiber_switch (fiber[3], fiber[0]);
      次のファイバに移ります。(元に戻る)
    }
}

thread_init 0: 83b5c-83960
thread_init 1: 83d5c-83b60
thread_init 2: 83f5c-83d60
thread_init 3: 8415c-83f60
thread_init 4: 8435c-84160
thread_init 5: 8455c-84360
thread_init 6: 8475c-84560
thread_init 7: 8495c-84760
thread create 1: pc=827a4 sp=83d5c
thread 1(sender): arg=0
thread 1: SEND 2 channel 2
fiber dispatch
thread 0 fiber 1 (receiver):
thread 0 fiber 1: RECV 2
write: /
W8 0 8                ここでbutton_0がリングバッファに8個書いた。
thread 0 fiber 2 (receiver):
thread 0 fiber 3 (receiver):
fiber dispatch done.
thread 1: SEND 4 channel 4
fiber dispatch
thread 0 fiber 2: RECV 4
R8 8 16
read 8:/usr/pkg       ここでbutton_1がリングバッファから8個読みだした。
fiber dispatch done.
thread 1: SEND 2 channel 2
fiber dispatch
thread 0 fiber 1: RECV 2
write: /
W0 8 8
fiber dispatch done.
thread 1: SEND 4 channel 4
fiber dispatch
thread 0 fiber 2: RECV 4
R0 0 16
read 8:/usr/pkg/bin/h83
fiber dispatch done.
thread 1: SEND 4 channel 4
fiber dispatch

こうやってリングバッファがスリープに入らないうちはいいけれど、読み出し
か書き込みかがリングバッファでブロックされると終了。ファイバはスケジュー
リングの対象にならないからだ。寝たところを起すところが、次のファイバだ
からデッドロックしてしまうのだ。ファイバをスレッドにすればスケジューリ
ングの対象になるから大丈夫。

スレッドなしで(ファイバのみ)周期的に同期をとる、散逸したルーチンにはファ
イバはいいとは思う。




昨日紹介した本、絶版でとんでもなくプレミアついてるのね。いろいろ蔵書を
調べてみた。プレミアついてたのを3冊ほど。

Mach(マーク)オペレーティングシステム—プログラミングと概念 (アジソンウェスレイ・トッパン情報科学シリーズ) J. ボイキン A. ランガーマン D. カーシェン S. ロゥバーソ 岩本 信一 

これは25000円だって。これはMachの開発者による本で、実装するにあたってどういう問題に対してどう実装したかをとうとうと書かれた本。Machに限らず、その知見を感じとれるいい本です。これが絶版なのは残念ですね。


分散OS Machがわかる本 (LUNAの本シリーズ) 乾 和志 菅原 圭資 LUNA-88KにMachを移植したチームによる本。使う側から見たMachのオーバービューがよくまとまっています。これはMachいじってなければいらないかな。
オペレーティングシステム-設計と実装 (Information & Computing) R. スウィッツア Robert Switzer 斎藤 靖 これは大学の講義をまとめたもの。著者はBach本の擬似コードの抽象化を目指 したのだけど、そうはいっていない。でもそこが実はわかりやすかったりする。 本の半分以降は全部ソースコード。 僕は好きな本。でも一般的にはプレミアついてまで買う本じゃない。


今日はスレッドにプライオリティを入れました。迷ったんだけどね。これを実 装すると、パンドラの箱を開けたことになる。実装しようとするものに対して これはいらないだろうとも思うのだけど...。
問題になるのは優先度逆転だ。マーズパスファインダーもこれにはまった。火 星に着陸した。さぁデータをとるぞと、データをとるスレッドを起動したはい いけど、再起動の連続。データをとるようなスレッドは優先度が低い。システ ムクリティカルな優先度の高いスレッドはタイマ割りこみで実行されるのだけ ど、データ取りのスレッドがリソース握ったままで、起こされてもリソース待 ちで寝るだけ。ウォッチドッグタイマでリセットがかかるという事態だった。 この事件は結局セマフォを優先度継承に設定するように変更して(火星のマーズ パスファインダーに)なんとかなった。


実装します。まずはスレッド側から。プライオリティを変更するルーチンと コンテキストスイッチで次のスレッドを選ぶ部分を変更します。
#define	THREAD_PRIORITY_MAX	4

struct thread_manager
{
  struct tc_queue ready_queue[THREAD_PRIORITY_MAX];
} thread_manager;

優先度はせいぜい4つとして、レディーキューを4つに分割しました。優先順位
はITRONにしたがって、小さい方が優先度が高いことにします。ITRONの仕様と
は違って0から使うことにします。

0 割りこみスレッド
1 アプリ高
2 アプリ低
3 システム

の予定です。

int
thread_context_switch ()
{
  // already interrupt disabled.
  struct thread_control *tc;
  struct tc_queue *q;
  int i;

  if (current_thread->state == THR_RUN)
    {
      current_thread->state = THR_READY;
      q = __ready_queue (current_thread);
      SIMPLEQ_REMOVE_HEAD (q, tc_link);
      SIMPLEQ_INSERT_TAIL (q, current_thread, tc_link);

実行状態(RUN)からREADYに移るときはそのスレッドのプライオリティのレディ
キューにつなぎ直します。

    }

  for (i = 0, tc = NULL; i < THREAD_PRIORITY_MAX; i++)
    {
      q = &thread_manager.ready_queue[i];
      if ((tc = SIMPLEQ_FIRST (q)) != NULL)
	break;
    }

優先度の高いレディキューから次に実行すべきスレッドを探します。レディ
キューが分割されたおかげで、高位のレディキューがうまっていれば、低位の
レディキューは絶対に起動されません。これはプログラミングに考えるべき要
素をかなり増やします。優先度がなければ、単に自分をレディキューの後につ
なぎかえる操作で、すべてのスレッドを実行できた。

  if (tc)
    current_thread = tc;
  current_thread->state = THR_RUN;

  return 0;
}

int
thread_priority (int id, int npri)
{
  int s = intr_suspend ();
  struct thread_control *tc;
  struct tc_queue *new_q, *old_q;
  int opri;
  int err = E_OK;

  assert (npri >= 0 && npri < THREAD_PRIORITY_MAX);
  tc = thread_control + id;
  opri = tc->priority;
  tc->priority = npri;

  new_q = &thread_manager.ready_queue[npri];
  old_q = &thread_manager.ready_queue[opri];

  SCI_PRINTF ("%s:thread%d: pri %d->%d state = %d\n",
	      __FUNCTION__, id, opri, npri, tc->state);
  switch (tc->state)
    {
    case THR_RUN:
      SIMPLEQ_REMOVE_HEAD (old_q, tc_link);
      SIMPLEQ_INSERT_TAIL (new_q, tc, tc_link);

実行状態なので、古い優先度のキューの先頭なのは確実なので、そこから抜い
て、新しいキューに突っこみます。後からです。(ITRONの仕様により)
      if (npri >= opri) // new priority is eqaual or lower.
	{
	  tc->state = THR_READY;
	  thread_switch ();
	}
優先度が同じあるいは下がるなら、スイッチします。同一プライオリティで
thread_priorityを呼んだ時にはrotate ready queueになるのはITRONの仕様です。

      break;

    case THR_READY:
      SIMPLEQ_REMOVE (old_q, tc, thread_control, tc_link);
      SIMPLEQ_INSERT_TAIL (new_q, tc, tc_link);
      if (current_thread->priority > tc->priority)
	{
	  thread_switch ();
	}
もし、設定したスレッドのプライオリティが今のスレッドより高くなるなら、
ここでスイッチします。(??? ITRONの仕様との整合性の確認はまだ ???)

      break;
    case THR_WAIT:
      // nothing to do.
レディキューにつながれてないので、キューの操作はありません。
      break;
    case THR_DORMANT:
      /*FALLTHROUGH*/
    case THR_NONEXISTENT:
      assert (0);
      err = E_OBJ;
      break;
    }
  intr_resume (s);

  return err;
}
スレッドの優先度対応はこのくらい。問題はモニター。例えばこんな感じに、
      monitor_enter (button.mon);
      thread_priority (id, 3); // lowest
      button.data = 333;
      monitor_signal (button.mon, 3);
      monitor_exit (button.mon);
ロックをとってから優先度を最下限まで下げたとする(こんなプログラムを書い てはいけないけれど)、そうするともうこのモニタに入ろうとするスレッドは 最下限のスレッドが動くまでずっと待ちになってしまう(優先度逆転状態)
モニタに優先度継承を組みこみます。モニタに入ろう(monitor_enter)として、 モニタのロックを持っているスレッドが自分より低ければ、そのスレッドのプ ライオリティを自分と同じにて、monitor_exitまで走らせて、ロックを解除さ せます。monitor_waitでも一度ロックを解除することがあるけれど、ここでは 優先度を元に戻しません。それはmonitor_exitでシグナルを送られたスレッド が確実に入口を待つスレッドより先に動くのを確定させるため。
struct thread_control
{
  struct reg regs;
  enum thread_state state;
  int id;
  int priority;

ここから優先度継承用に、
  int native_priority; // for inherit priority
  bool inherit_priority;
継承した時に元の優先度をとっておくのと、今は継承した優先度で動いてます
よ。というフラグを追加。

  addr_t stack_bottom;
  addr_t stack_top;
  int wakeup_request;

  SIMPLEQ_ENTRY (thread_control) tc_link;

  /* monitor ops. */
  int monitor_event;
  SIMPLEQ_ENTRY (thread_control) tc_monitor_lock;
  SIMPLEQ_ENTRY (thread_control) tc_monitor_event;
  int monitor_wakeup_request;
} __attribute__((packed, aligned(4)));


void
monitor_enter (struct monitor *m)
{
  int s = intr_suspend ();
  struct thread_control *lock = 0;

ロックはboolではなく、ロックを保持しているスレッドのコントロールブロックへの
ポインタに変更しました。
  while (m->lock_thread)
    {
      // add to lock wait queue.
      SIMPLEQ_INSERT_TAIL (&m->lock_queue, current_thread, tc_monitor_lock);

ロックを保持しているスレッドの優先度が、今のスレッドの優先度より低い時は
優先度継承をします。
      if (m->lock_thread->priority > current_thread->priority)
	{
	  // Inherit priority.
	  lock = m->lock_thread;
	  if (!lock->inherit_priority) // don't overwrite native priority.
	    lock->native_priority = lock->priority; // save native priority.
元の優先度が書きかえられないように(さらに優先度継承にあたるスレッドに突
入された場合)、最初の一回だけ記憶しておきます。
	  lock->inherit_priority = TRUE;

	  thread_priority (lock->id, current_thread->priority);
これで、呼び出しもとと同じレディキューに並び、同じなのでrotate ready
queueします。もしかしたらさらに上に上げてもいいかも???。
	}
      thread_sleep (current_thread->id);
    }
  m->state = MON_LOCKED;
  m->lock_thread = current_thread;

  intr_resume (s);
}


void
monitor_exit (struct monitor *m)
{
  struct thread_control *tc;
  int s = intr_suspend ();

  assert (m->lock_thread == current_thread);
  // wakeup scheduled thread if any.
  // monitor assures wakeuped thread can handle resource.
  SIMPLEQ_FOREACH (tc, &m->event_queue, tc_monitor_event)
    {
      if (!tc->monitor_wakeup_request)
	continue;

      thread_wakeup (tc->id);
    }

  while ((tc = SIMPLEQ_FIRST(&m->lock_queue)) != NULL)
    {
      SIMPLEQ_REMOVE_HEAD(&m->lock_queue, tc_monitor_lock);
      thread_wakeup (tc->id);
    }

  m->state = MON_UNLOCKED;
  m->lock_thread = NULL;
  if (current_thread->inherit_priority)
    {
      SCI_PRINTF ("return to normal priority.(exit)\n");
優先度継承で動いてきていたのなら、ここで元の優先度に戻して制御を明けは
なします。これの目的は優先度の高いスレッドがロックをとれればいいだけの
ことなので、ここまででいい。
      current_thread->inherit_priority = FALSE;
      thread_priority (current_thread->id, current_thread->native_priority);
確実に優先度の下がる操作なので、thread_priorityの中でthread_switchが実
行されます。

    }

  intr_resume (s);
}


void
monitor_wait (struct monitor *m, int event)
{
  int s = intr_suspend ();

  // monitor wait operation always blocked.
  SIMPLEQ_INSERT_TAIL (&m->event_queue, current_thread, tc_monitor_event);
  current_thread->monitor_wakeup_request = 0;
  current_thread->monitor_event = event;
  m->state = MON_WAIT;

  m->lock_thread = NULL;
シグナルを待ってロックを外します。

  if (current_thread->inherit_priority)
   {
     // nothing to do
ここはmonitor_enterでロックをとったけれど、そこで割りこまれて制御をとら
れた。優先度継承で優先度を上げられてここまできた状況。優先度が低いのが
待つのはいくらでも待たせていいので、ここで元の優先度に戻してもよさそう
だけれど、モニタはシグナルで起こされたスレッドが、入口で待っているスレッ
ドより前に確実に実行されることを保証しないといけない。monitor_exitまで
優先度を継承しないとモニタのセマンティクスを保証しないので、優先度はこ
のまま。
   }

  thread_sleep (current_thread->id);
  current_thread->monitor_wakeup_request = 0;
  SIMPLEQ_REMOVE (&m->event_queue, current_thread, thread_control,
		  tc_monitor_event);
  assert (!m->lock_thread);
  m->lock_thread = current_thread;

  intr_resume (s);
}
こんな感じで実装して、先の強引に低優先度でロックを保持したプログラムは
thread create 1: pc=815ec sp=82ffc
thread create 2: pc=815ec sp=830fc
thread create 3: pc=815ec sp=831fc
thread create 4: pc=81568 sp=832fc
control thread 0
thread_priority:thread0: pri 0->3 state = 0 ここで強引に低優先度にした。
thread 2(receiver):
0: inherit waiters(2) priority 0.(enter) 優先度0の受信スレッドが来た。
thread_priority:thread0: pri 3->0 state = 1 優先度上げた。
thread 4(sender):
return to normal priority.(exit)
thread_priority:thread0: pri 0->3 state = 0 モニタを抜けたので元に戻った。
thread 3(receiver):
thread 4: SEND 2 channel 2
thread 3: RECV 2
thread 2: RECV 2
thread 4: SEND 8 channel 2
thread 3: RECV 8
thread 2: RECV 8
なんとか動いてそう。大丈夫かな? もうこれ以上色気づいた実装はしないこと にします。これでハード、ソフトともに開発機の開発は終了です。長かった。 お膳立てで疲れきったよ。


同期プリミティブを実装しました。無難にはセマフォ、あるいは条件変数なの
だけど、敢えてモニタにしてみた。使ったことないし、興味がある。

UNIXカーネル内部解析-キャッシュとマルチプロセッサの管理 (プロフェッショナルコンピューティングシリーズ) カート シメル Curt Schimmel 岩本 信一 12.2章のモニタの説明から書いてみました。
この本は名著中の名著。キャッシュまわりをいじるなら必読。
SIMPLEQ_HEAD (tc_queue, thread_control);

struct monitor
{
  enum monitor_state state;
このステートはデバッグ用です。

  int lock;
リソースを含む区画のロックです。

  struct tc_queue lock_queue;
ロックが取得できなかった際にスリープに入るキューです。

  struct tc_queue event_queue;
ロックを取得後、wait()でイベントを待つ際のスリープキューです。

} monitor[MONITOR_MAX];

void __monitor_reset (struct monitor *);

void
monitor_init ()
{
  int i;

  for (i = 0; i < MONITOR_MAX; i++)
    __monitor_reset (monitor + i);
}

void
__monitor_reset (struct monitor *m)
{

  m->state = MON_FREE;
  SIMPLEQ_INIT (&m->lock_queue);
  SIMPLEQ_INIT (&m->event_queue);
}

struct monitor *
monitor_allocate ()
{
  struct monitor *m = NULL;
  int i;
  int s = intr_suspend ();

  for (i = 0; i < MONITOR_MAX; i++)
    {
      if (monitor[i].state == MON_FREE)
	{
	  m = monitor + i;
	  break;
	}
    }
  intr_resume (s);

  return m;
}

void
monitor_deallocate (struct monitor *m)
{
  int s = intr_suspend ();

  __monitor_reset (m);
  intr_resume (s);
}

void
monitor_enter (struct monitor *m)
{
  int s = intr_suspend ();

  while (m->lock)
    {
      // add to lock wait queue.
      SIMPLEQ_INSERT_TAIL (&m->lock_queue, current_thread, tc_monitor_lock);
      thread_sleep (current_thread->id);

ロックがとれなければ、他のスレッドがモニタから出る(monitor_exit())か、イベント
待ち(monitor_wait())になるかまで寝ます。

    }
  m->lock = 1;
  m->state = MON_LOCKED;

  intr_resume (s);
}

モニタの定義によってmonitor_wait,monitor_signalはmonitor_enterから
monitor_exit までの間だけしか実行してはいけない。

(???これによって寝ようとする時に起こしにかかられるレースを解決できる???)
モニタの区間のロックをとれなければ一切のことができないのがモニタの特徴。

void
monitor_wait (struct monitor *m, int event)
{
  int s = intr_suspend ();

モニタは過去のイベントを記憶しないので(そういう定義)、wait操作は常に寝る。
  // monitor wait operation always blocked.
  SIMPLEQ_INSERT_TAIL (&m->event_queue, current_thread, tc_monitor_event);
  current_thread->monitor_wakeup_request = 0;
  current_thread->monitor_event = event;
  m->state = MON_WAIT;

  m->lock = 0;

ロックを解除。ここで他のスレッドがモニタに入ることができる。新しくモニ
タに入ってきてwaitで寝るかもしれないし、シグナルを送ってくるかもしれな
い。新しく入ってきて寝るスレッドはこの後にスケジューリングされるし、シ
グナルを送ってくるスレッドなら、そのスレッドがmonitor_exit ()でモニタを
抜けるまで実行は再開されない。(monitor_signalではロックを明け渡すことはないので)
  thread_sleep (current_thread->id);

他のスレッドのsignal操作によって起こされた。シグナル側が新しく送ろうと
してもスケジューリングが既に決まっているので、自分がmonitor_exitするま
では何もできない。(monitor_enterで待ちになる)

  current_thread->monitor_wakeup_request = 0;
  SIMPLEQ_REMOVE (&m->event_queue, current_thread, thread_control,
		  tc_monitor_event);

  m->lock = 1;
ロックを保持したまま抜けます。

  intr_resume (s);
}

void
monitor_signal (struct monitor *m, int event)
{
  struct thread_control *tc;
  int s = intr_suspend ();

  // monitor don't memorize operation.
  SIMPLEQ_FOREACH (tc, &m->event_queue, tc_monitor_event)
    {
      if (tc->monitor_event & event)
	{
	  tc->monitor_wakeup_request = 1;
	  // dont' wakeup here.
	  //	  SCI_PRINTF ("monitor signal->%d\n", tc->id);
	}
    }

イベントの内容に対して寝ているスレッドがあれば、それを全部起こす予定に
します。モニタの中には1スレッドしか入れないので、この段階では起こされま
せん。

「モニタに入るのを待っているスレッドは起こされたスレッド全てが終了するまで
待たされる。」のを確定するため、レディキューに先に登録しておく。

どのスレッドも待っていなければ、なにもしません。(先にイベントがキューされ
ているから、monitor_waitが寝ずに抜けるというようなことはない。)

  m->state = MON_SIGNAL;

  intr_resume (s);
}

void
monitor_exit (struct monitor *m)
{
  struct thread_control *tc;
  int s = intr_suspend ();

  // wakeup scheduled thread if any.
  // monitor assures wakeuped thread can handle resource.
  SIMPLEQ_FOREACH (tc, &m->event_queue, tc_monitor_event)
    {
      if (!tc->monitor_wakeup_request)
	continue;
モニタを抜けるにあたってシグナルによって起こされる予定のスレッドを探します。

      thread_wakeup (tc->id);
まず最初にシグナルで起こされる予定だったスレッド達を起こします。とはい
えまだ自分がモニタ権をもっているのでレディキューにつながれるだけ。
    }

モニタのセマンティクスはシグナルによって起こされるスレッドが確実に次の
モニタの権利を得ることができること。(新しくモニタに入ろうとするスレッド
は後まわし)これで、起きたはいいけれど、ちょっとした隙に新参物にとられて
しまうことを防げる。

  while ((tc = SIMPLEQ_FIRST(&m->lock_queue)) != NULL)
    {
      SIMPLEQ_REMOVE_HEAD(&m->lock_queue, tc_monitor_lock);
      thread_wakeup (tc->id);
    }
新しくモニタに入ろうとしているスレッドがあれば、シグナルで起こされるスレッド
の後につなぎます。

  m->lock = 0;
  m->state = MON_UNLOCKED;
モニタから出た。

  intr_resume (s);
}

これで本当にいいのだろうか。使ったこともないのを実装するのは無謀だった かも。でもなんかこれ便利そうです。ロックの取得のオーバーヘッドが多きい けど、条件変数を確実に使う場合ならこっちの方がよさそう。ただの待ちあわ せならセマフォの方がよさそう。
テストはこんな感じで送信のスレッドを一つ。受信のスレッドを三つ。受信は スレッドIDのビットが立っているイベントを受ける。
struct
{
  struct monitor *mon;
  int data;
} button;
void button_put (int, int);
int button_get (int);

void
thread_test ()
{
  int id;

  button.mon = monitor_allocate ();

  thread_create (button_get_thread, &id);
  thread_start (id);
  thread_create (button_get_thread, &id);
  thread_start (id);
  thread_create (button_get_thread, &id);
  thread_start (id);

  thread_create (button_put_thread, &id);
  thread_start (id);
}

void
button_put (int data, int channel)
{

  monitor_enter (button.mon);
  button.data = data;
  monitor_signal (button.mon, channel);
  monitor_exit (button.mon);
}

int
button_get (int channel)
{
  int data;

  monitor_enter (button.mon);
  monitor_wait (button.mon, channel);
  data = button.data;
  monitor_exit (button.mon);

  return data;
}

void
button_put_thread ()
{
  int id;
  int channel = 0;

  id = thread_id ();
  SCI_PRINTF ("thread %d(sender):\n", id);
  while (/*CONSTCOND*/1)
    {
      int button;
      if ((button = push_switch ()) != 0)
	{
	  channel = button == PS_1 ? 1 : 2;
	  SCI_PRINTF ("thread %d: SEND %d channel %d\n", id, button, channel);
	  button_put (button, channel);
	  //	  thread_switch ();
テスト用にタイマ割り込みでラウンドロビンさせているのでここでスイッチしなくて
いい。そうでない時は受信側に明示的にスイッチする。
	}
    }
}

void
button_get_thread ()
{
  int id;

  id = thread_id ();
  SCI_PRINTF ("thread %d(receiver):\n", id);
  while (/*CONSTCOND*/1)
    {
      int button = button_get (id);
      SCI_PRINTF ("thread %d: RECV %d\n", id, button);
    }
}
1476 lines transferred in 6 seconds 
!
  Top Address=80000
  End Address=FE003
: g
bss: 0x82d10-0x8389c
MCU mode: 1
thread_init 0: 830cc-82fd0
thread_init 1: 831cc-830d0
thread_init 2: 832cc-831d0
thread_init 3: 833cc-832d0
thread_init 4: 834cc-833d0
thread_init 5: 835cc-834d0
thread_init 6: 836cc-835d0
thread_init 7: 837cc-836d0
thread create 1: pc=81834 sp=831cc
thread create 2: pc=81834 sp=832cc
thread create 3: pc=81834 sp=833cc
thread create 4: pc=817b0 sp=834cc
thread 1(receiver):
thread 2(receiver):
thread 3(receiver):
thread 4(sender):
thread 4: SEND 2 channel 2
thread 2: RECV 2
thread 3: RECV 2
thread 4: SEND 4 channel 1
thread 1: RECV 4
thread 3: RECV 4
thread 4: SEND 8 channel 2
thread 2: RECV 8
thread 3: RECV 8
なんとか想定通りに動いていそうだけれど...どうなんだ? これは。あってるの だろうか? とんでもない勘違いをしていそうで怖い。使ってみながら確認かな。 ソフト側の開発環境整備もあともうちょっと!


スレッドを実装します。まずは
  • 多重割り込みなし、
  • プライオリティなし。
の簡単な仕様からはじめました。スケジューリングポリシーは基本的にはITRON に従います。
  • 実行中のスレッドは割り込み以外にプリエンプトされることはない。
  • 割り込みの終了時にスケジューリングが行なわれる。この際に実行中スレッ ドより高いプライオリティのスレッドがレディキューに入っていない限り、 実行権を奪われることはない。(rotate ready queueしない)
細かい仕様についてはITRONの仕様に拘らず、作りたいように作ります。
まずはH8/300Hの命令から。
  • JSR Jump to SubRoutine (PC → @-SP, リターンアドレス→PC) calleeのSPに戻るべきアドレス(JSR命令の次のアドレス)が入る。
  • RTS ReTurn from Subroutine (@SP+ →PC) SPからPCをとってそこにジャンプ。
  • RTE ReTurn form Exception (@SP+ →PC, @SP+ →CCR) 例外からのリターンは 例外発生時にSPの位置に上位8bitにCCRが、下位24bitに例外発生時のPCが入っている ので、それぞれを設定してジャンプする。
JSRをした時は
  before      after
|   SP   |  |        |
|        |  |  PC    | 3
|        |  |  PC    | 2
|        |  |  PC    | 1
|        |  |reserved| 0← SP

MSB 1stなので|res|PC |PC |PC |
例外に入った時は
  before      after
|   SP   |  |        |
|        |  |  PC    | 3
|        |  |  PC    | 2
|        |  |  PC    | 1
|        |  |  CCR   | 0← SP

MSB 1stなので|ccr|PC |PC |PC |

僕はアドレスイメージが下から上なのでデータシートとは逆です。H8のデータシートは ひっくり返して見てます...。
SP(ER7)をアドレスレジスタとしてスタックをアクセスする時はバイトアクセス はできない。これは気をつけないといけない。(見事にはまりました。)
RTSとRTEの違いはCCRの設定をするかしないかだけ。 プログラム的にはPCとCCRを合わせて32bitとして扱うことにしました。
規模的にはthread.h
#define	THREAD_MAX		4
ターゲットのRAMの量が8Kということもあり、4つ、せいぜい8つまでを考えます。

#define	THREAD_STACK_SIZE	256
このスタック量は少な過ぎかも。とりあえずこの程度で。

enum thread_state
  {
    RUN, READY, WAIT, DORMANT, NONEXISTENT
  };

enum error_code
  {
    E_OK,
    E_OBJ,
    E_NOMEM,
  };

void thread_init (void);
int thread_create (void (*)(void), int *);
int thread_start (int);
int thread_switch (void);
int thread_sleep (int);
int thread_wakeup (int);
int thread_id (int *);
このくらいで。
まずはsetjmp,longjumpの延長系から、プログラムから明示的にコンテキストを スイッチするプログラム。ITRON的にはrotate ready queue.
_thread_switch:
	; disable interrupt
	stc.b	ccr, r2l
現在のCCRの状況をとっておきます。ここで欲しいのは割り込みだけ。フラグは
いらない(明示的にサブルーチンコールなので)。

	orc	#0x80, ccr
このルーチンの途中で割り込みがかかって、そこでスレッドの状態をいじられると
困るので割り込みを禁止します。

	; save context.
	mov.l	@_current_thread, er0
現在走っているスレッドのコンテキストをとってきます。

	mov.l	@sp, er1	; return address
	mov.l	er1, @(0x20, er0) ; pc

このルーチンに入った時点でJSRによってリターンアドレスがSPに入っているので
それをcurrent_thread->reg.ccr_pcに入れます。

	mov.b	r2l, @(0x20, er0) ; ccr    |ccr(8)|   pc(24)  |

current_thread->reg.ccr_pcの上位バイトにCCRを入れます。これでRTEを使って
CCR,PCともに復帰させる目論見。

	mov.l	sp,  @(0x1c, er0) ; sp

復帰すべきSPをとっておきます。

	mov.l	er6, @(0x18, er0) ; callee saved
	mov.l	er5, @(0x14, er0) ; callee saved
	mov.l	er4, @(0x10, er0) ; callee saved

callee saved(呼び出された側がリターン時に保証しないといけないレジスタ)
をとっておきます。caller saved(呼び出し側があらかじめサブルーチンコール
の前に保存しておかないといけないレジスタ)は、既にスタックの中に入ってい
るのでとっておく必要はありません。

この時点で呼び出し元に戻る情報は全て保存したので、この先は全部のレジスタ、
スタックを使い放題です。

	jsr	@_thread_context_switch

thread_context_switchで、current_threadが変更されたかもしれないし、元のまま
かもしれません。次のスレッドに向けてレジスタを設定します。

	mov.l	@_current_thread, er0

	mov.l	@(0x1c, er0), sp  ; sp
スタックをまず復帰。
	mov.l	@(0x20, er0), er2 ; pc
	mov.l	er2, @sp	  ; set return address and ccr

RTE命令が読み出すアドレス(SP)にPCとCCRを設定。サブーチンコールの場合、
割込みビットだけが必要。あとのビットはなんでもいい。

	mov.l	@(0x18, er0), er6 ; callee saved
	mov.l	@(0x14, er0), er5 ; callee saved
	mov.l	@(0x10, er0), er4 ; callee saved

	mov.l	@(0x0c, er0), er3 ; callar saved
	mov.l	@(0x08, er0), er2 ; callar saved
	mov.l	@(0x04, er0), er1 ; callar saved
	mov.l	@(0x00, er0), er0 ; callar saved

このサブルーチンだけでスイッチする分にはcallee savedだけ復帰させればい
いのだけれど、割り込みでスイッチされたコンテキストを復帰させるには全て
のレジスタの復帰が必要なので、caller savedも復帰させます。

	; resume interrupt. and return
	rte
ここでCCRが設定されるので、中断した状態の割り込み状態に戻り、リターンします。

struct reg
{
  uint32_t er0;		// 0x00
  uint32_t er1;		// 0x04
  uint32_t er2;		// 0x08
  uint32_t er3;		// 0x0c
  uint32_t er4;		// 0x10
  uint32_t er5;		// 0x14
  uint32_t er6;		// 0x18
  uint32_t sp;		// 0x1c (er7)
  uint32_t ccr_pc;	// 0x20
} __attribute__((packed));

実行状態そのものです。JSR,RTEの仕様を使ってCCRとPCは一緒になっています。
リオーダリングされないように(アセンブラの中でこの順番になっていることを
前提としているので)、packedに。この場合、4byteアラインなのでパディング
は不用。中途半端だった場合、これを内包したstructをpackedした時にとんで
もないことになるので、4byteアラインになるようにパディングしないとだめ。

struct thread_control
{
  struct reg regs;
実行状態です。このストラクチャもpackedなのでこのstructの先頭は必ずレジスタです。

  enum thread_state state;
  int id;
  int priority;

  addr_t stack_bottom;
  addr_t stack_top;
メモリの少なさとスタックの溢れがせめぎ合うのでチェック用にとっておきます。

  int wakeup_request;
RUNあるいはREADY状態でthread_wakeupを受けた時のカウント。

  SIMPLEQ_ENTRY(thread_control) tc_link;
レディキュー用のエントリ
} __attribute__((packed, aligned(4)));

レジスタのストラクチャを一番先頭にもっておきたいのと、アラインの保証の
ために。(まずaligned(4)しなくても大丈夫だとは思うけど。まだあまり調べて
ないので安牌に振った。)

struct thread_control thread_control[THREAD_MAX];
uint8_t stack[THREAD_MAX][THREAD_STACK_SIZE];

mallocは実装しないので、全部静的に割り当てです。状況によってはアロケー
トできなかった...なんてことは絶対に避けます。

struct thread_control *current_thread;

今動いているRUN状態のスレッドです。

SIMPLEQ_HEAD (tc_queue, thread_control);
struct thread_manager
{
  struct tc_queue ready_queue;
} thread_manager;

レディキューです。ここに、つなげられれば実行される可能性があります。


void
thread_init ()
{
  int i, s;
  struct tc_queue *ready_queue = &thread_manager.ready_queue;
  s = intr_suspend ();

  SIMPLEQ_INIT (ready_queue);

  for (i = 0; i < THREAD_MAX; i++)
    {
      struct thread_control *tc = thread_control + i;
      memset (tc, 0, sizeof (struct thread_control));
.bssに置いてるので無駄な作業です。スタートアップで一括してゼロクリアしてます。

      tc->id = i;
      tc->state = NONEXISTENT;
      tc->priority = 0;

      tc->stack_bottom = (addr_t)stack[i] + (THREAD_STACK_SIZE - 4);
ここの位置にuint32_tでPCが入るので-4。
      tc->stack_top = (addr_t)stack[i];
      *(uint32_t *)tc->stack_top = 0xac1dcafe; // canary
スタックオーバーフローをチェックするためにカナリヤを入れておきます。

      SCI_PRINTF ("init %d: %x-%x\n", i, tc->stack_bottom, tc->stack_top);
    }

  current_thread = &thread_control[0];
今の状態をスレッド0としておきます。スタックは違うけれど。ここはいずれ。

  current_thread->state = RUN;
  SIMPLEQ_INSERT_HEAD (ready_queue, current_thread, tc_link);
整合性のために設定します。

  intr_resume (s);
}

int
thread_id (int *id) 呼び出し元のスレッドIDを返します。
{
  int i;
  addr_t sp = 0;

  __asm volatile ("mov.l sp, %0" :: "r"(sp));

スタック位置によってどのスレッドかを見分けます。

  for (i = 0; i < THREAD_MAX; i++)
    {
      struct thread_control *tc = thread_control + i;
      if (sp < tc->stack_bottom && sp > tc->stack_top)
	break;
    }
  if (i == THREAD_MAX)
    return E_OBJ;

  *id = i;
  return E_OK;
}

int
thread_create (void (*start)(void), int *tid)
{
  struct thread_control *tc;
  int id;
  int s;

  s = intr_suspend ();

空いてるスレッドスロットを探します。
  for (id = 0; id < THREAD_MAX; id++)
    {
      tc = thread_control + id;
      if (tc->state == NONEXISTENT)
	break;
    }
  if (id == THREAD_MAX)
    return E_NOMEM;

  tc->regs.ccr_pc = (uint32_t)start;
コンテキストスイッチされる時にジャンプする場所を設定。CCRはどうでもいい。
割り込み許可でディスパッチされます。

  tc->regs.sp = (uint32_t)tc->stack_bottom;
  // install return address for 'rts'
  *(uint32_t *)tc->regs.sp = tc->regs.ccr_pc;
RTE命令用にスタックに埋めこんでおきます。

  tc->state = DORMANT;
thread_start()まで動きません。

  *tid = id;
  intr_resume (s);

  SCI_PRINTF ("thread create %d: pc=%x sp=%x\n", id, tc->regs.ccr_pc,
	      tc->regs.sp);

  return E_OK;
}

int
thread_start (int id)
{
  struct thread_control *tc = thread_control + id;
  int s;
  enum error_code err = E_OBJ;

  s = intr_suspend ();

  if (tc->state == DORMANT)
    {
      tc->state = READY;
      SIMPLEQ_INSERT_TAIL (&thread_manager.ready_queue, tc, tc_link);
レディキューに登録します。機会があれば実行されます。

      err = E_OK;
    }
  intr_resume (s);

  return err;
}

int
thread_sleep (int id)
{
  int s = intr_suspend ();
  struct thread_control *tc = thread_control + id;

  if (--tc->wakeup_request < 0)
    {
wakeup_requestはWAIT状態じゃないのにwakeupされた時のカウント。それがあ
ればWAIT状態に入ってもすぐに起きる。ITRONの仕様です。どこまでキューでき
るかは実装依存。

      SIMPLEQ_REMOVE (&thread_manager.ready_queue, tc, thread_control, tc_link);
      tc->state = WAIT;

レディキューから外してWAIT状態にします。

      SCI_PRINTF ("sleep: %d\n", id);
      thread_switch ();
次のスレッドに明け渡します。

      SCI_PRINTF ("wakeup: %d\n", id);
どこか他のスレッドや、割り込みからwakeupされればここまで来ます。二度とこないかも
しれません。
    }
  intr_resume (s);

  return E_OK;
}

int
thread_wakeup (int id)
{
  int s = intr_suspend ();
  struct thread_control *tc = thread_control + id;

  if (tc->state == READY)
    {
      tc->wakeup_request++;
ターゲットのスレッドがレディーキューに入っているなら、起床回数を増やします。
シングルプロセッサなので自分がRUNなら相手がRUNであることはありえません。
    }
  else
    {
      tc->state = READY;
      SIMPLEQ_INSERT_TAIL (&thread_manager.ready_queue, tc, tc_link);
WAIT状態からREADY状態にしてレディキューに追加します。WAIT状態なのを確認しないとだめだ。DORMANT, NONEXISTENTの場合がある。XXX

    }
  intr_resume (s);

  return E_OK;
}

int
thread_context_switch ()
{
  struct tc_queue *ready_queue = &thread_manager.ready_queue;
  // already interrupt disabled.

アセンブラのthread_switchから呼ばれるルーチンです。

  if (current_thread->state == RUN)
    {
      current_thread->state = READY;
      SIMPLEQ_REMOVE_HEAD (ready_queue, tc_link);
      SIMPLEQ_INSERT_TAIL (ready_queue, current_thread, tc_link);
    }
thread_sleepしてから呼ばれる場合、current_threadは既にレディキューから
外されてWAITになっているので、RUNの確認が必要。
そうでない場合、今のスレッドはレディーキューの最後につながれる。

  current_thread = SIMPLEQ_FIRST (ready_queue);
  current_thread->state = RUN;
次のスレッドをcurren_threadにしてリターンすれば、後のthread_switchで
コンテキストを切り替えてくれる。

  return 0;
}

void
thread_stack_check ()
{
  int i;

スタックオーバーフローしていないかチェックします。

  for (i = 0; i < THREAD_MAX; i++)
    {
      struct thread_control *tc = thread_control + i;
      if (*(uint32_t *)tc->stack_top != 0xac1dcafe)
	{
	  SCI_PRINTF ("thread %d stack overflow. %x\n", i,
		      *(uint32_t *)tc->stack_top);
	}
    }
}

ここまでで明示的にサブルーチンコール(thread_switch)で切り替えるのはでき た。割り込みから切り替えるには、すべてのレジスタを退避しないといけない。
テストにIRQ4に実装してみたのがこれ。
	.globl _irq4
_irq4:
	mov.l	er0, @-sp
	mov.l	@_current_thread, er0
	mov.l	er1, @(0x04, er0)
	mov.l	er2, @(0x08, er0)
	mov.l	er3, @(0x0c, er0)
	mov.l	er4, @(0x10, er0)
	mov.l	er5, @(0x14, er0)
	mov.l	er6, @(0x18, er0)

	mov.l	@sp+,er1
	mov.l	er1, @(0x00, er0) ; er0

	mov.l	@sp, er1
	mov.l	er1, @(0x20, er0) ; ccr_pc
	mov.l	sp,  @(0x1c, er0) ; sp

	jsr	@_thread_context_switch
これを呼ぶとrotate ready queueなのでITRONの仕様に合わないのだけれど、
いろいろコンテキストをスイッチするテストのため。

	mov.l	@_current_thread, er0

	mov.l	@(0x1c, er0), sp  ; sp
	mov.l	@(0x20, er0), er2 ; pc
	mov.l	er2, @sp	  ; set return address and ccr

	mov.l	@(0x18, er0), er6
	mov.l	@(0x14, er0), er5
	mov.l	@(0x10, er0), er4
	mov.l	@(0x0c, er0), er3
	mov.l	@(0x08, er0), er2
	mov.l	@(0x04, er0), er1
	mov.l	@(0x00, er0), er0

	; resume ccr and return.
	rte
このオーバーヘッドの評価はまだだけど、オーバーヘッドがきつければディス パッチしなくてもいいし、したければ、タイマ割りこみをここで仕掛ければい い(そこでディスパッチ)。



内蔵デバイス(LED,スイッチ,LCD)のドライバをもりもり書きました。SG12232は
8bit転送すると、それは縦に8bit描画されて一列横に移るという仕様なので
フォントは回転したものを作りました。clR8x8で。

スタートラインを変更することでハードウェアスクロールができるのでここは 便利。
右と左でコントローラが別で、それぞれ61ドットづつというのに多少イラつきます。せめてもう3ドットづつ増やしてくれれば...。フォントを書くと7列目の文字は 途中でコントローラを切り替えないといけないのだ。

これで内蔵デバイスの実装は終り。


delayを実装しました。今迄内蔵RAMで動かしていた時は

	.globl _delay_2state
_delay_2state:	/* one loop 24cycle: 25Mhz-> .96us */
a:	sub.l	#1, er0  /* 6 */
	nop /* 2 */
	nop /* 2 */
	nop /* 2 */
	nop /* 2 */
	nop /* 2 */
	nop /* 2 */
	nop /* 2 */
	bne	a	 /* 4 */
	rts /* 10 */
これでOKだった。H8/300Hシリーズプログラミングマニュアルの2.6命令実行ステート章の 式と表からステート数はみつもれる。この場合、内蔵RAMが16bit 2ステートなので nopは2ステートで実行できるのだけれど、拡張RAMは8bit 3ステートなので、nopの実行に 6ステートもかかってしまう。なのでこのdelayでは3倍の時間がかかってしまうのだ。
ハードの設計に失敗したよ。このボードはSRAM 256Kビット SRM2B256SLMX55 (55ns)を8bitで接続してモード5(1Mアドレス)。高速SRAM 4Mビット CY7C1041DV33 (10ns)を16bitで接続して2ステートアクセス、モード6(16Mアド レス)にしておけばよかった。0.8mmピッチのハンダ付けにビビったというのが 最初の理由だったのだけど...。
しかたないのでキャリブレーションすることにしました。
キャリブレーション可能なdelayルーチンの核はこれ。
	/* void __delay (uint16_t r0, uint16_t r1, uint16_t r2) */
___delay:
	mulxu.w	r0, er2	/* r2 = r0 * r2 */
	divxu.w	r1, er2	/* r2 = r2 / r1 */
dloop:	sub.w	#1, r2
	bpl	dloop
	rts
mulxu.wは16bitどうしのかけ算を32bitのレジスタに入れてくれる命令。 divxu.wは32bitを16bitで割って、商を下位16bitに、余りを上位16bitに入れて くれるという命令。実行は20ステート。コンパイラだとうまくdivxu.wを使えな いのでアセンブラで。
このルーチンをループせずに抜けるだけでも、拡張RAMで5.76us、内蔵RAMで3.36usかかる ので、10us以下はインラインアセンブラでnop差しこんだ方がいい。
mulxu,divxuを使うのでdelayの引数は16bitに制限されてしまう。32bitになると CPU命令で処理できなく、udivsi3やmulsi3を呼ぶことになるので、それはオーバーヘッドの点で避けたい。(呼ぶとさらに15usecくらいかかる)
こうなるとusec単位のdelayだけでは足りないので、usecオーダーのudelayとmsecオーダーのmdelayに分けることにしました。
キャリブレーションは、
extern uint16_t DELAY_CNT;
ここにキャリブレーションされた値が入る。

uint16_t __delay (uint16_t, uint16_t, uint16_t);
#define	udelay(n) __delay((n), DELAY_CNT, 125)

125というマジックナンバーは計測に25MHz/2(8/100 usec)を使ったので、式から
わかリやすいところで。

uint16_t DELAY_CNT;

void
delay_calibrate (bool verbose)
{
  uint16_t t0, t1;

  *ITU_TSTR = 0;	// All timer stop.
  // Internal clock/2, Don't clear TCNT.
  *ITU0_TCR = ITU__TCR_ICLK2;	// .08us (CPU 25MHz) ->OVF 5.242 msec
  *ITU0_TIOR = 0;	// no I/O
  *ITU0_TIER = 0;	// no interrupt.

  __timer_start ();
  __delay (0, 1, 1);
  t0 = __timer_stop ();

ここで、サブルーチンに入って帰ってくるまでのオーバーヘッドを測定。

  __timer_start ();
  __delay (1000, 1, 1);
  t1 = __timer_stop ();

  DELAY_CNT = (t1 - t0) / 100;

オーバーヘッドを抜いたとこで計算。

1000回のループで t1-t0 カウント
1カウント0.8us
1ループは(8*(t1-t0))/100000
1usecにするには100000/(8*(t1-t0))まわす。
->125 / ((t1-t0)/100)
}

extern inline void
__timer_start ()
{
  *ITU0_TCNT = 0;
  *ITU_TSTR |= ITU_TSTR_STR0;
}

extern inline uint16_t
__timer_stop ()
{
  uint16_t t = *ITU0_TCNT;
  *ITU_TSTR &= ~ITU_TSTR_STR0;
  return t;
}

void
mdelay (uint32_t n)
{

  while (n--)
    udelay (1000); //1msec
}

これでキャリブレーションすると、拡張RAM上でDELAY_CNTは120(なんとか125よ り小さいのでよかった。)、内蔵メモリ上で40。 これで、delayはなんとかなったけれども、ここまでタイミングが違うと開発段階 からロムに移した時のタイミングプロブレムがとても心配です。 とりあえずdelayはこれでいいかな。
次は割り込み。強制的にON/OFFするのは
#define	intr_enable()	asm volatile ("andc.b	#0x7f, ccr")
#define	intr_disable()	asm volatile ("orc.b	#0x80, ccr")
これでいい。H8の命令セットはなかなか便利です。ただ一つ疑問なのが1ビット シフトの命令しかないところだ。
さらに、現在の割り込み禁止/許可状態の如何にかかわらず、割り込みを禁止し て、元に戻すルーチンを作っておきます。
	.h8300h
	.section .text

	.global _intr_suspend
/*
	uint8_t
	intr_suspend ()
	{
	  r0 = *ccr & 0x80;
	  *ccr |= 0x80;
	  return r0;
	}
*/
_intr_suspend:
	stc.b	ccr, r0l
	and.b	#0x80, r0l
	orc	#0x80, ccr
	rts

	.global _intr_resume
/*
	uint8_t
	intr_resume (uint8_t r0)
	{
	   r1 = *ccr & 0x7f;
	   r0 |= r1;
	   *ccr = r0;
	   return r0;
	}
*/
_intr_resume:
	stc.b	ccr, r1l
	and.b	#0x7f, r1l
	or.b	r1l, r0l
	ldc.b	r0l, ccr
	and.b	#0x80, r0l
	rts
このくらいあれば十分かな。


LCD穴を切削し直しました。まぁ見れなくもないくらいに。製品のようにパリっと
仕上げたいのだけど、今の僕ではこのあたりが限界だ。うねりがひどい。



昨日はなんとなく動かした拡張RAM上での実行、きちんとやり直した。ルネサスの モニタで初期化するルーチン(C版)は
void
bsc_init ()
{
  // MCU Mode 5
このボードはMCUモード5、1MBのアドレス空間で動作させることを前提としています。

  // Bus width.
  *BSC_ABWCR = 0xff; // All 8bit. D15-D8. D0-D7 are I/O Port (4)

バス幅の設定。エリア0-7まで全て8bit。内蔵RAM,ROM,デバイスは、この設定に
よらないので、RAM,ROMは16bit,2ステートでアクセスされる。内蔵デバイスは
3ステート。拡張したSRAM とLCDが8bitアクセスになる。一つのエリアでも
16bitになるとD0-D7はI/Oポートとして使えなくなる。この場合D0-D7はI/Oポー
ト4として使える。

25MHzで動かしているので1ステートは40ns。

  // Access state.
  *BSC_ASTCR = 0x70;// CS4,5(Ext.RAM) and CS6 LCD are 3 state access.

拡張したSRAM、LCDは3ステートに。3ステートアクセスの場合、さらにWSC(Wait
State Controler)で追加の待ちのステートを(3まで)入れることができる。

  // WSC programmable wait mode. 3 wait state.
  *BSC_WCR = 3;

WSCを有効にして、追加ステートは3。これは固定なので、エリアごとに追加ス
テートを変更することはできない。WCERでWSCを有効にするか、切るかの二択。
外部端子からのWAIT#は使わない。外部端子を使う(WMS1=1)とした時はポート6
の設定に注意。

  // CS6(LCD) uses WSC. Ext.RAM don't use wait state.
  *BSC_WCER = 0x40;

拡張RAMは3ステートアクセス。LCDは3ステートアクセスで、さらにウェイトで
3ステートに設定(計6ステート)。WCERのいずれかが0になってしまうので、ポー
ト6の設定に注意。この場合WAIT#を使わなくてもP6-0はWAIT#端子になってしま
う。

LCDとSRAMでウェイトを変更したいのでしかたがない。SRAMが2ステートで動け
ば、2ステートの場合はウェイトを入れることがないのでWCERを0xffとして
WAIT#を空けることができるのだけど...。

  // Bus release disable. BACK#, BREQ# -> Port 6[1:2]
  *BSC_BRCR = 0;

外部にバス権を明け渡すことはないのでBRLEは0。モード5なのでA21E, A22E,
A23Eは関係ない。これでBACK#, BREQ#端子が空くのでポート6-1,6-2が使える。
これはボード内蔵のLEDの二個につながっている。

  // Enable Chip Select output 4-6
  *BSC_CSCR = 0x70;

拡張SRAM、LCD用のチップセレクト信号を出すように設定。これによって
PA-4,5,6が供出される。このレジスタによってチップセレクト信号を出力する
ように設定した場合、この設定が全てに優先されるのでPAのDDRはどう設定して
も問題ない。


  // Set address line (A0-A17) output.
  P1->DDR = 0xff;
  P2->DDR = 0xff;
  P5->DDR = 0x01;	//use A16 only. A17-A19 are I/O port. (3 Push buttons)

P5はアドレスラインの一部をI/Oポートに供出。これは拡張RAM上でプログラム
を実行する場合、二度と変更できない。P5[1:3]はボード内蔵のプッシュボタン
につながっている。

  // AS#, RD#, HWR#, LWR#, WAIT#  Don't use external bus.
  P6->DDR = 0x6;	// BACK#, BREQ# are I/O port. (LED Output)

BACK#, BREQ#を使わないことにしたので、P6-1,2はI/Oポートに使える。
WSCの設定によって#WAITのP6-0はI/Oポートとして使えない。P6-3:6は
モード5に設定した場合はAS#, RD#, HWR#, LWR#固定でDDRの設定は効かない。


}
これでロムモニタ側はOK(のはず)。ユーザプログラム側に移って、まずはスタックの設定、スタックは内蔵RAMにとります。
	.h8300h

	.section .vectors
	.long start

	.section .text
	.globl	start
start:
	mov.l	#stack_top, er7
loop:	jmp	@_machine_startup
	jmp	@loop
	/* NOTREACHED */
void
machine_startup ()
{
  extern char bss_start[], bss_end[];
  char *p;
  bool ram_ok;

  // Check BSS
  ram_ok = check_ram ((uint32_t)bss_start, (uint32_t)bss_end, FALSE);

.bss領域のSRAMのチェック。この段階では.dataと.stackしか使えないのに注意。

  // Clear BSS
  for (p = bss_start; p < bss_end; p++)
    *p = 0;

ここから普通に動く。

  // Initialize H8 devices
  sci_init (1);

ここからprintfが使える

  SCI_PRINTF ("bss: 0x%x-0x%x\n", (uint32_t)bss_start, (uint32_t)bss_end);
  if (!ram_ok)
    SCI_PRINTF ("bss RAM error.\n");

  // MCU mode. (5)
  SCI_PRINTF ("MCU mode: %x\n", *MDCR & 0x3);
  if ((*MDCR & 0x3) != 1)
    SCI_PRINTF ("Mode error.\n");

  // Check RAM exclude BSS.
  ram_ok = check_ram ((uint32_t)bss_end, ADDR1M_AREA6_START - 1, TRUE);
  if (!ram_ok)
    SCI_PRINTF ("heap RAM error.\n");

.bss以降の余った領域のチェック。ヒープとしてあるけど、alloc系は実装しな
い。ターゲットは内蔵RAMだけなので。開発の段階でちょこっと場所が欲しくて
使う用。

  // Initialize on-board devices.
  board_init ();

  switch_test ();

  while (/*CONSTCOND*/1)
    asm volatile ("sleep");

  /* NOTREACHED */
}

I/Oポートについて。
void
ioport_init ()
{
#define	IO_DEFAULT	0xff

  P4->DDR = IO_DEFAULT;
  P4->PCR = 0x0;
  P4->DR = 0x0;
  SCI_PRINTF ("P4\n");

  P5->PCR = 0;		// Don't use pull-up MOS.
  SCI_PRINTF ("P5\n");

P5のDDRはアドレスラインの設定に関するので、いじってはだめ。タイミング
によってはハングする。

  P6->DR = 0;		//  Internal LED P6-1, P6-2
  SCI_PRINTF ("P6\n");

  P8->DDR = IO_DEFAULT;
  P8->DR = 0x0;
  SCI_PRINTF ("P8\n");

  P9->DDR = ~0x30;	// 4,5 input.
  SCI_PRINTF ("P9\n");

SCI0,1と共有。クロックの端子をI/Oポートに割りあてている。SD/MMCカードを
つけることになったらSCK0は明け渡さないといけない...。

  PA->DDR = IO_DEFAULT;
  PA->DR = 0x0;
  SCI_PRINTF ("PA\n");

PAはCSCRのチップセレクトの設定が優先するのでDDRで変更をしてもかまわない。

  PB->DDR = IO_DEFAULT;
  PB->DR = 0x0;
  SCI_PRINTF ("PB\n");
}



端子台を作りました。やはり端子台はベークライトじゃないと。しかし、ベー
クライトは欠けるので、できれば使いたくない材料。穴開けではポンチでも欠
けるので、小さい穴からじっくり拡大していかないといけない。熱伝導率が悪
いので、割れを恐れてゆっくりしているとドリルがなまってしまう。



端子ユニット。M/B側は適当にピンヘッダ出しておいて、後でソケットをそれに 合うように小さく切ったユニバーサル基板に装着するといい感じ。

コンパレータのテスト。OKです。端子は左から4.0V,1.6V,GND,IRQ5,IRQ4になっ ています。(1.6V、4.0VはM/B上の半固定抵抗で変更可能)
ハードはこれで完成! 10日かかった。

次は拡張したRAMの上にプログラムを乗せる作業だ。これはルネサスのモニタの スタートアップでRAMを認識させておく必要がある。当初、お気楽に300h/monitor/monitor.cでやろうとしたのだけれど、秋月のCコンパイラでは
#pragma global_register(monitor=ER5)
これが使えない。どうしようもないので、monitor.srcを変更。

;************************************************************************
;*      H8/300H Monitor Program (Advanced Mode)         Ver. 3.0A       *
;*              Copyright (C) 2003 Renesas Technology Corp.             *
;************************************************************************
                .PROGRAM  INITIALIZE            ; Program Name
                .CPU      300HA                 ; CPU is H8/300H Advanced
                .SECTION  ROM,CODE,ALIGN=2      ; ROM Area Section
;************************************************************************
;*      Export Define                                                   *
;************************************************************************
                .EXPORT _INITIALIZE             ; User Initialize Module
;************************************************************************
;*      User Initialize Module                                          *
;*              Input   ER5 <-- Return Address                          *
;*              Output  Nothing                                         *
;*              Used Stack Area --> 0(0) Byte                           *
;************************************************************************
_INITIALIZE:
	mov.b	#H'3,r2l
	mov.b	r2l,@H'fffee

	mov.b	@H'ffff3,r2l

	and.b	#H'fe,r2l
	mov.b	r2l,@H'ffff3

	mov.l	#H'fff5f,er3

	mov.b	@er3,r2l
	or.b	#H'30,r2l
	mov.b	r2l,@er3
	mov.l	#H'fffec,er3

	mov.b	@er3,r2l
	or.b	#H'30,r2l
	mov.b	r2l,@er3
	adds	#H'1,er3
	mov.b	@er3,r2l
	or.b	#H'30,r2l
	mov.b	r2l,@er3
	adds	#H'2,er3
	mov.b	@er3,r2l
	and.b	#H'cf,r2l
	mov.b	r2l,@er3
	mov.b	#H'ff,r2l
	mov.b	r2l,@H'fffc0
	mov.b	r2l,@H'fffc1
	add.b	#H'2,r2l
	mov.b	r2l,@H'fffc8
	mov.b	#H'6,r2l
	mov.b	r2l,@H'fffc9
	JMP     @ER5                    ; Goto Monitor Program
        .END                            ;
これで
  *BSC_WCR = 3;
  *BSC_BRCR &= ~1;

  *BSC_CSCR |= 0x30;
  *BSC_ABWCR |= 0x30;
  *BSC_ASTCR |= 0x30;
  *BSC_WCER &= ~0x30;

  P1->DDR = 0xff;
  P2->DDR = 0xff;
  P5->DDR = 0x01;
  P6->DDR = 0x6;
を実行。gccの吐いたコードをディスアセンブルして秋月のアセンブラが読める形に 変更しました。
日立はレジスタの扱いに制限をつけることが多い(東芝、NECにくらべて)。DDRは Write Only。これを設定の確認に読めたらいいのにね。
なんとか拡張RAM上にプログラムをロードして実行できました。うっかりRAMの アクセスにかかわるPort1,2,5,6の設定を実行時にいじってしまっいて、はまっ た。今迄は内蔵RAMからいじっているプログラムで、いつでもいじってもOKだっ たのでうっかりしていた。




やはり前面パネルは端子台にして、I/Oポートの引き出し口は横にすることにし
ました。前面のLED、スイッチを取りつけてしまったので、マスキングして切削。

さすがにこの固定じゃつらかった。切削抵抗でワークが歪んで惨々な仕上りに。

詰め込みました。

カバーをつけるとこんな感じ。レトロフューチャー感がいい!やっぱりコンピュー タは無駄にピコピコしないと。
LCDの穴がずれてしまったのが残念だ。蓋は簡単に加工できるからいずれなんとかしたい。ケースだけだとそこそこかなと思っていたけれど、そこの穴の中にきっちりと長方形が入ると、すごいアラが目立つ。




前面パネルの仕様を変更しました。シリアルポートの横に3つ穴を新設し、シリアルポートの上の5個の穴は連結しました。ここを端子にするか、配線の取り出し口にするか迷い中。



蓋も作ります。例によって廃棄PCケースから。

メタルシャーで大まかに裁断。メタルシャーは意外にまっすぐ切れないもの。 そこそこの大きさまで、ざっと切り出す用途だけかな。250mmくらいまでしか切 れないのもちょっともの足りない。

バンドソーで綺麗に切断。刃をLENOXのDIEMASTER 2にして以来、楽しいくらい に良く切れる。しかしモーターがかなり不調。旋盤をインバータ化して、余っ たモーターをこれに装着しようと思っているのだけど、いつになることやら。

ベルトサンダーで鋸目を削りとります。

4片仕上げたら角をとります。

メタルブレーキで曲げます。外側にガムテープを貼っておくと、傷がつかない。 曲げるものがこの長さだとハンドルと干渉してしまって奥まで曲げれない。 真中じゃなく両側にハンドルがあればいいのだけど..。

ちょっと合わせてみる。まぁなんとか。曲げ加工だとどうも寸法があいまいに なってしまって、カチっといかない。

ちょっと曲げが弱いところは手曲げ。

上端はこんなL字金具を作って取りつけることにしました。

下端はこの状態で2.5φで開けて、ケース側はM3x0.5を切って、蓋は3.1φに。

LCD穴を開けます。この、井桁に組んだ木枠、いろんな場面でとても便利です。

フライスでφ6のエンドミルで仕上げ。こんな固定じゃビビってだめかなと思ったけどなんとか大丈夫でした。

バリ取り。

LCDを乗せる台も現物合わせで様子を見ながら作成。クリアランスがかなりギリ ギリ。整備性がかなり悪くなる。基板も、高さも、もうちょっと余裕をみておけばよかったな。
これはコネクタを外す時に指が入るため用の部分を切り抜き。鉄相手でもエンドミルを6φまで落とせばそこそこがんばれるかな。8φだとちょっときつい。

なんとか完成です。

基板の方にはIC一個乗りそうなスペースが残っていたので、LM339Nを乗せて、 コンパレータ回路を二個乗せました。いろんな信号をH8に取りこむにはいつも 使うだろうし。基板はこれで完成。

よくがんばった。アドレスとデータラインの配線の汚なさがイライラするけれど 直す気力もない。

あともうちょっとだ。



LED部分をつくりました。外部用の8回路と、内部用(P6[1-2])の2回路、合計10
回路。

LEDのドライブには手持ちの74HC04AP(インバータ)を使いました。74HC04APは 出力電流の最大が25mAなのでLEDくらいは十分OK。
LEDは5Vで560Ωの抵抗をつけて接続しました。8.9mA。74HC04APの出力がLowで 点灯するように(吸い込み)で接続。これはICの最終段がN型だから。トランジス タと同じように、NPNなら吸い込み、PNPなら掃き出しに設定するといいようで す。
インバータなので、H8側はポートをHighにするとLEDが点灯。
基板への実装は場所が足りなくて、大変でした。外部用のLEDはジャンパで ポート4に接続できるようにしました。 基板の配線時にもLEDを接続して 配線の確認もできるようにもなっています。

朝から晩まで延々とハンダ付けして、なんとかM/Bの配線は終了。もう二度とこ んなのは作りたくない。目ショボショボ。




LCDの動作がおかしかったのは配線ミスでした。昨日の写真で既に問題は見えて
いた。CS6#の隣りのピンに接続していたのだ。見ての通り、本来のCS6#のピン
とはまったく短絡する気配もないのに、ノイズでなんとなく動いていたのだ。
これはびっくり。ノイズは怖いね。

これに気づくまで、このピンの前と後でさんざ格闘した。
  • LCDモジュールからは、1個のOR回路につなぐ分にはCSが効くけれど、2個に つなぐと、両方ともLか、前段につながれているのしか有効にならない。
  • 書きこみタイミングにかなりの(300usec)ディレイを入れないとダンマリ。
  • たまに書きこめない。
といった状況だった。ここを直してバッチリです。ノーウェイトで書きこめま す。(LCDの書き込みタイミングの1usはH8/3052 25MHzだとnopで24命令程度なの で、明示的にdelay入れなくてもそのくらいの時間は経つ)

メモリモジュールと重ね合わせて完成です。モード5を使っているのでCS4,CS5,CS6の全てを使ったので、これ以上は拡張できません。
こういうソケットにする時はVccの隣のピンは空きにしておくのが安心ね。一回 ずれて差し込んでしまって冷汗をかいた。たまたま空きで助かった。

次はコントロールパネルのスイッチの回路を。ポート5のA17-A19、ポート6のWAIT#, BREQ, BACKが使われずに余っているので、ここを使うことにします。
ポート5をモード5,6で使う場合、P5DDRで出力に設定するとアドレス出力端子に、 入力に設定すると入力ポートになる。ので、ここは入力にしか使えないのでス イッチに決定。
74HC14でシュミットトリガにして端子に入れます。スイッチが入ったところで H→Lにしたい(H8がエッジトリガをとれるのは立ち下がりだけなので、全て同じ 論理にしておきたい)。74HC14の端子はプルダウンしてスイッチでVccに接続。
スイッチが入ると74HC14のポートがHになって、インバータでH8のポートにはL。
このプルダウン抵抗をいくつにするかなのだけど、74HC14は出力電流25mA。 とはいえこんなに流しっぱなしにするのはどうかと思うので、どこまで絞るか なのだけど、あまり絞ると電流が少なくなるのでノイズがのる。
6.8Kにしました。5Vなので0.7mA。こんなとこじゃないかな...流し過ぎかな。まだこのあたりはモヤモヤしています。




秋月のSG12232モジュールユニットを作りました。奥のメモリモジュールのスロッ
トにはまっているのがそれ。テストルーチンもそこそこ動いていそうだけど、
致命的な問題があり、これから考え直しです。

このモジュールはRES#(Low Active。いつもはHighでLowになるとリセット)と、 RES(High Active。いつもはLowでHighになるとリセット)を、そのエッジで 認識して、内部インターフェースを80系と68系でスイッチできる。
この80系というののシグナルタイミングを見ると、RAMのシグナルタイミング、 WE#とOE#と同じ使われ方だ。
ということで、
  • H8のRES#を74HC14(シュミットトリガ インバータ。シュミットじゃなくていいんだけど、ボタンのと共用で)で反転させてLCDのRESに。
  • H8のHWR#をLCDのWR#に
  • H8のRD#をLCDのRD#に
  • このLCDはH8のエリア6につなげるとして、CS6#とA0をORして(74HC32)LCDの CS2に。
  • CS6#とA1をORしてLCDのCS1に。(このLCDは左右で二つのコントローラが 入っているので、それぞれのチップイネーブル)
  • H8のA2をLCDのA0(コマンド/データの切り替え)に。
と、つなげてH8のバスコントローラで動かしてみました。3ステート、3ウェイトで。
LCDのコマンド受け付けタイミングはLCDのクロックによるので、連続アクセスには 1usecのdelayが必要。
こうすると、CS6#の一本だけで済むというのにひかれて実装してみた。
void
extlcd_area_init (int area)
{
  uint8_t r = 1 << area;

  *BSC_CSCR |= r; // enable CS#
  *BSC_ABWCR |= r; // 8bit access (data bus: D8-D15)
  *BSC_ASTCR |= r; // 3-state access
  *BSC_WCER |= r; // enable wait state controler
  *BSC_WCR = 3; // programmable wait mode. 3 wait state.
}

inline void
lcd_write (volatile uint8_t *addr, uint8_t data)
{
  *addr = data;
  delay (2);
}

void
extram_init ()
{
#define	MDCR	(volatile uint8_t *)0xffff1
  extlcd_area_init (6);

  // Set address line (A0-A19) output.
  P1->DDR = 0xff;
  P2->DDR = 0xff;
  P5->DDR = 0xff;
  //P5->DDR = 0x01;//use A16 only. A17-A19 are I/O port.

  delay (100000);
  SCI_PRINTF ("hello world. mode=%x\n", *MDCR);
#define	LCD_DATA_0	((volatile uint8_t *)0xc0005)
#define	LCD_DATA_1	((volatile uint8_t *)0xc0006)
#define	LCD_DATA_BOTH	((volatile uint8_t *)0xc0004)
#define	LCD_CMD_0	((volatile uint8_t *)0xc0001)
#define	LCD_CMD_1	((volatile uint8_t *)0xc0002)
#define	LCD_CMD_BOTH	((volatile uint8_t *)0xc0000)

  lcd_write (LCD_CMD_BOTH, 0xe2);//reset
  lcd_write (LCD_CMD_BOTH, 0xaf); // display on

  int i, j;
  uint8_t d;
  for (j = 0; j < 4; j++)
    {
      lcd_write (LCD_CMD_BOTH, 0xb8 | j); // set page;
      lcd_write (LCD_CMD_BOTH, 0); // column 0;
      for (i = 0, d = 1; i < 80; i++, d <<= 1)
	{
	  lcd_write (LCD_DATA_BOTH, d);
	  if (d == 0x80)
	    d = 1;
	}
    }

  SCI_PRINTF ("sleep.\n");
  while (/*CONSTCOND*/1)
    asm volatile ("sleep");
}
これがLCD_DATA_0、LCD_DATA_1がまったく効かずに、LCD_DATA_BOTH(A0,A1、両方ともLowで、LCDの2つのモジュールに同時にCSがかかる)しか効かないんだ。
タイミング的にはバスコントローラでも駆動できることがわかってこれはラッキー。これでI/Oポートが11本浮いた。

LCDモジュールの配線。一見綺麗っぽいけど、モジュールの上に線を乗せ過ぎて しまうと、困った時にとても困ってしまう。橙色のデータラインは外側から 迂回すべきだった。
だんだんコツがわかってきました。まず一方をハンダ付けして、ピンセットで きっちり曲げて、ギリギリでカット。一度上にひっぱりあげて、ピンセットで 押さえながら、皮剥きしてハンダメッキして、ピンセットでつかんでハンダ付 け。ピンセット使ってみたらかなり作業効率があがった。
ジュンフロン線は僕は0.4m/mが一番調子いい感じ。緑のラインは0.32m/m。ちょっ と皮膜の溶けがつらい。




昨日とりつけた外部用のLM317T電源ユニットのテストをしました。電源は秋月の6.2V 2Aを使ってみたところ、4.7Vで飽和だ。6.2Vからだと-3Vで3.2Vがいいところなので、無理して4.7Vというとこか。R1を120Ωじゃなく130Ωにすれば5.5Vくらいまでひきだせるけれど、そういうこともしたくない。5Vきっちり出すなら電源電圧を上げよう。とりあえずのとこ、3.7Vくらいあればいいし。これはこれでよしと。

LCDモジュールSG12232用に2KHzの発振器をつけました。秋月のLTC1799 1KHz-30MHzオシレータ(600円)です。2KHzが欲しいのにこれはどうかなと思った けれど、外付けに抵抗をつけるだけでいい。5V。ということでこれに。計算で は1KHz→996.7KΩ,2KHz→496.7KΩ, 3KHz→330.0KΩなので、470Kと、100Kの半 固定抵抗の直列にしました。

なんとか波形はでた。実は抵抗のとりつけを間違えてしまい、ちょっとじたばたしてました。Vccから抵抗つけてSETのところを、GNDから抵抗つけていた。三端子レギュレータがこういう接続なんだよね。間違えないように裏面からの回路図まで書いていたのに、作る時になって勘違いしてしまった。だめね〜。
実際どの程度2KHzに近いのかは不明。

結構場所がなくなってきた。後はボタンのシュミットトリガ用に74HC14と、LEDのドライブ回路をつければつける程度だからなんとかなるかな。




外部ボード用の電源を二つつけました。LM317Tです。半固定抵抗にして、
設定可能にしておきました。片方は300Ωの半固定抵抗のみで1.2V-3.6V。
もうひとつは、180Ωと300Ωの半固定抵抗の直結で3.1V-の設定です。

手前の小さい基板は、部品の置き方や配線の雰囲気をテストした習作。全部で 4つも作ることになったので、時間がかかった。
あとPA,PB,P7,P8用のコネクタをつけたところまで。配線はまだ。配線は気力が 十分な時じゃないと失敗しそうだ。

H8/3052 CPUボードへの電源供給のラインもやっぱり直して、CPUボードへの 電源供給(8V以上)と、CPUボードのレギュレータからの出力(5V)を別にしました。



せっかく苦労して作ったメモリモジュールが、おしゃかになってしまいまし
た...。

リセットボタンをつけようと、電源入れたまま基板の接点をつついてテストを していたのですが、うっかりどこかショートさせてしまったのか、次の起動か らメモリチェックが失敗ばかり。メモリを触ってみるとカンカンに熱くなって いた。....基板からひっぺがして、新しいのにつけかえました。
多めに5個買ってあるから。このくらいの失敗は織込み済みです。
そしてまたメモリチェックのプログラムを流してみたところ、片側のメモリだ け時たまエラーになる。どんな感じにメモリが崩れてるのかな?ともう一度リー ドしてみると、正しい値が読まれる。アクセスを2ステートから3ステート、ノー ウェイトに変更してみると、大丈夫でした。2ステートだとギリギリなとこなん だね。
これでメモリチェックプログラムを6時間ほどエージングしてみても大丈夫でし た。

    リセットボタン関係
  • リセットボタンは、CPUボード上にリセット回路が乗っているので、M/B側 では単にGNDに落とすだけでOK。
  • NMI端子はCPUボード上で10Kでプルアップされているので、M/B側はGNDに落 とすだけでいい。(ルネサスモニタを使っていると、これでモニタに落とせるの で便利)
ブートモード
  • MD0, MD1はCPUボード上で10KでプルアップされているのでジャンパでGNDに 落とすだけ。
  • MD2はCPUボード上でプルアップ、パスコン、逆流防止ダイオードがついているので、M/BはGNDに落とすだけ。
  • FWEはCPUボード上で10Kでプルダウンされて、パスコンがついているので、 M/BはVccにつなぐだけ。
  • CN4の1番ピン(POWER)はCPUボード上でCN1(39,40)のPOWERと接続されているのでつながなくてもいい。
  • CN4の3番ピン(5V)はCPUボード上のレギュレータの出力。
ということで、これもピンからスイッチに直結でOK。なかなか使いやすくまとまってるね。これでロムに書き込みができるようになりました。
ちょっと実装上の間違いに気付きました。CPUボードにレギュレータが乗ってる から、POWERにはさらに+3Vで、8Vは印加しないといけなかった。ジャンク箱か ら漁ってきた、5Vの電源を使ってたよ。CPUボードからのレギュレータ出力を調 べてみると、5V出てるから、まぁこれはこれでいいか。
さて、実行に必要なボタン(パワー、リセット、NMI、ブートモード(FWE,MD2)、 モード設定(MD0,MD1)ができたところで、次はI/Oポートの引き出しだ。RAMを増 設したので、A0-A16,D8-D15,RD,HWR,CS4,CS5の29本がこれに供出されてしまっ た。
CS4,CS5,CS6はポートAでここはI/Oポートの他にITU,TPCと兼用ピンなのでちょっ と微妙。A17-A19から外付けでデコードしてチップセレクト信号にすれば収まり はいいけれど、ちょっとアドバンスっぽい。ので、このまま。
できるだけ開発用にポートは残したいので、基板内で使うのは、虫喰い上に 残ったP5-1,P5-2,P5-3,P6-0,P6-1,P6-2,P6-3,P6-6の8個。
ポート9はシリアルと兼用で、P9-4,P9-5が余っている。これはIRQ4,IRQ5と 兼用なので、汎用の外部入力にしたい。
秋月のグラフィックLCDモジュールSG12232を買ってある。これをつなげたい のだけど、これは少なくともCS,A,RD,WR,D0-D7の12本は必要。
P7,P8,PA,PBは残す方針かな。DROの時はP8を3つ、PAを6つ、PBを6つ使った。 うまくいかなくなったらまた作り直せばいいし、思いつきでいこう。



アドレスが出ていないのはCS4#の配線ミスでした。隣りのCS5#につないでいた。
つなぎ直してちゃんと動きました。


プログラム側で必要なのは、A0-A19をI/Oポートのレジスタで出力にしておくこと と、エリア4-7はBSC_CSCRでCS#の出力をすること。エリア0-3はI/Oポートで出 力を設定する。
H8とSRAM(M68AF127BM55)の配線について。
  • E2->CS, E1#->CS#, W#->WE#, G#->OE#。
  • CS#(E1)とCS(E2)はANDでつながっているので、CSはVcc5Vに7Kでプルアップ 固定、CS#をH8のCS#に直結する。
  • 8bitアクセスなのでHWRとWE#(W#)を直結。SRAMのDQ0-DQ7をH8のD8-D15に直結。
  • H8のRD#とOE#(G#)を直結。
  • アドレスラインはSRAMのA0-A16をH8のA0-A16に直結
  • H8側からのCS#もプルアップしてSRAMに接続。バスステートコントローラの参考例でプルアップしてる。端子的には最大2mA。5V、1mAで5KΩ、ここは7KΩにしておきました。(E2のプルアップも)

増設したSRAMのテストプログラムは...
モード5(1MBモード)で起動するとバス幅8bitなのでBSC_ABWCRの設定はなくても いい。BSC_ASZTCRの設定もデフォルトの3ステートでよければそのままでもいい。
void check_ram (uint32_t, uint32_t);

#define	BSC_ABWCR	((volatile uint8_t *)0xfffec)
#define	BSC_ASTCR	((volatile uint8_t *)0xfffed)
#define	BSC_WCR		((volatile uint8_t *)0xfffee)
#define	BSC_WCER	((volatile uint8_t *)0xfffef)
#define	BSC_BRCR	((volatile uint8_t *)0xffff3)
#define	BSC_CSCR	((volatile uint8_t *)0xfff5f)

#define	ADDR1M_AREA4_START	0x80000
#define	ADDR1M_AREA5_START	0xa0000
#define	ADDR1M_AREA_SIZE	0x20000

void
extram_area_init (int area)
{
  uint8_t r = 1 << area;

  *BSC_CSCR |= r; // enable CS#
  *BSC_ABWCR |= r; // 8bit access (data bus: D8-D15)
  *BSC_ASTCR &= ~r; // 2-state access (can't insert wait-state)
}

void
extram_init ()
{
#define	MDCR	(volatile uint8_t *)0xffff1

  extram_area_init (4);
  extram_area_init (5);

  // Set address line (A0-A19) output.
  P1->DDR = 0xff;
  P2->DDR = 0xff;
  P5->DDR = 0xff;

  delay (100000);
  SCI_PRINTF ("hello world. mode=%x\n", *MDCR);
  check_ram (ADDR1M_AREA4_START, ADDR1M_AREA_SIZE);
  check_ram (ADDR1M_AREA5_START, ADDR1M_AREA_SIZE);


  while (/*CONSTCOND*/1)
    asm volatile ("sleep");
}

int
rand ()
{
  static int r = 1;

  r = r * 1103515245 + 12345;
  return r & 0x7fffffff;
}

int
__mulsi3 (uint a, uint b)
{
  int r = 0;

  while (a)
    {
      if (a & 1)
        r += b;
      a >>= 1;
      b <<= 1;
    }

  return r;
}

void
check_ram (uint32_t start, uint32_t size)
{
  uint32_t end = start + size;
  uint8_t *page;
  int i, chunk, x;
  uint32_t s;

  SCI_PRINTF ("Checking RAM...0x%x size %dKB\n", start, size/1024);

  for (chunk = size; chunk > 2; chunk >>= 1)
    {
      SCI_PRINTF ("test chunk %d...", chunk);
      for (s = start; s < end; s += chunk)
	{
	  page = (uint8_t *)s;
	  x = rand ();
	  for (i = 0; i < chunk; i += 4)
	    *(volatile int *)(page + i) = (x ^ i);
	  for (i = 0; i < chunk; i += 4)
	    if (*(volatile int *)(page + i) != (x ^ i))
	      goto bad;
	  x = rand ();
	  for (i = 0; i < chunk; i += 4)
	    *(volatile int *)(page + i) = (x ^ i);
	  for (i = 0; i < chunk; i += 4)
	    if (*(volatile int *)(page + i) != (x ^ i))
	      goto bad;
	}
      SCI_PRINTF ("OK\n");
    }
  SCI_PRINTF ("success.\n");
  return;
 bad:
  SCI_PRINTF ("failed.\n");
  return;
}
もう一つSRAMモジュールを作りました。連結ソケットを使って亀の子にできるようにしてあります。CS#だけパターンカットして別配線に。それ以外のラインは共有できるので、亀の子していけばさらに増設できます。
1列の連結ソケットが手に入ればよかったのだけど、探せなかったので二列の端を 切って使っています。
これはCS5につないで合計256KB増設しました。



ここでこのままがつがつ進めていきたいところだけど、グッと押さえて開発機 を開発するまでの環境を整備。例によって廃棄PCケースから切り出した鉄板で ケースを作りました。ここまでやっておかないとターンアラウンドタイムにイ ライラすることになる。プラプラした接点が外れてハンダ付けなんてことにも なるし。

ちょっと変わったケースの展開なのは、ひっくり返すとそのまま基盤のハンダ 付け作業ができるように、こうしてみました。
VccとGNDはCPUボードを囲むように最初からスズメッキ線で配線しておくべきだっ た。プルダウンとかプルアップの時にとても使う。

この開発機についているおびただしい量のスイッチは、断腸の思いでこれから 取り外しました。

コナミの必殺技コマンダーをHORIのジョイスティックに組みこんで、プログラム用のスイッチや同時押しボタンを付けたもの。これで、龍虎の拳の超必殺技もボタン一発!という「テクの無さはマシンでカバー」という僕のモットーを具現化した ジョイスティック。
せっかく作ったし...と、捨てずに残しておいたのだけど、もう使うこともない しね。
ちょっと名残り惜しいので写真に残しておきます。



まずはAKI-H8/3052M/Bを素で組みました。これはいざという時用に。二度目だと
早いです。午前中に組みあがりました。テストもOK。



次は開発用M/Bを作ります。まずは電源とシリアルポートのみ。CPUボードには モニタを入れておいたので、これで動く。本番用のボードはブートモードの回 路を削ってもいいね。



1MBit SRAM用の配線をしました。これ、本気で苦行だった。

1.27mmピッチのSOPのハンダ付けもなんとか。

メモリはまだうまく動いてません。1byteだけ認識しているようす。ということ はアドレスがまったくとれてない。これからがもっと修羅場だな〜。



ちょっと秋葉原寄ってH8開発機のパーツを買ってきました。メモリは1MBit
SRAM、M68AF127BM55にしました。5Vということ、SOPでもピッチは1.27mmとなん
とかがんばれそうなとこで。0.8mmはまず無理そう。

書泉で立ち読みしてたらOE#,WE#じゃなくR/W#のものもないことはないらしい。 リードする時にOE#をアサートした時はWE#はデサートしないといけない。でも ライトする時はOE#とWE#を両方ともアサートしてもよく、その時はWE#が優先 されるというような作りでリードモディファイライトが効率よくできるように なっているものもある。
リードの時はOE#をアサートしたままアドレスが変更されればそのままリード が可能というのもある。
あれやこれやデータシートにパターンが書いてあるのは機能の説明か。そこは H8側が良きに謀らってくれるので僕が気も揉むことはないのだけれど。
SOP変換基盤SSP-123も。SRAMが一個100円なのに、この基盤は一個350円相当。 高いんだ。

  • 内蔵RAM,ROMはバス幅16bit 2ステート。内蔵周辺モジュールは3ステート。
  • CS0#-CS3#は対応するI/OポートのDDRで出力を設定、CS4#-CS7#はバスコントローラのCSCRで設定。
  • DRAMを接続できるのはエリア3のみ。
  • 8bitアクセス空間ではRD#とHWR#でD8-D15が有効。D0-D7はI/Oポート4に使える。
  • バスサイクルはシステムクロック。1サイクル1ステート。25MHzの場合、1ステート40ns。
  • SRAM(M68AF127BM55)のアクセスタイムは55ns。この55nsは
    • tAVAV Read cycle time(min) 最低でもアドレスを確定させておく時間が55ns
    • tAVQV Address valid to output valid(Max) データが読み出されるまでにかかる最大の時間
    • tELQV Chip Enable low to output valid(Max) 上ののOE#をアサートしてからの時間
    • tPD Chip Enable or UB#/LB# low to power up(Max) CE#がデサートしてからスタンバイモードに入るまでの時間。
    2ステート、ノーウェイトで80nsでいいのかな。
  • 16bitアクセスにすると奇数番地、偶数番地でHWR,LWRで切り分ける。そう するとM68AF127BM55を2個使うとそれぞれ半分の64KBしか使えない。
とりあえず8bitで一個接続してみよう。あまり複雑になると自信がないし。


すまぐるta-1さんによる土曜の練習走行の写真です。撮影ありがとうございます。


相変わらず伏せが足りないね。あまり伏せると思いっきり首を上を向けること になる。そうすると血行が悪くなるのかボーッとしてしまうのだ。といって下 向いたまま上目だと焦点が合わない。SHOEIだと上方視界がいいらしいけれど、 スペンサーヘルじゃないと...。この手のデザインが好きっぽい。カダローラと か、最近の青山の兄貴のとか、いいなと思う。という割にボールドウィンは あまり好きじゃない。

コースイン前ですね。高揚感を思いだす。



腰開き過ぎな気もする。腰入れるのやめたからか。最近は乗りたいように乗っ てます。思えばこの04シートにも慣れた。90シートから交換した当初は、あま りにも腰を後に引けるので、ストレートからブレーキングに入るところで着座 位置の違いに戸惑っていた。



これは普通くらい。



DRO試作二号機(旋盤用の予定)を作ります。H8/3052はRAM8Kで、一号機はもう既に いっぱいいっぱい。実際はROM上で動かすのだけれど、開発はロムモニタからRAM に乗せて実行するので8Kが限界になってしまう。
ということで、開発用H8環境としてRAMを増設したマシンを作ることにしました。
内蔵ロム有効アドレス空間1MBモードはモード5。これは1エリア128KB。0-3はROMの 512KB、7は内蔵RAMとI/Oポートがあるので、拡張できるのは4,5,6の384KB。 それぞれのエリアのCS4-6はI/OポートPAの4-6。
DRAMを使った場合、RFSHがP8の0になるのでIRQ0が使えなくなる。P8[0-2]はシュミット入力の外部割り込みで、ここにDROのクロック信号をつなげているので、ちょっと辛い。SRAMかな。
  P8->DDR = ~0x7;	// Clock signal : 0-2(IN)
  PA->DDR = 0x38;	// Data signal :0-2(IN), Caliper power :3-5(OUT)
  PB->DDR = 0xff;	// Caliper command. (clock/data): 0-5(OUT)
モード6にすれば内蔵ロム有効アドレス空間16MBで、1エリア2MBになる。これなら 2MBまでならCSが一個で済む。
CS0 P8-4
CS1 P8-3
CS2 P8-2  DRO2 clk
CS3 P8-1  DRO1 clk
(RFSH) P8-0 DRO0 clk
CS4 PA-6  Caliper cmd
CS5 PA-5  Caliper cmd
CS6 PA-4
CS0は内蔵ロムと重なってしまう。とり得る選択はCS4,CS1か。CS1かな。
実はRAMについて恥かしながらまったく知らなかった。アドレスラインそのまま と、バス幅のデータライン、メモリのON/OFFのCS(チップセレクト)、書き込み するWE(ライトイネーブル)、読み込むOE(アウトプットイネーブル)でなんとか するのね。OE、WEまとめてR/WでCSでバス切り離しでいいのでは...。と、今日 一日中延々とそれじゃだめな理由を考えてたのだけど、わからなかった(今日買っ た本には理由が書いてあったけれど(OE,WEを独立させることでI/Oバス上のデータが他と競合するのを避ける)、それはCSで切り離せることができるのでは...
まぁいいや。なんか初歩的な勘違いをしてるんでしょう。



今年は、秋に台風がまったく来なかったせいで桜の紅葉が楽しめるという。そ
う言われてみると、こんな桜の紅葉を見るのははじめてかも。

今日、ふとCRを眺めてみたら、そうえいば土曜の練習走行から久々に筑波で 鋳鉄ローターを使っていたのに気付いた。突っこめる気がしてたのはこれかー。 マシンの変化をなかなか感じとれない僕にとってはこの結果はちょっとうれしい。
僕はステンローターのフィールが好きなので、筑波ではステンローター使って たんだよね。富士、岡山は止まれないのでフィールじゃなく止まれる鋳鉄にし てたのだけど。筑波でも鋳鉄の方がいいか。

久々にSH7709Aのデータシートを見てみたら思いだした。 sys/arch/sh3/dev/scif.cは scif_stsoftを実装していないのではなくて、実装 できないんだ。SCIFはTXD,RXDしか提供しない。普通のシリアルポートを実装す るにはさらにDCD,DTR,RTS,CTSが必要なので、これは適当にI/Oポートに接続し てそこからステータスを読まないといけない。これはJornada690の解析からや らないといけないのでパス。
(SH7709AはRTS,CTSの機能も実装しているのだけれど、ハードウェアバグで使え ない。)
そしてまだ問題があった。ノギスの更新速度を高速モードにするとJornada690 では処理が間に合わないのだ。実際のところフライスを送るスピードを考えれ ば問題はなさそうだけれども。
通信量減らす処理とかもあるけれど、実作業で問題が出るまで手をつけないこ とにしました。ここでフライス用DROは完成です。


DROの仕上げに、プログラムをいじってました。ガレージのマシンはWindows2000なので、そこでTcl/Tkのモニタプログラムをテストしていた。そのプログラムを
Jornada690 NetBSD/hpcshに持っていけば終了のはずだったのだけれど...



うまくいかなかった。最後に追加したH8ヘコマンドを送る部分が動かないのだ。 このテストプログラム(本来動くプログラムです)

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <termios.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h>

int
main ()
{
  int fd;
  struct termios t;
  int i;

  fd = open ("/dev/dscif0", O_RDWR);
  assert (fd >= 0);

  if (tcgetattr (fd, &t) != 0)
    {
      perror ("tcgetattr");
      exit (1);
    }

  cfsetispeed(&t, B57600);
  cfsetospeed(&t, B57600);
  t.c_cflag &= ~(CSIZE|PARENB);
  t.c_cflag |= CS8;
  t.c_iflag &= ~(ISTRIP|ICRNL);
  t.c_oflag &= ~OPOST;
  t.c_lflag &= ~(ICANON|ISIG|IEXTEN|ECHO);
  t.c_cc[VMIN] = 1;
  t.c_cc[VTIME] = 0;

  if (tcsetattr (fd, TCSANOW, &t) != 0)
    exit (1);

  char msg[] = "ohayo\n";
  if ((i = write (fd, msg, sizeof msg)) != sizeof msg) {
    printf ("%d %d\n", i, sizeof msg);
    perror ("write");
  }

  if (tcdrain (fd) != 0)
    perror ("tcdrain");

  close (fd);

  return 0;
}

がwrite(2)でEIO。延々と調べたところ、ttwrite@sys/kern/tty.cの
	if (!CONNECTED(tp)) {
		if (ISSET(tp->t_state, TS_ISOPEN)) {
			mutex_spin_exit(&tty_lock);
			return (EIO);
この部分でEIO。CONNECTEDは
#define	CONNECTED(tp)	(ISSET(tp->t_state, TS_CARR_ON) ||	\
			 ISSET(tp->t_cflag, CLOCAL | MDMBUF))
このTS_CARR_ONが立ってない。というのはsys/arch/sh3/dev/scif.cは scif_stsoftを実装していない。これは割り込みハンドラでステータスレジスタ の変化があったら、スケジューリングしてその後のソフトウェア割リ込みでステータスの変化をtty層に送る部分。ここがないのでキャリアがない(ケーブルが繋がっ てない)としてEIOになってしまうのだ。
とりあえずscifopenの最後で
	(void) (*tp->t_linesw->l_modem)(tp, 1);//XXX -uch
してごまかしました。これでキャリアが立つので書きこめる。DROの実用的には OK。scif.c直したいけれど、ここは悩ましいんだよね。



ピストンとヘッド状況。外側のリングの溝を内側と同じく1.5mmまで掘るかな。
リングの押しつぶし抵抗が大きい気もする。

このOリングは、JIS B 2401 一種A(NBR)のS-80とS-53。耐圧7MPa、耐熱100℃。 耐熱が厳しい感じだけれど、今迄のテストでは問題ないという結論。グチャーっ と溶けてしまうようになっている時は溝が浅くてつぶし過ぎてしまった時。
このOリング最近MonotaROでも注文できるようになったので、お気楽でうれしい。 S-80(93円),S-53(68円)です。

リン青銅の熱膨張係数(10^-6/℃)は18程度、アルミは23程度。デトネーション リングはデトネーションに悩まされない限り必要ないね。密閉性に寄与するこ とはない。

フライスDROの方は、プログラム側の煮詰めを終わりました。ターミナルからの コマンドをH8のSCIで受けとる部分も割込み駆動にしてかなり安定した。ノギス を高速更新モードにすると、ノギスのデータ受けとりに忙しくてターミナルか らのコマンドがたまに受けれなかった様子。そしてできればH8側からノギスの ステータスを送るようにしたいのだけど、そこは必要になったら実装で。
旋盤もDRO化したい。
MonotaRO(モノタロウ)
あわせて読みたい