Interface 2009/5月号の付録基板の続き。
今週中にマイOSのポートを終わらせたくて焦ってます。次のBS走行会までにCR85の クランク交換したいんだ。
UART0のクロックの設定、データシートの方法(昨日は手で追ってやった)をプロ グラムにまとめました。これで設定したPCLKに対して主なボードレートの設定 を吐きます。PCLK=18MHzなら230400bpsでも安定。これはユーザプログラムのロー ドが速くて楽です。
例によって
そしてスレッドを実装します。まずはsetjmp, longjmpから。 さすがARM
そして実装。ちょっとまだチェックが足りないのだけど、スレッドスイッチは
いざ実際書いてみるとちょっと楽しいかも。
なんとかそこそこスレッドも動いている様子だけれど、まだこの段階だと思いっ きり勘違いしている可能性大。
今週中にマイ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のインクリメントの指定)。できる限りアセンブラで書きたくない
な...と思っていたのだけど、
いざ実際書いてみるとちょっと楽しいかも。
なんとかそこそこスレッドも動いている様子だけれど、まだこの段階だと思いっ きり勘違いしている可能性大。
