ColdFire でアセンブラ (6) - そんなに最適化するなよ [ColdFire (ColdeFire) V1]
某所で 使われていた「TRAP命令に引数を渡す」プログラムを見て、 もしかしたら危ないかも知れないと思い実験してみました。
元のコード
もともとのコードはこんなものでした。 型宣言と定数宣言は適当に追加しています。
typedef byte UB; typedef int TMO; const UB FC_TSLP_TSK = 129; const UB FC_SLP_TSK = 130; const TMO TMO_FEVR = -1; #define tslp_tsk( tmout ) __slp_tsk( (UB)FC_TSLP_TSK, (TMO)tmout ) #define slp_tsk() __slp_tsk( (UB)FC_SLP_TSK, TMO_FEVR ) void __slp_tsk( UB fc, /*機能コード*/ TMO tmout /*タイムアウト時間*/ ) { __asm { trap #0; }; }
で、この関数を呼び出すために作ったサンプルがこれです。
void main(void) { for(;;) { tslp_tsk(100); slp_tsk(); tslp_tsk(200); slp_tsk(); } /* loop forever */ /* please make sure that you never leave main */ }
二つの擬似タスクを延々と呼び出します。 コンパイルすると、__slp_tsk関数の引数fcが 使われていないと文句(Warning)を言われます。 コンパイルの結果がこれです。
; 35: for(;;) { ; 36: tslp_tsk(100); ; 0x00000000 _main: ; main: 0x00000000 0x4E40 trap #0 ; ; 37: slp_tsk(); ; 0x00000002 0x4E40 trap #0 ; ; 38: tslp_tsk(200); ; 0x00000004 0x4E40 trap #0 ; ; 39: slp_tsk(); ; 0x00000006 0x4E40 trap #0 ; ; 40: } /* loop forever */ ; 0x00000008 0x60F6 bra.s *-8 ; 0x00000000 0x0000000A 0x51FC trapf
関数がインライン展開された上に 使われない引数が省略されています。 これでは、当初の目的を果たしません。
関数ごとアセンブラで記述する
そこで、策を考えました。 関数ごとアセンブラで記述してしまいます。
void __asm __slp_tsk_1( UB fc, /*機能コード*/ TMO tmout /*タイムアウト時間*/ ) { trap #0 rts }
すると、こんどは律儀にサブルーチン呼び出しをしてくれるようになります。
; 28: { ; 29: trap #0 ; 0x00000000 ___slp_tsk_1: ; __slp_tsk_1: 0x00000000 0x4E40 trap #0 ; ; 30: rts ; 0x00000002 0x4E75 rts
; 35: for(;;) { ; 36: tslp_tsk(100); ; 0x00000000 _main: ; main: 0x00000000 0x7264 moveq #100,d1 0x00000002 0x717C0081 mvs.w #129,d0 0x00000006 0x4EBA0000 jsr ___slp_tsk_1 ; ; 37: slp_tsk(); ; 0x0000000A 0xA141 mov3q #-1,d1 0x0000000C 0x717C0082 mvs.w #130,d0 0x00000010 0x4EBA0000 jsr ___slp_tsk_1 ; ; 38: tslp_tsk(200); ; 0x00000014 0x737C00C8 mvs.w #200,d1 0x00000018 0x717C0081 mvs.w #129,d0 0x0000001C 0x4EBA0000 jsr ___slp_tsk_1 ; ; 39: slp_tsk(); ; 0x00000020 0xA141 mov3q #-1,d1 0x00000022 0x717C0082 mvs.w #130,d0 0x00000026 0x4EBA0000 jsr ___slp_tsk_1 ; ; 40: } /* loop forever */ ; 0x0000002A 0x60D4 bra.s *-42 ; 0x00000000
何とか、このままインライン関数にしてくれないかなあ。
volatileという「おまじない」
次は、魔法のおまじない「volatile」をためしてみます。
void __slp_tsk_2( volatile UB fc, /*機能コード*/ volatile TMO tmout /*タイムアウト時間*/ ) { __asm { trap #0 }; }
; 46: for(;;) { ; 0x00000000 _main: ; main: 0x00000000 0x4FEFFFEC lea -20(a7),a7 ; ; 47: tslp_tsk(100); ; 0x00000004 0x7064 moveq #100,d0 0x00000006 0x2F400008 move.l d0,8(a7) 0x0000000A 0x1EBC0081 move.b #-127,(a7) ; '.' 0x0000000E 0x4E40 trap #0 ; ; 48: slp_tsk(); ; 0x00000010 0xA16F000C mov3q #-1,12(a7) 0x00000014 0x1F7C00820001 move.b #-126,1(a7) ; '.' 0x0000001A 0x4E40 trap #0 ; ; 49: tslp_tsk(200); ; 0x0000001C 0x717C00C8 mvs.w #200,d0 0x00000020 0x2F400004 move.l d0,4(a7) 0x00000024 0x1F7C00810002 move.b #-127,2(a7) ; '.' 0x0000002A 0x4E40 trap #0 ; ; 50: slp_tsk(); ; 0x0000002C 0xA16F0010 mov3q #-1,16(a7) 0x00000030 0x1F7C00820003 move.b #-126,3(a7) ; '.' 0x00000036 0x4E40 trap #0 ; ; 51: } /* loop forever */ ; 0x00000038 0x60CA bra.s *-52 ; 0x00000004 0x0000003A 0x51FC trapf
揮発しないように、スタックに積んでくれました。 それをレジスタ渡しにして欲しいんだけど。
屋上屋を重ねる
つまり、明示的にレジスタに引数を入れればいいのだな。
void __slp_tsk_3( UB fc, /*機能コード*/ TMO tmout /*タイムアウト時間*/ ) { __asm { move fc,d0 move tmout,d1 trap #0 }; }
; 57: for(;;) { ; 0x00000000 _main: ; main: 0x00000000 0x4FEFFFEC lea -20(a7),a7 ; ; 58: tslp_tsk(100); ; 0x00000004 0x7464 moveq #100,d2 0x00000006 0x2F420008 move.l d2,8(a7) 0x0000000A 0x1EBC0081 move.b #-127,(a7) ; '.' 0x0000000E 0x2017 move.l (a7),d0 0x00000010 0x222F0008 move.l 8(a7),d1 0x00000014 0x4E40 trap #0 ; ; 59: slp_tsk(); ; 0x00000016 0xA16F000C mov3q #-1,12(a7) 0x0000001A 0x1F7C00820001 move.b #-126,1(a7) ; '.' 0x00000020 0x202F0001 move.l 1(a7),d0 0x00000024 0x222F000C move.l 12(a7),d1 0x00000028 0x4E40 trap #0 ; ; 60: tslp_tsk(200); ; 0x0000002A 0x757C00C8 mvs.w #200,d2 0x0000002E 0x2F420004 move.l d2,4(a7) 0x00000032 0x1F7C00810002 move.b #-127,2(a7) ; '.' 0x00000038 0x202F0002 move.l 2(a7),d0 0x0000003C 0x222F0004 move.l 4(a7),d1 0x00000040 0x4E40 trap #0 ; ; 61: slp_tsk(); ; 0x00000042 0xA16F0010 mov3q #-1,16(a7) 0x00000046 0x1F7C00820003 move.b #-126,3(a7) ; '.' 0x0000004C 0x202F0003 move.l 3(a7),d0 0x00000050 0x222F0010 move.l 16(a7),d1 0x00000054 0x4E40 trap #0 ; ; 62: } /* loop forever */ ; 0x00000056 0x60AC bra.s *-82 ; 0x00000004
何でわざわざスタックを使うかなあ。 しかも「move.l 1(a7),d0」ってのは、おかしいだろう。
さて、次はどんな書き方をしてやろうか。
続く…かも
ColdFire V1 - インタプリタ始動 [ColdFire (ColdeFire) V1]
インタプリタ作成計画も、ボチボチ走っています。
今日は、文字列表示と改行処理を入れてみました。
*READY :10 "hello world!"/ :20 "what are you talking about ?" :0 00010 "hello world!"/ 00020 "what are you talking about ?" *READY : hello world! what are you talking about ? *READY :
ダブル・クォーテーション(")でくくると、文字列が表示されます。 また、スラッシュ(/)で改行されます。
分岐処理はまだ入っていないので、 空行を含む直接実行の後は強制的にプログラムを実行するようにしました。
// void executeStatement(void) // 文の実行 void executeStatement(void) { char ch; while ((ch = *columnPoint) != 0) { if (ch == ' ') { // 文の区切り columnPoint++; continue; } // 分岐先変数 lineNumber = 0; if (isDelimit(*(columnPoint+1))) { // 一文字コマンド // No commands implemented. } if (ch =='"') { // 文字列表示 columnPoint++; for (;;) { ch = *columnPoint++; if ((ch == 0) || (ch == '"')) { break; } putc(ch); } } else if (ch == '/') { // 改行表示 while (ch == '/') { putc('\n'); ch = *(++columnPoint); } } else { lhsPoint = columnPoint; columnPoint++; } } }
入力行の直接実行の中に行番号の変更を監視する部分が含まれています。
// void executeDirect(void) // ラインバッファ内コマンドの実行 void executeDirect(void) { executeStatement(); // ラインバッファの実行 lineNumber = 1; // 分岐エミュレーション while (lineNumber != 0) { // 分岐したら、 linePoint = findLine(lineNumber); // 分岐先を探す for (;;) { if (linePoint >= textEnd) { // プログラム終端に到達 return; // エディタに戻る } columnPoint = linePoint + 2; // 行番号を飛ばす if (*columnPoint != ' ') { // コメント行をスキップする linePoint = nextLine(linePoint); } else { executeStatement(); // 一行実行 if (lineNumber != 0) { // 行番号が変更されたら break; // 分岐 } // 次の行に進む linePoint = nextLine(linePoint); } } } }
プロジェクトは、 ここを 右クリックして"CFQE20.zip"という名前で保存すると出てきます。
ColdFire V1 - ラインエディタらしきもの [ColdFire (ColdeFire) V1]
シリアル・インターフェースを拡張してライン・エディタを作りました。
ソースコード
/* * $Source: /mnt/cvs/rep/CW/CFQE20/Sources/main.c,v $ * $Id: main.c,v 1.3 2008/02/03 02:10:58 noritan Exp $ */ #include <hidef.h> /* for EnableInterrupts macro */ #include "derivative.h" /* include peripheral declarations */ // 定数の宣言 #define BUF_LENGTH (72) #define TEXT_LENGTH (4096) // 大域変数の宣言 char lineBuf[BUF_LENGTH]; // ラインバッファ char *linePoint; // ラインポインタ char textBuf[TEXT_LENGTH]; // テキスト領域 char *textEnd; // テキスト終端ポインタ // お便利関数の宣言 int getc(void); void putc(int ch); void puts(const char *s); char *getLine(void); int getNumber(void); void insertLine(int lineno, char *line); void removeLineByNo(int lineno); char *findLine(int lineno); void listLines(void); void putNumber(int lineno); // メイン関数 void main(void) { int nLine; // 入力した数値 // SOPT1を設定する // COPは停止させる SOPT1 = 0 | SOPT1_WAITE_MASK // WAIT命令を許可 | SOPT1_RSTOPE_MASK // RSTO*出力を使う | SOPT1_BKGDPE_MASK // BKGDポートを使う | SOPT1_RSTPE_MASK ; // RESET*入力を使う // ICSを設定する // REFCLK = 31,250Hz // DCO = 48,000,000Hz // BUSCLK = DCO / 2 = 24,000,000Hz ICSTRM = NVICSTRM; ICSSC_FTRIM = NVFTRIM_FTRIM; ICSC2_BDIV = 0; // BDIV 1/1 ICSSC_DRST_DRS = 2; // FLL x1536 // SCI1を設定する // BAUD = 115200 // BR = BUSCLK / BAUD / 16 = 13 SCI1BD_SBR = 13; // クロック分周比 SCI1C2_TE = 1; // 送信機能を有効にする SCI1C2_RE = 1; // 受信機能を有効にする // テキストエリアの初期化 textBuf[0] = -1; textEnd = &textBuf[0]; EnableInterrupts; /* enable interrupts */ /* include your code here */ for(;;) { puts("\n*READY"); // プロンプトを表示する linePoint = getLine(); // 一行受信する nLine = getNumber(); // 数値を取り出す if (nLine < 0) { // 数値がない時には puts("*ERROR*"); // エラー表示 } else if (nLine == 0) { // 数値が0の時には、 listLines(); // リストを表示する } else if (!*linePoint) { // 数値だけの時には、 removeLineByNo(nLine); // 削除 } else { // それ以外は、 insertLine(nLine, linePoint); // 挿入処理 } } /* loop forever */ /* please make sure that you never leave main */ } // char *getLine(void) : // 一行受信ルーチン // ラインバッファの先頭を返す char *getLine(void) { char *p; // バッファポインタ char c; // 一文字バッファ for (;;) { putc(':'); // プロンプト出力 p = lineBuf; // バッファの先頭を指示 for (;;) { c = (char)getc(); // 一文字受け取り if (c == 8) { // Back Spaceの処理 putc(c); // カーソルをもどす p--; // 一文字戻す // ポインタがバッファの先頭に達したら、 // プロンプト出力からやりなおす。 if (p < lineBuf) break; } else if (c == '\n') { // Enterの処理 putc(c); // 改行出力 *p = 0; // 文字列末文字を書き込む return lineBuf; // バッファへのポインタを返す } else if (c < ' ') { // その他のコントロール文字 continue; // 単に無視する } else if (c == 127) { // DELETE文字の処理 putc('\n'); // 改行出力 break; // プロンプト出力からやりなおす } else { // 通常の文字の処理 putc(c); // エコーバック *p++ = c; // バッファに書き込む // バッファを使い切ったら、 if (p >= &lineBuf[BUF_LENGTH-1]) { putc('\n'); // 改行出力 break; // プロンプト出力からやりなおす } } } } } // int getc(void) : // 一文字受信ルーチン int getc(void) { int ch; // 文字バッファ // 受信バッファがいっぱいになるのを待つ while (!SCI1S1_RDRF) { // Do nothing } ch = SCI1D; // 一文字受け取る if (ch == '\r') { // Enterキーは、 ch = '\n'; // 行末文字に変換する } return ch; // 受け取った文字を返す } // void putc(int ch) : // 一文字送信ルーチン void putc(int ch) { if (ch == '\n') { // 行末文字の前にCRを付ける // 送信バッファが空くのを待つ while (!SCI1S1_TDRE) { // Do nothing } SCI1D = '\r'; // SCIにCR文字を送信する } // 送信バッファが空くのを待つ while (!SCI1S1_TDRE) { // Do nothing } SCI1D = (byte)ch; // SCIに一文字送信する } // void puts(const char *s) : // 文字列送信ルーチン void puts(const char *s) { while (*s) { // 文字列末でなければ、 putc(*s++); // 一文字送信する } } // int getNumber(void) : // ラインポインタの位置から数値を取り出す int getNumber(void) { int n = 0; // 数値 if (!isdigit(*linePoint)) { // 数字がなければ、 return -1; // -1 を返す } while (isdigit(*linePoint)) { // 数値の十進変換 n = n * 10 + ((*linePoint) - '0'); linePoint++; } return n; // 数値を返す } // void putNumber(int n) : // 数値の表示 - ZERO SUPPRESS は省略。 void putNumber(int n) { int m = 10000; // 着目する桁 while (m > 0) { // 一の位まで表示する putc((n / m) + '0'); // 着目桁の表示 n %= m; // 残りの桁の部分 m /= 10; // 着目桁の移動 } } // int getLineNo(line) // 行番号を取り出す int getLineNo(char *line) { return *((word *)line); } // char *nextLine(line) // 次の行にスキップする char *nextLine(char *line) { char *s; for (s = line + 2; *s++; ) { } return s; } // void removeLine(line) // ポインタで指示する行を削除する。 void removeLine(char *line) { const char *s; char *p; // 削除 p = (char *)line; s = nextLine(line); while (s < textEnd) { *p++ = *s++; } *p = *s; textEnd = p; } // char *findLine(lineno) // 行をさがす char *findLine(int lineno) { char *s; // 行を探す s = textBuf; for (;;) { // テキスト末端に到達 if (s >= textEnd) { break; } // 行番号を検証。 if (getLineNo(s) >= lineno) { // 行位置発見 break; } // 次の行にスキップ s = nextLine(s); } return s; } // void insertLine(lineno, line) : // 行挿入 void insertLine(int lineno, char *line) { char *s; char *p; char *pInsert; int length; // 挿入候補位置を探す pInsert = findLine(lineno); // 行番号を検証。 if (getLineNo(pInsert) == lineno) { // 置換 // 置換後の文字数差を計算する。 length = strlen(line) - strlen(&pInsert[2]); if (length > 0) { // 場所を拡張して挿入する if (textEnd - textBuf + length > TEXT_LENGTH) { // バッファ・オーバフロー pInsert = 0; } else { // 拡張 p = textEnd + length; s = textEnd; while (s >= pInsert) { *p-- = *s--; } textEnd += length; } } else if (length < 0) { // 場所を縮小して挿入する s = nextLine(pInsert); p = s + length; while (s <= textEnd) { *p++ = *s++; } textEnd += length; } else { // 上書き } } else { // バッファ・チェック length = strlen(line) + 3; if (textEnd - textBuf + length > TEXT_LENGTH) { // バッファ・オーバフロー pInsert = 0; } else { // 丸ごと拡張して挿入する p = textEnd + length; s = textEnd; while (s >= pInsert) { *p-- = *s--; } textEnd += length; } } // 挿入位置が見つからなければ、何もしない。 if (!pInsert) { return; } // 挿入開始 p = pInsert; // 行番号を書き込む *((word *)p) = (word)lineno; p += 2; // 本体を書き込む for (s = line; *s; ) { *p++ = *s++; } // 行末を書き込む *p++ = 0; } // void removeLineByNo(lineno) // 与えられた行番号の行を削除する void removeLineByNo(int lineno) { char *s; // 削除位置を探す s = findLine(lineno); // 行番号部分を比較する if (getLineNo(s) == lineno) { removeLine(s); } } // void listLines(void) // リスト出力 void listLines(void) { char *p; // テキスト終端まで表示する。 for (p = textBuf; p < textEnd; ) { // 行番号表示 putNumber(getLineNo(p)); p += 2; // 本文表示 while (*p) { putc(*p++); } putc('\n'); // 行末をスキップする p++; } }
使ってみよう
このラインエディタは、昔懐かしいインターフェースで構成されています。
*READY:10 i=1,100 *READY:0 00010 i=1,100 *READY:20 ?=i *READY:30 @=i+1 *READY:40 #=-1 *READY:0 00010 i=1,100 00020 ?=i 00030 @=i+1 00040 #=-1 *READY:20 *READY:0 00010 i=1,100 00030 @=i+1 00040 #=-1 *READY:20 ??=i *READY:0 00010 i=1,100 00020 ??=i 00030 @=i+1 00040 #=-1 *READY:20 ?$=i *READY:0 00010 i=1,100 00020 ?$=i 00030 @=i+1 00040 #=-1 *READY:20 ?=i *READY:0 00010 i=1,100 00020 ?=i 00030 @=i+1 00040 #=-1 *READY:???=i *READY:0 00010 i=1,100 00020 ???=i 00030 @=i+1 00040 #=-1 *READY:
putc/getcの実装時には、DEMOQE128を使いましたが、 それ以降のプログラムを作るのに、実機は使用していません。 すべて、CodeWarriorのシミュレータだけを使っています。
次は、インタプリタだな。
ColdFire V1 - ラインエディタの入り口 [ColdFire (ColdeFire) V1]
一行入力ルーチンを利用して、ラインエディタに拡張しました。 ただし、ユーザ・インターフェース部分だけ。
ソース・コード
/* * $Source: /mnt/cvs/rep/CW/CFQE20/Sources/main.c,v $ * $Id: main.c,v 1.1 2008/01/26 10:49:39 noritan Exp $ */ #include/* for EnableInterrupts macro */ #include "derivative.h" /* include peripheral declarations */ // 定数の宣言 #define BUF_LENGTH (72) // 大域変数の宣言 char lineBuf[BUF_LENGTH]; // ラインバッファ char *linePoint; // ラインポインタ
入力された行を解析するために大域変数にラインポインタを導入しました。 解析中のラインバッファの中の文字を指します。
// お便利関数の宣言 int getc(void); void putc(int ch); void puts(const char *s); void getLine(void); int getNumber(void); void insertLine(const int nLine, const char *line); void removeLine(const int nLine); char *findLine(const int nLine); void listLines(void); void putNumber(const int n);
関数をいくつか追加し、 getLineの定義を見直しました。 詳細は、後ほど。
// メイン関数 void main(void) { int nLine; // 入力した数値
入力された数値を記憶する局所変数を定義します。
// SOPT1を設定する SOPT1 = 0 | SOPT1_WAITE_MASK // Enable WAIT instruction | SOPT1_RSTOPE_MASK // Enable RSTO* port | SOPT1_BKGDPE_MASK // Enable BKGD port | SOPT1_RSTPE_MASK ; // Enable RESET* port // ICSを設定する // REFCLK = 31,250Hz // DCO = 48,000,000Hz // BUSCLK = 24,000,000Hz ICSTRM = NVICSTRM; ICSSC_FTRIM = NVFTRIM_FTRIM; ICSC2_BDIV = 0; // BDIV 1/1 ICSSC_DRST_DRS = 2; // FLL x1536 // SCI1を設定する // BAUD = 115200 // BR = BUSCLK / BAUD / 16 = 13 SCI1BD_SBR = 13; // BUSCLK / BAUD / 16 SCI1C2_TE = 1; // 送信機能を有効にする SCI1C2_RE = 1; // 受信機能を有効にする EnableInterrupts; /* enable interrupts */ /* include your code here */ for(;;) { puts("\n*READY"); // プロンプトを表示する getLine(); // 一行受信する nLine = getNumber(); // 数値を取り出す if (nLine < 0) { // 数値がない時には puts("*ERROR*"); // エラー表示 } else if (nLine == 0) { // 数値が0の時には、 listLines(); // リストを表示する } else if (!*linePoint) { // 数値だけの時には、 removeLine(nLine); // 削除 } else { // それ以外は、 insertLine(nLine, linePoint); // 挿入 } } /* loop forever */ /* please make sure that you never leave main */ }
メイン関数でエディタの動作を規定しています。
- 数字で始まらない入力は、エラー表示して無視する。
- 数値0で始まる入力は、リストを表示する。
- 数値だけの入力は、該当の行を削除する。
- 数値に文字列が続く入力は、その行を挿入する。
// 一行受信ルーチン void getLine(void) { char *p; // バッファポインタ char c; // 一文字バッファ for (;;) { putc(':'); // プロンプト出力 p = lineBuf; // バッファの先頭を指示 for (;;) { c = (char)getc(); // 一文字受け取り if (c == 8) { // Back Spaceの処理 putc(c); // カーソルをもどす p--; // 一文字戻す // ポインタがバッファの先頭に達したら、 // プロンプト出力からやりなおす。 if (p < lineBuf) break; } else if (c == '\n') { // Enterの処理 putc(c); // 改行出力 *p = 0; // 文字列末文字を書き込む linePoint = lineBuf; // バッファへのポインタを返す return; } else if (c < ' ') { // その他のコントロール文字 continue; // 単に無視する } else if (c == 127) { // DELETE文字の処理 putc('\n'); // 改行出力 break; // プロンプト出力からやりなおす } else { // 通常の文字の処理 putc(c); // エコーバック *p++ = c; // バッファに書き込む // バッファを使い切ったら、 if (p >= &lineBuf[BUF_LENGTH]) { putc('\n'); // 改行出力 break; // プロンプト出力からやりなおす } } } } }
一行入力ルーチンは、ラインバッファへのポインタを返すかわりに、 大域変数のラインポインタに値を返す方式に変更しました。 もしかしたら、次は元に戻してしまうかも。
// 一文字受信ルーチン int getc(void) { int ch; // 文字バッファ // 受信バッファがいっぱいになるのを待つ while (!SCI1S1_RDRF) { // Do nothing } ch = SCI1D; // 一文字受け取る if (ch == '\r') { // Enterキーは、 ch = '\n'; // 行末文字に変換する } return ch; // 受け取った文字を返す } // 一文字送信ルーチン void putc(int ch) { if (ch == '\n') { // 行末文字の前にCRを付ける // 送信バッファが空くのを待つ while (!SCI1S1_TDRE) { // Do nothing } SCI1D = '\r'; // SCIにCR文字を送信する } // 送信バッファが空くのを待つ while (!SCI1S1_TDRE) { // Do nothing } SCI1D = (byte)ch; // SCIに一文字送信する } // 文字列送信ルーチン void puts(const char *s) { while (*s) { // 文字列末でなければ、 putc(*s++); // 一文字送信する } }
これら三つのルーチンは、これまでと同じです。
// ラインポインタの位置から数値を取り出す int getNumber(void) { int n = 0; // 数値 if (!isdigit(*linePoint)) { // 数字がなければ、 return -1; // -1 を返す } while (isdigit(*linePoint)) { // 数値の十進変換 n = n * 10 + ((*linePoint) - '0'); linePoint++; } return n; // 数値を返す }
ラインポインタが指示する位置の文字列から数値を取り出します。 数値が無ければ、-1を返します。
// 数値の表示 - ZERO SUPPRESS は省略。 void putNumber(int n) { int m = 1000000; // 着目する桁 while (m > 0) { // 一の位まで表示する putc((n / m) + '0'); // 着目桁の表示 n %= m; // 残りの桁の部分 m /= 10; // 着目桁の移動 } }
与えられた数値を十進数で表示します。 あまりにもダサイので、後で何とかしましょう。
// 行挿入 void insertLine(const int nLine, const char *line) { // STUB puts("INSERT "); putNumber(nLine); puts(line); } // 行削除 void removeLine(const int nLine) { // STUB puts("REMOVE "); putNumber(nLine); } // リスト出力 void listLines(void) { // STUB puts("LIST "); }
肝心の編集機能部分は、スタブです。 実際には、何もしません。
シミュレータを使ってみよう
CodeWarriorには、シミュレータが付いています。 シミュレータには、端末のシミュレータ"Terminal"も入っているので、 CodeWarriorをインストールするだけで入出力の実験ができます。
"Terminal"シミュレータは、 メニューバーの"Component→Open..."で出てくる "Open Window Component"ダイアログから選択します。
"Terminal"ウィンドウが開いたら、右クリックでプルダウンメニューを出し、 "Configure Connections..."で設定を行います。
右下の"Virtual SCI"にある二つのポートの設定を"Sci0"から"Sci1"に変更して、 "OK"をクリックしたら、準備完了です。 シミュレータ上でプログラムを実行すると、 プロンプトが表示されて、入出力が出来るようになります。
ColdFire V1 - 一行入力ルーチン [ColdFire (ColdeFire) V1]
昨日の一文字送受信を拡張して、一行入力ルーチンをプログラムしてみました。
ソース・コード
#include <hidef.h> /* for EnableInterrupts macro */ #include "derivative.h" /* include peripheral declarations */
このプログラムでは、一行分の文字列をラインバッファに入れます。 ラインバッファの長さをBUF_LENGTHで宣言します。
// 定数の宣言 #define BUF_LENGTH (72) // 大域変数の宣言 char lineBuf[BUF_LENGTH]; // ラインバッファ
お便利関数に一行入力ルーチンgetLine(void)を加えました。
// お便利関数の宣言 int getc(void); void putc(int ch); void puts(const char *s); char *getLine(void);
メイン関数で使用する変数は、今回はすべて局所変数にしました。 一行入力ルーチンからのポインタを入れる変数として、 sを用意しました。
// メイン関数 void main(void) { char *s; // 行へのポインタ // SOPT1を設定する SOPT1 = 0 | SOPT1_WAITE_MASK // Enable WAIT instruction | SOPT1_RSTOPE_MASK // Enable RSTO* port | SOPT1_BKGDPE_MASK // Enable BKGD port | SOPT1_RSTPE_MASK ; // Enable RESET* port
前回は、内部参照クロックを38400Hzに設定して、 ボーレートをちょうどいい値に合わせたのですが、 今回は、デフォルトの31250Hzを使用することにしました。
バス・クロック周波数は、かなり高めの24MHzに設定しています。
// ICSを設定する // REFCLK = 31,250Hz // DCO = 48,000,000Hz // BUSCLK = 24,000,000Hz ICSTRM = NVICSTRM; ICSSC_FTRIM = NVFTRIM_FTRIM; ICSC2_BDIV = 0; // BDIV 1/1 ICSSC_DRST_DRS = 2; // FLL x1536 // SCI1を設定する // BAUD = 115200 // BR = BUSCLK / BAUD / 16 = 13 SCI1BD_SBR = 13; // BUSCLK / BAUD / 16 SCI1C2_TE = 1; // 送信機能を有効にする SCI1C2_RE = 1; // 受信機能を有効にする EnableInterrupts; /* enable interrupts */ /* include your code here */
メインループは、一行ずつの処理に変更しました。 かなり、簡単になりました。
for(;;) { puts("\n*READY"); // プロンプトを表示する s = getLine(); // 一行受信する puts(s); // 受信した文字を送信する } /* loop forever */ /* please make sure that you never leave main */ }
今回、新規に書き起こした一行入力ルーチンです。
// 一行受信ルーチン char *getLine(void) { char *p; // バッファポインタ char c; // 一文字バッファ for (;;) { putc(':'); // プロンプト出力 p = lineBuf; // バッファの先頭を指示 for (;;) { c = (char)getc(); if (c == 8) { // Back Spaceの処理 putc(c); // カーソルをもどす p--; // 一文字戻す // ポインタがバッファの先頭に達したら、 // プロンプト出力からやりなおす。 if (p < lineBuf) break; } else if (c == '\n') { // Enterの処理 putc(c); // 改行出力 *p = 0; // 文字列末文字を書き込む return lineBuf; // バッファへのポインタを返す } else if (c < ' ') { // その他のコントロール文字 continue; // 単に無視する } else if (c == 127) { // DELETE文字の処理 putc('\n'); // 改行出力 break; // プロンプト出力からやりなおす } else { // 通常の文字の処理 putc(c); // エコーバック *p++ = c; // バッファに書き込む // バッファを使い切ったら、 if (p >= &lineBuf[BUF_LENGTH]) { putc('\n'); // 改行出力 break; // プロンプト出力からやりなおす } } } } }
一文字戻す"Back space"キーと一行やりなおす"DELETE"キーを 特殊なキーとして、使用しています。 それ以外のコントロール文字は、無視しています。
残りの部分は、前回と同様です。
// 一文字受信ルーチン int getc(void) { int ch; // 受信バッファがいっぱいになるのを待つ while (!SCI1S1_RDRF) { // Do nothing } ch = SCI1D; // 一文字受け取る if (ch == '\r') { // Enterキーは、 ch = '\n'; // 行末文字に変換する } return ch; // 受け取った文字を返す } // 一文字送信ルーチン void putc(int ch) { if (ch == '\n') { // 行末文字の前にCRを付ける // 送信バッファが空くのを待つ while (!SCI1S1_TDRE) { // Do nothing } SCI1D = '\r'; // SCIにCR文字を送信する } // 送信バッファが空くのを待つ while (!SCI1S1_TDRE) { // Do nothing } SCI1D = (byte)ch; // SCIに一文字送信する } // 文字列送信ルーチン void puts(const char *s) { while (*s) { // 文字列末でなければ、 putc(*s++); // 一文字送信する } }
使ってみよう
プログラムは、プロンプトを出した後、一行入力して、それを一行出力するだけのものです。
*READY:10 i=1,100 10 i=1,100 *READY:20 @=i+1 20 @=i+1 *READY:30 #=-1 30 #=-1 *READY:
ColdFire V1 - CodeWarrior 今日のバグ [ColdFire (ColdeFire) V1]
BLOGに書けるほどたくさん出てくるとは思わなかった。
タイプミスが内部エラーになる
今日の例は簡単です。
__interrupt VectorNumber_Vtrap0 trap0_isr(void) { } __interrupt VectorNumber_Vtrap0 trap1_isr(void) { }
単にベクタ番号の記述を間違えて、 同じベクタ番号に二つのISRを登録してしまったものです。
期待されるのは、コンパイラかリンカのエラーですが、 CodeWarriorは、一味違います。
Error : internal compiler error (report to <cw_bug at freescale.com>) while executing in file 'ELFgen.c' line: 524 (compiling '_trap1_isr' in 'main.c') main.c line 19 }
「内部エラー発見に付き、レポートしてください。」だそうです。
Service Request からの返答
それは違うだろうと思いながら、Service Requestを出したところ、 返答がありました。
報告ありがとう!!!
このエラー・メッセージは、コンパイラのエラーとすべきで、内部エラーとすべきはありません。 技術部門に問題を報告しておきます。
実にあっさりとした返答でした。 「コンパイラのエラー (compiler error)」というのも理解に苦しみますが、 適切なメッセージに修正されることを期待しましょう。
ColdFire V1 とチャット - シリアル・インターフェースの実験 [ColdFire (ColdeFire) V1]
DEMOQE128には、シリアルインターフェースが付属しています。 これを利用して、通信を行ってみます。
お便利関数の宣言
このプログラムでは、SCIを使った通信を行います。 通信の最も低レベルな関数群をここで宣言しています。 実際の関数の中身は、後ろのほうで定義しています。
#include <hidef.h> /* for EnableInterrupts macro */ #include "derivative.h" /* include peripheral declarations */ // お便利関数の宣言 int getc(void); void putc(int ch); void puts(const char *s);
大域変数の宣言
続いて、大域変数を宣言していますが、 このプログラムでは、文字バッファひとつだけです。
// 大域変数の宣言 int c; // 文字バッファ
初期設定
メイン関数は、初期設定から始まります。
// メイン関数 void main(void) { // SOPT1を設定する SOPT1 = 0 | SOPT1_WAITE_MASK // Enable WAIT instruction | SOPT1_RSTOPE_MASK // Enable RSTO* port | SOPT1_BKGDPE_MASK // Enable BKGD port | SOPT1_RSTPE_MASK ; // Enable RESET* port
いつものように、SOPT1レジスタを設定して、 COPを黙らせます。
// ICSを設定する // REFCLK = 38,400Hz // DCO = 39,321,600Hz // BUSCLK = 19,660,800Hz ICSTRM = NVICSTRM; ICSSC_FTRIM = NVFTRIM_FTRIM; ICSC2_BDIV = 0; // BDIV 1/1 ICSSC_DRST_DRS = 1; // FLL x1024
このプログラムでは、バス・クロックの周波数をボーレートの512倍に設定することにより、 通信エラーが起きにくいようにしています。 このため、デバッガでプログラムを書き込むときに、 内部参照クロックのトリム値を38,400Hzにあわせるのを忘れないようにしなくてはなりません。 バス・クロックは、19.8808MHzとかなり高速動作させています。
// SCI1を設定する // BAUD = 38,400 // BR = BUSCLK / BAUD / 16 = 32 SCI1BD_SBR = 32; // BUSCLK / BAUD / 16 SCI1C2_TE = 1; // 送信機能を有効にする SCI1C2_RE = 1; // 受信機能を有効にする EnableInterrupts; /* enable interrupts */ /* include your code here */
SCIは、ボーレートを38,400baudに合わせています。 バス・クロックを合わせこんだため、 SBRレジスタは、32というキリの良い数値になっています。
for(;;) { puts("\nReady* "); // プロンプトを表示する for (;;) { c = getc(); // 一文字受信する if (c == '\n') break; // 行末文字なら脱出 putc(c); // 受信した文字を送信する } } /* loop forever */ /* please make sure that you never leave main */ }
メイン関数は、二つの無限ループから構成されています。 外側のループは、一行入力ごとに回ります。 内側のループは、一文字入力ごとにまわり、 行末を見つけたらbreak文で脱出します。
お便利関数の定義
ここから、お便利関数の定義が並びます。
// 一文字受信ルーチン int getc(void) { int ch; // 受信バッファがいっぱいになるのを待つ while (!SCI1S1_RDRF) { // Do nothing } ch = SCI1D; // 一文字受け取る if (ch == '\r') { // Enterキーは、 ch = '\n'; // 行末文字に変換する } return ch; // 受け取った文字を返す }
一文字受信ルーチンでは、 RDRFフラグで文字の到着を待ち、 到着した文字を受け取ります。 Enterキーが検出されたら、行末文字に変換して値を返します。
// 一文字送信ルーチン void putc(int ch) { if (ch == '\n') { // 行末文字の前にCRを付ける // 送信バッファが空くのを待つ while (!SCI1S1_TDRE) { // Do nothing } SCI1D = '\r'; // SCIにCR文字を送信する } // 送信バッファが空くのを待つ while (!SCI1S1_TDRE) { // Do nothing } SCI1D = (byte)ch; // SCIに一文字送信する }
一文字送信ルーチンは、 送信バッファが空いたのを確認してから文字をSCIに引き渡します。 このルーチンでは、行末文字を見つけたら、 CR+LFに変換して送信します。
// 文字列送信ルーチン void puts(const char *s) { while (*s) { // 文字列末でなければ、 putc(*s++); // 一文字送信する } }
文字列送信ルーチンでは、 文字列末まで一文字ずつ値を取り出して、一文字送信ルーチンを呼び出しています。
ツール・キットを入手しよう
プログラムを書き込んだら、シリアル・インターフェースをPCとつなぎます。 この時、通常であればPCにシリアル・インターフェースを準備しなくてはならないのですが、 DEMOQE128には、USB接続に使われているHCS12チップが シリアルインターフェースのフリをしてくれる機能があります。 ところが、 この機能は、どうやら規格が公になっているのではないらしいので、 P&Eが提供するアプリケーション・ソフトウェア(ツール・キット)を使用する必要があります。
DEMOQE128のクイック・スタート文書などには、同梱のDVD-ROMに アプリケーション・ソフトウェアが入っているという表現が見られるのですが、 少なくとも私のところに来たDVD-ROMでは、 該当するアプリケーションを見つけられませんでした。
ツール・キットは、以下の場所から入手できます。 http://www.pemicro.com/fixedlinks/DEMOQEToolkit.cfm このページに並んでいる、"Terminal"が目的のアプリケーション・ソフトウェアです。 注意書きに従って、ダウンロード、実行すると、端末ウィンドウが開きます。
ColdFire でアセンブラ (5) - タスク・コンテキストを作らせる [ColdFire (ColdeFire) V1]
ColdFire で割り込み(例外)が発生すると、スーパバイザモードに移行します。 割り込み処理ルーチンでタスク・コンテキストを切り替える処理を考えます。
ステートメント・レベルで記述する
最初の例は、コンテキストの退避と復帰だけをアセンブラで記述したものです。
dword count; void trap_content(void) { count++; } dword *this_SSP; dword *system_SSP; __interrupt VectorNumber_Vtrap0 trap0_isr (void) { __asm { lea -64(a7),a7 // スタックフレームの確保 movem d0-d7/a0-a6,(a7) // レジスタを退避する move usp,a0 // USPを取り出す move a0,60(a7) // USPを退避させる move a7,this_SSP // SSPを退避させる move system_SSP,a7 // システムSSPを設定する } trap_content(); // 処理の実体 __asm { move this_SSP,a7 // SSPを復帰させる move 60(a7),a0 // USPを取り出す move a0,usp // USPを復帰させる movem (a7),d0-d7/a0-a6 // レジスタを復帰させる lea 64(a7),a7 // スタックフレームの解放 } }
これをコンパイルすると、関数の最初と最後の部分は、このようになります。
; 28: __interrupt VectorNumber_Vtrap0 trap0_isr (void) { ; 28: { ; 29: __asm { 0x00000000 _trap0_isr: ; trap0_isr: 0x00000000 0x2F08 move.l a0,-(a7) 0x00000002 0x2F01 move.l d1,-(a7) ; ; 30: lea -64(a7),a7 // スタックフレームの確保 0x00000004 0x4FEFFFC0 lea -64(a7),a7 ; ; 31: movem d0-d7/a0-a6,(a7) // レジスタを退避する 0x00000008 0x48D77FFF movem.l d0-d7/a0-a6,(a7) : ; 42: movem (a7),d0-d7/a0-a6 // レジスタを復帰させる 0x00000034 0x4CD77FFF movem.l (a7),d0-d7/a0-a6 ; ; 43: lea 64(a7),a7 // スタックフレームの解放 ; 44: } 0x00000038 0x4FEF0040 lea 64(a7),a7 ; ; 45: } 0x0000003C 0x221F move.l (a7)+,d1 0x0000003E 0x205F movea.l (a7)+,a0 0x00000040 0x4E73 rte 0x00000042 0x51FC trapf
このようにタスク・コンテキストを構築する前に 作業領域として使用しているD1とA0を退避させるコードが作成されてしまい、 タスク・コンテキストとして使えなくなってしまいます。
キーワードnakedを使う
勝手に作業領域を確保させないためには、 nakedというキーワードを付けます。
__interrupt VectorNumber_Vtrap1 trap1_isr (void) { __asm { naked lea -64(a7),a7 // スタックフレームの確保 movem d0-d7/a0-a6,(a7) // レジスタを退避する move usp,a0 // USPを取り出す move a0,60(a7) // USPを退避させる move a7,this_SSP // SSPを退避させる move system_SSP,a7 // システムSSPを設定する } trap_content(); // 処理の実体 __asm { move this_SSP,a7 // SSPを復帰させる move 60(a7),a0 // USPを取り出す move a0,usp // USPを復帰させる movem (a7),d0-d7/a0-a6 // レジスタを復帰させる lea 64(a7),a7 // スタックフレームの解放 rte // 例外処理の終了 } }
このキーワードを付けると、 最後のRTE命令さえも生成してくれないので、 注意が必要です。 コンパイルするとこうなります。
; 47: __interrupt VectorNumber_Vtrap1 trap1_isr (void) { ; 48: __asm { ; 49: naked ; 50: lea -64(a7),a7 // スタックフレームの確保 0x00000000 _trap1_isr: ; trap1_isr: 0x00000000 0x4FEFFFC0 lea -64(a7),a7 ; ; 51: movem d0-d7/a0-a6,(a7) // レジスタを退避する 0x00000004 0x48D77FFF movem.l d0-d7/a0-a6,(a7) : ; 62: movem (a7),d0-d7/a0-a6 // レジスタを復帰させる 0x00000030 0x4CD77FFF movem.l (a7),d0-d7/a0-a6 ; ; 63: lea 64(a7),a7 // スタックフレームの解放 0x00000034 0x4FEF0040 lea 64(a7),a7 ; ; 64: rte // 例外処理の終了 ; 65: } 0x00000038 0x4E73 rte ; ; 66: } 0x0000003A 0x51FC trapf
前処理と後処理は、全く生成されないので、 ユーザの責任で確実にレジスタを退避させる必要があります。
関数レベルで記述する
上の二つの例では、処理の実体をCで書きたいがために、 ステートメントレベルでアセンブラ記述にしていました。 ところが、アセンブラでスタックを切り替えていることから、 局所変数などのスタックを使用する文などは事実上使用できません。 それだったら、全部アセンブラで書いてしまえというのが、次の例です。
__asm __interrupt VectorNumber_Vtrap2 trap2_isr (void) { lea -64(a7),a7 // スタックフレームの確保 movem d0-d7/a0-a6,(a7) // レジスタを退避する move usp,a0 // USPを取り出す move a0,60(a7) // USPを退避させる move a7,this_SSP // SSPを退避させる move system_SSP,a7 // システムSSPを設定する jsr trap_content // 処理の実体 move this_SSP,a7 // SSPを復帰させる move 60(a7),a0 // USPを取り出す move a0,usp // USPを復帰させる movem (a7),d0-d7/a0-a6 // レジスタを復帰させる lea 64(a7),a7 // スタックフレームの解放 rte // 例外処理の終了 }
この例では、引数も返り値もない関数trap_contentを使っていますが、 単純なもの数個でよければ、受け渡しをすることもできます。 コンパイル結果は、このようになります。
; 68: __asm __interrupt VectorNumber_Vtrap2 trap2_isr (void) { ; 69: lea -64(a7),a7 // スタックフレームの確保 0x00000000 _trap2_isr: ; trap2_isr: 0x00000000 0x4FEFFFC0 lea -64(a7),a7 ; ; 70: movem d0-d7/a0-a6,(a7) // レジスタを退避する 0x00000004 0x48D77FFF movem.l d0-d7/a0-a6,(a7) : ; 79: movem (a7),d0-d7/a0-a6 // レジスタを復帰させる 0x0000002A 0x4CD77FFF movem.l (a7),d0-d7/a0-a6 ; ; 80: lea 64(a7),a7 // スタックフレームの解放 0x0000002E 0x4FEF0040 lea 64(a7),a7 ; ; 81: rte // 例外処理の終了 0x00000032 0x4E73 rte
trap_contentには、 アセンブラでは書きにくいような複雑な処理を書くことができます。
ColdFire でアセンブラ (4) - ミスアセンブル解決編 [ColdFire (ColdeFire) V1]
「ColdFire でアセンブラ (3) - ミスアセンブル?」の件を freescale.comの Service Requestに問い合わせたところ、 返答を貰いました。
あなたの問題を確認し、コンパイラの開発者に意見を聞きました。 不幸なことにコンパイラにバグがあり、 in-line asmは、 局所変数をmulu/muls命令に使うと問題が起こります。 回避方法は、値をじかにレジスタに入れるか、 関数が簡単であればfrallocメカニズムを使わないことです。
この問題は、バグ・ベース(バグのデータ・ベース)に記録しました。 我々の開発部隊は、この問題を解析し、私が進捗についてお知らせします。
(今日も、ていねいな訳)というわけで、バグと認定されました。 まあ、アセンブラを使わなきゃ問題は起きないのですけどね。
ColdFire V1 - 謎のソフトウェア割り込み [ColdFire (ColdeFire) V1]
謎のソフトウェア割り込み
MCF51QE128のリファレンスマニュアルの "Chapter 8 Interrupt Controller"の例外ベクタテーブルには、 "Level X Software Interrupt"というベクタがあります。 日本語にすると、「ソフトウェア割り込み」です。
8-bitマイコンには、ソフトウェア割り込み"SWI"という命令があり、 この命令を実行すると割り込み相当の処理を行ってくれます。 ところが、32-bitマイコンである、 ColdFire V1には、そんな命令はありません。 近い命令は、"TRAP"命令ですが、この命令には別の例外ベクタが用意されています。
いくらリファレンスマニュアルを探しても、"Software Interrupt"という言葉が出てこないので、 "freescale.com"から"Service Request"を出しました。
Service Request
"Service Request"は、 freescale.com の正式な総合質問受付窓口です。 幅広い質問に答えてくれるのですが、 英語で受け答えしなくてはならないのが、日本人には難点でしょう。 今回の質問も英語で問い合わせて、英語で返答がありました。
割り込み制御モジュールは、ソフトウェア割り込みと呼ばれる7種類の割り込みを発生することが出来、一つ一つが割り込みレベルに対応しています。
ソフトウェア割り込みは、"INTC Force Interrupt Register (INTC_FRC)"や "Set/Clear Force Interrupt Register (INTC_SFRC, INTC_CFRC)"に 書き込みを行うことで発生させることができます。
詳しくは、MCF51QE128リファレンスマニュアルの"Chapter 7"を 参照してください。
ソフトウェア割り込みは、ソフトウェアがINTC_FRCレジスタに書き込みを行う事によって、 割り込みを発生させるために使われます。
[VBR+0x178; VBR+0x3FC]の領域をコードやデータのために使用することは、 お勧めできません。 この領域は、 ソフトウェアがソフトウェア割り込みを発生させなければ、 このマイクロ・コントローラにとっては、未使用領域として記載されています。 それでもなお、この領域は、予約領域です。
加えて、USBが付いたり、CANデバイスが付いたり、LCDコントローラが付いたりした、新たなFlexisマイクロ・コントローラが登場しようとしています。 これらの新しいマイクロ・コントローラは、おそらく、この領域にさらなる割り込みソースを持つことになるでしょう。
(いつになく、丁寧な訳)
"Chapter 7"を見ろと書いてありますが、きっと、これは、"Capter 8"の間違いでしょう。
というわけで、ソフトウェア割り込みというのは、 "Force Interrupt Register"で発生させられる割り込みであることが判明しました。
と、謎が解けた後、リファレンスマニュアルを読んでも、 やっぱり、これがソフトウェア割り込みのことを言っているとは思えません。 あとは、実機で確認するしかなさそうですね。
ColdFire でアセンブラ (3) - ミスアセンブル? [ColdFire (ColdeFire) V1]
アセンブル・ミス?発見
CodeWarrior V6.1 でアセンブラ・プログラムを書いていて不可解な現象を見つけました。
__asm dword func(dword a, dword b, dword c, dword d) { fralloc mulu b,a // a = a * b mulu d,c // c = c * d move a,d0 // D0 = a * b add c,d0 // D0 = a * b + c * d frfree rts }
よくある積和演算関数です。 わざわざアセンブラで書くこともありませんが、例題として準備していたものです。 ところが、コンパイル-ディスアセンブルして、ビックリ!
; 4: __asm dword func(dword a, dword b, dword c, dword d) { ; 4: { ; 5: fralloc 0x00000000 _func: ; func: 0x00000000 0x4E560000 link a6,#0 ; ; 6: mulu b,a // a = a * b 0x00000004 0x4C010000 mulu.l d1,d0 ; ; 7: mulu d,c // c = c * d 0x00000008 0x6C2E bge.s *+48 ; 0x00000038 0x0000000A 0x00000008 ori.b #0x8,d0 ; '.' ; ; 8: move a,d0 // D0 = a * b 0x0000000E 0x2000 move.l d0,d0 ; ; 9: add c,d0 // D0 = a * b + c * d 0x00000010 0xD082 add.l d2,d0 ; ; 10: frfree 0x00000012 0x4E5E unlk a6 ; ; 11: rts 0x00000014 0x4E75 rts 0x00000016 0x51FC trapf
二つ目のmuluは、どこに行っちゃったの? bgeって、何? もちろん、この関数はうまく動きませんし、bgeで あらぬ所に飛んでいくのでデバッガでも追いきれません。
あるべき姿
本来、この部分は、"MULU.L 8(A7),D2"または"MULU.L 8(A6),D2"に変換されます。 前者なら "0x4C2F 0x2000 0x0008" が、 後者なら "0x4C2E 0x2000 0x0008" が、 生成されます。 生成されたコードから考えて、後者のコードを間違えたように見えますね。
ColdFire でアセンブラ (2) - レジスタを使ってくれ [ColdFire (ColdeFire) V1]
前回は一部だけをアセンブラで書いた関数を作りました。 今回は、全てをアセンブラで記述します。
関数ごとアセンブラにする
関数ごとアセンブラで記述するには、関数に"__asm""指示子をつけます。
__asm dword func2(dword a, dword b) { dword x; move a,x // x = a add b,x // x *= b move x,d0 // return value rts }
前回と同じように二つの引数の和を返す関数です。 作業領域として使用している変数xを使って、 直接加算操作を行っています。 コンパイルすると、このようになります。
; 29: __asm dword func2(dword a, dword b) { ; 30: dword x; ; 31: move a,x // x = a 0x00000000 _func2: ; func2: 0x00000000 0x2E80 move.l d0,(a7) ; ; 32: add b,x // x *= b 0x00000002 0xD397 add.l d1,(a7) ; ; 33: move x,d0 // return value 0x00000004 0x2017 move.l (a7),d0 ; ; 34: rts 0x00000006 0x4E75 rts
一見、うまくできたように見えますが、 作業領域として使用している(a7)には、 リターン・アドレスが入っているため、 関数から戻るときに暴走してしまいます。
作業領域を安全に確保する
先のプログラムでは、作業領域が確保されませんでした。 作業領域を確実に確保するために、 frallocとfrfreeという 擬似命令が用意されています。
__asm dword func3(dword a, dword b) { dword x; fralloc move a,x // x = a add b,x // x *= b move x,d0 // return value frfree rts }
アセンブラの記述の前後にfrallocとfrfreeを 入れるだけです。
; 23: __asm dword func3(dword a, dword b) { ; 23: { ; 24: dword x; ; 25: fralloc 0x00000000 _func3: ; func3: 0x00000000 0x4E560000 link a6,#0 ; ; 26: move a,x // x = a 0x00000004 0x2400 move.l d0,d2 ; ; 27: add b,x // x *= b 0x00000006 0xD481 add.l d1,d2 ; ; 28: move x,d0 // return value 0x00000008 0x2002 move.l d2,d0 ; ; 29: frfree 0x0000000A 0x4E5E unlk a6 ; ; 30: rts 0x0000000C 0x4E75 rts 0x0000000E 0x51FC trapf
frallocは、レジスタに優先的に作業領域を割り当てます。 このため、変数xもレジスタに割り当てられました。
局所変数が多くなったらどうなる
上の例では、レジスタですべての作業領域をまかなうことができました。 それでは、レジスタでまかなえないほど多くの局所変数が定義されたらどうなるでしょうか。
__asm dword func4(dword a, dword b) { dword x, w1, w2, w3, w4; fralloc move a,x // x = a add b,x // x *= b add x,w1 add x,w2 add x,w3 add x,w4 move x,d0 // return value frfree rts }
局所変数w1からw4が、 新たに使用されています。 もちろん、このプログラムに深い意味はありません。 コンパイルすると、このようになります。
; 33: __asm dword func4(dword a, dword b) { ; 33: { ; 34: dword x, w1,w2,w3,w4; ; 35: fralloc 0x00000000 _func4: ; func4: 0x00000000 0x4E560000 link a6,#0 0x00000004 0x4FEFFFF4 lea -12(a7),a7 0x00000008 0x48D700E0 movem.l d5-d7,(a7) ; ; 36: move a,x // x = a 0x0000000C 0x2400 move.l d0,d2 ; ; 37: add b,x // x *= b 0x0000000E 0xD481 add.l d1,d2 ; ; 38: add x,w1 0x00000010 0xDE82 add.l d2,d7 ; ; 39: add x,w2 0x00000012 0xDC82 add.l d2,d6 ; ; 40: add x,w3 0x00000014 0xDA82 add.l d2,d5 ; ; 41: add x,w4 0x00000016 0xD282 add.l d2,d1 ; ; 42: move x,d0 // return value 0x00000018 0x2002 move.l d2,d0 ; ; 43: frfree 0x0000001A 0x4CD700E0 movem.l (a7),d5-d7 0x0000001E 0x4E5E unlk a6 ; ; 44: rts 0x00000020 0x4E75 rts 0x00000022 0x51FC trapf
関数の最初の部分で、 スタックに12バイトのエリアを確保して、そこにD5-D7のレジスタを退避させています。 これで、多くの作業領域を使用することができるようになりました。
ColdFire でアセンブラ (1) - 長くなりそうだ [ColdFire (ColdeFire) V1]
ColdFireのコンパイラは、Cのソース・コードの中にアセンブラを記述することができます。 どんな機能があるのか、しらべてみました。
単純な関数
まずは、簡単な例から考えます。
dword func1(dword a, dword b) { dword x; __asm { move a,d0 // D0 = a add b,d0 // D0 += b move d0,x // x = D0 } return x; }
二つの引数の和を返す関数です。 もちろん、これをわざわざアセンブラで記述する意味はありません。 これをコンパイルすると、こんなコードになります。
0x00000000 _func1: ; func1: 0x00000000 0x4FEFFFF4 lea -12(a7),a7 0x00000004 0x2E80 move.l d0,(a7) 0x00000006 0x2F410004 move.l d1,4(a7) ; ; 22: move a,d0 // D0 = a 0x0000000A 0x2017 move.l (a7),d0 ; ; 23: add b,d0 // D0 += b 0x0000000C 0xD0AF0004 add.l 4(a7),d0 ; ; 24: move d0,x // x = D0 ; 25: } 0x00000010 0x2F400008 move.l d0,8(a7) ; ; 26: return x; 0x00000014 0x222F0008 move.l 8(a7),d1 0x00000018 0x2001 move.l d1,d0 ; ; 27: } 0x0000001A 0x4FEF000C lea 12(a7),a7 0x0000001E 0x4E75 rts
一部コメント行を削除しました。 引数aとbに 局所変数xを加えた12バイト分の スタック・エリアを確保しています。
すべての操作がスタック・エリアを使った受け渡しになっているので、 処理が煩雑になっています。 わかりやすいと言えばわかりやすいのですけど。
ColdFire V1 で多重割り込みを禁止するには [ColdFire (ColdeFire) V1]
68000は、本質的に多重割り込みが出来るように設計されているのですが、 プログラムを書くほうは対応する心づもりがありません。 MCF51QE128の場合、RAM容量が少ないので、 多重割り込みでスタックの予定容量を超えてしまったら即暴走と言う事態になりかねません。 さて、どうやって、多重割り込みを防ごうか。
多重割り込みを禁止する方法は、MCF51QExxのリファレンスマニュアルの Chapter 8 Interrupt Controller にある 8.6.1 Emulation of the HCS08's 1-Level IRQ Handling に書いてあります。
- ISRの最初の命令として、 STLDSR #$2700 を実行せよ。
- ISRの最初の命令として、 MOVE.w #$2700,SR を実行せよ。
ColdFireは、割り込みサービス・ルーチン (Interrupt Service Routine : ISR) の 最初の命令だけは、割り込みを受け付けない仕様だそうです。 そのため、最初の命令で割り込みマスクレベルを7に設定すれば, マスク不可割り込み (Non-Maskable Interrupt : NMI) 以外の割り込みが 発生することはありません。 とはいっても、 Status Register (SR) の割り込みマスクだけを 7 に設定する 気の利いた命令は ColdFire V1 には、ありません。 そこで、 SR 全体に定数を書き込むことになります。
STLDSR 命令は、 SR をスタックに積んでから定数を設定しますので、 例外発生前の CCR の部分を取り戻すこともできます。 でも、よく考えたら、例外発生前の SR の値は元々スタックフレームに入っているから、 いくらでも取り戻せるんですよね。
多重割り込みを起こさせないためには、もう一つ効果的な方法があります。
- CPUCR レジスタの IME ビットをアサートせよ。
このビットをアサート(セット)すると、 割り込みが発生したときに割り込みマスクに 7 を入れてくれるので、 NMI 以外の割り込みは発生しません。 しかも、 ISR に妙な命令を入れなくて済みます。 なんだ、これでいいじゃん。
CodeWarrior が作る ColdFire V1 の割り込みサービスルーチンを調べた [ColdFire (ColdeFire) V1]
CodeWarrior V6.1 で ColdFire V1 のプログラムを作成するとき、 割り込みサービスルーチン (Interrupt Service Routine : ISR) を どうやって表現するかを考えました。 しかも、アセンブラで。
ColdFireでは、実行中のプログラムをある種の要因で中断する操作を「例外」と呼んでいます。 ここでは、なじみの言葉である「割り込み」を使っていますが、 扱っている内容は、「例外」でも同じです。
Cでの基本的な記述方法
Cで ISR を記述するときには、普通の関数定義の戻り値型の代わりに、 「__interrupt <ベクタ番号>」を追加します。
__interrupt VectorNumber_Vtrap0 trap0_isr (void) { }
これで、CodeWarriorは、正しいISRを作成してくれます。
0x00000000 _trap0_isr: ; trap0_isr: 0x00000000 0x4E73 rte 0x00000002 0x51FC trapf
普通の関数の最後が"RTS" (ReTurn from Subroutine) 命令になっているところが、 "RTE" (ReTurn from Exception) 命令になっています。 この結果を見て、 「なんだ、そんなに難しい話じゃないんだな。」と、最初は思ったのです。
保護すべきレジスタは、どれだ
ColdFireでは、割り込みが発生してもレジスタを自動的にはスタックに退避してはくれません。 これは、HCS08と異なっている点です。 そのため、ISRの中でレジスタを退避させる操作が必要になってきます。
まずは、こんなプログラムを作成してみました。
typedef long Data; typedef long *Pointer; void func(void) { Data x1,x2,x3,x4,x5,x6,x7,x8; Pointer y1,y2,y3,y4,y5,y6,y7,y8; for (x1=0,y1=0;x1<10;x1++,y1++) for (x2=0,y2=0;x2<10;x2++,y2++) for (x3=0,y3=0;x3<10;x3++,y3++) for (x4=0,y4=0;x4<10;x4++,y4++) for (x5=0,y5=0;x5<10;x5++,y5++) for (x6=0,y6=0;x6<10;x6++,y6++) for (x7=0,y7=0;x7<10;x7++,y7++) for (x8=0,y8=0;x8<10;x8++,y8++) { } } __interrupt VectorNumber_Vtrap0 trap0_isr (void) { Data x1,x2,x3,x4,x5,x6,x7,x8; Pointer y1,y2,y3,y4,y5,y6,y7,y8; for (x1=0,y1=0;x1<10;x1++,y1++) for (x2=0,y2=0;x2<10;x2++,y2++) for (x3=0,y3=0;x3<10;x3++,y3++) for (x4=0,y4=0;x4<10;x4++,y4++) for (x5=0,y5=0;x5<10;x5++,y5++) for (x6=0,y6=0;x6<10;x6++,y6++) for (x7=0,y7=0;x7<10;x7++,y7++) for (x8=0,y8=0;x8<10;x8++,y8++) { } }
プログラム自体に意味はありません。 全てのレジスタを使ってもまかなえないほどの局所変数を使わせたかったのです。 このプログラムをコンパイルさせると、それぞれの関数の冒頭は、以下のようになっています。
0x00000000 _func: ; func: 0x00000000 0x4FEFFFD0 lea -48(a7),a7 0x00000004 0x48EF5CF8000C movem.l d3-d7/a2-a4/a6,12(a7)
関数の中では、D0-D7/A0-A2/A4/A6を作業領域として使っています。 つまり、D0/D1/D2/A0/A1の五つのレジスタはまったく保護されていません。
0x00000000 _trap0_isr: ; trap0_isr: 0x00000000 0x4FEFFFBC lea -68(a7),a7 0x00000004 0x48EF5FFE0010 movem.l d1-d7/a0-a4/a6,16(a7)
これに対して、ISRの中では、D1-D7/A0-A4/A6を作業領域として使っています。 つまり、全てのレジスタが保護されていることになります。 なぜ、D0を使っていないのか? 理由は不明です。
関数の中にアセンブラを混ぜる
CPUの動作にかかわるような低レベルな操作は、アセンブラを使う必要があります。 Cで記述された関数の中にアセンブラでプログラムを記述するときには、 "__asm"キーワードを使います。
Data value; __interrupt VectorNumber_Vtrap1 trap1_isr (void) { __asm { move usp,a0 move (a0),d0 move d0,value } }
このISRでは、ユーザ・スタックの内容を取り出して変数valueに 代入しています。 取り出した内容に特に意味があるわけではありません。 このプログラムをコンパイルした結果を見てみます。
0x00000000 _trap1_isr: ; trap1_isr: 0x00000000 0x2F08 move.l a0,-(a7) 0x00000002 0x2F00 move.l d0,-(a7) ; ; 53: move usp,a0 ; 0x00000004 0x4E68 move usp,a0 ; ; 54: move (a0),d0 ; 0x00000006 0x2010 move.l (a0),d0 ; ; 55: move d0,value ; 56: } ; 0x00000008 0x23C000000000 move.l d0,_value ; ; 57: } ; 0x0000000E 0x201F move.l (a7)+,d0 0x00000010 0x205F movea.l (a7)+,a0 0x00000012 0x4E73 rte
どうやら、アセンブラ・プログラムの中でD0とA0が作業領域として使われているのを認識して、 その値をスタックに退避させてくれるようです。
アセンブラだけで記述したC関数?
上と同じ操作をアセンブラだけで、このように書くことが出来ます。
asm __interrupt VectorNumber_Vtrap2 trap2_isr (void) { move usp,a0 move (a0),d0 move d0,value rte }
これをコンパイルすると、このようになります。
0x00000000 _trap2_isr: ; trap2_isr: 0x00000000 0x4E68 move usp,a0 ; ; 61: move (a0),d0 ; 0x00000002 0x2010 move.l (a0),d0 ; ; 62: move d0,value ; 0x00000004 0x23C000000000 move.l d0,_value ; ; 63: rte ; 0x0000000A 0x4E73 rte
この場合には、レジスタを退避させるコードは生成されません。 従って、正しいISRとするために、レジスタを退避させるコードを追加する必要があります。 ただ、コンテキストスイッチに使う場合のように、勝手にスタックを使われると困る場合もあるので、 一概にどちらが使いやすいとも言えません。 はっきり言えるのは、「コンパイル結果を確認しながら使う」事が必要だということでしょう。
Cとアセンブラの連携
このコンパイラのアセンブラは、かなり賢くて、Cで定義した変数などをそのまま使うことができます。
__interrupt VectorNumber_Vtrap3 trap3_isr (void) { Pointer y1; __asm { move usp,a0 move a0,y1 } value = *y1; }
これもプログラム自身には、さして意味がありません。 uspレジスタの内容をCの局所変数であるy1に代入し、 Cの記述で、スタックの内容を取り出して、valueに保存します。 コンパイルするとこうなります。
0x00000000 _trap3_isr: ; trap3_isr: 0x00000000 0x4FEFFFF0 lea -16(a7),a7 0x00000004 0x48EF03020004 movem.l d1/a0-a1,4(a7) ; ; 71: move usp,a0 ; 0x0000000A 0x4E68 move usp,a0 ; ; 72: move a0,y1 ; 73: } ; 0x0000000C 0x2E88 move.l a0,(a7) ; ; 74: value = *y1; ; 0x0000000E 0x2257 movea.l (a7),a1 0x00000010 0x2211 move.l (a1),d1 0x00000012 0x2B410000 move.l d1,_value(a5) ; ; 75: } ; 0x00000016 0x4CEF03020004 movem.l 4(a7),d1/a0-a1 0x0000001C 0x4FEF0010 lea 16(a7),a7 0x00000020 0x4E73 rte 0x00000022 0x51FC trapf
Cでは書けないUSPレジスタの値を取り出す部分だけを アセンブラにしただけなので、 取り出した値の操作はCで記述することが出来て便利です。
Cold Fire V1 で RTCモジュールを使う - 割り込み処理の実例 [ColdFire (ColdeFire) V1]
割り込みを使ったプログラムを書いてみます。
RTCの割り込みで、LEDピカピカ
QEシリーズで新たに追加されたRTCモジュールを使い、 0.5秒毎に割り込みを発生させ、 LEDを点滅させます。
#include/* for EnableInterrupts macro */ #include "derivative.h" /* include peripheral declarations */ void main(void) {
メイン関数の最初の方で、 COPを停止させ、LEDを点滅させるためのRGPIOの設定を行います。
// Configure one-time-write SOPT1 SOPT1 = 0 | SOPT1_WAITE_MASK // Enable WAIT instruction | SOPT1_RSTOPE_MASK // Enable RSTO* port | SOPT1_BKGDPE_MASK // Enable BKGD port | SOPT1_RSTPE_MASK ; // Enable RESET* port // Configure PTC0 as output RGPIO_ENB = RGPIO_ENB_ENB8_MASK; RGPIO_DIR = RGPIO_DIR_DIR8_MASK;
RTCモジュールの初期設定は、こんなものです。 これだけで、RTCは、0.5秒ごとに割り込みを発生させます。
// Configure RTC module RTCSC_RTCLKS = 0; // 1kHz 発振器を使います RTCSC_RTCPS = 11; // プリスケーラを10ミリ秒に設定します RTCMOD = 50-1; // 50カウント (500m秒) ごとにイベントが発生します RTCSC_RTIE = 1; // 割り込みを使います
あとは、CPU全体として割り込みを許可して、 無限ループに突入します。
EnableInterrupts; /* enable interrupts */ for(;;) { __RESET_WATCHDOG(); /* feeds the dog */ } /* loop forever */ /* please make sure that you never leave main */ }
割り込みサービス・ルーチン(ISR)は、このように記述します。
__interrupt VectorNumber_Vrtc rtc_isr(void) { RTCSC_RTIF = 1; // Clear the flag. // Toggle the PTC0 RGPIO_TOG = RGPIO_TOG_TOG8_MASK; }
RTCのイベント発生を知らせるフラグは、RTCSC_RTIFに"1"を書き込むとクリアされます。 RTCのフラグのクリアの仕方は、TPMと異なっているので注意が必要です。何で、統一しないかなあ。 イベント発生ごとにRGPIO8をトグルするので、LEDは、1秒周期で点滅をします。
デバッガでスタックを調べる
デバッガでISRに入ったところに Breakpoint を仕掛けて、スタックを確認しました。
+0x0 0100 0001 0101 1000 0010 0000 0001 0100 +0x4 0000 0000 0000 0000 0000 0100 0110 0000
Format | 0100 | 8バイトスタックフレーム | ||
---|---|---|---|---|
FS[3:0] | 0000 | no fault | ||
Vector | 01 0101 10 | RTC割り込みのベクタ | ||
SR | 0010 0000 0001 0100 | ステータス・レジスタ | ||
T | 0 | トレース不許可 | ||
S | 1 | スーパバイザ・モード | ||
M | 0 | マスター・ステート | ||
I | 000 | 割り込みマスク | ||
CCR | 0001 0100 | コンディション・コード | ||
PC | $00000460 | プログラム・カウンタ |
おぉ、確かにリファレンスマニュアルどおりだ。
割り込みベクタテーブルは、 "Project Settings/Startup Code/exceptions.c" ファイルに作成されます。 このファイルの中では、デフォルトの ISR があてがわれているので、 "main.c" に書いた ISR と競合しそうな気がしますが、 実際には、 "main.c" で書いた "__interrupt" 宣言の方が優先されるようです。
ColdFire V1 でTPMモジュールを使う - 何だ、HCS08と同じじゃん。 [ColdFire (ColdeFire) V1]
クロック速度を計測するために、本格的にタイマ・モジュールを使うことにしました。 TPM1モジュールのCH0を "software only" の "output capture" で使います。
タイマの初期設定
初期設定は、こんなもんです。
TPM1SC_CLKSx = 1; // タイマにBUSCLK=4MHzを供給する TPM1SC_PS = 2; // 1/4プリスケーラで 1µ秒周期でカウンタを動作させる TPM1C0SC_MS0x = 1; // software output compare TPM1C0SC_ELS0x = 1; // software output compare
バスクロックを4MHz、プリスケーラの出力を1µ秒周期にして、後の計算で楽をします。 タイマは、フリーランニングで使うので、モジュロ設定はしません。
タイマイベントを捕まえる
"output compare" イベントの待ち合わせには、 割り込みを使わずにフラグを監視します。
while (!TPM1C0SC_CH0F) { // Do nothing }
whileの後ろに「セミコロンだけの空文」をつけると警告が出たので、 「括弧つき空文」としました。
イベントが発生したら、次回のイベント時刻を設定し、フラグをクリアします。
TPM1C0V += 10000; // 10msec later TPM1C0SC_CH0F = 0; // clear flag
16ビットのタイマに1µ秒周期のクロックをあたえると、 10m秒周期でイベントを発生させるのが精一杯です。 ColdFireに16-bitタイマというのは、ちと物足りないな。
イベント50回、0.5秒ごとにポートをトグルすると、1秒周期でLEDが点滅します。
if (++count >= 50) { // 500msec event RGPIO_TOG = RGPIO_TOG_TOG8_MASK; count = 0; }
HCS08と同じモジュールが入っているので、使い方もまったく同じです。 Cでプログラムを書いていると、CodFireを使っているのを忘れてしまいそうです。
ポートのトグルに使ったRGPIOモジュールの初期設定は、 ColdFire V1 のバス周波数を目で確認するに書きました。 実際のプログラムでは、COPの処理もお忘れなく。
ColdFire V1 のクロック設定 - オーバークロックへの道 [ColdFire (ColdeFire) V1]
デバッガから周波数を変更する
空プロジェクトをDEMOQE128に書き込んで、周波数を確認しました。 周波数を測定する装置は使いません。 Multilinkが測定して、デバッガのCommandウィンドウに報告してくれます。
デフォルト状態では、内部参照クロックが31.25kHzに設定されて、 FLLが512倍のクロックを生成するので、DCOの周波数は16MHzになります。 これをBDIVで2分周した8MHzのクロックがCPUに入ります。 バスクロックは、この半分の4MHzになります。 デバッガ画面には、 "~4005157hz"と表示されました。
この状態で、デバッガからICSC2($FFFF8039)の値を$40から$00に変更すると、 BDIV=00(1/1分周)となって、デバッガ画面に"~8002027hz"と表示されます。 ところが、それっきりBDMの接続が切れてしまいました。
同様にデバッガからICSSC($FFFF803B)の値を$11から$B1に変更すると、 DRS=10, DMX32=1 (1824倍FLL; 理論的には DCO=57MHz)となって、 デバッガ画面に"~14272350hz"と表示されます。 が、やはりBDMが切れてしまいました。
プログラムでクロック設定を切り替える
それではと、プログラムでこれらのレジスタを変更してみました。
// Configure ICS ICSC2_BDIV = 0; // BDIV 1/1 ICSSC_DRST_DRS = 2; // FLL x1536
このプログラムを書き込んで、デバッガから実行したところ、デバッガ画面には、 "~24056572hz"と表示され、BDMとの通信も切れません。
ところが、この状態からデバッガのResetとStartボタンを押して、 再実行させても周波数が変更されたという表示が出てきません。 "STEP OVER"でソースコードをステップ実行させると"~23906250hz"と表示が出てきて、 周波数が変わったらしいことがわかります。
何が悪いんだか、わかんないな。"CodeWarrior V6.0"でやっているせいもあるのかな? HCS08では、こんなにBDM通信が切れちゃうことはなかったんだけどな。
良い子はマネしてはいけない、オーバークロックの実験
仕様書に記述のある最大バスクロック周波数は、25.165MHzなのですが、 ICSの設定だけでそれ以上の周波数が出るらしいことがわかります。 そこで、実際に30MHz動作を実感してみました。
先の24MHzを出していたプログラムをそのまま使います。 そして、プログラムを書き込むときに内部参照クロックの周波数を39.06kHzに変更します。 やることは、これだけです。 これで、DCO出力は、39.60kHz*1536=60.0MHzとなります。
デバッガ画面でも "~30118050hz"の表示が出ています。 つまり、DEMOQE128に搭載されたMultilinkも、 しっかり追従してきているということです。 メモリダンプ画面も変更されている様子が見られるので、通信もできているのでしょう。
当のMCF51QE128ですが、多少暖かくなった程度で、 裏側のMC9S12UF32の方がよっぽど暖かくなっています。 まあ、実験レベルでは動くんじゃないかな?
あくまでも、自己責任で。
ColdFire V1 のバス周波数を目で確認する [ColdFire (ColdeFire) V1]
命令実行時間を測定するために、アセンブラでソフトウェア・ループを組み、 LEDを点滅させてみました。
#include/* for EnableInterrupts macro */ #include "derivative.h" /* include peripheral declarations */
プログラムは、全部main関数に押し込んであります。
void main(void) {犬のおまわりさん(COP)を黙らせるためには、HCS08同様にSOPT1レジスタを設定します。 このあたりの勝手は、HCS08と同じなので、今までの経験が役立ちます。
SOPT1 = 0b01010111; // |||| ||+--- RSTPE=1 Enable RST port // |||| |+---- BKGDPE=1 Enable BKGD port // |||| +----- RSTOPE=1 Enable RSTO port // |||+------- WAITE=1 Enable WAIT // ||+-------- STOPE=0 Disable STOP // |+--------- COPT=1 Disable COP // +---------- COPE=0 Disable COPバスクロック周波数を設定する方法もHCS08同様です。 QE128は、DRSというレジスタが追加されて、Digitally Controlled Oscillator (DCO)の 出力周波数倍率が変更できるようになっています。 ここでは、 内部参照クロックを31.25kHzに設定し、 DCO出力を内部参照クロックの1536倍、すなわち48MHzに設定します。 バス・クロック周波数は、その半分の24MHzになるはずです。
// Configure ICS ICSC2_BDIV = 0; // BDIV 1/1 ICSSC_DRST_DRS = 2; // FLL x1536
LEDを点灯させるために、RGPIOという新モジュールを使います。 ここで使用するポートは、RGPIO8(PTC0)のみです。 RGPIOを出力としてイネーブルします。
// Configure RGPIO8=PTC0 as output RGPIO_DIR_DIR8 = 1; RGPIO_ENB_ENB8 = 1;処理本体では、時間稼ぎループをアセンブラで記述しています。 ループ回数は、4,000,000回です。 MCF51QE128のリファレンス・マニュアルによれば、ループ一回で3バス・クロック要するので、 時間稼ぎ部分で12,000,000バスサイクルを消費するはずです。 バス周波数を24MHzに設定すると、0.5秒に相当します。 nopについては、のちほど。
for(;;) { __asm { move.l #4000000,d0 // nop L1: subq #1,d0 // 1(0/0) bne L1 // 2(0/0) backward taken }時間稼ぎが終わったら、RGPIOの出力を反転させます。 0.5秒毎に反転されるので、1秒周期でLEDが点滅します。
// Toggle RGPIO10 RGPIO_TOG = RGPIO_TOG_TOG8_MASK; } /* loop forever */ /* please make sure that you never leave main */ }
これで、1秒周期でLEDが点滅するはずです。 ところが、実際の点滅周期は、2/3秒になってしまいました。 なぜなんだろう。
ループの回数を間違えることはないと思われるので、 ループ当たりのバス・サイクル数が2サイクルになったと推測しました。
色々とやり直した結果、ラベル"L1"のアドレスが、"0-modulo-4"だった場合は、 ループ当たり2バス・サイクルを消費し、 "2-modulo-4"の場合は、3サイクルを消費するらしいことがわかりました。 nopをコメントからはずすと、結果が異なってきます。 えっ?リファレンス・マニュアルが違うの?それとも、シリコン?
まさに、パイプラインのなせる業。 インライン・アセンブラは、".align 4"を入れることが出来ないらしいので、 こういった使い方をする際には、要注意です。
RGPIOを使った理由
RGPIOというのは、簡単に16本のポートの操作を行うありがたい仕掛けなのですが、 これを使ったのには理由があります。 この解に至るまでにHCS08で良く使われる以下のような構文を試してみました。
- PTCD_PTCD0 = !PTCD_PTCD0;
- PTCD_PTCD0 = ~PTCD_PTCD0;
- PTCD ^= PTCD_PTCD0_MASK;
ところが、こいつらが見るに耐えない冗長なコードを出力してしまったので、 RGPIOの登場となったしだいです。 どんな、冗長なコードができたかは、ぜひ、ご自身でお試しください。
ColdFire V1でコンテキストスイッチ [ColdFire (ColdeFire) V1]
ColdFire V1を使って、コンテキストスイッチを行うプログラムを作成してみました。モデルは、OS-9/68000です。
//************************************************************** // $Id: main.c,v 1.3 2008/01/10 15:04:18 noritan Exp $ // // Multi tasking Practice //************************************************************** #include <hidef.h> /* for EnableInterrupts macro */ #include "derivative.h" /* include peripheral declarations */
定数の定義です。タスクの数、スタックのサイズ、ステートマシンの状態コードを定義しています。スタックサイズは、4Byte(Long Word)単位の数です。
#define N_TASK (10) #define N_SSTACK (30) #define N_USTACK (30) #define ST_NULL (0) #define ST_SLEEP (1) #define ST_ACTIVE (2) #define ST_RUN (3)
TaskDescriptor は、OS-9では、Process Descriptor と呼ばれていました。
他のオペレーティング・システムでは、 Task Control Block と呼ばれることもあります。
OS-9/68000 の Process Descriptor は、2KByteもありましたが、ここでは必要最小限としてあります。
count は、状況によって使い分ける万能カウンタです。
struct TaskDescriptor { const struct ModuleDescriptor *module; dword *workarea; dword *ssp; dword *usp; dword status; dword count; dword sStack[N_SSTACK]; };
ModuleDescriptor は、プログラム単位を定義しています。
OS-9/68000 では、ModuleDescriptor に指定された大きさの作業領域などを動的に割り当てることが出来るのですが、今回は、初期化と実行部のエントリだけ定義しています。
struct ModuleDescriptor { void (*init)( struct TaskDescriptor *task, int argc, char **argv ); void (*proc)(void); }; typedef struct TaskDescriptor TaskDescriptor; typedef struct ModuleDescriptor ModuleDescriptor;
TaskDescriptor は、同時実行可能なタスクの数だけ必要です。
thisTaskは、現在実行中のタスクを指します。
TaskDescriptor taskTable[N_TASK]; TaskDescriptor *thisTask = 0;
各タスクに必要な作業領域は、初期化ルーチンで確保します。
ユーザモードでのスタックは、ここに確保します。
作業領域は、割り当てるけど開放しないいいかげん方式です。
typedef struct WorkArea { dword toggle; dword period; dword uStack[N_USTACK]; } WorkArea; dword workareaUsed = 0; WorkArea workareaTable[N_TASK];
ModuleDescriptor を元に新しいタスクを作成し、実行中タスクリストに登録します。
もっとも、現在の実装では、リストを作成しているわけではなく、
タスクの状態を "ACTIVE" にするだけで、
あとはスケジューラが解決してくれています。
//---------------------------------------------- // Create a TASK with a MODULE and PARAMETER // and add to ACTIVE list. //---------------------------------------------- TaskDescriptor *tsk_start( const ModuleDescriptor * module, int argc, char * * argv ) { int i; struct TaskDescriptor *task; for (i = 0, task = &taskTable[0]; i < N_TASK; i++, task++) { if (task->status == ST_NULL) { task->module = module; task->ssp = &(task->sStack[N_SSTACK]); task->status = ST_ACTIVE; task->count = 0; (task->module->init)(task, argc, argv); return task; } } return 0; }「何もしないプログラム」の初期化部分です。 作業領域を確保し、スタックフレームを構築しています。 スタックフレームは、例外が発生した時のイメージを再現しています。
void idle_task_init( TaskDescriptor * task, int argc, char **argv ) { #pragma unused (argc, argv) WorkArea *workarea = &workareaTable[workareaUsed++]; register dword *usp = &(workarea->uStack[N_USTACK]); register dword *ssp; dword *_A5; dword i; void idle_task_proc(); void tsk_exit(void); #pragma warn_any_ptr_int_conv off // Establish DUMMY user stack *(--usp) = (word)tsk_exit; // Establish a stack frame ssp = task->ssp; *(--ssp) = (dword)idle_task_proc; // PC *(--ssp) = 0x40000000; // Vector & SR *(--ssp) = (dword)usp; // A6 __asm { move a5,_A5 } *(--ssp) = (dword)_A5; // copy of current A5 for (i = 0; i < 13; i++) { *(--ssp) = 0; // D0-D7/A0-A4 } #pragma warn_any_ptr_int_conv reset task->workarea = (dword *)workarea; task->usp = usp; task->ssp = ssp; }「何もしないプログラム」の実行部です。 犬にえさを与え続けます。
void idle_task_proc(void) { for (;;) { // Do nothing __RESET_WATCHDOG(); /* feeds the dog */ } }「何もしないプログラム」モジュールを定義します。 モジュールは、ROMに配置されます。
const ModuleDescriptor idle_module = { idle_task_init, idle_task_proc };メインルーチンです。 マイコンの初期化を行い、 「何もしないプログラム」モジュールを使って二つの「何もしないタスク」を作成し、 "trap #0" でコンテキストを切り替えます。
void main(void) { TaskDescriptor *idle_task; // Configure one-time-write SOPT1 SOPT1 = 0 | SOPT1_WAITE_MASK // Enable WAIT instruction | SOPT1_RSTOPE_MASK // Enable RSTO* port | SOPT1_BKGDPE_MASK // Enable BKGD port | SOPT1_RSTPE_MASK ; // Enable RESET* port // Configure RTC module RTCSC_RTCLKS = 0; // 1kHz OSC RTCSC_RTCPS = 11; // 10msec RTCMOD = 50-1; // 50counts (500msec) RTCSC_RTIE = 1; // Enable interrupts. // Invoke two tasks idle_task = tsk_start(&idle_module, 0, 0); idle_task = tsk_start(&idle_module, 0, 0); __asm { // yeild to context switch trap #0 } EnableInterrupts; /* enable interrupts */ for(;;) { __RESET_WATCHDOG(); /* feeds the dog */ } /* loop forever */ /* please make sure that you never leave main */ }ユーザモードの実行部プログラムが終了したら、"tsk_exit" にやってきます。 今回は、使われません。 あれ? "trap #0" が必要なはずだぞ。
void tsk_exit(void) { thisTask->status = ST_NULL; }次に実行すべきタスクを選択します。 OS-9/68000 がそうであるように、 タスクリストの中から、もっとも待ち時間が長い実行状態タスクを選びます。 待ち時間は、 "count" で数えていて、 "schedule" で選択されないと インクリメントされます。
void schedule(void) { int i; struct TaskDescriptor *task; dword maxCount = 0; struct TaskDescriptor *taskSelected = 0; for (i = 0, task = &taskTable[0]; i < N_TASK; i++, task++) { if (task->status == ST_ACTIVE) { task->count++; if (task->count > maxCount) { taskSelected = task; maxCount = task->count; } } } thisTask = taskSelected; if (thisTask) { thisTask->count = 0; } }"trap #0" 例外は、現在実行中のタスクを中断して、 別のタスクに切り替える機能があります。 この部分は、 タスク選択の部分だけは"schedule"関数を呼び出していますが、 スタックの深さが重要になってくるので、 残りはフルにアセンブラで記述しています。
asm interrupt VectorNumber_Vtrap0 trap0_isr(void) { // Save the stack frame == task context lea -60(a7),a7 movem d0-d7/a0-a6,(a7) // Save SSP/USP to the current TaskDescriptor move.l thisTask,a0 move.l a7,struct(TaskDescriptor.ssp)(a0) move.l usp,a1 move.l a1,struct(TaskDescriptor.usp)(a0) jsr schedule // Restore SSP/USP from the selected TaskDescriptor move.l thisTask,a0 move.l struct(TaskDescriptor.usp)(a0),a1 move.l a1,usp move.l struct(TaskDescriptor.ssp)(a0),a7 // Restore the stack frame movem (a7),d0-d7/a0-a6 lea 60(a7),a7 rte }タイマ割り込みのISRです。 基本的に"trap #0"と同じですが、 タイマフラグをクリアするシーケンス"rtc_isr_content"を 呼び出しています。
void rtc_isr_content(void) { RTCSC_RTIF = 1; // Clear the flag. } asm interrupt VectorNumber_Vrtc rtc_isr(void) { // Save the stack frame == task context lea -60(a7),a7 movem d0-d7/a0-a6,(a7) // Save SSP/USP to the current TaskDescriptor move.l thisTask,a0 move.l a7,struct(TaskDescriptor.ssp)(a0) move.l usp,a1 move.l a1,struct(TaskDescriptor.usp)(a0) // Do ISR specific task jsr rtc_isr_content // Do task scheduling jsr schedule // Restore SSP/USP from the selected TaskDescriptor move.l thisTask,a0 move.l struct(TaskDescriptor.usp)(a0),a1 move.l a1,usp move.l struct(TaskDescriptor.ssp)(a0),a7 // Restore the stack frame movem (a7),d0-d7/a0-a6 lea 60(a7),a7 rte } //************************************************************** // $Log: main.c,v $ // Revision 1.3 2008/01/10 15:04:18 noritan // Completed... maybe // //**************************************************************
参考文献