081218

|


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
このくらいあれば十分かな。