090618

|


Interface 2009/5月号の付録基板の続き。

今週中にマイOSのポートを終わらせたくて焦ってます。次のBS走行会までにCR85の クランク交換したいんだ。
UART0のクロックの設定、データシートの方法(昨日は手で追ってやった)をプロ グラムにまとめました。これで設定したPCLKに対して主なボードレートの設定 を吐きます。PCLK=18MHzなら230400bpsでも安定。これはユーザプログラムのロー ドが速くて楽です。
// LPC23xx UART clock configuration tool.

#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>

struct frac
{
  float f;
  int DivAddVal;
  int MulVal;
} frac [] =
  {
    { 1.000000, 0, 1 },
    { 1.066667, 1, 15 },
    { 1.071429, 1, 14 },
    { 1.076923, 1, 13 },
    { 1.083333, 1, 12 },
    { 1.090909, 1, 11 },
    { 1.100000, 1, 10 },
    { 1.111111, 1, 9 },
    { 1.125000, 1, 8 },
    { 1.133333, 2, 15 },
    { 1.142857, 1, 7 },
    { 1.142857, 2, 14 },
    { 1.153846, 2, 13 },
    { 1.166667, 1, 6 },
    { 1.166667, 2, 12 },
    { 1.181818, 2, 11 },
    { 1.200000, 1, 5 },
    { 1.214286, 3, 14 },
    { 1.222222, 2, 9 },
    { 1.230769, 3, 13 },
    { 1.250000, 1, 4 },
    { 1.266667, 4, 15 },
    { 1.272727, 3, 11 },
    { 1.285714, 2, 7 },
    { 1.285714, 4, 14 },
    { 1.300000, 3, 10 },
    { 1.307692, 4, 13 },
    { 1.333333, 1, 3 },
    { 1.357143, 5, 14 },
    { 1.363636, 4, 11 },
    { 1.375000, 3, 8 },
    { 1.384615, 5, 13 },
    { 1.400000, 2, 5 },
    { 1.416667, 5, 12 },
    { 1.428571, 3, 7 },
    { 1.428571, 6, 14 },
    { 1.444444, 4, 9 },
    { 1.454545, 5, 11 },
    { 1.461538, 6, 13 },
    { 1.466667, 7, 15 },
    { 1.500000, 1, 2 },
    { 1.533333, 8, 15 },
    { 1.538462, 7, 13 },
    { 1.545455, 6, 11 },
    { 1.555556, 5, 9 },
    { 1.571429, 4, 7 },
    { 1.571429, 8, 14 },
    { 1.583333, 7, 12 },
    { 1.600000, 3, 5 },
    { 1.615385, 8, 13 },
    { 1.625000, 5, 8 },
    { 1.636364, 7, 11 },
    { 1.642857, 9, 14 },
    { 1.666667, 10,15 },
    { 1.692308, 9, 13 },
    { 1.700000, 7, 10 },
    { 1.714286, 10,14 },
    { 1.714286, 5, 7 },
    { 1.727273, 8, 11 },
    { 1.733333, 11,15 },
    { 1.750000, 3, 4 },
    { 1.769231, 10,13 },
    { 1.777778, 7, 9 },
    { 1.785714, 11,14 },
    { 1.800000, 12,15 },
    { 1.818182, 9, 11 },
    { 1.833333, 10,12 },
    { 1.833333, 5, 6 },
    { 1.846154, 11,13 },
    { 1.857143, 12,14 },
    { 1.857143, 6, 7 },
    { 1.866667, 13,15 },
    { 1.875000, 7, 8 },
    { 1.888889, 8, 9 },
    { 1.900000, 9, 10 },
    { 1.909091, 10,11 },
    { 1.916667, 11,12 },
    { 1.923077, 12,13 },
    { 1.928571, 13,14 },
    { 1.933333, 14,15 },
  };

