4.oilファイル TOPへ 6.タスク間の排他制御

5.マルチタスク


※この章を学習するには、前章の oilファイルを習得することが必要です。

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


1.複数タスクのプログラム

まずは、タスクが2つあるプログラムを組んでみましょう。
次のプログラムを作成してください。ここでは task1.c , task1.oilとします。

task1.c
#include "kernel.h"
#include "kernel_id.h"
#include "ecrobot_interface.h"

#define COUNT 500		/* カウント数を500に定義 */

DeclareTask(Task1);				/* Task1を宣言 */
DeclareTask(Task2);				/* Task2を宣言 */

void ecrobot_device_initialize(){}

void ecrobot_device_terminate(){}

void user_1ms_isr_type2(void){}

TASK(Task1)
{
	int i;
	for(i=0 ; i<=COUNT ; i++){		/* カウント数だけ繰り返す */
		display_goto_xy(0,1);
   		display_string("TASK1=          ");		/* 変数の内容を表示 */
		display_goto_xy(6,1);
		display_int(i, 5);
		display_update();
		systick_wait_ms(10);		/* iをインクリメントする */
	}
	TerminateTask();					/* 処理終了 */
}


TASK(Task2)
{
	int j;
	for(j=0 ; j<=COUNT ; j++){		/* カウント数だけ繰り返す */
		display_goto_xy(0,2);
   		display_string("TASK2=          ");		/* 変数の内容を表示 */
		display_goto_xy(6,2);
		display_int(j, 5);
		display_update();
		systick_wait_ms(10);		/* jをインクリメントする */
	}

	TerminateTask();					/* 処理終了 */
}

task1.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;
  };


  APPMODE appmode1{}; 	/* アプリケーションモードを定義 */

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

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

このプログラムを動かすとLCDを見れば分かるように、まずTask2が500までインクリメントします。 次に、Task1が500までインクリメントして処理が終了します。
タスクの起動される順番は、タスクの優先度によって決まります。このプログラムでは、Task1の優先度は1、Task2の優先度は2になっています。
優先度は値が大きいほど高いので、Task2の方が優先度が高く、Task2が先に起動されたのです。
※2つのタスクの優先度が同じだった場合、先に起動されたタスクが処理されます。

これが、マルチタスクの最も簡単な形です。
しかし、これでは、同時に処理をしているとはいえません。優先度を同じにしても、先に宣言されていたタスクが先に処理されるので、同時には処理されません。
普通の処理系では、同じ優先度のタスクはラウンドロビンという方式により、ほぼ同時に処理されますが、nxtOSEKにはその仕組みがありません。
つぎは、どうしたらタスクが同時に処理されるかを考えて見ましょう。

※ラウンドロビン…タスクをを一定時間ずつ順番に実行する方式。持ち時間を使い果たしたタスクは一旦中断され、待ち行列の最後に回される。

2.同時に処理をする

同時に処理をするといっても、CPUは一つしかありませんから、一度に2つの計算をすることは出来ません。
よって、タスクの処理を細かく分け、タスクを切り替えて処理をすることで、見かけ上、同時に処理が出来るようにします。
ここで、基礎編で学んだ、COUNTERやALARMが役に立ちます。
次のプログラムを作成してください。ここでは task_cyclee.c , task_cycle.oilとします。
task_cycle.c
#include "kernel.h"
#include "kernel_id.h"
#include "ecrobot_interface.h"

#define COUNT 500		/* カウント数を500に定義 */

#define TEMPO 10
#define VOLUME 50

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

void ecrobot_device_initialize(){}

void ecrobot_device_terminate(){}

void user_1ms_isr_type2(void){
	SignalCounter(SysTimerCnt);    /* カウンタをIncrementする */
}

void RingTone(int freq, int time, int vol){	/* 音符を再生するユーザー関数を定義 */
	ecrobot_sound_tone(freq, time-5, vol);
	systick_wait_ms(time*10);
}

TASK(Task1)
{
	static int i=0;
	if(i<=COUNT){		/* iがCOUNT以下のとき真 */
		display_goto_xy(0,1);
   		display_string("TASK1  =          ");		/* 変数の内容を表示 */
		display_goto_xy(8,1);
		display_int(i, 5);
		display_update();
		i++;		/* iをインクリメントする */
	}else{
		display_goto_xy(0,4);
		display_string("TASK1 Terminated");		/* 終了メッセージ */
		display_update();
	}
	TerminateTask();					/* 処理終了 */
}


TASK(Task2)
{
	static int j=0;
	if(j<=COUNT){		/* jがCOUNT以下のとき真 */
		display_goto_xy(0,2);
   		display_string("TASK2  =          ");		/* 変数の内容を表示 */
		display_goto_xy(8,2);
		display_int(j, 5);
		display_update();

		j++;		/* jをインクリメントする */
	}else{
		display_goto_xy(0,5);
		display_string("TASK2 Terminated");		/* 終了メッセージ */
		display_update();
	}
	TerminateTask();					/* 処理終了 */
}

