9.音を鳴らす TOPへ 11.SemaphoreとMutex

10.マルチタスク


ここでは、マルチタスクについて学習します。
まず、タスクとはOSから見た処理の実行単位のことです。これまでのプログラムではタスクを意識することがなかったと思います。 なぜなら、これまではタスクが1つだけでそれを順序どおりに処理する「シングルタスク」のプログラムしか組んでいなかったからです。
しかし、複数の処理を同時に実行させたい場合は、これまでのシングルタスクではできません。 そこで複数のタスクを切り替えながら処理する「マルチタスク」を用いる必要があります。 これによりタスクはほぼ同時に処理されます。
ここでは、マルチタスクとはどういう事か、マルチタスクを扱うプログラミングについて学習します。


1.マルチタスクについて知る

まずは、マルチタスクのプログラムを組み、そしてマルチタスクを学びましょう。
次のプログラムを作成してください。ここでは task1.c とします。
task1.c
#define COUNT 5000

task task1(){
	int i;

	nSchedulePriority = 10;
	wait1Msec(1);

	for(i=0 ; i<COUNT ; i++) nxtDisplayTextLine(2,"task1:%5d",i);
}

task task2(){
	int j;

	nSchedulePriority = 100;
	wait1Msec(1);

	for(j=0 ; j<COUNT ; j++) nxtDisplayTextLine(3,"task2:%5d",j);
}

task task3(){
	int k;

	nSchedulePriority = 50;
	wait1Msec(1);

	for(k=0 ; k<COUNT ; k++) nxtDisplayTextLine(4,"task3:%5d",k);
}

task main(){
	int l;

	nSchedulePriority = kHighPriority;
	StartTask(task1);
	StartTask(task2);
	StartTask(task3);

	nSchedulePriority = kDefaultTaskPriority; // = 7;
	wait1Msec(1);

	for(l=0 ; l<COUNT ; l++) nxtDisplayTextLine(1,"main :%5d",l);
}

このプログラムを動かすとLCDを見れば分かるように、まずtask2が4999までインクリメントします。 次にtask3、その次にtask1、最後にmain のタスクが4999までインクリメントして処理が終了します。

まずは、今回新たに用いたAPIなどについて解説します。
nSchedulePriority = 10
カレントタスクの優先度を指定する変数です。優先度は0〜255までの整数かマクロで指定します。 マクロは、kHighPriority(=255:最高)kDefaultTaskPriority(=7:通常)kLowPriority(=0:最低)となっています。
ただしRobotCにおいては、複数のタスク優先度を適用するためには、タスク優先度の指定後にタスクテーブル更新のために、1msec程度タスクを待機させる必要があります。 そのため、タスク優先度の指定とセットで wait1Msec(1) を記述するということを覚えておくと良いでしょう。

StartTask(task1)
タスクを開始させるAPIです。引数には開始させるタスクの名前を指定します。 このAPIを実行すると指定されたタスクを処理を明け渡すまで実行します。
このAPIで連続して複数のタスクを起動させたい場合には、起動するタスクよりも高い優先度であるのが望ましいです。 その為、自身の優先度を一時的に最高(kHighPriority)にしておき、タスク起動後に元の優先度に戻すように記述するとよいでしょう。

それではマルチタスクの説明に入ります。このプログラムには、task1,task2,task3,mainの4つのタスクがあります。 このタスクの状態は次のようになっています。
task1.gif(4530 byte)
それぞれのタスクではタスクの優先度が異なっています。 マルチタスクにおいてはタスク優先度が高い順にタスクを起動して処理が実行されます。 このため、上記のプログラムを起動すると『task2(優先度:100)→task3(50)→task1(10)→main(7)』の順番で処理が実行されるのです。
RobotCでは、StartTask()でタスクが起動された時を除いて、1タイムスライス(=1msec)毎に全てのタスクの優先度が高い順に実行健を評価します。 そして、起動条件を満たしたタスクが見つかると、そのタスクを起動します。


2.ラウンドロビン方式

上のプログラムでは全てのタスクの優先度を変えて処理を行いました。 今回は全ての優先度を同じにした場合にどのような動作をするかを学習します。
次のプログラムを作成してください。ここでは task2.c とします。
task2.c
#define COUNT 5000

task task1(){
	int i;

	nSchedulePriority = kDefaultTaskPriority;
	wait1Msec(1);

	for(i=0 ; i<COUNT ; i++) nxtDisplayTextLine(2,"task1:%5d",i);
}

task task2(){
	int j;

	nSchedulePriority = kDefaultTaskPriority;
	wait1Msec(1);

	for(j=0 ; j<COUNT ; j++) nxtDisplayTextLine(3,"task2:%5d",j);
}

