| |
ここまでのすべてのプログラムは、ただ一つのタスクからできていた。しかし、NXCのプログラムでは多くのタスクを使うことができる。そのことは、いわゆるサブルーチン−これはプログラムのどの場所にあっても利用することができる−を内部に持つコードの固まりを複数配置することが可能であるということだ。タスクとサブルーチンを利用することでプログラムをより理解しやすく、よりコンパクトにできる。この章ではこの様々な可能性について見てゆく。 | |
1.タスク | |
NXCの1つのプログラムには、最大で255のタスクが利用できる。mainと呼ばれるタスクは必ず存在しなければならない。mainタスクはがプログラムの実行時に最初に実行される。他のタスクは、実行中のタスクが実行するように通知したとき、もしくは、mainタスクで明確にスケジュールされたときのみに実行される。mainタスクが他のタスクの実行前にそれを仕切らなければならない。このときから2つのタスクは同時に走り出す。 タスクの使い方を示す。以前のように正方形に沿ってロボットを走らせるプログラムを作りたい。だが、何か障害物に当たったとき、ロボットは反応しなければならない。これは1つのタスクで行うことは難しい。なぜならば、ロボットは一時に2つのことをしなければならないからだ。まわるように走る(これはモーターのオン・オフを切り替えなければならない)ことと、センサーをみることである。したがって、このためには2つのタスクを使う方が良い。1つのタスクはロボットを正方形に走らせ、他のタスクはセンサーを仕切る。これがそのプログラムだ。 | |
(Prog6-1) mutex moveMutex; task move_square() { while (true) { Acquire(moveMutex); OnFwd(OUT_AC, 75); Wait(1000); OnRev(OUT_C, 75); Wait(500); Release(moveMutex); } } task check_sensors() { while(true) { if (SENSOR_1 == 1) { Acquire(moveMutex); OnRev(OUT_AC, 75); Wait(500); OnFwd(OUT_A, 75); Wait(500); Release(moveMutex); } } } task main() { Precedes(move_square, check_sensors); SetSensorTouch(IN_1); } |
解説 排他処理タイプ(mutex)の変数 moveMutex を定義する。 move_square タスクを定義開始 エラーが無いうちは無限に繰り返す。 無限ループの始端 排他変数 moveMutex を確保する。 AとCのモーターを出力 75% で正転させる そのまま1秒 モーターCを逆転(Aは正転中):左に向きを変える そのまま0.5秒(だけ回る) 排他変数 moveMutex を解放する。 無限ループの終端 move_square タスクの終端 check_sensor タスクの定義 エラーが無いうちは無限に繰り返す 無限ループの始端 入力1(タッチセンサー)が触れる(入力値が1)のとき に、以下の行を実行する。 排他変数 moveMutex を確保する。 AとCのモーターを出力 75% で逆転させる そのまま1秒:障害物から離れる。 モーターAを正転(Cは逆転中):左に向きを変える そのまま0.5秒(だけ回る) 排他変数 moveMutex を解放する。 if文の終端 無限ループの終端 check_sensors タスクの終端 メインタスクはここに置いてある 2つのタスク、move_square, check_sensor を動かす。 ☆Precedeは「優先する」という意味 タッチセンサーが入力1にあることを定義 メインタスクの後端 |
mainタスクではセンサーのタイプをセットして、異なる2つのタスクをスケジューラーキューに追加して、それをスタートさせる。そしてそこでメインタスクは終了する。move_square タスクはロボットを永遠に正方形に走らせる。check_sensor タスクはタッチセンサーが押されたかどうかをチェックする。もし押された場合にはロボットを障害物から遠ざける。 動き出した2つのタスクは同時に走っていることを認識することは非常に重要だ。両方のタスクが指示されたとおりにモーターを動かそうとするならば、このことで予期しない結果を引き出すことになる。 この問題を回避するために、これまでとは異なるタイプの変数 mutex(mutual exclusion :排他 を意味する) を宣言した。このタイプの変数は一時にはただ一つのタスクがモーターをすべてコントロールすることを保証するために、互いに相容れないコードを持つ関数を取り入れたり、解放したりするときに限って使用する。 このような mutexタイプの変数は semaphores (腕木式信号)と呼ばれ、このプログラミングテクニックはコンカレントプログラミングと名付けられている。この話題は第10章で詳細に記述する。 | |
2.サブルーチン | |
プログラムの中の複数の場所で同じコードが必要となることはしばしがある。この場合、そのひとまとまりのコードをサブルーチンとして名前をつけてプログラム内に置くことができる。このコードは、タスク内で単純に名前で呼んで実行することができる。次の例を見てみよう。 | |
(Prog6-2) sub turn_around(int pwr) { OnRev(OUT_C, pwr); Wait(900); OnFwd(OUT_AC, pwr); } task main() { OnFwd(OUT_AC, 75); Wait(1000); turn_around(75); Wait(2000); turn_around(75); Wait(1000); turn_around(75); Off(OUT_AC); } |
解説 |
このプログラムの内部で、ロボットをその中心で回転させるサブルーチンを定義した。メインタスクはこのサブルーチンを3回呼び出している。サブルーチンは、その名前が記述されていることと、そのうしろの()内に書かれた数値(引数)を渡して呼び出されていることに注目しよう。もしサブルーチンに引数を渡さない場合は内部に何も書かないただの()をその名前に加える。 そう、これはここまで見てきたコマンドと多くの点で共通している。 サブルーチンの主なる効果は、NXTに一度だけ記憶させればよいことと、このことで余計なメモリーを使わないことにある。しかし、サブルーチンが短い時は代わりにインライン関数を使う方がよい。これは分離して記憶されないが、利用されるそれぞれの位置へコピーされる。これはより多くのメモリーを消費するが、インライン関数の数に制限はない。これらは以下のように記述される。 |
|
(使用例) inline int Name ( Args ) { //body; return x*y; } |
解説 |
インライン関数の定義と呼び出しはサブルーチンのそれと同一だ。よって上の例はインライン関数を用いると次のようになる。 |
|
(Prog6-3) inline void turn_around() { OnRev(OUT_C, 75); Wait(900); OnFwd(OUT_AC, 75); } task main() { OnFwd(OUT_AC, 75); Wait(1000); turn_around(); Wait(2000); turn_around(); Wait(1000); turn_around(); Off(OUT_AC); } |
解説 |
上の例で、ターンするための時間の引数を以下の例のように書くことができる。 |
|
(Prog6-4) inline void turn_around(int pwr, int turntime) { OnRev(OUT_C, pwr); Wait(turn_time); OnFwd(OUT_AC, pwr); } task main() { OnFwd(OUT_AC, 75); Wait(1000); turn_around(75, 2000); Wait(2000); turn_around(75, 500); Wait(1000); turn_around(75, 3000); Off(OUT_AC); } |
解説 |
インライン関数の名前の後にある()の内部に関数の引数を定義していることに注目しよう。この場合は引数が整数(違う選択肢もある)であることと、その名前が turntime であることを表している。()内に引数がもっとあるときには、カンマ( , )で区切らなければならない。NXCではsubはvoidと同じである。同様に、関数はvoidと異なる形式を返すことができる。呼び出し点に整数値や文字を返すこともまたできる。詳細についてはNXCガイドを見ること。 |
3.マクロを定義する。 |
短いコードに名前をつけるには、まだ他の方法がある。NXCではマクロを定義することができる。(BricxCCのマクロとは混乱しないように)以前、#define を用いて定数に名前をつけることができた。しかし、実際には数行のコードを定義することもできる。次は、同じプログラムではあるが、旋回するためにマクロを用いている。 | |
(Prog 6-5) #define turn_around \ OnRev(OUT_C, 75); Wait(3400); OnFwd(OUT_AC, 75); task main() { OnFwd(OUT_AC, 75); Wait(1000); turn_around; Wait(2000); turn_around; Wait(1000); turn_around; Off(OUT_AC); } |
解説 |
#define 命令のうしろの turn_around という単語が、そのうしろのテキストで定義されている内容を意味している。このテキストは1行で書かれるべきである。(実際、複数行に #define 命令を置く方法であるが、これは要求されたものではない。) Define文は実際はまだまだ強力である。define に引数を持つこともできる。たとえば文の中に転回する時間の引数を置くことができる。ここに4つのマクロ−前進・後退・左旋回・右旋回−を定義した例がある。それぞれは速度と時間の2つの引数を持っている。 | |
(Prog 6-6) #define turn_right(s,t) \ OnFwd(OUT_A, s); OnRev(OUT_C, s);Wait(t); #define turn_left(s,t) \ OnRev(OUT_A, s); OnFwd(OUT_C, s);Wait(t); #define forwards(s,t) OnFwd(OUT_AC s);Wait(t); #define backwords(s,t) OnRev(OUT_AC, s);Wait(t); task main() { backwords(50,10000); forwards(50,10000); turn_left(75,750); forwards(75,1000); backwords(75,2000); forwards(75,1000); turn_right(75,750); forwords(30,2000); Off(OUT_AC); } |
解説 |
このようにマクロを定義することはとても有効だ。このことでプログラムがよりコンパクトに、より読みやすくなる。また、たとえばモータを付け替えたときのようなときにより簡単にプログラムを書き換えることができる。 | |
4.まとめ | |
この章ではタスク、サブルーチン、インライン関数とマクロの使い方をみた。タスクは常時は同時に実行される。同時に実行される異なるものに注意を払う。サブルーチンは同一のタスク内の違った場所で利用される大きなコードの固まりに効果的である。インライン関数は異なるタスクの多くの異なる場所で同じコードが利用されるときに効果的であるが、より多くのメモリーを消費する。最後にマクロは異なる場所で使用されなければならない短いコードのためには非常に役に立つ。マクロはもっと役に立たせるためにパラメータを持つことができる。 ここまでの章で試してきた内容で、あなたはロボットに複雑な動きをさせるためのすべてのスキルを持った。このチュートリアルの他の章では、一定の応用下での限定的に重要な他のことについて学ぶことになる。 |
|
問題 | |
上記のプログラムを変更して次のような動きを作ってみよう。 @ (答えはこちら) A |