EV3RTのタスクの作り方

**これはEV3RTβ版向けに書かれた情報です。
EV3RT 1.0では周期タスクの書き方、時間がミリ秒からマイクロ秒で指定など若干の変更がありますのでご注意ください。**

EV3RTのタスクの増やし方がわからないとの声がありましたので、紹介します。
対象はC, C++, mruby(mruby-ev3rt)です。ただし、mruby on EV3RT+TECSは対象外ですのでご注意。
以降、mrubyとはmruby-ev3rtをさします。
（mrubyの人はサンプルコードを変更して使うのが無難な気がします。こういうふうに変えたいとコメントいただければ回答しますので）

## ここで紹介するもの
* タスク
* 周期ハンドラ

## 変更対象のファイル
* app.cfg
* app.h
* app.cまたはapp.cpp

以下、
mruby用のサンプルコード
https://github.com/yamanekko/mrb_way_2018

C用サンプルコード
https://github.com/ETrobocon/etroboEV3/tree/master/SampleCode/EV3way_EV3RT_sample/sample_c4

を例に解説しますが、ETロボコン参加者はsample_cpp4(参加者専用ページから取得できるサンプルコード)を同じように修正してみてください。


## app.cfgファイルにタスクの定義を追加する


### cfgファイルのタスク定義の読み方

mruby-ev3rtの場合、タスクが３つ、周期ハンドラが２つあります。
* 4ミリ秒周期の周期ハンドラ（BALANCE_CYC）
* BALANCE_CYCから実行されるタスク
* 500ミリ秒周期ハンドラ(WATCH_CYC)
* WATCH_CYCから実行されるタスク
* mainタスク(プログラム開始時から実行する用のタスク)

```
DOMAIN(TDOM_APP) {  
CRE_TSK(MAIN_TASK, { TA_ACT , 0, main_task, TMIN_APP_TPRI + 1, STACK_SIZE, NULL });
EV3_CRE_CYC(BALANCE_CYC  , { TA_NULL, 0, cyclick_handler  , 4, 0 });
CRE_TSK(BALANCE_TASK  , { TA_NULL, 0, cyclick_balance  , TMIN_APP_TPRI + 2, STACK_SIZE, NULL });
EV3_CRE_CYC(WATCH_CYC  , { TA_ACT, 0, cyclick_handler_status_check  , 500, 0 });
CRE_TSK(WATCH_TASK  , { TA_NULL, 0, watch_task  , TMIN_APP_TPRI + 1, STACK_SIZE, NULL });
}
```

### タスクの定義
まず、

```
CRE_TSK(MAIN_TASK, { TA_ACT , 0, main_task, TMIN_APP_TPRI + 1, STACK_SIZE, NULL });
```
に着目してみましょう。
これがタスクの定義です。
CRE_TSK（) でタスクの定義ができます。

#### 引数について
1番目. MAIN_TASK：　タスクの名称、通常大文字で書きます。xxx_TASKという名前がよく使われているみたいですが、こうしないとダメというものではないです。
コード内でタスクを指定するのに使うので、わかりやすい名前にしましょう

2番目. TA_ACT：　プログラムを実行したときにタスクを起動するかどうかを指定します。TA_ACTとすると、プログラムを開始したときに動き出します。最初から動いてほしくないもの、途中で起動したいものはTA_NULLとします。
（上記の例だと最初はMAIN_TASKのみ動いている感じになります）

3番目. 0：拡張情報（通常０を指定しておけばOK）

4番目. main_task タスクから呼び出される関数名

5番目. TMIN_APP_TPRI + 1 タスクの優先度
TMIN_APP_TPRIはev3.hに定義されています。優先度を指定します。
数字が小さい方が優先度が高くなります。ただし、アプリケーションで高優先度を指定すると暴走する原因になりかねないので、TMIN_APP_TPRIより高い優先度（小さい数字）を指定しないようにしましょう。
通常はアプリの中で使う複数のタスク間の優先度を考慮して決めます。