task task3(){
	int k;

	nSchedulePriority = kDefaultTaskPriority;
	wait1Msec(1);

	for(k=0 ; k<COUNT ; k++) nxtDisplayTextLine(4,"task3:%5d",k);
}

task main(){
	int l;

	nSchedulePriority = kHighPriority;
	StartTask(task1);
	StartTask(task2);
	StartTask(task3);

	nSchedulePriority = kDefaultTaskPriority;
	wait1Msec(1);

	for(l=0 ; l<COUNT ; l++) nxtDisplayTextLine(1,"main :%5d",l);
}

このプログラムを動かしてみてください。すると、先ほどとは異なり全てのタスクが同時に実行されていることが分かると思います。
このときのタスクの状態は次のようになります。

task2.gif(4688 byte)
このように、優先度が同じ場合は1つのタスクにつきCPUを一定時間(1タイムスライス分)一定時間利用させ、その後次のタスクへCPUの処理を回すという方式となっています。 これをラウンドロビン方式と言います。よって、このプログラムの場合は『task1→task2→task3→main→task1→…』と処理します。 この処理スピードが1msecととても早い時間で行われているため、人間には全てのタスクをほぼ同時に実行しているように見えるのです。


3.マルチタスクとwait関数について

ここではこれまで頻繁に用いたwait1Msec関数、wait10Msec関数がどのような動作をしているか、タスクはどうなっているかを見ていきます。
次のプログラムを作成してください。ここでは task3.c とします。
task3.c
#include "nxtlib.h"
#define COUNT 100

task task1(){
	int i;

	nSchedulePriority = 10;
	wait1Msec(1);

	for(i=0 ; i<COUNT ; i++){
		nxtDisplayTextLine(2,"task1:%5d",i);
		wait1Msec(100);
	}
}

task task2(){
	int j;

	nSchedulePriority = 50;
	wait1Msec(1);

	for(j=0 ; j<COUNT ; j++){
		nxtDisplayTextLine(3,"task2:%5d",j);
		wait1Msec(100);
	}
}

task main(){
	SetSensorTouch(S1);

	nSchedulePriority = kHighPriority;
	StartTask(task1);
	StartTask(task2);

	nSchedulePriority = kDefaultTaskPriority; // = 7;
	wait1Msec(1);

	while(SensorValue(S1) != 1){
		wait1Msec(1);
	}

	nxtDisplayCenteredTextLine(1,"Touch Sensor Pushed!");
	StopTask(task1);
	StopTask(task2);

	wait1Msec(500);
}

プログラムを実行すると、task1とtask2が実行されますが、本体に取り付けられたタッチセンサを押すと、両タスクが終了します。
それでは、今回新たに用いたAPIなどについて解説します。

StopTask(task1)
指定されたタスクを終了するAPIです。引数には終了させたいタスク名を記述します。 このAPIが処理されると、直ぐに指定されたタスクは終了します。

このプログラムでのタスクの状態は次に示す通りです。
task3.gif(6809 byte)
まず、もっとも優先度の高いtask2が起動し処理が開始されます。そして、その処理においてwait関数が呼ばれた場合は、 タイムスライスの途中であっても処理を待っているタスクへCPUの実行権を移します。
そして、全てのタスクがwaitしている場合には、無条件にアイドルタスクへと実行権が移ります。 このタスクの優先度は最低(0)となっており、無駄な電力を消費しないようにするタスクなのです。
このようにして、タスクとwaitが行われるのです。

さらに、センサとタスクについても見てみましょう。
このプログラムのmainタスクは、「タッチセンサが押されたら、task1とtask2を止める」という処理を行います。 センサの状態は1タイムスライス毎にその評価が行われます。 よって、タイムスライスの間にセンサの状態が変化しても、その後の処理はタイムスライス後になります。
上図の場合では、task2の処理中にタッチセンサが押されましたが、task2の処理に割り込むことはできません。 また、その次のタイムスライスでは、task1の方がmainタスクよりも優先度が高いため、task1の処理が優先されます。 そして、これら2つのタスクが処理されていないときにタッチセンサ後の処理が行われるようになっているのです。

このようにして、マルチタスク中でのwait関数やセンサ関数が処理されます。


4.課題

ライトセンサによりライントレースを行いながら、超音波センサにより障害物との距離がある程度(7cmぐらい)になったらその場で停止し、プログラムを終了するようなプログラムを作成せよ。 なお、ライトセンサと超音波センサをロボット前面に取り付けられるようにバンパーを工夫せよ。

【ヒント】
ライントレースを行うタスクと超音波センサの探索・処理を行うタスクを作成し、マルチタスク化すればよい。



9.音を鳴らす TOPへ 11.SemaphoreとMutex