float fr_guess[] = { 1.5, 1.4, 1.6, 1.3, 1.7, 1.2, 1.8, 1.1, 1.9 };
int bps_table [] = { 9600, 19200, 38400, 57600, 115200, 230400 };

void calc_parameter (int, int);

int
main (int argc, char *argv[])
{
  int peripheral_clock, i;

  if (argc < 2)
    {
      fprintf (stderr, "Specify peripheral clock frequency (Hz).\n");
      return 1;
    }

  peripheral_clock = atoi (argv[1]);

  for (i = 0; i < sizeof bps_table / sizeof (bps_table[0]); i++)
    calc_parameter (peripheral_clock, bps_table[i]);

  return 0;
}

void
calc_parameter (int pclk, int br)
{
  float PCLK, BR, DLest, FRest;
  int iDLest;
  int i;

  PCLK = (float)pclk;
  BR = (float)br;

  DLest= PCLK / (16. * BR);
  if (DLest == (float)((int)PCLK / (16 * (int)BR)))
    {
      i = 0;
      iDLest = (int)DLest;
    }
  else
    {
      // Determne Fractional Divider.
      for (i = 0; i < sizeof fr_guess / sizeof (fr_guess[0]); i++)
	{
	  FRest = fr_guess[i];
	  iDLest = PCLK / (16 * BR * FRest);
	  FRest = PCLK / (16 * BR * iDLest);
	  if (FRest < 1.9 && FRest > 1.1)
	    break;
	}
      for (i = 0; i < sizeof frac / sizeof (frac[0]); i++)
	{
	  if (frac[i].f > FRest)
	    break;
	}
      // best fit.
      if (FRest - frac[i - 1].f < frac[i].f - FRest)
	i--;
    }

  FRest = frac[i].f;
  int DivAddVal = frac[i].DivAddVal;
  int MulVal = frac[i].MulVal;
  int iDMest = (int)(PCLK / (16 * BR * FRest - (float)iDLest)) / 256.;
  float bps = PCLK / ((16. * (256. * (float)iDMest + (float)iDLest) *
		       (1. + ((float)DivAddVal / (float)MulVal))));

  printf (" { %d, %d, %d | (%d << 4), 0 };"
	  " // PCLK %.4fMHz %.0fbps error %.2f%%\n",
	  iDMest, iDLest, DivAddVal, MulVal,
	  PCLK / 1000000, bps, (1 - BR / bps) * 100.);
}

さて、次は割り込みまわり。このあたりからガチでアセンブラで書かないとい けなくなる。コーリングコンベンションはr0-r3が最初の4つまでの引数で、 caller saved。他のは全部callee saved.
例によって
int
a (int a0, int a1, int a2, int a3)
{

  return a0 + a1 + a2 - a3;
}

int
b ()
{
  return a (0, 1, 2, 3);
}

int
main ()
{

  return a (0xaa55, 4, 3, 2);
}

をarm-elf-gcc -O -S a.cでアセンブラに落とす。

	.file	"a.c"
	.text
	.align	2
	.global	a
	.type	a, %function
a:
	@ args = 0, pretend = 0, frame = 0
	@ frame_needed = 0, uses_anonymous_args = 0
	@ link register save eliminated.
	add	r1, r1, r0
	add	r1, r1, r2
	rsb	r0, r3, r1
	bx	lr
	.size	a, .-a
	.align	2
	.global	b
	.type	b, %function
