YouTube 進出記念 - MCF52233付録基板で作るネットワーク電飾の動画 [ColdFire V2]
Yahoo!ビデオキャストが終了するため、乗換え先を探しました。 やっぱり、 YouTube しか無いのかな?
点滅パターンは、Javaで記述したPC上のプログラムからUDPで与えます。 元ネタは、ネットワーク電飾 (1)です。
参考文献
Interface (インターフェース) 2008年 09月号 [雑誌]
- 作者:
- 出版社/メーカー: CQ出版
- 発売日: 2008/07/25
- メディア: 雑誌
RiverSea その後 [ColdFire V2]
***HTML文書にGIFを埋め込んでみた***のその後。 やっとの思いで、ここまで、来たけど、完成させられるかどうか、わかりません。 誰か、仕上げて。
サーバプログラム Main
char *CgiFlag=0x20007f2e; main() { char *board=MemoryAlloc(64); char *cmd=MemoryAlloc(81); char row,col; char ch; int i; char done=0; while (!done) { for (i = 0;i < 80; i++) { ch = Getc(1); cmd[i] = ch; if (ch == 0) break; } cmd[i] = 0; switch(cmd[0]) { case 'B': setBoard(board, cmd+3); break; case 'R': row = Atoi(cmd+3); break; case 'C': col = Atoi(cmd+3); break; case 'b': putBoard(board); break; case 'p': putPiece(board, cmd+3); break; case 'X': done = 1; } *CgiFlag = 0; } MemoryFree(cmd); MemoryFree(board); } setBoard(char *b, char *c) { char *num_buf=MemoryAlloc(8); int x,y; long line; MemClear(num_buf,8); StrCpy(num_buf,"0x"); MemClear(b, 64); for (y = 0; y < 8; y++) { BufCopy(num_buf+2,c,4); c += 4; line = Atoi(num_buf); for (x = 8; --x >= 0; ) { b[y*8+x]=line & 3; line >>= 2; } } MemoryFree(num_buf); } putBoard(char *b) { int x,y; int line; for (y = 0; y < 8; y++) { line = 0; for (x = 0; x < 8; x++) { line <<= 2; line += b[y*8+x]; } PrHexWord(line); } } putPiece(char *b, char *c) { int index=Atoi(c); switch (b[index]) { case 1: PrStr("<img src=\"W.gif\"/>"); break; case 2: PrStr("<img src=\"B.gif\"/>"); break; default: PrStr(" "); break; } }
HTML文書プロトタイプ RiverSea.htm
<html><head> <title>RiverSea</title> <style> table.board {text-align:center;} table.board td {width:40px;height:40px;background:#0F0;} </style> </head><body> <h1 style="text-align:center;">RiverSea</h1> <form action="RiverSea.htm" style="text-align:center;"> <table cellpadding="10" style="margin:0 auto;"> <td> <table border="1" cellpadding="2" cellspacing="1" class="board"> <tr><th colspan="14"/></tr> <tr> <th rowspan="12"/><th/><th rowspan="12"/> <th>A</th><th>B</th><th>C</th><th>D</th> <th>E</th><th>F</th><th>G</th><th>H</th> <th rowspan="12"/> </tr> <tr><th/><th colspan="8"/></tr> <tr><th>1</th> <td>$EXEC'p("0") '</td> <td>$EXEC'p("1") '</td> <td>$EXEC'p("2") '</td> <td>$EXEC'p("3") '</td> <td>$EXEC'p("4") '</td> <td>$EXEC'p("5") '</td> <td>$EXEC'p("6") '</td> <td>$EXEC'p("7") '</td> </tr> <tr><th>2</th> <td>$EXEC'p("8") '</td> <td>$EXEC'p("9") '</td> <td>$EXEC'p("10") '</td> <td>$EXEC'p("11") '</td> <td>$EXEC'p("12") '</td> <td>$EXEC'p("13") '</td> <td>$EXEC'p("14") '</td> <td>$EXEC'p("15") '</td> </tr> <tr><th>3</th> <td>$EXEC'p("16") '</td> <td>$EXEC'p("17") '</td> <td>$EXEC'p("18") '</td> <td>$EXEC'p("19") '</td> <td>$EXEC'p("20") '</td> <td>$EXEC'p("21") '</td> <td>$EXEC'p("22") '</td> <td>$EXEC'p("23") '</td> </tr> <tr><th>4</th> <td>$EXEC'p("24") '</td> <td>$EXEC'p("25") '</td> <td>$EXEC'p("26") '</td> <td>$EXEC'p("27") '</td> <td>$EXEC'p("28") '</td> <td>$EXEC'p("29") '</td> <td>$EXEC'p("30") '</td> <td>$EXEC'p("31") '</td> </tr> <tr><th>5</th> <td>$EXEC'p("32") '</td> <td>$EXEC'p("33") '</td> <td>$EXEC'p("34") '</td> <td>$EXEC'p("35") '</td> <td>$EXEC'p("36") '</td> <td>$EXEC'p("37") '</td> <td>$EXEC'p("38") '</td> <td>$EXEC'p("39") '</td> </tr> <tr><th>6</th> <td>$EXEC'p("40") '</td> <td>$EXEC'p("41") '</td> <td>$EXEC'p("42") '</td> <td>$EXEC'p("43") '</td> <td>$EXEC'p("44") '</td> <td>$EXEC'p("45") '</td> <td>$EXEC'p("46") '</td> <td>$EXEC'p("47") '</td> </tr> <tr><th>7</th> <td>$EXEC'p("48") '</td> <td>$EXEC'p("49") '</td> <td>$EXEC'p("50") '</td> <td>$EXEC'p("51") '</td> <td>$EXEC'p("52") '</td> <td>$EXEC'p("53") '</td> <td>$EXEC'p("54") '</td> <td>$EXEC'p("55") '</td> </tr> <tr><th>8</th> <td>$EXEC'p("56") '</td> <td>$EXEC'p("57") '</td> <td>$EXEC'p("58") '</td> <td>$EXEC'p("59") '</td> <td>$EXEC'p("60") '</td> <td>$EXEC'p("61") '</td> <td>$EXEC'p("62") '</td> <td>$EXEC'p("63") '</td> </tr> <tr><th colspan="14"/></tr> </table> </td> <td> <p> <input type="text" name="EXEC'B'" value="$EXEC'b("00000000000001800240000000000000")'" /> </p> <table cellpadding="5" style="text-align:center;"> <tr><th style="text-align:left;" colspan="8">COLUMN:</th></tr> <tr> <td>A</td> <td>B</td> <td>C</td> <td>D</td> <td>E</td> <td>F</td> <td>G</td> <td>H</td> </tr> <tr> <td><input type="radio" name="EXEC'C'" value="0" checked/></td> <td><input type="radio" name="EXEC'C'" value="1"/></td> <td><input type="radio" name="EXEC'C'" value="2"/></td> <td><input type="radio" name="EXEC'C'" value="3"/></td> <td><input type="radio" name="EXEC'C'" value="4"/></td> <td><input type="radio" name="EXEC'C'" value="5"/></td> <td><input type="radio" name="EXEC'C'" value="6"/></td> <td><input type="radio" name="EXEC'C'" value="7"/></td> </tr> <tr><th style="text-align:left;" colspan="8">ROW:</th></tr> <tr> <td>1</td> <td>2</td> <td>3</td> <td>4</td> <td>5</td> <td>6</td> <td>7</td> <td>8</td> </tr> <tr> <td><input type="radio" name="EXEC'R'" value="0" checked/></td> <td><input type="radio" name="EXEC'R'" value="1"/></td> <td><input type="radio" name="EXEC'R'" value="2"/></td> <td><input type="radio" name="EXEC'R'" value="3"/></td> <td><input type="radio" name="EXEC'R'" value="4"/></td> <td><input type="radio" name="EXEC'R'" value="5"/></td> <td><input type="radio" name="EXEC'R'" value="6"/></td> <td><input type="radio" name="EXEC'R'" value="7"/></td> </tr> <tr><td colspan="9"><input type="submit" value="Decide"/></td></tr> <tr><td colspan="9"><a href="SetEnd.htm?EXEC%27X%27">EXIT</a></td></tr> </table> </td> </table> </form> </body></html>
呼び出し方
http://192.168.1.10/RiverSea.htm?EXEC%27B%27=00000000000001800240000000000000
続 HTTP サーバの CGI 機能を極める [ColdFire V2]
ともぞうさんの記事「ColdFireマイコン温度計にLED(2)」で再確認したのですが、 http サーバの CGI 機能は、 SilentC のプログラムが走っていると機能しないようです。 でも、機能していないわけじゃないらしいんだな。
SilentC のプログラムが走っている場合の実験
以下の実験は、シリアル・インターフェースで接続してお試しください。 telnet では、同じ結果が得られません。
まずは、普通に SilentC のプログラムを実行してから、加速度センサ表示ページを呼び出します。 実行したプログラムは、これです。
main(){#stop 0;while(Getc(0)!='q')SystemSleep();}
すると、みごとにブラウザが停止して、ページが表示されません。 ここで、コンソールから、 'q' をタイプすると、プログラムの実行が停止し、ブラウザには、文字が表示されます。
この実験から、 SilentC のプログラムが実行されている事が原因となって CGI 機能が動作しないらしいことがわかります。
文字表示プログラムが走っている場合の実験
最初の実験は、何もしないプログラムを使いました。 今度は、メッセージを表示するプログラムを使ってみます。
main(){#stop 0;while(Getc(0)!='q'){PrStr("HELLO WORLD\r\n";Sleep(100);}}
一秒ごとに有名なメッセージを表示するプログラムです。 このプログラムを走らせて、加速度センサ表示ページを呼び出すと、やはりブラウザにページが表示されません。 さらに、コンソールの文字も出てこなくなりました。
ここで、コンソールから、 'q' をタイプすると、プログラムの実行が停止します。 ところが、ブラウザには、あのメッセージの断片が表示されます。 ここは、 "ad::adinit" 関数を呼び出している箇所です。 どうも、コンソール出力が埋め込まれているようです。
サーバ・プログラムを作成した
さて、いよいよ、実用的なプログラムを作成します。 最初は、単なる文字列入出力で対応できるのかと思っていたのですが、一筋縄ではいきませんでした。
main(){ char ch; char *msg=MemoryAlloc(81); long index; #stop 0 for (;;) { index=0; for(;;) { ch=Getc(1); if (ch=='\r') break; msg[index++]=ch; if (ch==0) break; } if (ch=='\r') break; PrStr("HELLO "); PrStr(msg); PrStr("\r\n"); Pokeb(0x20007F2E,0); } MemoryFree(msg); }
コンソールから入力された文字列の頭に"HELLO "を付けて返答する簡単なサーバ(?)です。
コンソールから入力された文字は、 msg 配列に格納されます。 配列の長さに関するエラー処理はしていませんので、ご注意ください。 入力文字列は、ヌル文字 (0) で終了します。 このため、この処理は Gets 関数で代用することが出来ず、 Getc で一文字ずつ処理しなくてはなりません。
コンソールから "Enter" が入力されたらプログラムは終了します。
応答には、 "PrStr" 関数を使っています。 全ての応答を出し終わったら、 0x20007F2E 番地の CgiFlag に 0 を書き込みます。 すると、 CGI 処理が応答を受け取って、 HTML 文書に応答を埋め込みます。
このプログラムを実行している状態で、加速度センサ表示ページを呼び出すとCGIが受け取ったリクエストの先頭に "HELLO " が付加された文字列が表示されます。 この例では、あまり意味のある処理は行われていません。 通常の CGI 機能と異なっているのは、 SilentC のプログラムが実行されている状態なので、内部状態を保持しているということです。 何か、怪しいことが出来るかも。
参考文献
Interface (インターフェース) 2008年 12月号 [雑誌]
- 作者:
- 出版社/メーカー: CQ出版
- 発売日: 2008/10/25
- メディア: 雑誌
Interface (インターフェース) 2008年 09月号 [雑誌]
- 作者:
- 出版社/メーカー: CQ出版
- 発売日: 2008/07/25
- メディア: 雑誌
SilentC のメモリ管理をあばく [ColdFire V2]
以前、SilentC のメモリ管理をのぞくでヒープ領域の使い方について少しのぞいてみましたが、管理領域が何を意味しているのか判然としませんでした。 その後、すっきり謎が解けたのでレポートします。
ヒープは線形リストである
メモリ領域の概要は、この図のようになっています。 ヒープ領域の先頭は、 0x20007DBC 番地にある HeapStart というポインタで示されています。 ヒープ領域は、任意の個数のブロックに分割されており、それぞれのブロックの最初の4バイトに管理情報が入っています。
4バイトの管理情報の最初の2バイトは、そのブロックのサイズを4バイト単位で示しています。 ブロックは、隙間無く配置されるので、この2バイトは事実上次のブロックへの相対アドレスを示すポインタとして機能します。 そして、管理情報後半の2バイトは、そのブロックで実際に使用されている領域のサイズを4バイト単位で示しています。 このような構造になっているため、ひとつのブロックには、ひとつの使用中領域とひとつ以内の未使用領域が存在することになります。
ヒープ領域の最後のブロックの管理情報には、 0 が書き込まれていて、線形リストの終端をあらわしています。
メモリの割り当て状況を調べる
管理情報の意味が判ったので、ヒープ領域の割り当て領域と未使用領域の計算させるプログラムを作成しましょう。 と、思ったのですが、ヒープ領域は時々刻々と変化しており、しかも SilentC インタプリタ自身がヒープを使用して作業をしているので、 SilentC のプログラムでヒープを調べるわけにはいきません。 そこで、ユーザ・ドライバの出番となります。
#include "support_common.h" /* include peripheral declarations and more */ #include "silentmoon.h" uint32 *HeapStart @ 0x20007DBC; // // VECTOR #0 : main // uint32 main(uint32 arg) { #pragma unused(arg) uint32 size; uint32 *result; uint32 *p; result = MemoryAlloc(16); MemClear(result, 16); // result[0] (nBlocks) Number of memory blocks // result[1] (allocSize) Total allocated memory size // result[2] (usedSize) Total used memory size asm { // Disable interrupts move.w #0x2700,sr } p = HeapStart; while (*p) { result[0]++; size = (*p >> 16); result[1] += size * 4; result[2] += (*p & 0xFFFF) * 4; p += size; } asm { // Enable interrupts move.w #0x2000,sr } return (uint32)result; } // // Jump table for vectors // __declspec(jump_table) asm void jump_table(void) { jmp main // Driver #0 }
プログラムの実行中にヒープの割り当てや開放が行われると、管理情報に矛盾が生じるので、プログラム実行中は割り込みを禁止しています。
このユーザ・ドライバは、配列へのポインタを返します。 配列のそれぞれの要素には、ブロック数、総ブロック容量、総使用容量の三つの値が入っています。 また、この配列は、ユーザ・ドライバ自身が MemoryAlloc 関数で割り当てています。
この配列を受け取って、結果を表示させる作業は、 SilentC のプログラムにやらせます。
main() { long *result = UserDriver(0); PrStr("BLOCKS =");PrNum(result[0]);PrStr("\r\n"); PrStr("ALLOCATED =");PrNum(result[1]);PrStr("\r\n"); PrStr("USED =");PrNum(result[2]);PrStr("\r\n"); MemoryFree(result); }
ユーザ・ドライバ内で配列を割り当てているので、使い終わったら、開放してやります。 このプログラムを実行すると、それぞれの値が表示されます。
OK run BLOCKS =29 ALLOCATED =11712 USED =11304 OK
このプログラムを実行した時には、29個のブロックが11712バイトにわたって割り当てられていました。 そのうち、実際に使用されているのは、11304バイトで、差分の408バイトに管理情報と未使用領域が存在するというわけです。
OK MemoryAlloc(1024) OK run BLOCKS =30 ALLOCATED =12740 USED =12328 OK
ここで、1024バイトの領域を新たに割り当てると、1028バイトのブロックがひとつ割り当てられ、使用されている領域が1024バイト増加したことがわかります。
プロジェクト・アーカイブ
ユーザ・ドライバのプロジェクトは、これを CFCQ08.zip という名前で保存すると再現できます。
参考文献
Interface (インターフェース) 2008年 12月号 [雑誌]
- 作者:
- 出版社/メーカー: CQ出版
- 発売日: 2008/10/25
- メディア: 雑誌
Interface (インターフェース) 2008年 09月号 [雑誌]
- 作者:
- 出版社/メーカー: CQ出版
- 発売日: 2008/07/25
- メディア: 雑誌
ユーザ・ドライバに複数の引数を与える方法 [ColdFire V2]
おおやけには、ユーザ・ドライバには、引数を一つしか渡すことが出来ないとされています。 何とか複数の引数を渡す方法は無いでしょうか。
スタック・フレームと引数構造体
CodeWarrior のコンパイラが生成するコードは、 A6 レジスタを使用して、スタック・フレームの管理を行っています。 たとえば、ユーザが記述したユーザ・ドライバ "uint32 driver(uint32 arg)" が呼び出されると、 "driver" 関数に与えられた引数とそこで使用される局所変数へは、 A6 レジスタを介してアクセスすることができます。
また、スタック・フレームには、呼び出し元のスタック・フレームへのポインタも含まれています。 そのため、スタック・フレームをさかのぼると、次々と呼び出し元の関数のスタック・フレームにアクセスすることが出来ます。
また、 SilentC は、インタプリタなので、字句解析を行ったのち、該当する関数に相当するコンパイル済み関数を呼び出します。 たとえば、 "UserDriver" 関数の場合には、 "UserDriverSub" という関数が呼び出されます。 この呼び出しの際には、 SilentC の引数を表現した引数構造体へのポインタが渡されます。 そこで、この構造体を介して、ユーザ・ドライバに複数の引数を渡すことが出来ます。
UserDriver 関数の引数をコピーするプログラム
さっそく、プログラムを作って試してみます。 このプログラムは、 UserDriver 関数に与えられた引数構造体を 0x20007F80 からの空きエリアに単純にコピーするプログラムです。
#include "support_common.h" /* include peripheral declarations and more */ #include "silentmoon.h" // // Stack frame structure // struct stack_frame { struct stack_frame *invoker; void *return_value; uint32 args[0]; }; // // SilentC's argument structure // struct arguments { uint32 mode; uint32 args[0]; }; // // VECTOR #0 : main // uint32 main(uint32 arg) { #pragma unused(arg) struct stack_frame *this_frame; struct stack_frame *invoker_frame; struct arguments *silentc_args; uint32 *table = (uint32 *)0x20007f80; asm { move a6,this_frame } invoker_frame = this_frame->invoker; silentc_args = (struct arguments *)invoker_frame->args[0]; BufCopy(table, silentc_args, 64); return (uint32)0; } // // Jump table for vectors // __declspec(jump_table) asm void jump_table(void) { jmp main // Driver #0 }
書き込んで実行してみます。 9個の引数が、 0x20007F88 から 0x20007FA7 まで並んでいるのがわかります。
OK UserDriver(0,1,2,3,4,5,6,7,8) OK m::d(0x20007f80) 20007f80 41 00 00 09 00 00 00 00 A....... 20007f88 00 00 00 01 00 00 00 02 ........ 20007f90 00 00 00 03 00 00 00 04 ........ 20007f98 00 00 00 05 00 00 00 06 ........ 20007fa0 00 00 00 07 00 00 00 08 ........ 20007fa8 00 00 6e e8 00 00 65 c0 ..n...e. 20007fb0 02 00 00 00 00 02 00 00 ........ 20007fb8 00 00 00 00 00 00 00 00 ........
違う引数を与えると、こうなりました。 7個の引数が、 0x20007F88 から 0x20007F9F まで並んでいるのがわかります。
OK UserDriver(0,256,257,258,259,260,261) OK m::d(0x20007f80) 20007f80 41 00 00 07 00 00 00 00 A....... 20007f88 00 00 01 00 00 00 01 01 ........ 20007f90 00 00 01 02 00 00 01 03 ........ 20007f98 00 00 01 04 00 00 01 05 ........ 20007fa0 00 00 01 00 00 00 01 01 ........ 20007fa8 00 00 01 02 00 00 01 03 ........ 20007fb0 00 1d 00 07 00 00 00 06 ........ 20007fb8 00 00 00 00 00 00 01 00 ........
この結果から、 "UserDriver" 関数の第二引数が args[1] に格納され、第三引数が args[2] に格納され、以下、順に格納されていることがわかります。 また、 mode の下8ビット、ダンプ・リストでいう所の 0x20007f83 番地には、 "UserDriver" 関数に与えられた引数の数も保存されています。 この仕掛けを使えば、可変長の引数をユーザ・ドライバに渡すことも出来ますね。
プロジェクト・アーカイブ
ユーザ・ドライバのプロジェクトは、これを CFCQ06.zip という名前で保存すると再現できます。
参考文献
Interface (インターフェース) 2008年 12月号 [雑誌]
- 作者:
- 出版社/メーカー: CQ出版
- 発売日: 2008/10/25
- メディア: 雑誌
Interface (インターフェース) 2008年 09月号 [雑誌]
- 作者:
- 出版社/メーカー: CQ出版
- 発売日: 2008/07/25
- メディア: 雑誌
I2CでLEDピカピカ ユーザ・ドライバ版 [ColdFire V2]
ユーザ・ドライバが使えるようになったので、「I2CでLEDピカピカ」を移植してみました。
元記事
この記事の元になった記事は、以下の通りです。
メイン・プログラム
まずは、 SilentC で記述されたメイン・プログラムを変更しました。
main(){ int i; char *cmd = MemoryAlloc(4); UserDriver(0, 0); // i2c_init cmd[0] = 0x20; // address cmd[1] = 0x03; // command (DDR) cmd[2] = 0x00; // data UserDriver(1, cmd); // i2c_send #stop 0 i=0; for(;;){ if(Getc(0)=='q')break; Sleep(25); cmd[1] = 0x01; // command (DATA) cmd[2] = 0xEF << i; // data UserDriver(1, cmd); // i2c_send i = (i + 1) & 3; } MemoryFree(cmd); }
ユーザ・ドライバとして、I2Cモジュールを操作するための二つのベクタを用意しています。
- #0 - i2c_init : I2C モジュールを初期化します。
- #1 - i2c_send : I2C モジュールにコマンドをデータを送ります。
コマンドを送るためのデータは、 char 型の配列として渡します。 配列には、I2Cデバイスのアドレス、デバイスへのコマンド、デバイスへのデータの3バイトのデータが格納されます。
ユーザ・ドライバの記述
このメイン・プログラムから呼び出すためのユーザ・ドライバは、こんな風になりました。
#include "support_common.h" /* include peripheral declarations and more */ uint32 i2c_init(uint32 arg); uint32 i2c_send(uint32 arg); asm void jump_table(void) { jmp i2c_init // Driver #0 jmp i2c_send // Driver #1 } asm void SystemSleep(void) { move 0x210,a0 jmp (a0) } uint32 i2c_init(uint32 arg) { #pragma unused(arg) // Configure ports. MCF_GPIO_PQSPAR = MCF_GPIO_PQSPAR_QSPI_CLK_SCL | MCF_GPIO_PQSPAR_QSPI_CS0_SDA; // Specify I2C clock rate. MCF_I2C_I2FDR = MCF_I2C_I2FDR_IC(0x38); // (x640) // Configure I2C module MCF_I2C_I2CR = MCF_I2C_I2CR_MTX | // Transmit MCF_I2C_I2CR_IEN; // I2C enable } uint32 i2c_send(uint32 arg) { uint8 address = ((uint8*)arg)[0]; uint8 command = ((uint8*)arg)[1]; uint8 data = ((uint8*)arg)[2]; while (MCF_I2C_I2SR & MCF_I2C_I2SR_IBB) { SystemSleep(); // Wait for BUS idle } MCF_I2C_I2SR = 0x00; // Clear flags MCF_I2C_I2CR |= MCF_I2C_I2CR_MSTA; // START do { MCF_I2C_I2DR = (uint8)(address << 1); // WRITE for 0100000 while (!(MCF_I2C_I2SR & MCF_I2C_I2SR_IIF)) { SystemSleep(); // Wait for IIF } if (MCF_I2C_I2SR & MCF_I2C_I2SR_RXAK) { break; // Quit if no ACK } MCF_I2C_I2SR = 0x00; // Clear flags MCF_I2C_I2DR = command; // command code while (!(MCF_I2C_I2SR & MCF_I2C_I2SR_IIF)) { SystemSleep(); // Wait for IIF } if (MCF_I2C_I2SR & MCF_I2C_I2SR_RXAK) { break; // Quit if no ACK } MCF_I2C_I2SR = 0x00; // Clear flags MCF_I2C_I2DR = data; // data to be sent while (!(MCF_I2C_I2SR & MCF_I2C_I2SR_IIF)) { SystemSleep(); // Wait for IIF } } while (0); MCF_I2C_I2CR &= ~MCF_I2C_I2CR_MSTA; // STOP }
CodeWarrior 純正のシンボルを使っているので、かなり長ったらしくなりました。
ユーザ・ドライバから呼び出している "SystemSleep" 関数は、アセンブラで記述された分岐命令になっており、 SilentMoon オペレーティングシステムを呼び出します。 また、 I2C の通信は、機械語の実行に比べて長い時間を要します。 このため、通信終了待ちループでは "SystemSleep" 関数を呼び出して、他のスレッドに実行権を明け渡しています。
メイン・プログラムを実行すると、LEDが点滅を始めます。
プロジェクト・アーカイブ
ユーザ・ドライバのプロジェクトは、これを CFCQ05.zip という名前で保存すると再現できます。
参考文献
Interface (インターフェース) 2008年 12月号 [雑誌]
- 作者:
- 出版社/メーカー: CQ出版
- 発売日: 2008/10/25
- メディア: 雑誌
Interface (インターフェース) 2008年 09月号 [雑誌]
- 作者:
- 出版社/メーカー: CQ出版
- 発売日: 2008/07/25
- メディア: 雑誌
ユーザ・ドライバでLEDピカピカ [ColdFire V2]
ユーザ・ドライバが書けるようになったので、応用プログラムを書いてみます。 題材は、もちろん、LEDピカピカです。
ユーザドライバで割り込みを使う
今回のプログラムのコンセプトは、例外ベクタを調べた (5)で取り上げた PIT1 割り込みを使った処理の移植です。 前回は、SilentCで全ての設定を行わせたため、機械語で書いた割り込みサービス・ルーチン (Interrrupt Service Routine) をRAMに書き込む部分までSilentCのプログラムで書きました。 今回は、すべての部分を CodeWarrior の C で記述します。
#include "support_common.h" /* include peripheral declarations and more */ // // Exception vector table on RAM // extern uint32 __VECTOR_RAM[]; // // Jump table for drivers. // uint32 init(uint32 arg); asm void jump_table(void) { jmp init // Driver #0 } // // PIT1 interrupt service routine // __declspec(interrupt) pit1_isr(void) { MCF_PIT1_PCSR |= MCF_PIT_PCSR_PIF; // Clear PIF MCF_GPIO_PORTLD ^= MCF_GPIO_PORTLD_PORTLD1; // Toggle LNKLED } // // Driver #0 // Initialize PIKA PIKA // arg 0 : Stop LED blinking // 1 : Start LED blinking // uint32 init(uint32 arg){ switch (arg) { case 1: // Enable PIKA // Prepare GPIO MCF_GPIO_PORTLD = 0x00; // PORTLD[1:0]=0 MCF_GPIO_DDRLD = 0x03; // DDRLD[1:0]=1 MCF_GPIO_PLDPAR = 0x00; // PLDPAR[1:0]=0 // Prepare INT __VECTOR_RAM[120] = (uint32)pit1_isr; // Register ISR MCF_INTC0_IMRH &= ~MCF_INTC_IMRH_INT_MASK56; // enable PITI MCF_INTC0_ICR56 = 0x08; // PRIORITY=0,LEVEL=1 // Prepare PIT MCF_PIT1_PMR = 29297-1; // Period count MCF_PIT1_PCSR = 0x081F; // Start PIT1 return 1; case 0: // Disable PIKA MCF_PIT1_PCSR = 0x0000; // Stop PIT1 return 0; } return (uint32)-1; }
使用しているベクタは、#0のひとつだけです。 引数に 1 を指定すると、PIT1割り込みに必要な処理をすべて行い、呼び出し元に 1 を返します。 すると、割り込み処理によって、LEDは勝手に点滅を行います。 引数に 0 を指定すると、PIT1の動作を停止し、呼び出し元に 0 を返します。 割り込みイベントが発生しないので、LEDの点滅は止まります。 引数にそれ以外の値を指定すると、呼び出し元に -1 を返します。
RAM上にある割り込みベクタテーブルの場所は、リンカ命令ファイル (lcf) に書いてあります。 プログラムからは、 "__VECTOR_RAM" というシンボルとして、この場所を知ることが出来ます。 ここでは、32ビット幅、256要素の配列を示すシンボルとして宣言しています。
割り込みサービス・ルーチンには、関数の型宣言の代わりに "__declspec(interrurpt)" を付けます。 すると、通常の関数呼び出しは保存されないレジスタの退避・回復などが行われ、呼び出し元に戻る際には、 RTS (ReTurn from Subroutine) の代わりに RTE (ReTurn from Exception) が使われるようになります。
使ってみよう
コンパイル後、"COLDFIRE.BIN"を作成し、転送したら、準備完了です。
OK ?UserDriver(0,1) 1
基板上の LED3 が点滅を始めます。
OK ?UserDriver(0,0) 0
LED3 の点滅が止まります。
OK ?UserDriver(0,10) -1
0 と 1 以外の引数は、受け付けられません。
参考文献
Interface (インターフェース) 2008年 12月号 [雑誌]
- 作者:
- 出版社/メーカー: CQ出版
- 発売日: 2008/10/25
- メディア: 雑誌
Interface (インターフェース) 2008年 09月号 [雑誌]
- 作者:
- 出版社/メーカー: CQ出版
- 発売日: 2008/07/25
- メディア: 雑誌
TELNET が勝手に切れちゃう [ColdFire V2]
SOUND ONLY
付録基板を telnet で接続していて、しばらく放って置くと切れちゃう現象を確認しました。 10分ぐらい置いて、久しぶりにターミナル・ソフトをつつくと、telnetサーバが応答してくれなくて、ターミナル・ソフトがあきらめて落ちちゃいます。 それでも、再度ターミナル・ソフトで接続すると、ちゃんと接続できますので、 telnet サーバが止まっているわけでもなさそうです。
この現象、色々と条件があるようで、再現が難しいのですが、ゲートウェイの存在しない隔離されたLAN環境で作業をしていると発生するみたいです。 原因は、いまだ不明ですが、落とさないためには、たまにつついてやるのが良いようです。
Sレコードをバイナリに変換するプログラム [ColdFire V2]
いよいよ、Sレコードからバイナリ・ファイル COLDFIRE.BIN を作成します。 あと、一歩だよ。
どこで誰がフォーマット変換するか
要するに、Sレコードを読み込んで、バイナリ・ファイルを作成してやれば良いのですが、この作業を誰にやらせるのかが、問題になります。 メモリが少ない環境で作成する場合には、Sレコードを一行ずつ呼んで、途中結果を逐一ファイルに書き出すことになると思います。 付録基板でプログラムを作成する場合にも、一行ずつ処理して、逐次FLASHに書き込むことになりますが、Sレコードの送受信でハンドシェイクが働くかどうかが心配です。
どうも、付録基板にやらせると、ハードルがいくつもありそうなので、まずは、PCでフォーマット変換する方法を考えました。 最近のPCで処理する場合には、Sレコードを全て読み込んで、主記憶上にメモリ・イメージを展開して、それを一気にファイルに書き出せば簡単です。 128kバイトのメモリなんて、十分小さいことでしょう。
じゃ~ん。フォーマット変換プログラム
というわけで、急ごしらえではありますが、フォーマット変換プログラムを作成しました。
size = 128*1024 offset = 128*1024 mem=[255 for x in xrange(0, size)] def parse(line): if line[0] != 'S': return -1, [] try: nibbles = [int(ch, 16) for ch in line[1:]] except ValueError: return -1, [] type = nibbles[0] bytes = [(nibbles[i*2+1] * 16 + nibbles[i*2+2]) for i in xrange(0, len(nibbles) / 2) ] if bytes[0] != len(bytes) - 1: return -1, [] csum = sum(bytes) & 255 if csum != 255: return -1, [] return type, bytes[1:-1] def type0(payload): print "".join([chr(ch) for ch in payload[2:]]) def write(address, data): address -= offset for x in data: # print "WRITE %02X to %08X" % (x, address) if address < 0: return False if address >= size: return False mem[address] = x address = address + 1 return True def type1(payload): address = payload[0] * 256 + payload[1] return write(address, payload[2:]) def type2(payload): address = (payload[0] * 256 + payload[1]) * 256 + payload[2] return write(address, payload[3:]) def type3(payload): address = ((payload[0] * 256 + payload[1]) * 256 + payload[2]) * 256 + payload[3] return write(address, payload[4:]) def type5(payload): return True def type7(payload): address = ((payload[0] * 256 + payload[1]) * 256 + payload[2]) * 256 + payload[3] print "Entry Point=%08X" % address return True def type8(payload): address = (payload[0] * 256 + payload[1]) * 256 + payload[2] print "Entry Point=%06X" % address return True def type9(payload): address = payload[0] * 256 + payload[1] print "Entry Point=%04X" % address return True def loadLine(line): if not line: return True (type, payload) = parse(line) if type == 0: return type0(payload) elif type == 1: return type1(payload) elif type == 2: return type2(payload) elif type == 3: return type3(payload) elif type == 5: return type5(payload) elif type == 7: return type7(payload) elif type == 8: return type8(payload) elif type == 9: return type9(payload) else: print "INVALID: %s\n" % line return False def loadFile(fileName): mem=[255 for x in xrange(0, size)] f = open(fileName, 'r') for line in f.readlines(): loadLine(line.strip('\n')) f.close() def saveFile(fileName): f = open(fileName, 'wb') for ch in mem: f.write(chr(ch)) f.close()
え~と、プログラミング言語は、どの環境でも実行できると思われる "Python" です。 クラスにすれば良かった。 条件分岐は連想を使えば良かった。 と、色々と突っ込みどころ満載ですので、どなたか適宜ブラッシュ・アップしてください。
このプログラムを smem.py という名前のファイルにして、 Python のコンソールから、手動で呼び出して使用します。
>>> import smem >>> smem.loadFile("C:/Projects/CW/CFCQ03/bin/MCF52233_INTERNAL_FLASH.elf.S19") Entry Point=00020000 >>> smem.saveFile("Z:/noritan/COLDFIRE.BIN") >>>
読み出しているSレコードは、前回の記事で作成したファイルです。 読み出すファイルと書き込むファイルは、各自の環境に合わせてください。
付録基板にバイナリ・ファイルを送り込む
Sレコードには、100バイトほどのプログラムしか書かれていませんが、出来上がったバイナリ・ファイルは、もれなく128kバイトの大きさがあります。 これを tftp でターゲットの付録基板に送り込みます。 もちろん、前々回の記事で紹介したベクタ・テーブルが書き込まれている付録基板を使用してください。
Z:\noritan>tftp -i 192.168.1.105 put COLDFIRE.BIN Transfer successful: 131072 bytes in 6 seconds, 21845 bytes/s
あっという間に転送完了です。 これで、ユーザ・ドライバが使用できるようになりました。
使ってみよう
今回作成したユーザ・ドライバは、引数の値に従って、三つのメッセージを返すという仕様でした。
OK PrStr(UserDriver(0,0)) HELLO WORLD!! OK PrStr(UserDriver(0,1)) Nice to meet you. OK PrStr(UserDriver(0,2)) Good bye. OK
いい感じです。 ユーザ・ドライバを入れ替えたくなったら、再度、バイナリ・ファイル COLDFIRE.BIN を送り込むだけで済みます。
今後の課題
たかが100バイトのプログラムに128kバイトのバイナリ・ファイルを作らせるなんて、もったいなさ過ぎます。 PCのプログラムが必要な所も、引っかかります。
そこで、次なる課題は、 SilentC のプログラムだけでSレコードを読み込ませて、 FLASH に書き込むことです。 さて、間に合うかな?
参考文献
Interface (インターフェース) 2008年 09月号 [雑誌]
- 作者:
- 出版社/メーカー: CQ出版
- 発売日: 2008/07/25
- メディア: 雑誌
なぜか、これも。
Interface (インターフェース) 2008年 12月号 [雑誌]
- 作者:
- 出版社/メーカー: CQ出版
- 発売日: 2008/10/25
- メディア: 雑誌
楽してユーザ・ドライバを開発する方法の探求 [ColdFire V2]
前回の記事で、ユーザ・ドライバのためのベクタ・テーブルを書き込みました。 しかし、肝心のプログラム本体を書き込んでいないので、使えるのはヘルプ表示だけです。 どうやったら、楽してユーザ・ドライバが書けるかな?
CodeWarriorをインストールするの?
MCF52233などの"ColdFire V2"には、Interface誌に付いてきた「CodeWarrior for ColdFire」が使えることは想像に難くないと思います。 でも、あの巨大な"CodeWarrior"をもうひとつインストールするのは、気が引けます。
その理由は、HCS08を使った事があれば、"ColdeFire V1"の開発環境が含まれた「CodeWarrior for Microcontrollers」がインストールされているからです。 こいつが、 "ColdFire V2" のコンパイルぐらいはやってくれないかなと画策しました。 ところが、致命的な問題が判明しました。
ColdFire V1 の CPU は、 ColdFire V2 の CPU のスーパー・セットである。
MCF52233 の命令セットは、 ISA (Instruction Set Architecture) Rev.A と呼ばれています。 一方、 MCF51QE128 の命令セットは、 ISA Rev.C という MCF52233 よりも新しいものです。 つまり、「CodeWarrior for Microcontrollers」でコンパイルしたプログラムは、 MCF52233 ではサポートしていない命令セットを生成してくれるので、使用することが出来ません。 反対に MCF52233 のプログラムを 「CodeWarrior for Microcontrollers」 のデバッガなどで扱うことは可能です。 MCF51QE128 のコンパイル結果の方が洗練されているように見えたのは、こういう理由があったのか。
と、いう訳で、不本意ながら「CodeWarrior for ColdFire」をインストールしました。
必要の無いものは、削除、削除
まずは、MCF52233 をターゲットとして、新規プロジェクトを作成します。
項目 | 設定 |
---|---|
Device | MCF52233 を選びます。 |
Connection | どれも使うつもりはないのだけど、P&E USB BDM Multilinkでも選んでおきますか。 |
Processor Expert | SilentC を使うのであれば、 Processor Expert を使える環境ではないので、 None を選びます。 |
Hardware Support | コンソールもシミュレーションも使う予定が無いので、 Minimal を選びます。 |
Compiler Optimization | 今は、がんばって最適化する必要も無いので Easy Debug を選びます。デバッガは使わないんだけどね。 |
新規プロジェクトには、三つのターゲットが定義されています。 このうち二つは必要ないので削除してしまいます。
ターゲット | 解説 |
---|---|
CONSOLE_INTERNAL_RAM | これは、プログラムをRAM上において実行させる設定で、デバッグ用にコンソールも利用します。ユーザ・ドライバ向きではないので、削除してしまいます。 |
INTERNAL_RAM | これも、プログラムをRAM上において実行させる設定ですが、コンソールは使用しません。ユーザ・ドライバ向きではないので、削除してしまいます。 |
INTERNAL_FLASH | プログラムをFLASHに置く、通常の組み込みプログラムを開発する設定です。ユーザ・ドライバの条件に最も近いので、これを使います。 |
不要なターゲットは、"Targets"タブから右クリックで"Delete"を選び削除します。
新規プロジェクトには、ユーザ・ドライバの作成には必要の無いファイルがたくさん出てきてしまいます。 これも、不要なものは削除してしまいます。
不要ファイル | 解説 |
---|---|
Sources/ board_sysinit.c | パワーON時の初期設定ルーチンが含まれています。初期設定は、SilentCがやってくれるので、このファイルは不要です。 |
Sources/ uart_support.c | UARTの最下層ルーチンが記述されています。使えるのかも知れませんが、今は使わないので削除してしまいます。 |
Sources/ exceptions.c | 例外ベクタ・テーブルと標準的な割り込みハンドラが定義されています。どちらもSilentCが提供してくれていますので、削除してしまいます。 |
Project Settings/ Startup Code/ startcf.c | リセット後の初期設定を行うルーチンが記述されています。これもSilentCが提供してくれるので、削除します。 |
Project Settings/ Startup Code/ cfm.c | プロテクション・キーなどが入ったFLASHのconfiguration領域の値が定義されています。ここは、ユーザ・ドライバが書くべき領域ではないので、ファイルも削除します。 |
不要なファイルは、 "Files" タブから右クリックで "Remove" を選び削除します。 これらのファイルは、 IDE の表示からは消えてしまいますが、ファイルそのものは削除されないので、後で回復させることも出来ます。
とりあえず、Make
ファイルを削除したら、とりあえず Make ボタンを押してみます。 すると、リンカ・エラーが発生します。
Link Error : Linker command file error at line 45 File not found: exceptions.c
リンカ命令ファイル(Linker command file)が、削除してしまった "exceptions.c" ファイルを参照していたようです。 リンカ命令ファイルは、 "Project Settings/ Linker Files/ MCF52233_INTERNAL_FLASH.lcf" にあります。
このファイルの45行目に問題の箇所がありました。
# .vectors : # { # exceptions.c(.vectortable) # . = ALIGN (0x4); # } > vectorrom
これは、一括してコメント・アウトしてしまいます。
Link Error : Undefined : "_startup"
このエラーは、リセット後のプログラムカウンタの値として指定された"_startup"が見つからないというものです。
これは、メニューの "Edit → INTERNAL_FLASH Settings..." ダイアログの "Linker → ColdFilre Linker" の "Entry Point" に書いてあります。 この項目は、削除してしまうと別のエラーが発生するので、とりあえず"_main"と入れておきます。
同じ場所にある "Force Active Symbols" に入っている "__vect,__cfm" も不要なので、削除します。
これで、エラー無く "Make" が出来るようになりました。
最初のユーザ・ドライバ
ここで、 "Make" していたのは、デフォルトの main 関数です。 このままでは、仕事をしてくれないので、ここで最初のユーザ・ドライバを "main.c" ファイルに書いてみます。
#include "support_common.h" /* include peripheral declarations and more */ uint32 main(uint32 arg); asm void jump_table(void) { jmp main // Driver #0 } uint32 main(uint32 arg) { char *msg; switch (arg) { case 0: msg = "HELLO WORLD!!\r\n"; break; case 1: msg = "Nice to meet you.\r\n"; break; default: msg = "Good bye.\r\n"; } return (uint32)msg; }
コメントは省いてあります。 また、条件付きでヘッダファイル stdio.h をインクルードする部分も削除してしまいました。
ユーザ・ドライバ0番は、与えられた引数に対応するメッセージ文字列を返す関数です。 "uint32" という型は、プログラムのどこにも宣言されていませんが、これは、インクルード・ファイル "support_common.h" のなかで宣言されています。 ユーザ・ドライバの規約では、 "uint32" 型の引数をひとつ受け取り、 "uint32" 型の値を返すことになっています。 このプログラムも無事に "Make" することが出来ます。
Sレコードを見てみよう
コンパイル後のコードは、プロジェクト・ディレクトリの "bin/ MCF52233_INTERNAL_FLASH.elf.S19" というファイルにSレコード形式で書き込まれます。
S0030000FC S321000005004E560000202E00084A80670A0C8000000001670A601041F900000534C3 S3210000051C600E41F900000544600641F90000055820084E5E4E7551FC48454C4CC6 S321000005384F20574F524C4421210D0A004E69636520746F206D65657420796F7587 S315000005542E0D0A00476F6F64206279652E0D0A001E S30900000564000000008D S31D00000568000005642000040000000004000000000000000000000000E4 S70500000500F5
コンパイル後のコードは、0x00000500から書き込まれています。 本来は、0x00020000から書き込みたいので、後で修正します。 6行にわたる"S3"レコードのうち、最初の4行はプログラムと文字定数です。 残りの2行は、さて、何でしょう。
リンカ命令ファイルの修正
プログラムの開始番地を変更するためには、リンカ命令ファイル(Linker command file)を変更します。 具体的には、 "Project Settings/ Linker Files/ MCF52233_INTERNAL_FLASH.lcf" を開いて、 "MEMORY" の宣言を変更します。
MEMORY { vectorrom (RX) : ORIGIN = 0x00000000, LENGTH = 0x00000400 cfmprotrom (RX) : ORIGIN = 0x00000400, LENGTH = 0x00000020 code (RX) : ORIGIN = 0x00020000, LENGTH = 0x00020000 vectorram (RWX) : ORIGIN = 0x20000000, LENGTH = 0x00000400 userram (RWX) : ORIGIN = 0x20007F80, LENGTH = 0x00000080 }
変更箇所は、 "code" と "userram" の二つです。 これで、プログラムを配置すべき場所と作業領域が指定されます。
"code" のサイズを 64k バイトから 128kバイトに変更しました。 (2008-10-23 06:34)
次に以下の箇所をコメント・アウトまたは削除します。
# .data : AT(___ROM_AT) # { # ___DATA_RAM = .; # . = ALIGN(0x4); # *(.exception) # . = ALIGN(0x4); # __exception_table_start__ = .; # EXCEPTION # __exception_table_end__ = .; # # ___sinit__ = .; # STATICINIT # __START_DATA = .; # # *(.data) # . = ALIGN (0x4); # __END_DATA = .; # # __START_SDATA = .; # *(.sdata) # . = ALIGN (0x4); # __END_SDATA = .; # # ___DATA_END = .; # __SDA_BASE = .; # . = ALIGN (0x4); # } >> userram
これは、静的変数領域の初期状態を格納する場所の宣言です。 最後から2行目の"S3"レコードがこれに相当します。 さらに以下の箇所もコメント・アウトまたは削除します。
# _romp_at = ___ROM_AT + SIZEOF(.data); # .romp : AT(_romp_at) # { # __S_romp = _romp_at; # WRITEW(___ROM_AT); # WRITEW(ADDR(.data)); # WRITEW(SIZEOF(.data)); # WRITEW(0); # WRITEW(0); # WRITEW(0); # }
これは、静的変数領域の場所と長さとその初期値を格納する場所の宣言です。 最後の"S3"レコードに相当します。
これらの静的変数領域の初期値情報を出さないようにしてしまうということは、静的変数は初期化されないということを示します。 通常のCのプログラムには、静的変数が初期化されていることを期待するプログラムもあるのですが、SilentCのユーザ・ドライバに限って言えば、静的変数は全く初期化されませんので、ユーザのプログラム内で明示的にクリアする必要があります。
リンカ命令ファイルには、他にも不要な記述があるのですが、今のところ、ワルサをする様子も無いので、そのままにしておきます。 ここまでの変更で、Sレコードファイルは、こうなりました。
S0030000FC S321000200004E560000202E00084A80670A0C8000000001670A601041F900020034C9 S3210002001C600E41F900020044600641F90002005820084E5E4E7551FC48454C4CCF S321000200384F20574F524C4421210D0A004E69636520746F206D65657420796F758A S315000200542E0D0A00476F6F64206279652E0D0A0021 S70500020000F8
開始アドレスも 0x00020000 からに変更されて、純粋なプログラムだけのスリムな姿になりました。
ジャンプ・テーブルが無い
次にシンボル・ファイルを確認してみます。 シンボル・ファイルは、 "bin/ MCF52233_INTERNAL_FLASH.elf.xMAP" にあります。 FLASHへのプログラムの配置を見てみると、ジャンプ・テーブル(jump_table)がありません。
# .text 00020000 00000034 .text main (main.c) 00020034 00000010 .rodata @15 (main.c) 00020044 00000014 .rodata @16 (main.c) 00020058 0000000C .rodata @17 (main.c) #>00020064 ___ROM_AT (linker command file) #>00020064 ___DATA_ROM (linker command file)
これは、メニューの "Edit → INTERNAL_FLASH Settings..." ダイアログの "Linker → ColdFilre Linker" の "Entry Point" を"_main"に書き換えてしまったのが原因です。 ここには、全てのプログラムをたどることが出来るシンボルを入れる必要があります。 このユーザ・ドライバの場合には、 "_main" に代えて "_jump_table" と入れます。 "Make" しなおすと、
# .text 00020000 00000008 .text jump_table (main.c) 00020008 00000034 .text main (main.c) 0002003C 00000010 .rodata @15 (main.c) 0002004C 00000014 .rodata @16 (main.c) 00020060 0000000C .rodata @17 (main.c) #>0002006C ___ROM_AT (linker command file) #>0002006C ___DATA_ROM (linker command file)
"jump_table" が並びました。
この後、「引数の渡し方を変更する」という項目がありましたが、不必要かつ有害なステップであることが判明しました。 謹んで訂正いたします。
Sレコードをバイナリ・ファイルに変換するには?
あとは、Sレコードファイルをバイナリ・ファイル "COLDFIRE.BIN" に変換して、 tftp で送り込むだけです。 ところで、バイナリ・ファイルへの変換って、どうやってやるの?
乞うご期待。
参考文献
Interface (インターフェース) 2008年 09月号 [雑誌]
- 作者:
- 出版社/メーカー: CQ出版
- 発売日: 2008/07/25
- メディア: 雑誌
ユーザ・ドライバ領域にベクタを書き込む [ColdFire V2]
SilentC のユーザ・ドライバ機能は、処理速度の遅いインタプリタを補うために必要です。 でも、 ColdFire にとって 4096 バイトの領域は決して十分とはいえません。 できれば、 0x00020000 からの 128k バイトの領域を優雅に使いたいところです。 いっそのこと、 0x00020000 からの領域へのベクタ・テーブルを書いて固定してしまおう。
SilentCだけで十分ですよ
"UserDriver" 領域に何か書き込むためには、バイナリ・ファイル "UserDriver.bin" を tftp で送り込んでおいて次回再起動の際に 0x00013000 からの領域へ SilentC に転記させるという方法が、公式とされています。 でも、バイナリ・ファイルを作らなきゃいけないし、ファイル・システムに 4096 バイトの空きが無ければ、ファイルを置くこともできないし。
今回書き込みたいのは、ベクタ・テーブルだけです。 このテーブルは、機械的な繰り返しで簡単に生成できるので、あらかじめ 4096 バイトのファイルを作る必要は無いと考えます。 そこで、 SilentC でベクタ・テーブルを書き込ませるプログラムを作成しました。
main(){ long *jump_table = 0x00013000; long table_size = 128; long hook_table = 0x00020000; long index; long *table_content; for (index = 0; index < table_size; index++) { if (jump_table[index] != -1) { PrStr("Jump Table not Erased\r\n"); return; } } table_content = MemoryAlloc(table_size * 4); for (index = 0; index < table_size; index++) { table_content[index] = hook_table + index * 6; } table_content[127] = 0x00013200; NvmWrite(jump_table+0xFFF00000, table_content, table_size * 4); MemoryFree(table_content); }
対応するベクタは、0から126の127種類です。 0x00020000 番地から6バイトずつ絶対番地 JMP 命令が並んでいるものとしてベクタを決めています。 また、ベクタ127だけは、 0x00013200 に分岐させて後述する別の用途で使用します。
00013000 0002 0000 dc.l hook_table+6*0 00013004 0002 0006 dc.l hook_table+6*1 00013008 0002 000C dc.l hook_table+6*2 0001300C 0002 0012 dc.l hook_table+6*3 : 000131F4 0002 02EE dc.l hook_table+6*125 000131F8 0002 02F4 dc.l hook_table+6*126 000131FC 0001 3200 dc.l 0x00013200
00013000 00 02 00 00 00 02 00 06 ........ 00013008 00 02 00 0c 00 02 00 12 ........ 00013010 00 02 00 18 00 02 00 1e ........ 00013018 00 02 00 24 00 02 00 2a ...$...* : 000131e0 00 02 02 d0 00 02 02 d6 ........ 000131e8 00 02 02 dc 00 02 02 e2 ........ 000131f0 00 02 02 e8 00 02 02 ee ........ 000131f8 00 02 02 f4 00 01 32 00 ......2.
ヘルプ表示機能
ベクタ・テーブルで消費したのは、512バイトです。 ユーザ・ドライバ領域は、 3.5k バイトも残っていますが、もともと固定して使うつもりでベクタ・テーブルを書いているので、プログラムを入れと本末転倒になってしまいます。 そこで、文字列形式のデータを入れておいて、その先頭アドレスをベクタ#127で返すようにしました。 プログラムは 0x00013200 から、データは 0x00013208 から入っています。
013200 203C 0001 3210 move.l #0x00013210,d0 013206 4E75 rts
プログラムは、こうなりました。
main(){ char *help_module = 0x00013200; long module_size = 0x00000E00; long program_size = 0x00000010; long content_size = module_size-program_size; char *help_file = "help.txt" long index; long fp; int *program; char *content; long actual_size; long addr; for (index = 0; index < module_size; index++) { if (help_module[index] != -1) { PrStr("Help module not Erased\r\n"); return; } } fp = OpenFile(help_file); if (fp == 0) { PrStr("Help file not found.\r\n"); return; } program = MemoryAlloc(program_size); content = MemoryAlloc(content_size); MemClear(program, program_size); MemClear(content, content_size); program[0] = 0x203c; // move.l #xx,d0 program[1] = help_module >> 16; program[2] = (help_module + program_size); program[3] = 0x4E75; // rts actual_size = ReadFile(fp, content, content_size-1); CloseFile(fp); actual_size = (actual_size + 4) & 0xFFFFFFFC; addr = help_module + 0xFFF00000; NvmWrite(addr, program, program_size); NvmWrite(addr + program_size, content, actual_size); MemoryFree(content); MemoryFree(program); }
文字列データ本体は、 "help.txt" というファイルに入れておいて書き込むようにしました。 ファイルのサイズが3576バイト以内に収まるように注意してください。 また、行末を "CR+LF" にするのもお忘れなく。 本当は、ネットワーク経由で送るようにすると、ファイル領域も喰わないし、もっと良いのでしょうね。
さて、文字列データの中身ですが、何を入れましょうか。 0x00000200 からの領域には、 SilentMoon オペレーティング・システムのシステム・コールへの入り口アドレスが記述されています。 ここでは、システム・コールのアドレス一覧を返すようにしてみました。 以下、実行後のメモリの内容です。
00013200 20 3c 00 01 32 10 4e 75 <..2.Nu 00013208 00 00 00 00 00 00 00 00 ........ 00013210 24 30 32 30 30 20 2d 20 $0200 - 00013218 4d 65 6d 6f 72 79 41 6c MemoryAl : 00013290 6d 65 72 0d 0a 24 30 32 mer..$02 00013298 31 43 20 2d 20 47 65 74 1C - Get 000132a0 54 69 6d 65 72 43 6f 75 TimerCou 000132a8 6e 74 0d 0a 00 00 00 00 nt...... 000132b0 ff ff ff ff ff ff ff ff ........
4バイト単位で必要なサイズだけ書き込んであります。
さあ、使ってみよう
早速、使ってみようと思いましたが、まだベクタ・テーブルを定義しただけで、肝心の中身がありません。 動くのは、ベクタ#127だけですね。
OK PrStr(UserDriver(127)) $0200 - MemoryAlloc $0204 - BufCopy $0208 - MemClear $020C - MemoryFree $0210 - SystemSleep $0214 - Sleep $0218 - CreateTimer $021C - GetTimerCount OK
ヘルプ・テキストは、まだ未完成です。
ベクタ・テーブルを書き換えたくなったら
このプログラムは、ユーザ・ドライバ領域が消去された状態であることを確認して、書き込み作業を行っています。 そのため、ユーザ・ドライバ領域を書き換える時にはあらかじめ消去する必要があります。 消去するためには、こんな命令を使います。
NvmErase(0xFFF13000); NvmErase(0xFFF13800);
プログラムに入れてしまっても良いのだけれど、うっかり走ってしまう事態を考えるとちょっと怖いので入れていません。 くれぐれもタイプミスにはご注意を。
参考文献
Interface (インターフェース) 2008年 09月号 [雑誌]
- 作者:
- 出版社/メーカー: CQ出版
- 発売日: 2008/07/25
- メディア: 雑誌
これも
Interface (インターフェース) 2008年 12月号 [雑誌]
- 作者:
- 出版社/メーカー: CQ出版
- 発売日: 2008/10/25
- メディア: 雑誌
eval関数を実現する (2) [ColdFire V2]
前回の記事では、プログラム・ファイルを作成する方法を考えました。 今回は、ファイルを作らずに済む方法を考えます。
タイマに実行させる
ファイルを書き換えるのではなく、もっと手軽な方法はないでしょうか。 「SilentC (Oct 10 2008)版」以降のアップデート版では、 "CreateTimer" 関数で func 引数に指定したスクリプトが動作するように変更されました。 この機能のスクリプトの実行部分だけを利用させてもらいます。
char done; eval(char *func){ char *command; char th; done=0; command=MemoryAlloc(StrLen(func)+32); StrCpy(command, "{"); StrCat(command, func); StrCat(command, ";done=1;}"); PrStr(command);PrStr("\r\n"); th=CreateTimer(0,1,command); while (!done) { SystemSleep(); } CreateTimer(th); MemoryFree(command); }
このプログラムでは、スクリプトの実行が終了したことをグローバル変数 "done" でメインルーチンに知らせています。
OK Main::eval("PrStr(\"HELLO WORLD\\r\\n\");") {PrStr("HELLO WORLD\r\n");;done=1;} HELLO WORLD OK Main::eval("PrStr(\"HELLO WORLD\\r\\n\");") {PrStr("HELLO WORLD\r\n");;done=1;} HELLO WORLD OK
タイマに渡したコマンドは、 "eval" 関数で渡したスクリプトに "done" をセットするステートメントを加えたものです。
何度か実行した限りは、うまく動いているように見えますが、タイマは周期的にスクリプトを実行しようとしますので、メインルーチンが "done" を検出する前にスクリプトの二回目の実行が動いてしまう可能性もあるので、確実に一回だけ実行するためには、タイマに渡すコマンドに "done" がクリアされているときに限りプログラムを実行する仕掛けを入れなくてはならないでしょう。
タイマを使わなくても
スクリプトつき "CreateTimer" 関数でやっているのは、実行すべきスクリプトの保存と内部フラグを立てるコールバックを持ったタイマの起動の二つです。 だったら、スクリプトを保存して、すぐに内部フラグを立てたら、スクリプトが実行されるんじゃないかな。 というわけで、プログラムにしてみました。
long *LibTimerFunc =0x20007F6A; char *LibTimerFlag =0x20007F7A; eval(char *func){ char *command; command=MemoryAlloc(StrLen(func)+32); StrCpy(command, "{"); StrCat(command, func); StrCat(command, "}"); PrStr(command);PrStr("\r\n"); LibTimerFlag[3]=0; LibTimerFunc[3]=command; LibTimerFlag[3]=1; // Invoke while (LibTimerFlag[3]) { SystemSleep(); } MemoryFree(command); }
スクリプト付きタイマは、最大4個まで対応しています。 ここでは、その最後のエントリである3番目を借りました。 "LibTimerFunc" の三番目のエントリにスクリプトを格納し、 "LibTimerFlag" の三番目のエントリを1にセットしてトリガをかけると、スクリプトの実行が始まります。
OK Main::eval("PrStr(\"HELLO WORLD\\r\\n\");") {PrStr("HELLO WORLD\r\n");} HELLO WORLD OK Main::eval("PrStr(\"HELLO WORLD\\r\\n\");") {PrStr("HELLO WORLD\r\n");} HELLO WORLD OK
お、何だかうまく動いているみたいです。 これなら、ファイルも使わないので、安心して使えますね。
参考文献
Interface (インターフェース) 2008年 09月号 [雑誌]
- 作者:
- 出版社/メーカー: CQ出版
- 発売日: 2008/07/25
- メディア: 雑誌
eval関数を実現する (1) [ColdFire V2]
SilentCは、インタプリタなので、逐次、字句解析を行って処理を行っています。 このため、プログラム実行中に作成した文字列を実行する事だって出来そうです。 あるいは、ネットワークを通じて、スクリプトを送り込むこともできます。 こんな事のできる、「eval関数が欲しい」な。 何だか、危険な香りがするぞ。
CGIを利用する
SilentC のhttpサーバには、 CGI と呼ばれる機能があり、HTML文書に埋め込まれたスクリプトを実行することができます。 外部からHTML文書を入れ替えて、そのHTML文書を呼び出せば、スクリプトが実行できます。
これと同等の機能を備えた関数をSilentCから呼び出すことが出来ます。
ReplaceCgi関数を使う
"ReplaceCgi"関数は、HTML文書に埋め込んだCGI表現を置き換えるために使われる関数です。 これを使って、実行すべき関数を外部から指定するプログラムを作成しました。
char *message; main(char *func){ char *buf; if (message==0) { message=MemoryAlloc(256); } buf=MemoryAlloc(256); MemClear(buf,256); StrCpy(buf,"$EXEC'Main::"); StrCat(buf,func); StrCat(buf,"()'"); PrStr(buf);PrStr("\r\n"); ReplaceCgi(buf,255); PrStr(message); PrStr(buf);PrStr("\r\n"); MemoryFree(buf); } a(){StrCpy(message,"HELLO");} b(){StrCpy(message,"GOOD BYE");}
実行してみると、
OK Main::main("a") $EXEC'Main::a()'
どこかに飛んでいってしまいました。 グローバル変数を使用したことに問題があるのかと思いましたが、別の方法で実行結果を受け取るようにしても、飛んでいってしまいました。 原因は、不明ですが、別の方法を考えましょう。
プログラム・ファイルを入れ替えちゃえ
SilentCで実行しているプログラムは、メモリ上に展開されたソースコードではなく、ファイルシステム上のファイルということになっています。 そのため、あるファイルのある関数をメインプログラムから呼び出す前に、呼び出されるファイルを入れ替えちゃえば実行すべきプログラムを書き換えるのは簡単です。
eval(char *func){ char *filename="f"; DeleteFile(filename); CreateFile(filename); WriteFile("run(){",6); WriteFile(func, StrLen(func)); WriteFile("}\r\n",3); CloseFile(0); f::run(); }
このサンプルでは、実行するスクリプトをeval関数に渡します。
OK Main::eval("PrStr(\"HELLO WORLD\\r\\n\");") HELLO WORLD OK type f run(){PrStr("HELLO WORLD\r\n");} OK
ファイル"f"にプログラムが書き込まれているのが分かります。 この方法の難点は、"eval"の実行のたびにファイル、つまりFLASHに書き込みが発生してしまうことです。
参考文献
Interface (インターフェース) 2008年 09月号 [雑誌]
- 作者:
- 出版社/メーカー: CQ出版
- 発売日: 2008/07/25
- メディア: 雑誌
TCPサーバは、無事に終了できるのか [ColdFire V2]
付録基板同士のクライアント・サーバ・システムで作成したサーバは、最後に Listen ソケット(親ソケット、ルート・ソケット、何て呼べばいいんだ?)をクローズするようにはなっていましたが、無限ループでリクエストを待っているので事実上クローズはされていませんでした。 某所で、「Listen ソケットをクローズすると SilentC が沈黙する」との話題がありましたので、検証してみます。
停止機能付きサーバプログラム
今回作成したサーバ・プログラムは、TCPでリクエストを受け付けて、コネクション終了後に処理を行うインタプリタです。
main() { char *recv_buf; char listen,soc; long stat; recv_buf = MemoryAlloc(256); do { listen = CreateSocket(1); if (listen < 0) { PrStr("Failed to open LISTEN\r\n"); break; } PrStr("Listen socket=");PrNum(listen);PrStr("\r\n"); Bind(listen, 30049, 1); for (;;) { GetNetLine(0,0,0,0); soc = Accept(listen, -1); if (soc < 0) break; do { stat = GetNetLine(soc, recv_buf, 255, -1); if (stat < 0) { recv_buf[0] = '\0'; } } while (0); CloseSocket(soc); GetNetLine(0,0,0,1); if (StrStr(recv_buf, "hello") != 0) { PrStr("HELLO\r\n"); } else if (StrStr(recv_buf, "bye") != 0) { PrStr("BYE BYE\r\n"); break; } else { PrStr("What ?\r\n"); } } CloseSocket(listen); } while (0); MemoryFree(recv_buf); }
通信文に"hello"が含まれていたら"HELLO"とコンソールに表示し、"bye"が含まれていたら"BYE BYE"と表示しLISTENソケットを閉じます。 それ以外は"What ?"と表示します。 こういう構成なので、 telnet でお話できます。
telnetで話しかけたら
プログラムが準備できました。 ColdFire でプログラムを実行しておいて、telnetで"hello"を送信します。 続いて、"bye"を送信すると、
OK run Listen socket=4 HELLO BYE BYE
げっ、確かに沈黙してしまいました。
どこで止まっているのだろう
"CloseSocket(listen)"をコメントアウトすると、プログラム自体は正常に終了しますが、ソケットが閉じられていないので、?CreateSocket(0)と打ち込むと新しいソケットが割り当てられます。 また、再度実行すると沈黙してしまいます。 どうやら、CloseSocket関数で止まっているのは間違いなさそうです。
深追いしたところ、"LISTEN"状態からソケットを閉じる状態遷移が定義されていないように見えます。 "LISTEN"から"CLOSED"へのアーク(弧)が定義されていないので、ソケットを閉じるための条件である"FIN"の到着を待っているのだけどそんなものは来ない。 といった所でしょうか。
コンテストの〆切が間近なのに、解析作業ばっかりで、作品に取りかかれないよ。
参考文献
Interface (インターフェース) 2008年 09月号 [雑誌]
- 作者:
- 出版社/メーカー: CQ出版
- 発売日: 2008/07/25
- メディア: 雑誌
外部ネットワークのIPアドレスはARPテーブルに登録されるか [ColdFire V2]
ARPテーブルには、外部ネットワークのIPアドレスも並ぶのか、調べました。
HTTPクライアントを作る
ARPテーブルは、IPアドレスとMACアドレスを関連付ける表です。 そのため、ルータの向こう側にある外部ネットワークのIPアドレスとルータのMACアドレスが関連付けられてもおかしくないはずです。 もし、外部ネットワークのIPアドレスがARPテーブルに登録されるとしたら、ARPテーブルはあっという間にパンクするでしょう。 外部ネットワークがARPテーブルに登録されるかどうか、確認する必要があります。
これを確認するためには、付録基板から外部ネットワークに接続するクライアントが必要になります。 ここは、ひとつ、HTTPクライアントでもつくってみますか。 以下のプログラムを"http"というファイルとして保存しました。
get(char *name){ char soc; long stat; long server; char *command; long n; char *getBase="GET / HTTP/1.0\r\nHost:"; char *buf; buf=MemoryAlloc(256); command=MemoryAlloc(StrLen(getBase)+StrLen(name)+16); StrCpy(command, getBase); StrCat(command, name); StrCat(command, "\r\n\r\n"); do { server=GetHostByName(name); if (server==0){PrStr("%% Server?\r\n");break;} GetNetLine(0,0,0,0); soc=CreateSocket(1); if (soc<0){PrStr("%% Soc?\r\n");break;} do { PrStr("%% Connect HTTP server ");PrStr(name); PrStr("\r\n"); stat=Connect(soc,server,80); if (stat<0){PrStr("%% HTTP?\r\n");break;} PrStr("%% GET request\r\n"); stat=Write(soc,command,StrLen(command)); if (stat<0) break; stat=WaitWriteComplete(soc); if (stat<0) break; for(n=0;n<15;n++){ stat=GetNetLine(soc,buf,255,100); if (stat<0) break; PrStr(buf);PrStr("\r\n"); } } while (0); PrStr("%% Close\r\n"); CloseSocket(soc); GetNetLine(0,0,0,1); } while (0); MemoryFree(command); MemoryFree(buf); }
このプログラムの原案は、masatoさんの記事にあったHTTPサーバです。 引数で与えられたホストのHTTPサーバに接続し、ルート・ページの最初から15行を表示します。
ネットワーク内で確かめた
最初は、ネットワーク内のHTTPサーバに接続します。 ターゲットは、別の付録基板です。
arp::show 192.168.1.102 00-1f-e8-49-**-76 192.168.1.1 00-15-c5-5b-**-38 OK http::get("192.168.1.105") %% Connect HTTP server 192.168.1.105 %% GET request HTTP/1.0 200 OK Content-Type: text/html Content-Length: 409 <HTML> <HEAD> <META http-equiv="Content-Type" content="text/html; charset=Shift_JIS"> <TITLE>トップページ</TITLE></HEAD> <BODY><CENTER> <H1><FONT size="6" face="Arial Black">インターフェース ColdFireボード</FONT></H1> <BR><H2><A href="netconfig.htm">ネットワーク設定</A></H2> <BR><H2><A href="accel.htm">加速度センサー</A></H2> <BR><H2><A href="port.htm">ポート操作</A></H2> </CENTER></BODY> %% Close OK arp::show 192.168.1.102 00-1f-e8-49-**-76 192.168.1.1 00-15-c5-5b-**-38 192.168.1.105 00-1f-e8-49-**-bf OK
"192.168.1.1"は、コンソール兼ルータとして使っているPCです。 HTTPサーバに接続すると"192.168.1.105"のエントリが追加されたのがわかります。
外部ネットワークで確かめた
それでは、いよいよ外部ネットワークに接続します。 ターゲットは、"www.yahoo.com"にお願いしました。
http::get("www.yahoo.com") %% Connect HTTP server www.yahoo.com %% GET request HTTP/1.1 200 OK Date: Thu, 09 Oct 2008 13:22:50 GMT P3P: policyref="http://info.yahoo.com/w3c/p3p.xml", CP="CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV" Cache-Control: private Vary: User-Agent X-XRDS-Location: http://open.login.yahooapis.com/openid20/www.yahoo.com/xrds Last-Modified: Thu, 09 Oct 2008 13:20:34 GMT Accept-Ranges: bytes Content-Length: 9554 Connection: close Content-Type: text/html; charset=utf-8 <html> <head> <title>Yahoo!</title> %% Close OK arp::show 192.168.1.102 00-1f-e8-49-**-76 192.168.1.1 00-15-c5-5b-**-38 OK
ご覧のように、ARPテーブルには、"www.yahoo.com"のエントリは追加されていません。 油断していたら、"192.168.1.105"のエントリも削除されてしまっていました。
今日の考察
- 外部ネットワークに接続してもARPテーブルには記録されない。
参考文献
Interface (インターフェース) 2008年 09月号 [雑誌]
- 作者:
- 出版社/メーカー: CQ出版
- 発売日: 2008/07/25
- メディア: 雑誌
(続)付録基板が発行するシーケンス番号を調べた [ColdFire V2]
前回の記事で衝突が確認されたシーケンス番号を取りなおしました。
シーケンス番号一覧
着目したのは、3ウェイ・ハンドシェイクの二つ目のサーバから送られた"[SYN, ACK]"パケットです。
コネクション番号 | サーバ側シーケンス番号(Seq) | クライアント側シーケンス番号(Ack) |
---|---|---|
0 | 0x0115A007 | 0x0109263A |
1 | 0x011C0007 | 0x011C050A |
2 | 0x01226002 | 0x01226506 |
3 | 0x0128D920 | 0x0128C50A |
4 | 0x012F2002 | 0x012F2506 |
5 | 0x01359919 | 0x0135850A |
6 | 0x013BF91C | 0x013BE506 |
7 | 0x01425919 | 0x0142450A |
8 | 0x0148B916 | 0x0148A50A |
9 | 0x014F1914 | 0x014F050B |
サーバ基板とクライアント基板は、電源分配器につないで同時に電源を投入したため、シーケンス番号の初期値が近い値を示していることがわかります。 また、これらの初期値は、時間を追うごとに値が増えていきます。
何をしたのかは、今はナイショ。
参考文献
Interface (インターフェース) 2008年 09月号 [雑誌]
- 作者:
- 出版社/メーカー: CQ出版
- 発売日: 2008/07/25
- メディア: 雑誌
ARPテーブルを探求する [ColdFire V2]
そろそろ、複数の付録基板同士で通信をさせてみたくなりました。 付録基板もネットワーク機器としての当然の機能としてARP (Address Resolution Protocol) テーブルを持っています。 ちょっと、のぞいてみましょう。
ARPテーブル表示プログラム
ARPテーブルは、0x20000440番地から6エントリ分あります。 もちろん、ダンプ・リストで見ることもできますが、せっかくだから表示させるプログラムを作りました。
show(){ long i, j, *plong; char *p; p=0x20000440; for(i=0;i<6;i++,p+=16){ if (p[0]==0) continue; plong=p+12; PrAdrs(*plong);PrStr(" "); for(j=0;j<6;j++){ if(j>0) PrChar('-'); PrHexByte(p[j+4]); } PrStr("\r\n"); } }
これを"arp"という名前のファイルとして保存して使います。
OK arp::show 192.168.1.106 00-1f-e8-49-**-d4 192.168.1.2 00-15-c5-5b-**-38 OK
桁そろえ関数は、装備されていないんだよな。
複数のホストにUDPパケットを送るプログラム
ここからが、本日の本題です。 一枚の付録基板から複数の付録基板およびPCにUDPパケットを送ります。 ひとつのプログラムで送り先の数を可変できるようにプログラムを工夫してみました。
main(int n){ arp::show(); PrStr("\r\n"); switch(n){ case 7: send("192.168.1.10"); case 6: send("192.168.1.102"); case 5: send("192.168.1.103"); case 4: send("192.168.1.104"); case 3: send("192.168.1.105"); case 2: send("192.168.1.106"); default: send("192.168.1.2"); } PrStr("\r\n"); arp::show(); } send(char *host){ char soc, err_code; long ip; soc=CreateSocket(0); ip=GetHostByName(host); err_code=SendTo(soc, ip, 30049, host, StrLen(host)+1); CloseSocket(soc); PrStr(host);PrStr(" : err_code="); PrNum(err_code);PrStr("\r\n"); }
最後のIPアドレス以外は、すべて付録基板です。 受け側のポートは開いていないので、送りっぱなしになります。
6個のホストにパケットを送る
自分自身(192.168.1.106)も含めて、6個のホストに対してパケットを送ります。
Main::main(6) 192.168.1.106 00-1f-e8-49-**-d4 192.168.1.102 : err_code=0 192.168.1.103 : err_code=0 192.168.1.104 : err_code=0 192.168.1.105 : err_code=0 192.168.1.106 : err_code=22 192.168.1.2 : err_code=0 192.168.1.106 00-1f-e8-49-**-d4 192.168.1.102 ff-ff-ff-ff-ff-ff 192.168.1.103 00-1f-e8-49-**-79 192.168.1.104 00-1f-e8-49-**-e8 192.168.1.105 00-1f-e8-49-**-bf 192.168.1.2 00-15-c5-5b-**-38 OK
最初は、ARPテーブルには、自分自身だけが登録されてるので、その他のホストに対してはARP要求だけが発信されます。 このため、これらのホストについては、"err_code=0"が返されます。 パケット送信後は、ほとんどのホストのMACアドレスについては解決していますが、"192.168.1.102"だけは応答が返ってきていないらしく、暫定値ff-ff-ff-ff-ff-ffが入っています。 パケット・モニタで見ると、こうなりました。
#1 0.000000 Kurusuga_49:**:d4 Broadcast ARP Who has 192.168.1.103? Tell 192.168.1.106 #2 0.000017 Kurusuga_49:**:79 Kurusuga_49:**:d4 ARP 192.168.1.103 is at 00:1f:e8:49:**:79 #3 0.013762 Kurusuga_49:**:d4 Broadcast ARP Who has 192.168.1.104? Tell 192.168.1.106 #4 0.013820 Kurusuga_49:**:e8 Kurusuga_49:**:d4 ARP 192.168.1.104 is at 00:1f:e8:49:**:e8 #5 0.027552 Kurusuga_49:**:d4 Broadcast ARP Who has 192.168.1.105? Tell 192.168.1.106 #6 0.027649 Kurusuga_49:**:bf Kurusuga_49:**:d4 ARP 192.168.1.105 is at 00:1f:e8:49:**:bf #7 0.041418 192.168.1.106 192.168.1.106 UDP Source port: 1024 Destination port: 30049 #8 0.055215 Kurusuga_49:**:d4 Broadcast ARP Who has 192.168.1.2? Tell 192.168.1.106 #9 0.055231 Dell_5b:**:38 Kurusuga_49:**:d4 ARP 192.168.1.2 is at 00:15:c5:5b:**:38 #10 2.157876 Kurusuga_49:**:d4 Broadcast ARP Who has 192.168.1.102? Tell 192.168.1.106 #11 2.157917 Kurusuga_49:**:76 Kurusuga_49:**:d4 ARP 192.168.1.102 is at 00:1f:e8:49:**:76
プログラム上、"192.168.1.102"が一番にパケットの送信を要求したはずなのですが、これを見るとARP要求は後回しにされちゃったのですね。
Main::main(6) 192.168.1.106 00-1f-e8-49-**-d4 192.168.1.102 00-1f-e8-49-**-76 192.168.1.103 00-1f-e8-49-**-79 192.168.1.104 00-1f-e8-49-**-e8 192.168.1.105 00-1f-e8-49-**-bf 192.168.1.2 00-15-c5-5b-**-38 192.168.1.102 : err_code=22 192.168.1.103 : err_code=22 192.168.1.104 : err_code=22 192.168.1.105 : err_code=22 192.168.1.106 : err_code=22 192.168.1.2 : err_code=20 192.168.1.106 00-1f-e8-49-**-d4 192.168.1.102 00-1f-e8-49-**-76 192.168.1.103 00-1f-e8-49-**-79 192.168.1.104 00-1f-e8-49-**-e8 192.168.1.105 00-1f-e8-49-**-bf 192.168.1.2 00-15-c5-5b-**-38 OK
二回目の実行は、全てのホストのMACアドレスが解決しているので、すんなりと実行されます。
#12 11.107054 192.168.1.106 192.168.1.102 UDP Source port: 1024 Destination port: 30049 #13 11.120758 192.168.1.106 192.168.1.103 UDP Source port: 1024 Destination port: 30049 #14 11.134493 192.168.1.106 192.168.1.104 UDP Source port: 1024 Destination port: 30049 #15 11.148224 192.168.1.106 192.168.1.105 UDP Source port: 1024 Destination port: 30049 #16 11.162056 192.168.1.106 192.168.1.106 UDP Source port: 1024 Destination port: 30049 #17 11.175863 192.168.1.106 192.168.1.2 UDP Source port: 1024 Destination port: 30049
パケットの送受信もきれいです。 三回目の実行も同様でしたので、ここでは省略します。
7個のホストにパケットを送る
さらにホストの数を7個に増やします。
Main::main(7) 192.168.1.106 00-1f-e8-49-**-d4 192.168.1.102 00-1f-e8-49-**-76 192.168.1.103 00-1f-e8-49-**-79 192.168.1.104 00-1f-e8-49-**-e8 192.168.1.105 00-1f-e8-49-**-bf 192.168.1.2 00-15-c5-5b-**-38 192.168.1.10 : err_code=0 192.168.1.102 : err_code=22 192.168.1.103 : err_code=0 192.168.1.104 : err_code=0 192.168.1.105 : err_code=0 192.168.1.106 : err_code=22 192.168.1.2 : err_code=20 192.168.1.106 00-1f-e8-49-**-d4 192.168.1.105 00-1f-e8-49-**-bf 192.168.1.103 00-1f-e8-49-**-79 192.168.1.10 00-1f-e8-49-**-2d 192.168.1.104 00-1f-e8-49-**-e8 192.168.1.2 00-15-c5-5b-**-38 OK
7個中4個について失敗したという実行結果ですが、実際にはそれだけではありません。 "192.168.1.10"への送信時に長時間待たされてしまいました。
#24 61.166842 Kurusuga_49:**:d4 Broadcast ARP Who has 192.168.1.2? Tell 192.168.1.106 #25 61.166860 Dell_5b:**:38 Kurusuga_49:**:d4 ARP 192.168.1.2 is at 00:15:c5:5b:**:38 #26 61.166874 Kurusuga_49:**:d4 Broadcast ARP Who has 192.168.1.10? Tell 192.168.1.106 #27 61.166935 Kurusuga_49:**:2d Kurusuga_49:**:d4 ARP 192.168.1.10 is at 00:1f:e8:49:**:2d #28 61.180441 192.168.1.106 192.168.1.102 UDP Source port: 1024 Destination port: 30049 #29 61.194215 Kurusuga_49:**:d4 Broadcast ARP Who has 192.168.1.103? Tell 192.168.1.106 #30 61.194231 Kurusuga_49:**:79 Kurusuga_49:**:d4 ARP 192.168.1.103 is at 00:1f:e8:49:**:79 #31 61.208001 Kurusuga_49:**:d4 Broadcast ARP Who has 192.168.1.104? Tell 192.168.1.106 #32 61.208016 Kurusuga_49:**:e8 Kurusuga_49:**:d4 ARP 192.168.1.104 is at 00:1f:e8:49:**:e8 #33 64.167279 Kurusuga_49:**:d4 Broadcast ARP Who has 192.168.1.105? Tell 192.168.1.106 #34 64.167296 Kurusuga_49:**:bf Kurusuga_49:**:d4 ARP 192.168.1.105 is at 00:1f:e8:49:**:bf #35 64.181126 192.168.1.106 192.168.1.106 UDP Source port: 1024 Destination port: 30049 #36 64.194973 192.168.1.106 192.168.1.2 UDP Source port: 1024 Destination port: 30049
パケット・モニタで見ると原因が分かりました。 ARP要求が発行されずに待ち時間が発生したようです。 どうやら、ARPテーブルに空きが無くなった場合、古いエントリを削除するのではなく、いずれかのエントリが時間切れになるのを待っているようです。 有効期限は実験的に60秒と判断しました。
Main::main(7) 192.168.1.106 00-1f-e8-49-**-d4 192.168.1.105 00-1f-e8-49-**-bf 192.168.1.103 00-1f-e8-49-**-79 192.168.1.10 00-1f-e8-49-**-2d 192.168.1.104 00-1f-e8-49-**-e8 192.168.1.2 00-15-c5-5b-**-38 192.168.1.10 : err_code=21 192.168.1.102 : err_code=0 192.168.1.103 : err_code=0 192.168.1.104 : err_code=0 192.168.1.105 : err_code=22 192.168.1.106 : err_code=22 192.168.1.2 : err_code=20 192.168.1.106 00-1f-e8-49-**-d4 192.168.1.105 00-1f-e8-49-**-bf 192.168.1.103 00-1f-e8-49-**-79 192.168.1.102 ff-ff-ff-ff-ff-ff 192.168.1.104 00-1f-e8-49-**-e8 192.168.1.2 00-15-c5-5b-**-38
二回目の実行でも待ち時間が発生しました。
#37 81.911598 192.168.1.106 192.168.1.10 UDP Source port: 1024 Destination port: 30049 #38 122.175817 Kurusuga_49:**:d4 Broadcast ARP Who has 192.168.1.2? Tell 192.168.1.106 #39 122.175834 Dell_5b:**:38 Kurusuga_49:**:d4 ARP 192.168.1.2 is at 00:15:c5:5b:**:38 #40 122.175848 Kurusuga_49:**:d4 Broadcast ARP Who has 192.168.1.102? Tell 192.168.1.106 #41 122.175957 Kurusuga_49:**:76 Kurusuga_49:**:d4 ARP 192.168.1.102 is at 00:1f:e8:49:**:76 #42 122.189641 Kurusuga_49:**:d4 Broadcast ARP Who has 192.168.1.103? Tell 192.168.1.106 #43 122.189730 Kurusuga_49:**:79 Kurusuga_49:**:d4 ARP 192.168.1.103 is at 00:1f:e8:49:**:79 #44 122.203479 Kurusuga_49:**:d4 Broadcast ARP Who has 192.168.1.104? Tell 192.168.1.106 #45 122.203496 Kurusuga_49:**:e8 Kurusuga_49:**:d4 ARP 192.168.1.104 is at 00:1f:e8:49:**:e8 #46 122.217283 192.168.1.106 192.168.1.105 UDP Source port: 1024 Destination port: 30049 #47 122.231076 192.168.1.106 192.168.1.106 UDP Source port: 1024 Destination port: 30049 #48 122.244911 192.168.1.106 192.168.1.2 UDP Source port: 1024 Destination port: 30049
#38のARP要求がいずれかのエントリ(この場合は、192.168.1.10)が時間切れになるまで待たされたようです。
本日の考察
- 付録基板のARPテーブル長は、自身も含めて6エントリである。
- ARPテーブルが足りなくなったら、いずれかのエントリの有効期限が切れて欠員が出るまで待つ。
いわゆる"arp"コマンドのように、SilentCから積極的にARPテーブルを操作することは、できないようです。 私が考える最終形態は、付録基板7枚とPCという構成だから、6エントリじゃ足りません。 ネットワークを工夫するしかないかな。
参考文献
Interface (インターフェース) 2008年 09月号 [雑誌]
- 作者:
- 出版社/メーカー: CQ出版
- 発売日: 2008/07/25
- メディア: 雑誌
SilentC で作った乱数生成プログラム [ColdFire V2]
main(){ long seed=0x5180009F; long i; for (i=0;i<20;i++){ seed+=102; seed = ((seed << 8) ^ 0x31415926) + 0x65358979; if (seed < 0) seed = ~seed; PrHex(seed); PrStr("\r\n"); } }
102は、魔法の数字です。
OK run 1675e59f 56231a60 7791289f 059de59f 11dce59f 52dce59f 52dce59f 52dce59f 52dce59f 52dce59f 52dce59f 52dce59f 52dce59f 52dce59f 52dce59f 52dce59f 52dce59f 52dce59f 52dce59f 52dce59f OK
あれっ?
付録基板が発行するシーケンス番号を調べた [ColdFire V2]
前回実験したクライアント・サーバ・システムのパケットで、不可解な[TCP Retransmission]がレポートされていました。 今回は、その原因を探ります。
シーケンス番号一覧
前回は簡易レポートしか見ていませんでしたが、今回はパケットの中身まで深く調べました。 その結果、使い回しされていたのがポート番号だけではなかったことが判明してしまいました。
#7 2.014995 1024 > 30049 [SYN] Seq=0 Win=1454 Len=0 #8 2.015247 30049 > 1024 [SYN, ACK] Seq=0 Ack=1 Win=1454 Len=0 #9 2.015419 1024 > 30049 [ACK] Seq=1 Ack=1 Win=1454 Len=0
着目したのは、3ウェイ・ハンドシェイクの二つ目のサーバから送られたパケットです。 この簡易レポートでは、"Seq=0 Ack=1"となっていますが、これらの値は、相対値でしかありません。 そのため、これらの絶対値を表にしてみました。
コネクション番号 | サーバ側シーケンス番号(Seq) | クライアント側シーケンス番号(Ack) |
---|---|---|
0 | 0x5180009F | 0x5180C8A0 |
1 | 0x1675E59F | 0x16BE1DA0 |
2 | 0x56231A60 | 0x0B6B1A61 |
3 | 0x7791289F | 0x406ED761 |
4 | 0x059DE59F | 0x3B33D761 |
5 | 0x11DCE59F | 0x67CC28A0 |
6 | 0x52DCE59F | 0x629DE5A0 |
7 | 0x52DCE59F | 0x11DCE5A0 |
8 | 0x52DCE59F | 0x52DCE5A0 |
9 | 0x52DCE59F | 0x52DCE5A0 |
なんと、サーバ側のシーケンス番号は、6番目のコネクションで0x52DCE59Fに収束してしまい、クライアント側のシーケンス番号も9番目と10番目で(0x52DCE59F+1)に収束してしまいました。 道理で Wireshark が「再送」と解釈してしまうわけです。 問題は、シーケンス番号の生成アルゴリズムにありそうです。
それにしても、"E59F"というのは、何のおまじない?
参考文献
Interface (インターフェース) 2008年 09月号 [雑誌]
- 作者:
- 出版社/メーカー: CQ出版
- 発売日: 2008/07/25
- メディア: 雑誌
付録基板同士のクライアント・サーバ・システム [ColdFire V2]
MCF52233付録基板を二枚使って、TCPによるサーバ・クライアント通信をさせてみました。 パケット・モニタでの監視も行います。
サーバは、これだ
サーバは、クライアントから到着したメッセージの末尾に'*'を加えてクライアントに返信します。
main() { char *send_buf,*recv_buf; char soc,newsoc,err_code; send_buf = MemoryAlloc(32); soc = CreateSocket(1); Bind(soc, 30049, 1); for (;;) { newsoc = Accept(soc, -1); if (newsoc < 0) break; do { err_code = Read(newsoc, -1); if (err_code < 0) break; recv_buf = GetReceiveBuffer(newsoc,1); StrCpy(send_buf, recv_buf); StrCat(send_buf, "*"); MemoryFree(recv_buf); Write(newsoc, send_buf, StrLen(send_buf)); err_code = WaitWriteComplete(newsoc); if (err_code < 0) break; } while (0); CloseSocket(newsoc); } CloseSocket(soc); MemoryFree(send_buf); }
クライアントは、これだ
クライアントは、1秒インターバルでサーバに短い電文"I=?"を送り、サーバからの返信を受け取ります。 全部で10回のコネクションを行います。
main() { char *send_buf,*recv_buf; char soc,err_code; long i; send_buf = MemoryAlloc(32); for (i = 0; i < 10; i++) { soc = CreateSocket(1); if (soc < 0) continue; do { err_code = Connect(soc,GetHostByName("192.168.1.103"),30049); if (err_code < 0) break; StrCpy(send_buf, "I="); GetDigit(i, &send_buf[2]); Write(soc, send_buf, StrLen(send_buf)); err_code = WaitWriteComplete(soc); if (err_code < 0) break; err_code = Read(soc, -1); if (err_code < 0) break; recv_buf = GetReceiveBuffer(soc,1); MemoryFree(recv_buf); } while (0); CloseSocket(soc); Sleep(100); } MemoryFree(send_buf); }
リピータ・ハブでパケットのモニタリングもできる
中古のリピータ・ハブ(通称バカ・ハブ)を入手したので、パケット・モニタでクライアントとサーバの通信の様子を見てみます。
#7 2.014995 1024 > 30049 [SYN] Seq=0 Win=1454 Len=0 #8 2.015247 30049 > 1024 [SYN, ACK] Seq=0 Ack=1 Win=1454 Len=0 #9 2.015419 1024 > 30049 [ACK] Seq=1 Ack=1 Win=1454 Len=0
今回は、IPアドレスとプロトコルのカラムを削除しました。 今までどおりの3ウェイ・ハンドシェイクです。 それぞれを Ethernet フレームの単位で詳しく見たところ、60バイトのフレームの最後の6バイトは、 "Trailer" と書いてあり、 00 で埋まっています。 54バイトじゃ、だめなの?
#10 2.019569 1024 > 30049 [PSH, ACK] Seq=1 Ack=1 Win=1454 Len=3 #11 2.019672 30049 > 1024 [ACK] Seq=1 Ack=4 Win=1454 Len=0
#10で"I=0"をサーバに送り、#11でサーバから受信確認が届きます。 いずれのフレームも60バイトで、 Trailer の長さがそれぞれ3バイト、6バイトと異なっています。
#12 2.025681 30049 > 1024 [PSH, ACK] Seq=1 Ack=4 Win=1454 Len=4 #13 2.025938 1024 > 30049 [ACK] Seq=4 Ack=5 Win=1454 Len=0
#12で"I=0*"がサーバから返され、#13でサーバに受信確認が届きます。 いずれのフレームも60バイトで、 Trailer の長さはそれぞれ2バイトと6バイトです。 #12の Trailer には 00 が入っていますが、#13の Trailer には "I=0" が入っています。 何だこりゃ?
#14 2.027416 30049 > 1024 [FIN, ACK] Seq=5 Ack=4 Win=1454 Len=0 #15 2.028981 1024 > 30049 [FIN, ACK] Seq=4 Ack=5 Win=1454 Len=0 #16 2.028990 [TCP Keep-Alive] 1024 > 30049 [ACK] Seq=4 Ack=6 Win=1454 Len=0 #17 2.029080 [TCP Keep-Alive] 30049 > 1024 [ACK] Seq=5 Ack=5 Win=1454 Len=0
コネクションの終了要求は、サーバとクライアントからほぼ同時に発行され、それに対する"ACK"が送信されます。 いずれも60バイトのフレームで、 Trailer に "I=0" と "I=0*" が残ったままです。 なぜ、#16と#17が「TCP Keep-Alive」と判断されたのかは、わかりません。 一言、「This is a TCP keep-alive segment」とだけ表示されています。
#18 2.029693 1024 > 30049 [RST] Seq=5 Win=1454 Len=0
"RST"というのが、初めて出てきました。 コネクションを強制的に終了するのですね。
#19 3.035180 [TCP Port numbers reused] 1024 > 30049 [SYN] Seq=0 Win=1454 Len=0 #20 3.035420 30049 > 1024 [SYN, ACK] Seq=0 Ack=1 Win=1454 Len=0 #21 3.035593 1024 > 30049 [ACK] Seq=1 Ack=1 Win=1454 Len=0 #22 3.039698 1024 > 30049 [PSH, ACK] Seq=1 Ack=1 Win=1454 Len=3 #23 3.039980 30049 > 1024 [ACK] Seq=1 Ack=4 Win=1454 Len=0 #24 3.045840 30049 > 1024 [PSH, ACK] Seq=1 Ack=4 Win=1454 Len=4 #25 3.046068 1024 > 30049 [ACK] Seq=4 Ack=5 Win=1454 Len=0 #26 3.047556 30049 > 1024 [FIN, ACK] Seq=5 Ack=4 Win=1454 Len=0 #27 3.049124 1024 > 30049 [FIN, ACK] Seq=4 Ack=5 Win=1454 Len=0 #28 3.049257 [TCP Keep-Alive] 1024 > 30049 [ACK] Seq=4 Ack=6 Win=1454 Len=0 #29 3.049292 [TCP Keep-Alive] 30049 > 1024 [ACK] Seq=5 Ack=5 Win=1454 Len=0 #30 3.049891 1024 > 30049 [RST] Seq=5 Win=1454 Len=0
二つ目のコネクションは、#22で"I=1"をサーバに送り、#24で"I=1*"をサーバから受け取ります。 #19の「TCP Port numbers reused」でポート番号が再利用されたことがわかります。 いずれのフレームも60バイトでした。
#115 11.196476 1024 > 30049 [SYN] Seq=0 Win=1454 Len=0 #116 11.196695 30049 > 1024 [SYN, ACK] Seq=0 Ack=1 Win=1454 Len=0 #117 11.196900 1024 > 30049 [ACK] Seq=1 Ack=1 Win=1454 Len=0 #118 11.201017 [TCP Retransmission] 1024 > 30049 [PSH, ACK] Seq=1 Ack=1 Win=1454 Len=3 #119 11.201248 30049 > 1024 [ACK] Seq=1 Ack=4 Win=1454 Len=0 #120 11.207180 [TCP Retransmission] 30049 > 1024 [PSH, ACK] Seq=1 Ack=4 Win=1454 Len=4 #121 11.207406 [TCP Keep-Alive] 1024 > 30049 [ACK] Seq=4 Ack=5 Win=1454 Len=0 #122 11.208912 30049 > 1024 [FIN, ACK] Seq=5 Ack=4 Win=1454 Len=0 #123 11.210478 1024 > 30049 [FIN, ACK] Seq=4 Ack=5 Win=1454 Len=0 #124 11.210613 [TCP Keep-Alive] 1024 > 30049 [ACK] Seq=4 Ack=6 Win=1454 Len=0 #125 11.210640 [TCP Keep-Alive] 30049 > 1024 [ACK] Seq=5 Ack=5 Win=1454 Len=0 #126 11.211247 1024 > 30049 [RST] Seq=5 Win=1454 Len=0
順調に通信をこなしましたが、最後の10回目のコネクションで「TCP Retransmission」が発生しています。 Wiresharkの解説によると、#118は#111の、#120は#110の再送と理解されたようです。
#110 10.188839 30049 > 1024 [FIN, ACK] Seq=5 Ack=4 Win=1454 Len=0 #111 10.190404 1024 > 30049 [FIN, ACK] Seq=4 Ack=5 Win=1454 Len=0 #112 10.190462 [TCP Keep-Alive] 1024 > 30049 [ACK] Seq=4 Ack=6 Win=1454 Len=0 #113 10.190534 [TCP Keep-Alive] 30049 > 1024 [ACK] Seq=5 Ack=5 Win=1454 Len=0 #114 10.191173 1024 > 30049 [RST] Seq=5 Win=1454 Len=0
でも、#110と#111は、前回のコネクションの終了要求パケットですよ。 しかも#114で"RST"まで発行されているし。 想像するに、ポート番号の使い回しが激しすぎたので、誤解したのだとおもわれます。 実際には、再送は行われていません。
本日の考察
- TCP クライアントは、ポート番号が使い回しされる。
- Ethernet フレームにTrailer が付加されることがあり、そこには直前の通信の残りが入っている。
- Wireshark の解析機能も万能ではない。
参考文献
Interface (インターフェース) 2008年 09月号 [雑誌]
- 作者:
- 出版社/メーカー: CQ出版
- 発売日: 2008/07/25
- メディア: 雑誌