8.マルチタスク TOPへ 10. ステートマシン

9.タスク間の排他制御


前章では、マルチタスクについて学習しました。
しかし、マルチタスクのプログラムを作る際には、今まで無かった問題が発生します。
最も代表的な例がタスクが他のタスクの実行を妨害することです。
それがどんなとき発生するか、どう解決するかを学びます。



1.動作の妨害

マルチタスク特有の問題を体験してみましょう。
次のプログラムを作成してください。ここではresource1.cとしました。
resource1.c
#include "kernel.h"
#include "kernel_id.h"
#include "ecrobot_interface.h"
#include "myrobot_interface.h"

DeclareCounter(SysTimerCnt);
DeclareTask(Task1);				/* Task1を宣言 */
DeclareTask(Task_bg);				/* Task_bgを宣言 */

int i=0;

void ecrobot_device_initialize(){		/* OSEK起動時の処理(モータ停止)*/
nxt_motor_set_speed(NXT_PORT_B,0,1);
nxt_motor_set_speed(R_MOTOR,0,1);
}

void ecrobot_device_terminate(){		/* OSEK終了時の処理(モータ停止)*/
nxt_motor_set_speed(L_MOTOR,0,1);
nxt_motor_set_speed(R_MOTOR,0,1);
}

void user_1ms_isr_type2(void){
    SignalCounter(SysTimerCnt);		/* カウンタ簡易版 */
}
#define SPEED 60
#define TURN_CNT 40
#define MOVE_CNT 120

#define TURN_TIME 500
#define MOVE_TIME 1000

TASK(Task1)
{
	if(i <= MOVE_CNT){
		nxt_motor_set_speed(L_MOTOR,SPEED,1);
		nxt_motor_set_speed(R_MOTOR,SPEED,1);
	}else if(i > MOVE_CNT && i <= TURN_CNT+MOVE_CNT){
		nxt_motor_set_speed(L_MOTOR,SPEED,1);
		nxt_motor_set_speed(R_MOTOR,-1*SPEED,1);	
	}else{
		i=0;
	}
	
	display_clear(0);
	display_goto_xy(0,1);
   	display_string("I=");		/* 変数の内容を表示 */
	display_int(i, 5);
	display_update();
	
	i++;
	
	TerminateTask();					/* 処理終了 */
}

TASK(Task_bg){
	while(1){
		if(ecrobot_get_touch_sensor(PORT_TOUCH) == 1){
			nxt_motor_set_speed(L_MOTOR,-1*SPEED,1);
			nxt_motor_set_speed(R_MOTOR,-1*SPEED,1);
			systick_wait_ms(MOVE_TIME);
			nxt_motor_set_speed(L_MOTOR,SPEED,1);
			nxt_motor_set_speed(R_MOTOR,-1*SPEED,1);
			systick_wait_ms(TURN_TIME);
			nxt_motor_set_speed(L_MOTOR,0,1);
			nxt_motor_set_speed(R_MOTOR,0,1);
		}
	}
	
	TerminateTask();
}


resource1.oil
#include "implementation.oil"

CPU ATMEL_AT91SAM7S256
{
  OS LEJOS_OSEK
  {
    STATUS = EXTENDED;
    STARTUPHOOK = FALSE;
    SHUTDOWNHOOK = FALSE;
    PRETASKHOOK = FALSE;
    POSTTASKHOOK = FALSE;
    USEGETSERVICEID = FALSE;
    USEPARAMETERACCESS = FALSE;
    USERESSCHEDULER = FALSE;
  };

  /* Definition of application mode */
  APPMODE appmode1{}; 

   TASK Task1						/* Task1 を定義する */
  {
    AUTOSTART = FALSE;
    PRIORITY = 2;
    ACTIVATION = 1;
    SCHEDULE = FULL;
    STACKSIZE = 512;
  };

  TASK Task_bg						/* Task_bg を定義 */
  {
    AUTOSTART = TRUE { APPMODE = appmode1; };
    PRIORITY = 1;
    ACTIVATION = 1;
    SCHEDULE = FULL;
    STACKSIZE = 512;
  };

  COUNTER SysTimerCnt			/* SysTimerCntを定義 */
  {
    MINCYCLE = 1;
    MAXALLOWEDVALUE = 10000;
    TICKSPERBASE = 1; /* One tick is equal to 1msec */ 
  };

  ALARM cyclic_alarm1		/* 周期アラーム1を定義 */
  {
    COUNTER = SysTimerCnt;
    ACTION = ACTIVATETASK
    {
      TASK = Task1;
    };
    AUTOSTART = TRUE
    {
      ALARMTIME = 1;
      CYCLETIME = 10; /* Task1は10msecごとに起動 */
      APPMODE = appmode1;
    };
  };

};