TASK(Task_bg)
{
/*===========かえるの歌=============*/
	RingTone(523, TEMPO*2, VOLUME);
	RingTone(587, TEMPO*2, VOLUME);
	RingTone(659, TEMPO*2, VOLUME);
	RingTone(698, TEMPO*2, VOLUME);
	RingTone(659, TEMPO*2, VOLUME);
	RingTone(587, TEMPO*2, VOLUME);
	RingTone(523, TEMPO*3, VOLUME);
	systick_wait_ms(TEMPO*10);

	RingTone(659, TEMPO*2, VOLUME);
	RingTone(698, TEMPO*2, VOLUME);
	RingTone(784, TEMPO*2, VOLUME);
	RingTone(880, TEMPO*2, VOLUME);
	RingTone(784, TEMPO*2, VOLUME);
	RingTone(698, TEMPO*2, VOLUME);
	RingTone(659, TEMPO*3, VOLUME);
	systick_wait_ms(TEMPO*10);

	RingTone(523, TEMPO*2, VOLUME);
	systick_wait_ms(TEMPO*2*10);
	RingTone(523, TEMPO*2, VOLUME);
	systick_wait_ms(TEMPO*2*10);
	RingTone(523, TEMPO*2, VOLUME);
	systick_wait_ms(TEMPO*2*10);
	RingTone(523, TEMPO*2, VOLUME);
	systick_wait_ms(TEMPO*2*10);

	RingTone(523, TEMPO, VOLUME);
	RingTone(523, TEMPO, VOLUME);
	RingTone(587, TEMPO, VOLUME);
	RingTone(587, TEMPO, VOLUME);
	RingTone(659, TEMPO, VOLUME);
	RingTone(659, TEMPO, VOLUME);
	RingTone(698, TEMPO, VOLUME);
	RingTone(698, TEMPO, VOLUME);
	RingTone(659, TEMPO, VOLUME);
	systick_wait_ms(TEMPO*10);
	RingTone(587, TEMPO, VOLUME);
	systick_wait_ms(TEMPO*10);
	RingTone(523, TEMPO*3, VOLUME);
	systick_wait_ms(TEMPO*10);
/*==================================*/

	display_goto_xy(0,6);
   	display_string("TASKbgTerminated");		/* 終了メッセージ */
	display_update();

	TerminateTask();					/* 処理終了 */
}

task_cycle.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;
  };


  APPMODE appmode1{}; 	/* アプリケーションモードを定義 */

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

  TASK Task2						/* Task2 を定義 */
  {
    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;
    };
  };

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

プログラムを実行してみると、かえるの歌が流れながら、画面では2つのタスクが500までほぼ同時にインクリメントします。
タスクの動作が終わると、画面に終了メッセージが表示されます。
かえるの歌のタスク(Task_bg)は普通のタスク、そのほか(Task1,Task2)は周期アラームに起動される周期タスクです。
タスク切り替えの様子を下図に表しました。

mulch_2.png(29812 byte)

プログラムの動作の過程を上図をもとに説明します。

@ Task_bgが起動される。
A 1msec経過すると、2つのアラームが最初のexpireになる。
   アラームによりTask1,Task2が実行されようとするが、優先度は同じだが、先に宣言されたTask1が実行される。
   他のタスクはready状態になる。Task_bgの処理は中断される。
B Task1の処理が終わると、次に優先度が高く、先に宣言されたTask2が起動される。Task1は、suspended状態になる。
D Task2の処理が終わると、最も優先度が低かったTask_bgが中断されていた処理を再開する。
E 再び2つのアラームがexpireになると、Task1が起動される。


こうして、ほぼ同時に複数の処理が出来るようになりました。ただし、周期タスクの処理は、CYCLETIMEより早く終わることが必要です。

3.課題

1.サンプルプログラムを実行し、動作を確認せよ。
2.下に示す、ライントレースのプログラム"trase.c"と、音楽を鳴らすプログラム"music.c"を参考に、音楽を鳴らしながらライントレースするプログラムを作成せよ。oilファイルは、前のものを改造して作成せよ。
    ※このとき、ライントレースのタスクは周期タスク、音楽のタスクは普通のタスクにするとよい。
    ※ライントレースのプログラムを周期タスクにする際、タイマ割り込み用フック関数と、whileループに注意せよ。

・ ライントレースするプログラム
myrobot_interface.h
#define PORT_LIGHT NXT_PORT_S1	/* 入出力ポートの定義 */
#define PORT_TOUCH NXT_PORT_S2
#define L_MOTOR NXT_PORT_B
#define R_MOTOR NXT_PORT_C