6番目. STACK_SIZE スタックサイズ
タスクの使用するスタックのサイズです。通常このままで良いかなと思います。
mrubyの場合はev3.hの79行目あたりのスタックサイズの指定を ```#define STACK_SIZE  80960``` このように大きくしておくことをすすめています。
ev3.hの設定を変更しない場合はこの引数のスタックサイズにて指定する必要があります。

7番目. NULL
タスクのスタックの先頭番地。通常NULLで良いです。

正確に知りたい人向け情報）
TOPPERS次世代カーネル統合仕様書では以下のように解説されています。

```
CRE_TSK(ID tskid, { ATR tskatr, intptr_t exinf, TASK task, PRI itskpri, SIZE stksz, STK_T *stk })

ATR tskatr タスク属性
intptr_t exinf タスクの拡張情報
TASK task タスクのメインルーチンの先頭番地
PRI itskpri タスクの起動時優先度
SIZE stksz タスクのスタック領域のサイズ（バイト数）
STK_T * stk タスクのスタック領域の先頭番地
SIZE sstksz タスクのシステムスタック領域のサイズ

細かいことを言うと、保護機能対応カーネルなので、あと２つ省略化な引数があります。
詳しくは仕様書をみてください
```

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

**周期ハンドラについてはv1.0で変更がありました。EV3_CRE_CYCは廃止され、CRE_CYCを使います。** 記事の更新はしばらくお待ちください。
以下にリンクしているTOPPERSのサイトに解説があるので、そちらを参照してください。


EV3RT β版では、EV3_CRE_CYCを使用します。こちらは **EV3_** で始まるので注意してください