このプログラムでは2つのタスクは以下のような動作をするようにされています。
プログラムが正しく動作するか確認してください・・・・正しく動作しません。
このプログラムでは、Task1のほうがTask_bgより優先度が高く設定されています。
すると、プログラムの流れは以下のようになります。

resource1.png(9799 byte)

Task_bgの流れを見ると、実行が途切れ途切れになっていて、その間にTask1が実行されていることがわかります。 Task_bgの動作は、モータに指令を出して指定された時間待つ、という動作です。
しかし、折角Task_bgがモータに指令を出しても、その後Task1からのモータへの指令によって消されてしまいます。
これが高速で繰り返されるので、Task_bgは動作していないように感じるのです。

ならば、Task_bgの優先度をTask1より高くすれば良いのでしょうか。そのときのプログラムの流れはこうなります。

resource2.png(9799 byte)

Task1が実行されていません。これでは、目的の動作をしないのは明確です。
やはり、他に何か工夫が必要です。



2.タスク間の排他制御

問題の原因が分かった所で、解決法を学習しましょう。
次のプログラムを実行してください
resource2.c
#include "kernel.h"
#include "kernel_id.h"
#include "ecrobot_interface.h"
#include "myrobot_interface.h"

DeclareCounter(SysTimerCnt);
DeclareResource(resource1);
DeclareTask(Task1);				/* Task1を宣言 */
DeclareTask(Task_bg);				/* Task_bgを宣言 */

int i=0;

void ecrobot_device_initialize(){		/* OSEK起動時の処理(モータ停止)*/
nxt_motor_set_speed(NXT_PORT_B,0,0);
nxt_motor_set_speed(R_MOTOR,0,0);
}

void ecrobot_device_terminate(){		/* OSEK終了時の処理(モータ停止)*/
nxt_motor_set_speed(L_MOTOR,0,0);
nxt_motor_set_speed(R_MOTOR,0,0);
}

void user_1ms_isr_type2(void){
    SignalCounter(SysTimerCnt);		/* カウンタ簡易版 */
}
#define SPEED 60
#define TURN_CNT 40
#define MOVE_CNT 120

#define TURN_TIME 500
#define MOVE_TIME 1000

TASK(Task1)
{
	if(i <= MOVE_CNT){
		nxt_motor_set_speed(L_MOTOR,SPEED,1);
		nxt_motor_set_speed(R_MOTOR,SPEED,1);
	}else if(i > MOVE_CNT && i <= TURN_CNT+MOVE_CNT){
		nxt_motor_set_speed(L_MOTOR,SPEED,1);
		nxt_motor_set_speed(R_MOTOR,-1*SPEED,1);	
	}else{
		i=0;
	}
	
	display_clear(0);
	display_goto_xy(0,1);
   	display_string("I=");		/* 変数の内容を表示 */
	display_int(i, 5);
	display_update();
	
	i++;
	
	TerminateTask();					/* 処理終了 */
}

TASK(Task_bg){
	while(1){
		if(ecrobot_get_touch_sensor(PORT_TOUCH) == 1){
			GetResource(resource1);
			nxt_motor_set_speed(L_MOTOR,-1*SPEED,1);
			nxt_motor_set_speed(R_MOTOR,-1*SPEED,1);
			systick_wait_ms(MOVE_TIME);
			nxt_motor_set_speed(L_MOTOR,SPEED,1);
			nxt_motor_set_speed(R_MOTOR,-1*SPEED,1);
			systick_wait_ms(TURN_TIME);
			nxt_motor_set_speed(L_MOTOR,0,1);
			nxt_motor_set_speed(R_MOTOR,0,1);
			ReleaseResource(resource1);
		}
	}
	
	TerminateTask();
}

resource2.oil
#include "implementation.oil"

