EV3RTを使ってみる(8)

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

EV3RTのβ4がリリースされました。console機能が追加されたり、bluetooth機能が整理されたり、C++開発環境が整備されたりしています。ただし、現在 1のEV3RTの最新バージョンはβ4ということですので、リリース版までに動作仕様が変更される可能性がありそうです。ETロボコンのスケジュールを考えるとリリース版を待たずに開発を始めるチームが多いと思いますが、EV3RTの仕様変更に対応できるように設計しておかないとひどい目に会いそうな予感がします。

さて、今回はbluetooth通信のプログラムを作っていきますが、その前にbluetoothの接続名とPINコードの設定を行います。β3版では接続名とPINコードがEV3RTのソースコードにハードコーディングされていました 2。しかし、β4版ではSDカード上に設定ファイル「/ev3rt/etc/rc.conf.ini」を作成し 3、その設定ファイルに接続名とPINコードを記述するようになっています。この設定ファイルの記述例は下記の通りで、「LocalName」が接続名の設定となり、「PinCode」がPINコードの設定となります。

[Bluetooth]
LocalName=Mindstorms EV3
PinCode=0000

次に、EV3本体への接続を確認するのですが、Windows, Linux, OSXからEV3本体へbluetooth経由で接続する方法についてはEV3RTの公式ベージで説明されていますので、こちらを参考にしながらbluetooth経由で接続できることを確認します。

さて、bluetooth経由で接続できることを確認したところで、この通信路を使ってデータの送受信を行うプログラムを作成します。今回のプログラムではタスク「task1」を生成し、このタスク内でbluetooth経由で送られてきたデータを受信し、受信したデータをそのまま返信するようにします。また、受信したデータの内容をLCDに表示するようにします。

まず、設定ファイルapp.cfgにタスク「task1」を実行可能状態で生成するように記述します。

INCLUDE("app_common.cfg");

#include "app.h"

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

ATT_MOD("app.o");

次に、ヘッダファイルapp.hにタスク処理関数「task1」と情報表示関数「display」のプロトタイプ宣言を記述します。

void task1(intptr_t exinf);
void display();

最後にソースファイルapp.cに処理を記述します。タスク「task1」ではbluetooth経由でデータを受信し、受信したデータをそのまま送信しています。また、受信したデータを文字配列変数「message」に保存し、この内容をLCDに表示しています。

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

#define MESSAGE_LEN  8

static char message[MESSAGE_LEN + 1] = {0};

/* タスク */
void task1(intptr_t exinf)
{
  FILE *bt;

  // 接続状態を確認
  while(!ev3_bluetooth_is_connected()){
    tslp_tsk(100);
  }

  // シリアルポートを開く
  bt = ev3_serial_open_file(EV3_SERIAL_BT);

  display();

  // 通信処理
  while(1){
    int size = fread(message, 1, MESSAGE_LEN, bt);
    
    if(size > 0){
      fwrite(message, 1, size, bt);
    }

    display();
  }
}

/* 状態を表示する。 */
void display()
{
  ev3_lcd_set_font(EV3_FONT_SMALL);
  ev3_lcd_draw_string("Program is running", 10, 30);
  ev3_lcd_set_font(EV3_FONT_MEDIUM);
  ev3_lcd_draw_string(message, 10, 40);
}

EV3RTでは、bluetoothを介したデータ通信処理をファイルストリームを扱う場合と同じように実装するとができます。ただし、EV3RTのβ4版ではbluetooth機能の仕様がβ3版から少し変更されまして、bluetoothの接続を完了してから通信処理(open, read, write)を実行しないと正常に通信できないようになりました 4

そこで、まずは関数「ev3_bluetooth_is_connected」を呼び出し、bluetoothの接続を完了しているかどうかを確認します。もしbluetooth接続を完了していれば関数「ev3_serial_open_file」を実行し、データを送受信するためのファイルポインタを作成します。あとは、作成したファイルポインタを引数にしてfread, fwrite, fgets, fprintfなどの出入力関数を呼び出すことによりデータの送受信処理を実行できます。