trace.h
#define BLACK 700
#define WHITE 450
#define SPEED 50
#define P_GAIN 0.3
#define D_GAIN 0.6

trace.c
#include "kernel.h"
#include "kernel_id.h"
#include "ecrobot_interface.h"
#include "myrobot_interface.h"
#include "trace.h"

DeclareTask(Task1);				/* Task1を宣言 */

void ecrobot_device_initialize(){		/* OSEK起動時の処理 */
	ecrobot_set_light_sensor_active(PORT_LIGHT);
}

void ecrobot_device_terminate(){		/* OSEK終了時の処理 */
	ecrobot_set_light_sensor_inactive(PORT_LIGHT);
	nxt_motor_set_speed(L_MOTOR,0,0);
	nxt_motor_set_speed(R_MOTOR,0,0);
}

void user_1ms_isr_type2(void){}			/* タイマ割り込み用フック関数 */

int turn;
float light,light_tmp = (BLACK-WHITE)/2;

TASK(Task1){

	while(1){
		light = ecrobot_get_light_sensor(PORT_LIGHT);
		turn = P_GAIN * (light - (BLACK+WHITE)/2 )*100 / (BLACK-WHITE) + D_GAIN * (light-light_tmp)/(BLACK-WHITE)*100;
		nxt_motor_set_speed(L_MOTOR,SPEED-turn,1);
		nxt_motor_set_speed(R_MOTOR,SPEED+turn,1);
		light_tmp=light;
	}
	TerminateTask();
}

・ 音楽を鳴らすプログラム
music.c
#include "kernel.h"
#include "kernel_id.h"
#include "ecrobot_interface.h"

#define COUNT 500		/* カウント数を500に定義 */

#define TEMPO 1
#define VOLUME 10

int i=0,j=0,k=0,l=0;

DeclareTask(Task1);				/* Task1を宣言 */

void ecrobot_device_initialize(){}

void ecrobot_device_terminate(){}

void user_1ms_isr_type2(void){}

//==============NCT_SOUND_API================
void Wait(int leng){
	systick_wait_ms(leng*TEMPO);
}
void PlayTone(int fleq,int leng){
	ecrobot_sound_tone(fleq, leng*TEMPO, VOLUME);
}
//===========================================

TASK(Task1)
{
	  Wait(1920);
	  PlayTone(659,60);
	 (中略)
	  PlayTone(523,540);
	  Wait(560);

	TerminateTask();					/* 処理終了 */
}



※.補足:同時に処理をするその2

同時に処理をさせる方法として、周期タスクを用いるほかに、ChainTaskという命令を用いる方法があります。

次のプログラムを作成してください。ここでは task2.cとします。oilファイルはtask1と同じものを使ってください。

task2.c
#include "kernel.h"
#include "kernel_id.h"
#include "ecrobot_interface.h"

#define COUNT 500		/* カウント数を500に定義 */

int i=0,j=0;

DeclareTask(Task1);				/* Task1を宣言 */
DeclareTask(Task2);				/* Task2を宣言 */

void ecrobot_device_initialize(){}

void ecrobot_device_terminate(){}

void user_1ms_isr_type2(void){}

TASK(Task1)
{
	if(i<=COUNT){		/* iがCOUNT以下のとき真 */
		display_goto_xy(0,1);
   		display_string("TASK1=          ");		/* 変数の内容を表示 */
		display_goto_xy(6,1);
		display_int(i, 5);
		display_update();
		systick_wait_ms(10);

		i++;		/* iをインクリメントする */
	}

	ChainTask (Task2);					/* Task1に切り替え */

	TerminateTask();					/* 処理終了 */
}


TASK(Task2)
{
	if(j<=COUNT){		/* jがCOUNT以下のとき真 */
		display_goto_xy(0,2);
   		display_string("TASK2=          ");		/* 変数の内容を表示 */
		display_goto_xy(6,2);
		display_int(j, 5);
		display_update();
		systick_wait_ms(10);

		j++;		/* jをインクリメントする */
	}

	ChainTask (Task1);					/* Task1に切り替え */

	TerminateTask();					/* 処理終了 */
}

今度のプログラムは、Task1とTask2が同時に、同じ速さで実行されているのが分かります。
上の例では、変数をインクリメントするごとにもう一方のタスクに処理を切り替えています。
今回用いたAPIを説明します。

ChainTask (Task_ID);
自タスクを"suspended状態"に遷移させ、指定したタスクを"ready状態"に遷移させます。
"ready状態"になったタスクは、自分より高い優先度のタスクが動いていない場合、すぐに実行されます。(図)
mulch_1.png(29812 byte)
"suspended状態"とは、休止状態のことです。



4.oilファイル TOPへ 6.タスク間の排他制御