[TOPPERSのサイトに解説](https://dev.toppers.jp/trac_user/ev3pf/wiki/FAQ#%E3%82%A2%E3%83%97%E3%83%AA%E3%82%B1%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E3%81%AE%E9%96%8B%E7%99%BA%E6%96%B9%E6%B3%95%E3%81%AB%E9%96%A2%E3%81%99%E3%82%8B%E8%B3%AA%E5%95%8F)があるのでそちらを参照してください。

```
EV3_CRE_CYC(BALANCE_CYC  , { TA_NULL, 0, cyclick_handler  , 4, 0 });
```

#### 引数について
上記リンク先のTOPPERSのページをみてもらうとわかりますが、ざっくり説明すると、

1番目. BALANCE_CYC：　周期ハンドラの名称、通常大文字で書きます。タスクの名称同様命名ルールは特にないはずです。
タスク同様コード内で使う場合があるので、わかりやすい名前にしましょう

2番目. TA_NULL：　プログラムを実行したときはまだ動作してほしくない場合はTA_NULLを指定します。起動時から開始したい場合はTA_STAとします。（タスクで指定した値とは違うので注意）

3番目. 0：拡張情報（通常０を指定しておけばOK）

4番目. cyclick_handler 周期的に実行したい関数名

5番目. 4 起動周期をミリ秒で指定

6番目. 0 起動位相。とりあえず0でいいんじゃないでしょうか。ちゃんと理解して設定したい人は上記TOPPERSのページやTOPPERS次世代カーネル統合仕様書の **CRE_CYC** を参照してください。(仕様書にはEV3_CRE_CYCの説明はないです)

上記mrubyの例では周期ハンドラからタスクを起動していますが、必ずしもタスクでなければならないわけではありません。
（mrubyの場合は特に理由がなければタスクにしておいていただくのが良いかなと思います）

## app.hファイルに関数のプロトタイプ宣言を追加する

```

#ifndef TOPPERS_MACRO_ONLY

extern void main_task(intptr_t exinf);
extern void cyclick_balance(intptr_t exinf);
extern void cyclick_handler(intptr_t exinf);
extern void balance_tsk(intptr_t exinf);
extern void cyclick_handler_status_check(intptr_t exinf);
extern void watch_task(intptr_t exinf);

#endif /* TOPPERS_MACRO_ONLY */
```

このように、 ```#ifndef TOPPERS_MACRO_ONLY``` の中に、.cfgファイルで定義したタスクや周期ハンドラで指定した関数名を宣言します。
```extern void 引数で指定した関数名(intptr_t exinf);```　という感じです。
```intptr_t exinf``` のところは通常このままでOK。気になる人は仕様書を調べてください。

参考）

```
#define MAIN_PRIORITY   5
```
このような感じで優先度の定数をここで宣言しておいて、使ったりする人もいます。参考まで。


## app.cまたはapp.cppに関数を追加する

.cfgファイルで定義したタスクや周期ハンドラで指定した関数名の関数を作成します。

```
void main_task(intptr_t exinf){
//C/C++の場合はここにmain_taskで実行したいプログラムをかく
}
```

周期ハンドラからタスクを実行したい場合は

```wup_tsk(WATCH_TASK);``` とか ```act_tsk(WATCH_TASK);```  を呼び出します。
引数はタスクの名称（１番目の引数で指定した大文字でつけた名前）

例えばこんな感じですね。

```
void cyclick_handler(intptr_t exinf){
    wup_tsk(WATCH_TASK);
}
```

C/C++の人はここまでで完成です。

#### タスクからmrubyを実行したい場合

https://github.com/yamanekko/mrb_way_2018/blob/master/app.c
こちらを参考に作成してください。
とりあえずタスクの場合はmain_task()をそのままコピーして使うことをおすすめします。

#### main_task()を自分の作ったタスク用に変更する

##### タスクの名前をRubyのコードからも使えるようにする

**Rubyから使えるタスク名を作る**
app.cの66行目以降にある、以下の部分を.cfgに定義したタスク、周期ハンドラを使って書き換えます。

```
mrb_define_const(mrb, ev3rt, "BALANCE_TASK_ID", mrb_fixnum_value(BALANCE_TASK));
```
この例の場合、
BALANCE_TASK_ID：Rubyのコードでこのタスクを指定する時に使う変数名
BALANCE_TASK：.cfgで定義したタスク名

参考）.cfgに定義した１番目の引数、大文字で指定したタスク名称は内部で数字に変換されています。（コンフィギュレータが実行されたときに決まります）
なので、BALANCE_TASK_IDの正体は内部的に決められた数字です。コーディング時にはどんな数字になるかわからないので、このような指定方法にしています。

**mrubyで呼び出したいバイトコードを指定する**

``` #include "app_ruby.h" ```
mrbcを使って作成したバイトコードを指定します。
この例では、build.shの以下の場所で作成したものを使用しています。

```
/Users/yuri/git/mruby/bin/mrbc -g -Bbcode -oapp_ruby.h app_ruby2.rb
```

xxx.hと名前にして#includeで呼ぶのが無難じゃないかなと思って作った感じです。
これじゃないとダメですというわけではないですが、特にこだわりがなければこうしておいてください。
ご自身で作成された場合はbuild.shにある
**mrbcで生成されたバイトコードに含まれている "extern const uint8_t" を消去するための暫定処理**
を参考にして、extern const uint8_tの行が生成されていたら消さないとエラーになります
。
そこで使っているdelete_extern.rbが使い回しできるんじゃないでしょうか？

**mrubyに渡して実行するバイトコードを指定する**

84行目の

```
ret = mrb_load_irep (mrb, bcode);
```
2番目の引数のみ必要に応じて変更します。
bcodeというのは、上記mrbcでバイトコードを生成した時に指定した名称です。

```
/Users/yuri/git/mruby/bin/mrbc -g -Bbcode -oapp_ruby.h app_ruby2.rb
```

２番目の引数の-Bの後ろの文字列です。好きな名前をつけて構いません。

後は、mrbcで指定した.rbのコードを実装すれば完了です。

参考１）

```if(mrb->exc){``` の中のコードはRubyから出力されるエラーを液晶とシリアル(bluetooth)のログに出力するためのコードです。


参考２）

```
/* LCD画面にログを表示 */
  ev3_lcd_fill_rect(0, 0, EV3_LCD_WIDTH, EV3_LCD_HEIGHT, EV3_LCD_WHITE);
  ev3_lcd_draw_string("EV3way-ET step 3", 0, CALIB_FONT_HEIGHT*
```

これはデバッグ用にEV3の液晶に文字列を表示しています。
"EV3way-ET step 3"のところを好きな文字列に変更して使ってください。
不要な場合はざっくり消してかまいません。