#11 タスクと周期ハンドラを使おう【もっと!後輩たちのためのEV3rt講座】

# 目次

| 章 | タイトル | 内容 |
| :-: | :-: | :-: |
| 1 | [はじめに](#1-はじめに) | 今回やることの説明 |
| 2 | [マルチタスクとは](#2-マルチタスクとは) | そもそもマルチタスクとは |
| 3 | [新しいタスクを作る](#3-新しいタスクを作る) | 新しいタスクを作成する方法を説明 |
| 4 | [マルチタスクを実行する](#4-マルチタスクを実行する) | 実際のコードで説明 |
| 5 | [周期ハンドラを作る](#5-周期ハンドラを作る) | 周期ハンドラを作成する方法を説明 |
| 6 | [周期ハンドラを実行する](#6-周期ハンドラを実行する) | 実際のコードで説明 |
| 7 | [マルチタスク実践編](#7-マルチタスク実践編) | より実践的なコードを紹介 |
| 8 | [まとめ](#8-まとめ) | 今回のまとめ |

# 1. はじめに

これまで10回に渡り、EV3rtを通してEV3のセンサーやモーター、インテリジェントブロックを動かす方法を学んできました。
ここまで出来た皆さんが次にやりたい事と言ったら、やはり **マルチタスク** ではないでしょうか？

![スクリーンショット 2024-12-30 123535.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183170/a5e38823-f13a-2fe8-5b65-4ecff0ce854b.png)
(マルチタスク👆: 例えばこんな感じ)

マルチタスクとは2つの異なる動きを同時に行うこと、ロボット競技で言えば「移動しながらアームを動かす」などです。
今の例、単純に動かすだけなら全てのモータをオンにすれば良いのですが、 **「アームはこの角度で止めたい！」** など、モータによって別々の制御をしようとすると話がややこしくなってきます。
そこで、命令を2つに分けることで、より分かりやすく確実な制御をしようというのがマルチタスクの発想です。
EV3ソフトウェアではごく自然に行っているのではないでしょうか？

![スクリーンショット 2024-12-30 124112.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183170/8f12286e-a29b-9f3b-00ae-51b5539e4bd2.png)
(シングルタスクでやる場合👆: 意味はほぼ同じだけどこんなことはややこしいのでしたくない)

今回はこれをEV3rtで実現します!!

# 2. マルチタスクとは

まずは、マルチタスクがどのように成り立っているかを改めて考えてみましょう。
そもそも、コンピューターというのは原則的に「1つのプロセッサ」につき「1つの作業」しか行えません。
「あれをやりながらこれをやる」なんていうのは、本来不可能なのです。
ただ現に、マルチタスクはPCやスマホなどで実現されています。
ではどのように行っているのでしょうか？

答えは、 **「同時に行っているように見せている」** です。
マルチタスクの話になると良く出てくる図を使って説明していきます。

![マルチタスク1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183170/99cf586b-5d10-5519-8f46-c9d6a06fe458.png)
今、3つのタスク A, B, Cがあるとします。
シングルタスクでは図に示す通り、Aが終わったらB、Bが終わったらC、と順にタスクを実行していきます。

では、これらのタスクを **細かく切り刻んで** 以下の図のように配置してみましょう。

![マルチタスク2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183170/e125ffbe-1576-9eec-d176-ff880ebdc514.png)

それぞれのタスクを少しやっては別のタスクに移り、また少しやっては別のタスクに移り…という状態です。
そしてそれぞれのタスクに割く時間が人には感じ取れないくらい短い時、人はそれを **「同時に行っている」** と感じるのです。

# 3. 新しいタスクを作る

ここからはEV3rtでマルチタスクを実現するにあたり、実際に新しいタスクを作ってみましょう。
タスクを新規作成する場合、普段扱っている `app.c` ファイルだけでなく、`app.h` ファイルと `app.cfg` ファイルも触る必要があります。
順番に見ていきましょう。

## app.c

まずは、新しいタスクで実行する関数を作成しておきましょう。
普段使っている `main_task` とは別に、新しい関数を定義するということですね。

```c:app.c
#include "ev3api.h"
#include "app.h"
#include <stdio.h>

int flag = 0;
int count = 0;

void main_task(intptr_t unused)
{
    // メインタスクの中身は省略
}

// サブタスク
void sub_task(intptr_t unused)
{
    char str[64];
    int a = 0;
    ev3_lcd_draw_string("sub_task", 0, 40);

    while(flag==0){
        sprintf(str,"%.1lf (s)",a*0.1);
        ev3_lcd_draw_string(str, 0, 60);
        a++;
        tslp_tsk(100*1000);
    }

    ext_tsk();
}

```

今回はサブタスクとして、100ミリ秒ごとに変数を1つ上げて、LCD上に表示するプログラムを書きました。
言い換えれば、0.1秒間隔のタイマーといったところです。

## app.h

[前回](https://qiita.com/koushiro/items/2ba33587ea25faf3b8e5)の関数の回の最後で、関数の定義には順序が大事であり、メイン関数とサブ関数の定義が逆転してしまう場合は「プロトタイプ宣言」が必要だというお話をしました。
このプロトタイプ宣言について、前回は `app.c` 上に書いていましたが、今回からは `app.h` に書いていきましょう。

```c:app.h
/*
 *  関数のプロトタイプ宣言
 */
#ifndef TOPPERS_MACRO_ONLY

extern void	main_task(intptr_t exinf);
extern void	sub_task(intptr_t exinf); // 追加
```

`app.h` ファイルの最下部に、「関数のプロトタイプ宣言」というコメントから始まるエリアがあるかと思います。
そこに、今回作成した関数のプロトタイプ宣言を追加しておきましょう。

## app.cfg

[第3回](https://qiita.com/koushiro/items/e3d04e3a4e266c603865#appcfg-%E3%82%B3%E3%83%B3%E3%83%95%E3%82%A3%E3%82%AE%E3%83%A5%E3%83%AC%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB)で少し説明しましたが、 `app.cfg` ファイルではタスクの管理を行っています。

デフォルトでは以下のような内容になっているはずです。

```app.cfg
INCLUDE("app_common.cfg");

#include "app.h"

DOMAIN(TDOM_APP) {
CRE_TSK(MAIN_TASK, { TA_ACT, 0, main_task, TMIN_APP_TPRI + 1, STACK_SIZE, NULL });
}


ATT_MOD("app.o");
```

このうち6行目の `CRE_TSK` で始まる行が、タスクを生成しているコードとなります。
この `CRE_TSK` の書式を順にみていきましょう。

```
CRE_TSK(タスクID,{タスク属性,タスクに渡す引数,タスクの関数名,優先度,スタックサイズ,スタックの先頭番地})
```

### タスクID

タスクの名前を決めます。任意の名前を決められますが、今後の混同を避けるため大文字で指定してください。

### タスク属性

プログラム開始時の、タスクの初期状態を設定します。
タスクが起動状態でスタートする場合は `TA_ACT`、スリープ状態でスタートする場合は `TA_NULL` を指定します。

### タスクに渡す引数

タスクとして起動する関数に、一つだけ引数を渡すことができます。
必要なければ `0` としておきましょう。

### タスクの関数名

タスクとして実行する関数を指定します。型やカッコなどは必要ありません。関数名だけ指定してください。

### 優先度

タスク管理で一番重要と言っても過言ではない、 **タスク優先度** です。
複数のタスクが同時に起動している場合、「どのタスクを優先的に実行するのか」を決める値です。
値が小さいほど、優先度が高くなります。

EV3rtにおいては、我々ユーザーサイドが指定できる最高優先度の値は `TMIN_APP_TPRI` で、値としては `9` です。これより下の値はOSの処理に関わるタスクが動いているので、指定しないようにしましょう。
この値に `+1` や `+2` など足していくのが良いかと思います。

### スタックサイズ

タスク用に確保するメモリの領域を指定します。`app.h`ファイルに `STACK_SIZE` が `4096` と定義されていますので、基本的にはこれを使用していきましょう。(よっぽどタスクを沢山作らない限りは、メモリ不足にはならないはず…)

### スタックの先頭番地

メモリ上のどこにタスクを作成するか指定する事が出来ますが、基本的には `NULL` で大丈夫です。
これにより自動的にスタックサイズ分の領域を確保してくれます。

ではこれを踏まえて、新しいタスクを作ってみます。

```app.cfg
INCLUDE("app_common.cfg");

#include "app.h"

DOMAIN(TDOM_APP) {
CRE_TSK(MAIN_TASK, { TA_ACT, 0, main_task, TMIN_APP_TPRI + 1, STACK_SIZE, NULL });
CRE_TSK(SUB_TASK, { TA_NULL, 0, sub_task, TMIN_APP_TPRI + 2, STACK_SIZE, NULL }); // 追加文
}

ATT_MOD("app.o");
```

新しく `SUB_TASK` というタスクを作成し、優先度は `MAIN_TASK` よりも1つ低くなるように(値的には1大きく)設定しました。

# 4. マルチタスクを実行する

では、実際にマルチタスクを用いたプログラムを実行してみましょう。
今回は以下のような構成のマルチタスクを実行してみたいと思います。

- メインタスク: 1秒ごとに変数を1上げてLCD上に表示 (→ 1秒ごとのタイマー)
- サブタスク: 0.1秒ごとに変数を1上げてLCD上に表示 (→ 0.1秒ごとのタイマー)

今回の例は、冒頭に説明した **タスクを細かく切り刻む** というのをやらない構成になっていますが、まずは分かりやすい例から説明していきたいので、ご理解の上進めていただければと思います。

改めて、コードを書き記したいと思います。

```c:app.c
#include "ev3api.h"
#include "app.h"
#include <stdio.h>

int flag = 0;
int count = 0;

void main_task(intptr_t unused)
{
    //プログラムをここから書く
    char str[64];
    int a = 0;

    ev3_lcd_set_font(EV3_FONT_MEDIUM);

    ev3_lcd_draw_string("main_task", 0, 0);

    // main_task のみ動作
    while(a < 5){
        sprintf(str,"%d (s)",a);
        ev3_lcd_draw_string(str, 0, 20);
        a++;
        tslp_tsk(1*1000*1000);
    }

    // SUB_TASK を起動
    act_tsk(SUB_TASK);

    // main_task と sub_task が動作
    while(a < 10){
        sprintf(str,"%d (s)",a);
        ev3_lcd_draw_string(str, 0, 20);
        a++;
        tslp_tsk(1*1000*1000);
    }

    // flagをたてる
    flag = 1;

    ext_tsk();
}

void sub_task(intptr_t unused)
{
    char str[64];
    int a = 0;
    ev3_lcd_draw_string("sub_task", 0, 40);

    while(flag==0){
        sprintf(str,"%.1lf (s)",a*0.1);
        ev3_lcd_draw_string(str, 0, 60);
        a++;
        tslp_tsk(100*1000);
    }

    ext_tsk();
}

```

最初の5秒間はメインタスクのみ実行、その後の5秒間はメインタスクとサブタスクの両方が動きます。
ここで、サブタスクを起動状態にするコードを紹介しておきます。

```c:app.c
// SUB_TASK を起動
act_tsk(SUB_TASK);
```

この `act_tsk()` 関数を実行すると、第一引数に指定したタスクIDのタスクを起動状態にします。
タスクIDは、先ほど `app.cfg` で決めたタスクの名前の事です。

そしてこのプログラムの大事な点が、メインタスクはループ中 **「1秒間スリープする」** ということです。
メインタスクは1秒ごとのタイマーなので、1秒待機するという意味でもありますが、**スリープ=実行権の放棄** を意味し、すなわち **メインタスクがスリープすると、優先度が次に高いサブタスクへ実行権が移ります。**

![マルチタスク3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183170/58169570-8a32-aa75-dfc5-39b0d168a64f.png)

これにより、冒頭の説明のマルチタスクが実現されています。
この **実行権の移動** の間隔を短くすれば、人間には同時に行っているように見える、いわゆるマルチタスクが実現することが出来ます。
（繰り返しですが、今回の例ではマルチタスクの動作が分かりやすいように、実行権の移動は1秒ごとに行っています。）

では、アプリをコンパイルし、EV3で実行してみてください。
最初は1秒ごとのタイマーが起動し、途中から0.1秒ごとのタイマーがスタートするのが確認できたでしょうか。
![IMG_8610.GIF](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183170/f3c316b3-9fee-9cac-d9f0-42f08330c2df.gif)

ひとまずこれで、新しいタスクを作成し、実行することは出来ました。
ただ一つ欠点があって、というのも **サブタスクを実行するためにメインタスクをスリープする** 必要があるのって面倒ですよね？
特にロボット競技で言えば、「定期的にセンサ値を取得したい」や「移動しながらアームを動かしたい」など、一定周期で同時並行的にやってほしい事がほとんどであり、周期が決まっているのであれば自動で実行して欲しいところです。
これを解決するのが、次に紹介する **周期ハンドラ** です。

# 5. 周期ハンドラを作る

ということで今度は **周期ハンドラ** の作り方を紹介していきます。
周期ハンドラは **一定周期でタスクを自動的に起動状態** にしてくれる機能です。

これを使えばメインタスクでわざわざスリープしなくとも、自動的に **実行権の移動** を行ってくれて非常に便利なので、使いこなせるようになりましょう!

## 周期ハンドラで実行したいタスクを作る

周期ハンドラの作り方の前に、周期ハンドラはあくまでも **一定周期でタスクを自動的に起動状態** であり、起動するタスクを先ほど同様作っておく必要があります。
よって、先ほどと同じ要領で新しいタスクおよび関数を `app.c` `app.h` `app.cfg` に作成していきましょう。

## app.c

```c:app.c
#include "ev3api.h"
#include "app.h"
#include <stdio.h>

int flag = 0;
int count = 0;

void main_task(intptr_t unused)
{
    // メインタスクの中身は省略
}

// サブタスク
void sub_task(intptr_t unused)
{
    // サブタスクの中身は省略
}

// 周期ハンドラで実行するタスク
void cychdr(intptr_t unused)
{
    char str[64];
    ev3_lcd_draw_string("cychdr", 0, 80);

    sprintf(str,"%.1lf (s)",count*0.5);
    ev3_lcd_draw_string(str, 0, 100);
    count++;
}

```

新しい関数として `cychdr()` を定義しました。
0.5秒間隔で変数の値を1上げて、LCDに表示します。
言い換えれば0.5秒間隔のタイマーです。

## app.h

先ほど同様、`app.h` には新しく定義した関数のプロトタイプ宣言をしておきましょう。

```c:app.h
/*
 *  関数のプロトタイプ宣言
 */
#ifndef TOPPERS_MACRO_ONLY

extern void	main_task(intptr_t exinf);
extern void	sub_task(intptr_t exinf);
extern void	cychdr(intptr_t exinf); // 追加
```

## app.cfg

まずは、タスクを追加したいので以下のように追記します。

```app.cfg
INCLUDE("app_common.cfg");

#include "app.h"

DOMAIN(TDOM_APP) {
CRE_TSK(MAIN_TASK, { TA_ACT, 0, main_task, TMIN_APP_TPRI + 1, STACK_SIZE, NULL });
CRE_TSK(SUB_TASK, { TA_NULL, 0, sub_task, TMIN_APP_TPRI + 2, STACK_SIZE, NULL });

CRE_TSK(CYCHDR_TASK, { TA_NULL, 0, cychdr, TMIN_APP_TPRI, STACK_SIZE, NULL }); // 追加文
}


ATT_MOD("app.o");
```

タスクの作り方は先ほどと同じです。
一つ注意点として、 **優先度をメインタスクより高く設定** してください。

周期ハンドラは自動的にタスクを実行する機能ですが、この時言うなれば **実行権を取り上げる** 作業を行っています。
ただし実行権を取り上げようとしても、優先度が高い相手から実行権を取り上げることは出来ません。
よって、メインタスクよりも優先度を高く設定しておく必要があります。

![周期ハンドラ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183170/c7e8a941-5ebe-39fd-eaf8-2dc288ed638b.png)

### 周期ハンドラの設定

ではいよいよ、 `app.cfg` ファイルに周期ハンドラの定義を行っていきます。
先に定義を追記したものをお見せします。

```app.cfg
INCLUDE("app_common.cfg");

#include "app.h"

DOMAIN(TDOM_APP) {
CRE_TSK(MAIN_TASK, { TA_ACT, 0, main_task, TMIN_APP_TPRI + 1, STACK_SIZE, NULL });
CRE_TSK(SUB_TASK, { TA_NULL, 0, sub_task, TMIN_APP_TPRI + 2, STACK_SIZE, NULL });

CRE_TSK(CYCHDR_TASK, { TA_NULL, 0, cychdr, TMIN_APP_TPRI, STACK_SIZE, NULL });
CRE_CYC(CYC_HDR, { TA_NULL, { TNFY_ACTTSK, CYCHDR_TASK }, 500 * 1000, 0 }); // 追加文
}

ATT_MOD("app.o");
```

このうち10行目の `CRE_CYC` で始まる行が、周期ハンドラを生成しているコードとなります。
この `CRE_CYC` の書式を順にみていきましょう。

```
CRE_CYC(周期ハンドラの名称,{周期ハンドラの属性,タスクの起動による通知,起動するタスクのID,起動周期,起動位相})
```

### 周期ハンドラの名称

周期ハンドラの名前を決めます。こちらも任意の名前を決められますが、今後の混同を避けるため大文字で指定してください。

### 周期ハンドラの属性

プログラム開始時の、周期ハンドラの初期状態を設定します。
周期ハンドラが起動状態でスタートする場合は `TA_STA`、スリープ状態でスタートする場合は `TA_NULL` を指定します。

### タスクの起動による通知

通知処理モードを指定します。ここは `TNFY_ACTTSK` としておいてください。

### 起動するタスクのID

周期ハンドラにより起動させるタスクのIDを指定します。
タスクのIDは `CRE_TSK` 文の先頭パラメータで指定したタスクの名前の事です。

### 起動周期

タスクを起動させる周期を設定します。
EV3rt v1.0 以降はマイクロ秒単位で指定します。
例えば `500 * 1000` と指定すると、500ミリ秒(=0.5秒) に一回、タスクの起動がかかります。

### 起動位相

周期ハンドラを開始してから一番最初にタスクを起動させるまでに、遅れを生じさせることが出来ます。
例えば、周期ハンドラの属性に `TA_STA` をした状態で、`0` とすればプログラム実行と同時に周期ハンドラも実行、さらにそれにより指定されたタスクも実行されます。一方で、 `100` など値を指定すると 100マイクロ秒遅れて周期ハンドラが実行開始されます。
「最初の1回はメインタスクに処理をさせたい」といったときに使えそうですね。

以上で、周期ハンドラの作成は完了です!

# 6. 周期ハンドラを実行する

それでは、周期ハンドラを用いたプログラムを実行してみましょう。
先ほどの構成に少し手を加えて、周期ハンドラを含めたマルチタスクを実行してみたいと思います。

- メインタスク: 1秒ごとに変数を1上げてLCD上に表示 (→ 1秒ごとのタイマー)
- サブタスク: 0.1秒ごとに変数を1上げてLCD上に表示 (→ 0.1秒ごとのタイマー)
- 周期ハンドラによるタスク: 0.5秒ごとに変数を1上げてLCD上に表示 (→ 0.5秒ごとのタイマー)

ここでも改めて、コードを書き記したいと思います。

```c:app.c
#include "ev3api.h"
#include "app.h"
#include <stdio.h>

int flag = 0;
int count = 0;

void main_task(intptr_t unused)
{
    //プログラムをここから書く
    char str[64];
    int a = 0;

    ev3_lcd_set_font(EV3_FONT_MEDIUM);

    ev3_lcd_draw_string("main_task", 0, 0);

    // main_task のみ動作
    while(a < 5){
        sprintf(str,"%d (s)",a);
        ev3_lcd_draw_string(str, 0, 20);
        a++;
        tslp_tsk(1*1000*1000);
    }

    // SUB_TASK を起動
    act_tsk(SUB_TASK);

    // main_task と sub_task が動作
    while(a < 10){
        sprintf(str,"%d (s)",a);
        ev3_lcd_draw_string(str, 0, 20);
        a++;
        tslp_tsk(1*1000*1000);
    }

    // 周期ハンドラ CYC_HDR を起動
    sta_cyc(CYC_HDR);

    // main_task と sub_task と cychdr が動作
    while(a < 15){
        sprintf(str,"%d (s)",a);
        ev3_lcd_draw_string(str, 0, 20);
        a++;
        tslp_tsk(1*1000*1000);
    }

    // 周期ハンドラ CYC_HDR が停止
    stp_cyc(CYC_HDR);

    // flagをたてる
    flag = 1;

    ext_tsk();
}

void sub_task(intptr_t unused)
{
    char str[64];
    int a = 0;
    ev3_lcd_draw_string("sub_task", 0, 40);

    while(flag==0){
        sprintf(str,"%.1lf (s)",a*0.1);
        ev3_lcd_draw_string(str, 0, 60);
        a++;
        tslp_tsk(100*1000);
    }

    ext_tsk();
}

void cychdr(intptr_t unused)
{
    char str[64];
    ev3_lcd_draw_string("cychdr", 0, 80);

    sprintf(str,"%.1lf (s)",count*0.5);
    ev3_lcd_draw_string(str, 0, 100);
    count++;
}

```

ここで新たに出てくるのが、周期ハンドラを起動する関数 `sta_cyc()` と、終了する関数 `stp_cyc()` です。

```c:app.c
// 周期ハンドラ CYC_HDR を起動
sta_cyc(CYC_HDR);

// 周期ハンドラ CYC_HDR が停止
stp_cyc(CYC_HDR);
```

どちらも第一引数に「周期ハンドラの名前」を指定すると、その周期ハンドラを起動・終了します。

ここでは、10秒経過後に周期ハンドラを起動し、`cychdr()`関数を0.5秒周期で実行します。

ここで重要なポイントですが、周期ハンドラで実行する関数は **出来るだけ短い処理** にする必要があります。

先ほども申したように、周期ハンドラで実行するタスクは優先度が一番高く設定されています。そんな優先度の高いタスクが長々と処理をしてしまうと、メインタスクやサブタスクに順番が回ってこなくなってしまいます。

従って、周期ハンドラで実行するタスク(関数)ではループや〇〇まで待つといった処理はしてはいけないということになります。むしろループ的な動作は周期ハンドラが実行してくれるので、それを踏まえたコードにしましょう。

では、アプリをコンパイルし、EV3で実行してみてください。
0-4秒では1秒ごとのタイマー、5-9秒では0.1秒ごとのタイマー、そして10-14秒では0.5秒ごとのタイマーが動作するのが確認できたでしょうか。

![IMG_8613.GIF](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183170/e92cf654-3624-031c-2d09-87099df53481.gif)

以上で、マルチタスク、及び周期ハンドラの解説は終了です!!
最後に、より実践的なマルチタスクの使い方を紹介して締めたいと思います。

# 7. マルチタスク実践編

これまで紹介してきた例はあくまで **分かりやすくマルチタスクを感じ取る** ための例であったため、より実践的なマルチタスク実装例を提示したいと思います。
目指す形はこんな感じです👇

![スクリーンショット 2024-12-30 151443.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183170/6e436aaf-5734-6fb1-76af-f5f08f1e71e4.png)

- ロボットはカラーセンサで赤色を検知するまで直進する
- 移動中、アーム(モータD)の位置をリセットする
- 移動中、別のカラーセンサで色を測定し続け、LCDに表示する

（ポート2のカラーセンサにHiTechnincカラーセンサを使っていますが、EV3カラーセンサでも大丈夫です。）

上記を実現するようなコードを書いてみましたので、以下に記します。
どのような制御になっているかはこれまでの解説を基にご自身で考えてみてください。
(コメント多めにしておりますので、多分大丈夫です👍)

```c:app.c
#include "ev3api.h"
#include "app.h"
#include <stdio.h>

void main_task(intptr_t unused)
{
    ev3_motor_config(EV3_PORT_B, LARGE_MOTOR); // ポートBをLモータに設定
    ev3_motor_config(EV3_PORT_C, LARGE_MOTOR); // ポートCをLモータに設定
    ev3_motor_config(EV3_PORT_D, MEDIUM_MOTOR); // ポートDをMモータに設定

    ev3_sensor_config(EV3_PORT_3, COLOR_SENSOR); // ポート3をカラーセンサに設定
    ev3_sensor_config(EV3_PORT_2, HT_NXT_COLOR_SENSOR); // ポート2をHiTechnicカラーセンサに設定

    ev3_lcd_set_font(EV3_FONT_MEDIUM); // フォントを中サイズに設定

    ev3_lcd_draw_string("PROGRAM START", 10, 10); // LCDにプログラム開始を表示

    act_tsk(ARM_REST_TASK); // アームをリセットするタスクを起動
    sta_cyc(COLOR_CYC_HDR); // カラーセンサの周期ハンドラを起動

    // カラーセンサの値が赤色になるまで前進
    while(ev3_color_sensor_get_color(EV3_PORT_3) != COLOR_RED)
    {
        ev3_motor_set_power(EV3_PORT_B, 30); // モータBを50%のパワーで前進
        ev3_motor_set_power(EV3_PORT_C, 30); // モータCを50%のパワーで前進
        tslp_tsk(1); // 1us待機
    }

    ev3_motor_stop(EV3_PORT_B, true); // モータBを停止
    ev3_motor_stop(EV3_PORT_C, true); // モータCを停止

    stp_cyc(COLOR_CYC_HDR); // カラーセンサの周期ハンドラを停止

    ev3_lcd_draw_string("PROGRAM END  ", 10, 10); // LCDにプログラム終了を表示

    ext_tsk(); // タスクを終了
}

void arm_reset(intptr_t unused)
{
    ev3_motor_set_power(EV3_PORT_D, -50); // モータDを-50%のパワーで回転
    tslp_tsk(500*1000); // 500ms待機
    ev3_motor_reset_counts(EV3_PORT_D); // モータDのカウントをリセット

    // モータDのカウントが150になるまで回転
    while (ev3_motor_get_counts(EV3_PORT_D) < 150) 
    {
        ev3_motor_set_power(EV3_PORT_D, 50); // モータDを50%のパワーで回転
    }
    ev3_motor_stop(EV3_PORT_D, true); // モータDを停止

    ext_tsk(); // タスクを終了
}

void color_sensor_lcd(intptr_t unused)
{
    int color = 0; // カラーセンサの値を格納する変数
    ht_nxt_color_sensor_measure_color(EV3_PORT_2, &color); // HiTechnicカラーセンサの値を取得
    char str[16];
    sprintf(str, "Color: %d  ", color); // カラーセンサの値を文字列に変換
    ev3_lcd_draw_string(str, 10, 30); // カラーセンサの値を表示
}

```

```c:app.h
/*
 *  TOPPERS/ASP Kernel
 *      Toyohashi Open Platform for Embedded Real-Time Systems/
 *      Advanced Standard Profile Kernel
 *
 *  Copyright (C) 2000-2003 by Embedded and Real-Time Systems Laboratory
 *                              Toyohashi Univ. of Technology, JAPAN
 *  Copyright (C) 2004-2010 by Embedded and Real-Time Systems Laboratory
 *              Graduate School of Information Science, Nagoya Univ., JAPAN
 *
 *  上記著作権者は，以下の(1)〜(4)の条件を満たす場合に限り，本ソフトウェ
 *  ア（本ソフトウェアを改変したものを含む．以下同じ）を使用・複製・改
 *  変・再配布（以下，利用と呼ぶ）することを無償で許諾する．
 *  (1) 本ソフトウェアをソースコードの形で利用する場合には，上記の著作
 *      権表示，この利用条件および下記の無保証規定が，そのままの形でソー
 *      スコード中に含まれていること．
 *  (2) 本ソフトウェアを，ライブラリ形式など，他のソフトウェア開発に使
 *      用できる形で再配布する場合には，再配布に伴うドキュメント（利用
 *      者マニュアルなど）に，上記の著作権表示，この利用条件および下記
 *      の無保証規定を掲載すること．
 *  (3) 本ソフトウェアを，機器に組み込むなど，他のソフトウェア開発に使
 *      用できない形で再配布する場合には，次のいずれかの条件を満たすこ
 *      と．
 *    (a) 再配布に伴うドキュメント（利用者マニュアルなど）に，上記の著
 *        作権表示，この利用条件および下記の無保証規定を掲載すること．
 *    (b) 再配布の形態を，別に定める方法によって，TOPPERSプロジェクトに
 *        報告すること．
 *  (4) 本ソフトウェアの利用により直接的または間接的に生じるいかなる損
 *      害からも，上記著作権者およびTOPPERSプロジェクトを免責すること．
 *      また，本ソフトウェアのユーザまたはエンドユーザからのいかなる理
 *      由に基づく請求からも，上記著作権者およびTOPPERSプロジェクトを
 *      免責すること．
 * 
 *  本ソフトウェアは，無保証で提供されているものである．上記著作権者お
 *  よびTOPPERSプロジェクトは，本ソフトウェアに関して，特定の使用目的
 *  に対する適合性も含めて，いかなる保証も行わない．また，本ソフトウェ
 *  アの利用により直接的または間接的に生じたいかなる損害に関しても，そ
 *  の責任を負わない．
 *
 *  $Id: sample1.h 2416 2012-09-07 08:06:20Z ertl-hiro $
 */

/*
 *  ターゲット依存の定義
 */
#include "target_test.h"

/*
 *  各タスクの優先度の定義
 */

#define MAIN_PRIORITY	5		/* メインタスクの優先度 */
								/* HIGH_PRIORITYより高くすること */

#define HIGH_PRIORITY	9		/* 並行実行されるタスクの優先度 */
#define MID_PRIORITY	10
#define LOW_PRIORITY	11

/*
 *  ターゲットに依存する可能性のある定数の定義
 */

#ifndef STACK_SIZE
#define	STACK_SIZE		4096		/* タスクのスタックサイズ */
#endif /* STACK_SIZE */

#ifndef LOOP_REF
#define LOOP_REF		ULONG_C(1000000)	/* 速度計測用のループ回数 */
#endif /* LOOP_REF */

/*
 *  関数のプロトタイプ宣言
 */
#ifndef TOPPERS_MACRO_ONLY

extern void	main_task(intptr_t exinf);
extern void	arm_reset(intptr_t exinf);
extern void	color_sensor_lcd(intptr_t exinf);

#endif /* TOPPERS_MACRO_ONLY */

```

```app.cfg
INCLUDE("app_common.cfg");

#include "app.h"

DOMAIN(TDOM_APP) {
CRE_TSK(MAIN_TASK, { TA_ACT, 0, main_task, TMIN_APP_TPRI + 1, STACK_SIZE, NULL });
CRE_TSK(ARM_REST_TASK, { TA_NULL, 0, arm_reset, TMIN_APP_TPRI + 2, STACK_SIZE, NULL });

CRE_TSK(COLOR_TASK, { TA_NULL, 0, color_sensor_lcd, TMIN_APP_TPRI, STACK_SIZE, NULL });
CRE_CYC(COLOR_CYC_HDR, { TA_NULL, { TNFY_ACTTSK, COLOR_TASK }, 100 * 1000, 0 });
}


ATT_MOD("app.o");
```


## 周期ハンドラを扱う上での注意点

今回の記事を書くにあたりテストをしていて気づいた点を記しておきます。
周期ハンドラを扱う際、周期ハンドラ終了せずプログラムを終了すると、次回のプログラム実行時に不具合を起こすことが分かりました。
具体的には、周期ハンドラより優先度が低いメインタスクが動かなくなるという症状です。
周期ハンドラを使う際は、必ず周期ハンドラを終了 (`stp_cyc()`) してからプログラムを終了するようにしてください。
(もし不具合が発生した場合は、EV3rtを再起動すると元に戻ります)

# 8. まとめ

今回はタスクの追加、及び周期ハンドラの作り方を紹介し、EV3rtでのマルチタスクを実現しました。
最初は難しいかもしれませんが、これが使いこなせるようになるとプログラミングの幅が大きく広がりますので、是非頑張って使ってみてください。

次回は、「テキストファイルの扱い方」を紹介したいと思います。
センサの値を記録する、いわゆるデータロギングが出来るようになります。

前回: [#10 関数を使おう](https://qiita.com/koushiro/items/2ba33587ea25faf3b8e5)
次回: [#12 テキストを扱おう&ファイルを読み書きしよう](https://qiita.com/koushiro/items/0c5a916d431a36dcdbbc)