EV3RTを使ってみる(7)

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

2015年度のETロボコンの申し込み期間も終了し、そろそろ本格的に開発をはじめているチームもあるかと思いますが、私の方は慌ただしい日が続いているために落ち着いて開発できていません。間に合うのかな、これ。

さて、今回はEV3RTの周期実行の仕組みを使ってみます。Toppersの仕様にも周期実行機能やAlarm実行機能が含まれています。しかし、EV3RTのAPIやサンプルプログラムを読んでみたところ、EV3RTには独自の周期実行機能が実装されているようで、こちらの機能を使ったほうがよさそうです。

今回作るプログラムでは1個のタスク「task1」と1個の周期処理「cyc1」を作ります。task1はカウントと表示を繰り返す処理であり、途中にsleepなどの待機処理は含まれません。一方、cyc1は1秒おきに実行される周期処理で、こちらもカウントと表示処理を行います。処理の流れは下図のようになります。

2015-04-15-1

ここで、task1は待機処理を含まないので、task1とcyc1の優先度が重要になってきます。cyc1の処理が実行されるためには、cyc1の優先度がtask1の優先度よりも高くなくてはなりません。もし、cyc1の優先度がtask1の優先度よりも低い場合は、常にtask1が実行され、cyc1は実行されないことになります。このあたりの実装も実際に動かして試してみます。

まずはタスクと周期処理のプロセスを作成するようにapp.cfgに記述します。タスク「task1」の作成の部分は今までと同じです。一方、周期処理「cyc1」を作成を指示している部分はEV3_CRE_CYCと書いてある箇所になります。ここの引数ですが、CYC1は周期処理のID、TA_STAは周期処理を自動実行させるための指定、cyc1は周期処理を実装する関数名、1000が周期処理の実行間隔(単位はミリ秒)となります。ちなみに、最後の引数の0は起動までの時間だと思いますが、3個目の引数の0はよくわかりません 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 });
  EV3_CRE_CYC(CYC1, { TA_STA, 0, cyc1, 1000, 0 });
}

ATT_MOD("app.o");

ヘッダファイルapp.hは今までとほぼ同じです。ただし、周期処理を行う関数cyc1の宣言も行っています。

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

ソースファイルapp.cにタスク「task1」と周期処理「cyc1」の内容を記述していきます。タスクと周期処理以外に表示と時間計測の関数がありますが、これらの関数は前回までと同じです。task1は指定時間が経過するまでカウントと表示処理を繰り返しています。一方、cyc1では、この関数が呼ばれるたびにカウントと表示処理を実行します。

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

int count1 = 0;
int count2 = 0;

/* タスク */
void task1(intptr_t exinf)
{
  while(getTime() < 10000){
    count1 += 1;
    display();
  }
}

/* 周期処理 */
void cyc1(intptr_t exinf)
{
  if(getTime() >= 10000){
    return;
  }

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

これらのプログラムをkernelと一緒にコンパイルし、EV3本体で実行します。実行結果は下の写真の通りで、cyc1が約1秒おきに実行されているのことが確認できました。また、上のプログラムでcyc1が実行されるということは、周期処理の優先度はタスク処理の優先度よりも高いということになります。この優先度の関係を考えながらプログラムを設計しないといけないかもしれません。

2015-04-15-2

ちなみに、ソースコードの中にはToppersの周期実行機能であるCRE_CYCを実装したような形跡があります。ただ、ソースコードを読んだりはしていないので、このCRE_CYCの部分が何を意味するかは分かりません。EV3_CRE_CYCもCRE_CYCも引数の数や型が同じであることから想像すると、これらは同じ実装になっているのかもしれません。まぁ、ドキュメントもなければソースコードも読んでいないので、想像でしかありませんが。

追記(2015/04/16):
EV3RTの公式ページのFAQで周期処理の記述方法が説明されていました。

Notes:

  1. 実行される関数に渡される値だと思いますが、私の手元では確かめていません。