このプログラムをEV3本体で実行し、他のPCからbluetooth経由でEV3本体に接続します。この状態でPC側からデータを送信すると、8byteごとに返信と表示を行います。

上記のようなプログラムを作成することでbluetoothを介した通信が可能になります。ただし、受信データが不足している状態でfreadやfgetsなどの入力関数を実行した場合、その入力関数によって処理がブロックされます。そのため、ロボット制御などのリアルタイム性の高いプログラムを実装する場合は「制御タスク」と「通信タスク」をわけて実装したほうがよさそうです。

Notes:

  1. 2015年4月28日現在
  2. 私の環境ではEV3RT-β3のPINコードの設定部分を書き換えても正常には反映されませんでした。
  3. uImageをEV3本体で実行すると設定ファイルが自動的に作成されます。
  4. β3ではfreadを呼び出してからbluetoothの接続を行っても正常に通信出来ました。

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の処理が実行されます。周期実行機能を積極的に使っていく場合、上記のようなプログラムであれば実行順序を上手くスケジュールできそうです。

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. 実行される関数に渡される値だと思いますが、私の手元では確かめていません。

EV3RTを使ってみる(6)

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

2015年度の新学期がはじまり、ETロボコンに向けて本格的に活動を開始している人達もいるかと思います。そのような人にとって少しでも役に立てばと思ってこの記事を書いていますが、記事を書くのが遅いので役に立っているか分かりません。さて、今回はイベント機能について書いていきます。あと、近日中に周期処理とBluetooth通信についても書いていく予定です。

イベント機能といっても、「ボタンを押したら○○する」といった処理を行うものではなく、イベントフラグを設定したり、イベントフラグが設定されるのを待機したりというような同期機能のことです。この機能を使うことで「他のタスクの処理が終了するまで待機する」といった同期処理を実装することができます。

では、今回のプログラムですが、下図に描いた流れで動作するプログラムを作成していきます。まず、高優先度タスクtask1と低優先度タスクtask2を作成し、これらを並列で実行します。そして、task2の実行中はtask1は待機し、task2の処理終了後にtask1の処理を実行するようにします。このように動作するプログラムをイベント機能を使って実装していきます。

2015-04-07-1

まずは、イベントフラグを作成するようapp.cfgに記述します。CRE_FLGの部分がイベントフラグを作成している部分で、ここではFLG1というIDのイベントフラグを作成しています。また、TA_TPRIで優先度を考慮したキューイングをするように設定しており、最後の引数0x00でイベントフラグの初期値を設定しています。これで、0x00に設定されたイベントフラグFLG1が作成されます。それ以外の部分(タスク生成など)は今までと同じですね。

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 });
  CRE_FLG(FLG1, { TA_TPRI, 0x00 });
}

ATT_MOD("app.o");

ヘッダファイルapp.hは今までと同じです。

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

ソースファイルapp.cにtask1とtask2の内容を書いていきます。task2はcount2の値を増やしながら表示処理を時間制限まで行い、制限時間を過ぎるとイベントフラグFLG1の値を0x01に変更して終了します。関数task2の中のset_flgの部分がイベントフラグFLG1の値を0x01に変更している部分です。一方、task1はイベントフラグFLG1の値が0x01に変化するまで待機し、イベントフラグFLG1の値が0x01になるとcount1の値を増加させて表示処理を行います。関数task1の中のwai_flgの部分がイベントフラグFLG1の値が0x01になるまで待機している部分です。また、clr_flgの部分でイベントフラグの値を0x00に戻しています 1

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

int count1 = 0;
int count2 = 0;

/* 高優先度タスク */
void task1(intptr_t exinf)
{
  FLGPTN flag;

  wai_flg(FLG1, 0x01, TWF_ANDW, &flag);
  clr_flg(FLG1, 0x00);

  count1 += 1;
  display();
}

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

  set_flg(FLG1, 0x01);
}

/* 状態を表示する。 */
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と一緒にコンパイルして実行してみると、task2の処理が行われてからtask1の処理が行われているのを確認できます。

2015-04-07-2

