EV3RTを使ってみる(4)

Toppers/EV3RTを使ってプログラムを作成したときの記事です。
この記事を書いた時のEV3RTのバージョンはβ3です。
目次はこちら

2015年度のETロボコンの申し込みが開始されました。私もさっそく申し込んだのですが「今年もあなたのチームは1人だけなの?マジで大丈夫??去年も同じようなこと聞いたけどさ。」という内容(もちろん、本当の文面は十分に丁寧なものです)のメールをもらったりしています。一緒に参加してくれるような友達がいないので仕方がありません。そんなことよりも、若手技術者を対象とした大会なのに、おっさんが参加してもよいものか悩んでいます 1

さて、EV3RTでは複数のタスクを並列に実行することができます。ただし、並列に実行するといっても、最初に最も優先度の高いタスクが実行され、そのタスクが停止している間に次に優先度の高いタスクが実行されるという方式です 2。今回は、このマルチタスク実行時の優先度制御機能を確認していきます。

先日の記事で書いたように、現在のToppers/EV3RTのバージョンでは、ダイナミックローダを使うとマルチタスクを制御するための排他制御機能が正しく動きません。というわけで、今回からはkernelと一緒にコンパイルしてSDカードからロードする方式で実行確認をしています。

今回作成するプログラムは、優先度の高いタスク「task1」と優先度の低いタスク「task2」を作成し、これらのタスクを起動時に実行します。ただし、「task1」にはsleep時間が存在し、「task2」にはsleep時間が存在しません。予想される実行順序は下図のようになります。

2015-03-13-1

優先度の高い「task1」が実行されている間は「task2」は待機状態になっており、「task1」がsleep状態になっている間に「task2」が実行されるプログラムです。

まずは、2個のタスクを定義するapp.cfgを作成します。優先度の高い「task1」と優先度の低い「task2」を定義しています。ここで、task1の優先度をしている場所は「TMIN_APP_TPRI + 1」ですが、この優先度の設定値が低いタスクほど優先度の高いタスクとなります。

INCLUDE("app_common.cfg");

#include "app.h"

DOMAIN(TDOM_APP) {
  CRE_TSK(TASK1, { TA_ACT, 0, task1, TMIN_APP_TPRI + 1, STACK_SIZE, NULL });
  CRE_TSK(TASK2, { TA_ACT, 0, task2, TMIN_APP_TPRI + 2, STACK_SIZE, NULL });
}

ATT_MOD("app.o");

つぎに、ヘッダファイルapp.hです。それぞれのタスクに対応する関数「task1」「task2」の他に、状態表示のための関数「display」と実行時間を計算する関数「getTime」を定義しています。

void task1(intptr_t exinf);
void task2(intptr_t exinf);
void display();
ulong_t getTime();

最後に、プログラムの本体app.cです。関数task1が優先度の高いタスクとして実行される関数で、count1の値を増やして表示処理を行った後にsleep(tslp_tsk)するように実装しています。一方、関数task2は優先度の低いタスクとして実行される関数で、処理の途中にsleepする箇所はありません。

#include "ev3api.h"
#include "app.h"

int count1 = 0;
int count2 = 0;

/* 高優先度タスク */
void task1(intptr_t exinf)
{
  while(getTime() < 10000){
    count1 += 1;
    display();
    tslp_tsk(100);
  }
}

/* 低優先度タスク */
void task2(intptr_t exinf)
{
  while(getTime() < 10000){
    count2 += 1;
    display();
  }
}

/* 状態を表示する。 */
void display()
{
  char message1[32];
  char message2[32];
  char message3[32];
  int time = (int) getTime();

  sprintf(message1, "time = %d", time);
  sprintf(message2, "count1 = %d", count1);
  sprintf(message3, "count2 = %d", count2);
  
  ev3_led_set_color(LED_GREEN);
  ev3_lcd_set_font(EV3_FONT_MEDIUM);
  ev3_lcd_draw_string(message1, 20, 30);
  ev3_lcd_draw_string(message2, 20, 50);
  ev3_lcd_draw_string(message3, 20, 70);
}

/* 経過時間を返す。 */
ulong_t getTime()
{
  static ulong_t start = -1;
  ulong_t time;

  get_tim(&time);

  if(start < 0){
    start = time;
  }

  return time - start;
}

上記のプログラムをEV3の実機で実行してみたところ、count1の値も増加しているのが確認できました。上の図に書いたように、task1がsleepしている間にtask2が実行されているようです 3

2015-03-18-1

組み込み分野のプログラムを作成していると、「優先度は低いけど長時間かかる処理」を実行しながら「優先度は高いけど短時間で終了する処理」を実行しなければならないという状況が多いのですが、これであれば問題なく対応できそうです。

Notes:

  1. 年齢的にはおっさんですが、組み込み分野の技術力という意味でははまだまだ初心者レベルなので、大会に参加させてください
  2. Mindstorms EV3はSitara ARM Processorを採用しているので、COREの数は1個だけです。その上、リアルタイム性重視の組み込み環境であることを考えると、TSSよりも現実的な実装方法ですね。
  3. そうでなければ、task1は1回しか実行されないはずです