第6章 タスク と サブルーチン
 ここまでのすべてのプログラムは、ただ一つのタスクからできていた。しかし、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つのタスクは同時に走っていることを認識することは非常に重要だ。両方のタスクが指示されたとおりにモーターを動かそうとするならば、このことで予期しない結果を引き出すことになる。
 この問題を回避するために、これまでとは異なるタイプの変数 mutexmutual 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);
}

解説
サブルーチン turn_around を定義。()内は引数。
 呼び出し時の()の値を整数型変数 pwr として受ける。
 モーターCを、指定された出力で逆転させる。
 0.9秒間、左にまわる。
 両方のモーターを指定された出力で正転させる。
サブルーチン turn_around はここまで
メインタスクの開始

 両方のモーターを75%の出力で正転
 1秒前進する。
 turn_aroundサブルーチンに 75 を渡して実行する。
 2秒間前進する。(turn_aroundの最後で定義)
 turn_aroundサブルーチンに 75 を渡して実行する。
 1秒間前進する。(turn_aroundの最後で定義)
 turn_aroundサブルーチンに 75 を渡して実行する。
 停止する。

 このプログラムの内部で、ロボットをその中心で回転させるサブルーチンを定義した。メインタスクはこのサブルーチンを3回呼び出している。サブルーチンは、その名前が記述されていることと、そのうしろの()内に書かれた数値(引数)を渡して呼び出されていることに注目しよう。もしサブルーチンに引数を渡さない場合は内部に何も書かないただの()をその名前に加える。
 そう、これはここまで見てきたコマンドと多くの点で共通している。
 サブルーチンの主なる効果は、NXTに一度だけ記憶させればよいことと、このことで余計なメモリーを使わないことにある。しかし、サブルーチンが短い時は代わりにインライン関数を使う方がよい。これは分離して記憶されないが、利用されるそれぞれの位置へコピーされる。これはより多くのメモリーを消費するが、インライン関数の数に制限はない。これらは以下のように記述される。
(使用例)
inline int Name ( Args )
{
 //body;
 return x*y;
}

解説
整数値を受けるインライン関数を定義する。
 Nameは関数名、Argsは引数
 以下が関数定義の本体
 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);
}

解説
インライン関数 turn_around を定義。引数はない。
 void は無効・虚空という意味。
 モーターCを、75%の出力で逆転させる。
 0.9秒間、左にまわる。
 両方のモーターを75%の出力で正転させる。
インライン関数 turn_around はここまで
メインタスクの開始

 両方のモーターを75%の出力で正転
 1秒前進する。
 turn_around関数を実行する。
 2秒間前進する。(前進は関数の最後で定義)
 turn_around関数を実行する。
 1秒間前進する。(前進は関数の最後で定義)
 turn_around関数を実行する。
 停止する。

 上の例で、ターンするための時間の引数を以下の例のように書くことができる。
(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);
}

解説
関数 turn_around を定義。引数は、それぞれ整数で、順に pwr , turntime という変数で受け取る。
 
 モーターCを変数 pwr の出力で逆転させる。
 1000分のturntime 秒間左にまわる。
 両方のモーターを変数 pwr の出力で正転させる。
関数 turn_around の定義終了
メインタスクの開始

 両方のモーターを75%の出力で正転
 1秒前進する。
 turn_around関数を実行( pwr=75, turntime=2000 )
 2秒間前進する。(前進は関数で定義)
 turn_around関数を実行( pwr=75, turntime=500 )
 1秒間前進する。(前進は関数で定義)
 turn_around関数を実行( pwr=75, turntime=3000 )
 停止する。

 インライン関数の名前の後にある()の内部に関数の引数を定義していることに注目しよう。この場合は引数が整数(違う選択肢もある)であることと、その名前が turntime であることを表している。()内に引数がもっとあるときには、カンマ( , )で区切らなければならない。NXCではsubvoidと同じである。同様に、関数は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);
}

解説
マクロ turn_around を定義した。引数は受けない。
 モーターCを 75% の出力で逆転、3.4 秒間左転回、両方のモーターを 75% の出力で正転。

メインタスクの開始

 両方のモーターを75%の出力で正転
 1秒前進する。
 turn_aroundマクロを実行する。
 2秒間前進する。(前進はマクロ内で定義)
 turn_aroundマクロを実行
 1秒間前進する。(前進はマクロ内で定義)
 turn_aroundマクロを実行
 停止する。

 #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);
}

解説
turn_right を定義。引数2つを s と t で受ける。
 モーターAを s の力で正転、モーターCを s の力で逆転、1000分の t 秒維持。
turn_left を定義。引数2つを s と t で受ける。
 モーターAを s の力で逆転、モーターCを s の力で正転、1000分の t 秒維持。
forwards を定義。引数2つを s と t で受ける。
 モーターACを s の力で正転、1000分の t 秒維持。
backwords を定義。引数2つを s と t で受ける。
 モーターACを s の力で逆転、1000分の t 秒維持。

メインタスクの開始

 50%の速度で10秒間後退
 50%の速度で10秒間前進
 75%の速度で0.75秒左に回る。
 75%の速度で1秒間前進
 75%の速度で2秒間後退
 75%の速度で1秒間前進
 75%の速度で0.75秒右に回る。
 30%の速度で2秒間前進
 停止

 このようにマクロを定義することはとても有効だ。このことでプログラムがよりコンパクトに、より読みやすくなる。また、たとえばモータを付け替えたときのようなときにより簡単にプログラムを書き換えることができる。
4.まとめ
 この章ではタスク、サブルーチン、インライン関数とマクロの使い方をみた。タスクは常時は同時に実行される。同時に実行される異なるものに注意を払う。サブルーチンは同一のタスク内の違った場所で利用される大きなコードの固まりに効果的である。インライン関数は異なるタスクの多くの異なる場所で同じコードが利用されるときに効果的であるが、より多くのメモリーを消費する。最後にマクロは異なる場所で使用されなければならない短いコードのためには非常に役に立つ。マクロはもっと役に立たせるためにパラメータを持つことができる。
 ここまでの章で試してきた内容で、あなたはロボットに複雑な動きをさせるためのすべてのスキルを持った。このチュートリアルの他の章では、一定の応用下での限定的に重要な他のことについて学ぶことになる。
問題
上記のプログラムを変更して次のような動きを作ってみよう。
@ (答えはこちら
A