イベント機能は単純な機能ですが、初期化処理が終了するのを待機したり、通信データが到着するのを待機したりと使い勝手のよい機能だと思います。詳しい使い方はToppersのマニュアルに書かれていますので、イベント機能を使い倒したい方はマニュアルを参考にしてください 2

Notes:

  1. このプログラムでは何の意味もありません。
  2. 本気で開発するのであれば、一次資料を参考にするのは当たり前ですけど。

EV3RTを使ってみる(5.5)

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

前回の記事で排他制御の1つであるmutex機能の紹介をしましたが、このmutex機能を使うときに注意しなくてはならないことがあるようですので、今回は下記のプログラムを使ってmutexの実装方式を確認してみます。今回使うプログラムは前回のプログラムとほとんど同じですが、高優先度タスクであるtask1の中のアンロック処理(unl_mtx)とロック処理(loc_mtx)との間にあったsleep処理(tslp_tsk)を削除しています。つまり、task1はMTX1をアンロックすると待機処理を含まずに再びロックを試みるようなプログラムになっています。

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 });
  CRE_MTX(MTX1, { TA_TPRI, 0 });
}

ATT_MOD("app.o");
void task1(intptr_t exinf);
void task2(intptr_t exinf);
void display();
ulong_t getTime();
#include "ev3api.h"
#include "app.h"
#include "kernel_cfg.h"

int count1 = 0;
int count2 = 0;

/* 高優先度タスク */
void task1(intptr_t exinf)
{
  while(getTime() < 10000){
    loc_mtx(MTX1);

    for(int i = 0; i < 10; i++){
      count1 += 1;
      display();
      tslp_tsk(100);
    }

    unl_mtx(MTX1);
    /* 待機せずにMTX1のロックを試みる */
  }
}

/* 低優先度タスク */
void task2(intptr_t exinf)
{
  while(getTime() < 10000){
    loc_mtx(MTX1);

    for(int i = 0; i < 10; i++){
      count2 += 1;
      display();
      tslp_tsk(100);
    }

    unl_mtx(MTX1);
    tslp_tsk(100);
  }
}

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

さて、この排他制御の実装方式には大きく分けて2通りが存在するため、このプログラムを実行した場合に予想される実行結果も2通り存在します。1つ目の実装方式は、アンロック処理(unl_mtx)では資源の解放だけを行い、ロック処理(loc_mtx)が呼ばれたときに資源を獲得するタスクを判定する方式です。この場合、高優先度タスクであるtask1が資源を専有することになりますので、下図の実行パターンAのようにtask1が資源を使い続けることになります。2つ目の実装方式は、アンロック処理(unl_mtx)が呼ばれた時に待機状態となっているタスクに資源解放を通知する方式です。この方式の場合、task1のアンロック処理(unl_mtx)からtask2のロック処理(loc_mtx)に処理が移ることになりますので、下図の実行パターンBのようにtask1とtask2が交互に実行されることになります。

2015-04-01-1

実際に上のプログラムを実行してみたところ、実行結果は上図の実行パターンBと同じ結果になりました。EV3RTのmutex実装では、アンロック処理(unl_mtx)を呼び出した時に待機状態のタスクに対して実行可能であることを通知する仕組みになっているようです 1

2015-04-01-2

さて、この実装方式を採用しているmutex機能ですが、使用上の注意点があります。それは、高優先度タスクがアンロック処理を実行した時点で、低優先度タスクのロック処理が実行される可能性があるという点です。例えば、高優先度タスクの中でロック処理とアンロック処理を繰り返している場合、高優先度タスクの処理途中に低優先度タスクの処理が割り込んでしまう可能性があります。そして、低優先度タスクの排他制御範囲内の処理が長い時間を要する場合、それは高優先度タスクの実行時間に決定的な影響を与えるかもしれません。

以前、ETロボコンのモデリング審査委員の先生から排他制御の設計についても書いて欲しいという話を聞いたのですが、この辺りの機能や特性を理解してモデリングしているのかを見たいのかもしれません。5ページしかないモデリング資料に排他制御の設計まで書くスペースなんか無いよと思ったりもしますけどね。

Notes:

  1. まさに教科書通りの正統派の実装方式ですね。