EV3RTを使ってみる(7.5)

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

前回の記事でEV3RTの周期処理の優先度は通常タスクの優先度より高いということがわかりました。周期処理の優先度が高いということは、1回の周期処理に必要となる時間が長い場合、その周期処理を実行している間は他のタスクが停止してしまうことを意味します。

例えば、以下のプログラムを実装した場合を考えてみます。このプログラムではタスク「task1」と周期処理「cyc1」を実装しています。task1はカウントと表示処理を行うタスクで、1回の処理が終了するごとに0.1秒sleepします。一方、cyc1は2秒おきに呼ばれる周期処理で、1回の処理が終了するまでに1秒の時間を必要としています。また、cyc1にはsleepなどの待機処理は含まれていません。

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, 2000, 0 });
}

ATT_MOD("app.o");
void task1(intptr_t exinf);
void cyc1(intptr_t exinf);
void display();
ulong_t getTime();
#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 cyc1(intptr_t exinf)
{
  ulong_t start = getTime();

  if(start >= 10000){
    return;
  }

  while(getTime() - start < 1000){
    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;
}

このプログラムを実行したときの流れは下図のようになります。実行開始時よりtask1は実行とsleepを繰り返していますが、cyc1が呼び出されるとtask1は待機状態になります。cyc1の処理にかかる時間は1秒ですので、task1は1秒間待機することになります。

2015-04-16-1

cyc1で行われる処理の方を優先する場合は上記の実装で問題ありません。しかし、task1で行われる処理を優先したい場合は上記のプログラムでは困ることになります。

task1の処理を優先したい場合、下図の実行手順で動作するプログラムを作ることになります。ポイントは、周期処理「cyc1」から低優先度タスク「task2」を呼び出すようにするところです。この実装であれば、周期的に起動されるtask2の処理はtask1がsleepしている間に実行されるようになります。

2015-04-16-2

プログラムは以下のようになります。まず、低優先度タスク「task2」を生成するようにapp.cfgに記述します。このとき、task2が自動実行されないようにするため、引数にTA_NULLを指定しておきます。

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_NULL, 0, task2, TMIN_APP_TPRI + 2, STACK_SIZE, NULL });
  EV3_CRE_CYC(CYC1, { TA_STA, 0, cyc1, 2000, 0 });
}

ATT_MOD("app.o");

ヘッダファイルapp.hはいつもと同じです。

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

ソースファイルapp.cでは、周期処理cyc1から低優先度タスクtask2を起動するプログラムを記述します。関数cyc1の中のact_tsk(TASK2)がtask2を起動している部分になります。

#include "ev3api.h"
#include "app.h"
#include "kernel_cfg.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)
{
  ulong_t start = getTime();

  if(start >= 10000){
    return;
  }

  while(getTime() - start < 1000){
    count2 += 1;
    display();
  }
}

/* 周期ハンドラ */
void cyc1(intptr_t exinf)
{
  act_tsk(TASK2);
}

/* 状態を表示する。 */
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;
}

上記のプログラムを実行すると、周期処理cyc1が低優先度タスクtask2を起動し、task1がsleepしている間にtask2の処理が実行されます。周期実行機能を積極的に使っていく場合、上記のようなプログラムであれば実行順序を上手くスケジュールできそうです。