CPU ATMEL_AT91SAM7S256
{
  OS LEJOS_OSEK
  {
    STATUS = EXTENDED;
    STARTUPHOOK = FALSE;
    SHUTDOWNHOOK = FALSE;
    PRETASKHOOK = FALSE;
    POSTTASKHOOK = FALSE;
    USEGETSERVICEID = FALSE;
    USEPARAMETERACCESS = FALSE;
    USERESSCHEDULER = FALSE;
  };

  /* Definition of application mode */
  APPMODE appmode1{}; 

   TASK Task1						/* Task1 を定義する */
  {
    AUTOSTART = FALSE;
    PRIORITY = 2;
    ACTIVATION = 1;
	RESOURCE = resource1;
    SCHEDULE = FULL;
    STACKSIZE = 512;
  };

  TASK Task_bg						/* Task_bg を定義 */
  {
    AUTOSTART = TRUE { APPMODE = appmode1; };
    PRIORITY = 1;
    ACTIVATION = 1;
	RESOURCE = resource1;
    SCHEDULE = FULL;
    STACKSIZE = 512;
  };

  COUNTER SysTimerCnt			/* SysTimerCntを定義 */
  {
    MINCYCLE = 1;
    MAXALLOWEDVALUE = 10000;
    TICKSPERBASE = 1; /* One tick is equal to 1msec */ 
  };

  ALARM cyclic_alarm1		/* 周期アラーム1を定義 */
  {
    COUNTER = SysTimerCnt;
    ACTION = ACTIVATETASK
    {
      TASK = Task1;
    };
    AUTOSTART = TRUE
    {
      ALARMTIME = 1;
      CYCLETIME = 10; /* Task1は10msecごとに起動 */
      APPMODE = appmode1;
    };
  };

  RESOURCE resource1
  {
    RESOURCEPROPERTY = STANDARD;
  };

};

プログラムを動作させて見ましょう。
今度は正しく動くはずです。
それでは、新しく用いている関数の解説をしていきます

・ ソースファイル

DeclareResource(resource1)
リソースを宣言します。

GetResource(resource1)
リソースに割り当てられたクリティカルセクションに入ります。
ReleaseResource(resource1)
リソースに割り当てられたクリティカルセクションから抜けます。

・ oilファイル

RESOURCE = resource1
獲得するリソースのリストです。リソースを獲得しえるタスクすべてに定義します。
RESOURCE resource1{…}
リソースオブジェクトを定義します。

RESOURCEPROPERTY = STANDARD
STANDARDのほかに、LINKED, INTERNALが指定できます。
通常はSTANDARDを使用します。

プログラムの流れはこうなっています。

resource3.png(9799 byte)

順を追って説明すると、

①Task_bgがGetResourceすると、Task_bgは排他的処理となる。
②Task_bgは排他的処理状態にあるので、Task1は実行待ちとなる。
③Task_bgがReleaseResourceすると、通常の処理に戻る。
④優先度の高いTask1に実行権が渡される。
⑤Task1の処理が終わると、Task_bgが実行される。あとは普通の動作をする。

リソースを取得すると、他のTaskオブジェクトの中でリソースを指定しているタスクは実行されません。
これは、リソースを取得した際に優先度が他より高くなることにより実現されています。
モータなど、同じ資源を共有するタスクに同じリソースを指定しておけば、マルチタスクによる問題を防ぐことが出来ます。


※普通の処理系にも詳しい方へ
普通の処理系において排他制御を実現するには、セマフォやミューテックスなどがあります。
nxtOSEKの場合、ラウンドロビンなどの機能が無く、タスク切り替えのタイミングが明確である為からか、セマフォやミューテックスの機能は提供されていません。
また、多重起動はoilファイル中のタスク定義により防止できるため、多重起動防止ためにミューテックス等を導入することもありません。
これが、OSEKの特徴となっています。
ただし、Get/ReleaseResourceによる排他制御は、ミューテックスと同等の役割を果たします。

3.課題

1.上の2つのサンプルプログラムをNXTにダウンロードし、動作を確認せよ。
2.ライントレースするコース上に障害物があった場合、180度旋回して、逆方向にトレースするプログラムを作成せよ。



8.マルチタスク TOPへ 10. リアルタイム制御とステートマシン