b:
	@ args = 0, pretend = 0, frame = 0
	@ frame_needed = 0, uses_anonymous_args = 0
	str	lr, [sp, #-4]!
	mov	r0, #0
	mov	r1, #1
	mov	r2, #2
	mov	r3, #3
	bl	a
	ldr	pc, [sp], #4
	.size	b, .-b
	.align	2
	.global	main
	.type	main, %function
main:
	@ args = 0, pretend = 0, frame = 0
	@ frame_needed = 0, uses_anonymous_args = 0
	str	lr, [sp, #-4]!
	mov	r0, #43520
	add	r0, r0, #85
	mov	r1, #4
	mov	r2, #3
	mov	r3, #2
	bl	a
	ldr	pc, [sp], #4
	.size	main, .-main
	.ident	"GCC: (GNU) 4.3.2"
これで大体の雰囲気をつかんで、割り込みの処理はPSRのI(IRQ)とF(FIQ)を同時に 扱うことにして、
FUNC (intr_status)
	mrs	r0,	cpsr
	and	r0,	r0,	#0xc0
	bx	lr

FUNC (intr_disable)
	mrs	r0,	cpsr
	orr	r0,	r0,	#0xc0
	msr	cpsr_c,	r0
	bx	lr

FUNC (intr_enable)
	mrs	r0,	cpsr
	bic	r0,	r0,	#0xc0
	msr	cpsr_c,	r0
	bx	lr

FUNC (intr_suspend)
	mrs	r1,	cpsr
	mov	r0,	r1
	orr	r1,	r1,	#0xc0
	msr	cpsr_c,	r1
	and	r0,	r0,	#0xc0
	bx	lr

FUNC (intr_resume)
	mrs	r1,	cpsr
	bic	r1,	r1,	#0xc0
	orr	r1,	r1,	r0
	msr	cpsr_c,	r1
	bx	lr
こんなとこで。
そしてスレッドを実装します。まずはsetjmp, longjmpから。 さすがARM
FUNC (cpu_regs_save)
	stmia	r0,	{r0-r15}
	bx	lr

FUNC (cpu_regs_load)
	ldmia	r0,	{r0-r15}
	bx	lr
これだけでいいだろう。 これを
void
ohayo_func ()
{
  iprintf ("ohayo\n");
  while (1)
    ;
}

uint32_t
test (int32_t argc, const char *argv[])
{
  uint32_t r[16];
  int i;

  memset (r, 0, sizeof r);
  iprintf ("%x\n", r);
  cpu_regs_save (r);
  for (i = 0; i < 16; i++)
    {
      iprintf ("%x\n", r[i]);
    }
  r[15] = (uint32_t)ohayo_func;

  cpu_regs_load (r);
  iprintf ("OHAYO\n");
}
のように使ってみて確認。"ohayo"と表示されて成功だ。
そして実装。ちょっとまだチェックが足りないのだけど、スレッドスイッチは
	.long	current_thread
FUNC (do_thread_switch)
	// save context.
	mrs	r1,	cpsr
	ldr	r0,	[pc, #-16]	// current_thread
pc-16で上の.long current_threadを指定しているのはパイプラインが3段だから。
	ldr	r0,	[r0]		// r0 = *current_thread
	str	r1,	[r0], #4	// *r0++ = r1
	stmia	r0!,	{r0-r15}	// 全部のレジスタ退避(r0-r3は余計)
	str	lr,	[r0, #-4]	// overwrite PC to LR. *(r0 -4) = lr
ここで退避されたPCはstrmia命令の2つ先。それを本来この関数が戻るべきアドレス
(lr)に設定。

	bl	thread_context_switch
	ldr	r0,	[pc, #16]
	ldr	r0,	[r0]
	ldr	r1,	[r0], #4	// r1 = *r0++;
	msr	cpsr_cf,r1		// restore all CPSR
	ldmia	r0,	{r0-r15}
ここでPCが更新されてジャンプするはず。
	bl	md_thread_machdep_noreturn_assert	// for debug.
しなかった時用のデバッグ。
	.long	current_thread
これで済む。ARMのアセンブラは条件実行とシフタがあるのでとてもパズル的で、 さらにアセンブラの書き方が直感的じゃない(上のコードでいうとstmiaの'!'と か。この! はr0のインクリメントの指定)。できる限りアセンブラで書きたくない な...と思っていたのだけど、
いざ実際書いてみるとちょっと楽しいかも。
なんとかそこそこスレッドも動いている様子だけれど、まだこの段階だと思いっ きり勘違いしている可能性大。