EV3RTを使ってみる(5)

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

前回の記事で「1人でETロボコンに参加予定」という趣旨のことを書きましたが、1人で参加すると色々と大変なのでお勧めできません。特に、モデル締め切り前や大会前になると作業が多く、1人チームの場合はわけがわからない状態になります。あと、レース前にキャリブレーションの時間があるのですが、この間にインタビューにも応える必要がありますので、キャリブレーションや車体の位置合わせに使える時間は20秒程度しかありません。実際、私が去年の参加したときは、車体を適当にスタート地点近くに置いて命令を出すだけでキャリブレーションと位置合わせを自動で行うように準備していました 1

というわけで、1人チームが大変だということはわかっているのですが、私は今年も1人チームです 2

さて、マルチタスクのプログラムを書くときに必須となる排他制御のプログラムを紹介していきます。Toppers/EV3RTでは複数の排他制御機能がサポートされているのですが、今回はその排他制御機能の中のmutexを使っていきます。ちなみに、他にはsemaphoreを使った排他制御ができますし、event queueやmessage queueを使って同期制御もできるようです。

今回のプログラムでは2個のタスク「task1」「task2」が並列に実行され、それぞれのタスクでは変数「count1」もしくは「count2」の値を増加させた後に一定時間sleepします。ただし、10回分(count1もしくはcount2の値が10だけ増加するまで)の処理中にはもう一方の処理が行われないように排他制御を行います。図で書くと下図のような流れになって、それぞれの実行フェーズで「変数countの値の増加」「一定時間sleep」を10回繰り返します。

2015-03-17-1

このような動作をするプログラムをmutexの機能を使って実装していきます。

まずは、ファイルapp.cfgにタスクやmutexに関する記述を書いていきます。タスクに関する記述は今までと同じです。一方、CRE_MTXと記述しているところがmutexのに関する記述で、MTX1というIDをもつmutexを作成しています。あと、TA_TPRIと書いておくとタスクの優先度を考慮してキューイングしてくれるみたいですが、今のところ私は効果をよく理解していません。

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");

次にヘッダファイルapp.hですが、こちらは今までと同じです。

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

最後にプログラムの本体であるapp.cです。まず、プログラムの中でapp.cfgで定義したID(MTX1)を使うことになりますので、このIDを引っ張ってくるためにkernel_cfg.hをincludeしています。ただし、このkernel_cfg.hはkernelごとコンパイルするときのヘッダファイルでして、ダイナミックローダを使う場合はmodule_cfg.hをincludeすることになります 3。次に、mutex(MTX1)で排他制御を開始する場合はloc_mtx(MTX1)と書き、排他制御を終了する場合はunl_mtx(MTX1)と書きます。loc_mtxとunl_mtxの間に書かれた処理が排他制御の対象領域となります。

#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);
    tslp_tsk(100);
  }
}

/* 低優先度タスク */
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;
}

上のプログラムの例では、関数task1の中にloc_mtxとunl_mtxがあり、その間でcount1を増加させています。また、関数task2の中にもloc_mtxとunl_mtxがあり、その間でcount2を増加させています。これらの処理は同時には行われないため、count1とcount2の値は10ずつ増加することになります。EV3で実行してみると下のような結果になりました。

2015-03-18-2

これで排他制御もできます。ただ、使い方にも注意点があるようで、その辺は次回にでも書こうかなと思っています 4

Notes:

  1. それでもバタバタしてしまった。
  2. ぼっちだから仕方ないね。
  3. ただし、ダイナミックローダを使った場合、mutex機能は動作しません。
  4. mutexを使うときの注意点はToppersの仕様書にしっかり書かれています。

Eclipseから起動するTomcatにユーザライブラリを配置する

Eclipseを使ってTomcatを対象とした動的Webページを開発している場合、Eclipse上でプロジェクトを右クリックした後に[Run AS(実行)]→[Run on Server(サーバで実行)]をクリックすると自動的にTomcatを起動してくれます。そのとき、プロジェクトのビルド対象となっているJavaのプログラムをコンパイルし、それをWEB-INF/classes以下にdeployしてくれるのですが、そのままではビルドパスで設定されているJarファイルをdeployしてくれません。

Tomcatなどで動作する動的Webページの場合、ユーザが独自に追加するJarファイルはWEB-INF/lib以下に配置します。しかし、Eclipseプロジェクトのビルドパスにはservlet-api.jarなどのサーブレットコンテナ側のライブラリも含まれているため、これらすべてをWEB-INF/libに配置するわけにはいきません。ですので、ユーザで追加したライブラリファイルのdeployについては別個に設定する必要があります。

さて、プロジェクトを右クリックして[Properties(プロパティ)]をクリックすると、プロジェクトの設定画面を開きます。この画面の中に[Deployment Assembly]という項目がありまして、この項目が[Run on Server(サーバで実行)]をクリックした時のdeploy設定となります。というわけで、この項目で表示されている一覧にdeployしたいユーザライブラリを追加すると、EclipseからTomcatを起動したときにWEB-INF/lib以下に指定したライブラリを自動で配置してくれます。

2015-03-20-1

上の例ではGsonというJsonを解析するためのライブラリをdeploy対象のライブラリとして追加しています。これで、開発用Tomcatの実行環境やプロジェクトのWebContent以下にユーザライブラリを手動で追加しなくてもよくなりますね。

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回しか実行されないはずです

EV3RTを使ってみる(3.5)

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

ここ最近、時間を見つけてはToppers/EV3RTの動作確認をしていたりします。ただ、私はToppersの中の人ではない 1ので、動作仕様については公式ドキュメントに書かれている以上のことは知りません。ですから、あまり難しいことは考えず、「あー、こんな動作するんだー」という感じのいい加減な動作確認をしています。

さて、現在 2のEV3RT最新版はβ3-1となっていますが、まだまだ開発中の段階のようで、センサーポート1番がタッチセンサしか認識しないといった不具合が報告されています 3。また、ダイナミックローダを使った場合、ミューテックス機能はサポートされておらず、セマフォ機能は動作しない 4ので、まともに排他制御をするのは難しいと思われます。というわけで、マルチタスクのプログラムを作る場合は、ダイナミックローダを使う方式ではなく、カーネルと一緒にコンパイルしたプログラムをSDカード経由でロードする方式をとった方がよさそうです。SDカードをEV3本体から引き抜くときに苦労してしまいますが。。。

Toppers/EV3RTの方も開発が継続されているようですので、今後はダイナミックローダでも排他制御機能を使えるようになるかもしれません。とはいっても、現在のバージョンであっても、ダイナミックローダを使わななければ排他制御機能を問題なく利用できますし、シングルタスクのプログラムであれば排他制御を必要としませんのでダイナミックローダを使っても問題ありません。

Notes:

  1. Toppers会員でもありません。というか、私は組み込み分野の人ではないのでToppersの中の人とは接点がないかも。
  2. 2015年3月12日
  3. 最初にサンプルプログラムを実行したときは、ポート1を指定したときだけフリーズしたので、ハードウェアの初期不良かと勘違いしてしまいました。
  4. さらに、アプリケーションのロードができなくなります。カーネルかダイナミックローダにバグがあるのかも?

EV3RTを使ってみる(3)

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

簡単なアプリケーションを作成できたのだけれども、複雑なアプリケーションを開発していくとなると、プログラムを複数のファイルに分割したくなってきます。むしろ、ファイルを分割しないと途中で投げ出したくなります。というか、app.cという適当すぎる名前のファイルにすべてのプログラムを書いていくなど美しくありません。

もちろん、EV3RTは複数のソースファイルを結合してアプリケーションを構築する方法もサポートしているようですので、今回はそれを試してみました。アプリケーションの動作は前回作成したものとまったく同じです。

まずは、アプリケーションのプログラムを作成していきます。

タスク処理の起動部分はhello.cという名前のファイルに書き、表示処理の部分はdisplay.cという名前のファイルに書きました。また、これらのソースファイルにあわせてhello.hとdisplay.hという名前のヘッダファイルも作成しています。このあたりは普通のC言語プログラムでの開発と同じです 1

void main_task(intptr_t exinf);   /* externを書く必要はなかった */
#include "ev3api.h"
#include "hello.h"
#include "display.h"

void main_task(intptr_t exinf) {
  int battery;
  ulong_t time;

  while(1){
    battery = ev3_battery_voltage_mV();
    get_tim(&time);

    display(time, battery);
    tslp_tsk(100);
  }
}
void display(ulong_t time, int battery);
#include "ev3api.h"
#include "display.h"

void display(ulong_t time, int battery)
{
  char battery_str[32];
  char time_str[32];

  sprintf(battery_str, "Battery: %d", battery);
  sprintf(time_str, "Time: %ld", time);
  
  ev3_lcd_set_font(EV3_FONT_MEDIUM);
  ev3_lcd_draw_string("HELLO EV3", 20, 20);
  ev3_lcd_draw_string(battery_str, 20, 40);
  ev3_lcd_draw_string(time_str, 20, 60);
}

次に、app.cという名前のファイルを必ず要求されるので、このファイルも作っておきます。中身はコンパイルが通るのであれば何でもいいですので、空のファイルでも作っておくのが無難でしょう。

/* 空のファイル。日記でも書いとけばいいんじゃないかな。 */

作成が必要なプログラムはここまで。次に、ビルドするための設定を作成していきます。

まずは、makeのコンパイル対象となるファイルにhello.cとdisplay.cを追加します。前回の環境構築時に作成した空のファイルMakefile.incにファイル追加の設定を書き込みます。hello.cとdisplay.cをコンパイルの対象にするには、APPL_COBJS変数にhello.oとdisplay.oを追加します。

APPL_COBJS += hello.o display.o

拡張子が .o となっていることに注意してください。「 XXX.o の基となるファイルは XXX.c である」というルールがMakefileに書かれていて、自動的に拡張子を置換してhello.cとdisplay.cをコンパイルしてくれるようになります 2

最後に、アプリケーションのビルド設定ファイルapp.cfgを作成します。前回と異なるところは、main_taskの定義をしているhello.hを読み込んでいるところと、オブジェクトファイルであるhello.oとdisplay.oを組み込むよに設定してあるところです。

INCLUDE("app_common.cfg");

#include "hello.h"

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

ATT_MOD("hello.o");
ATT_MOD("display.o");

あとは、makeを使ってアプリケーションをビルドして、ビルドしたものをEV3本体に転送してあげれば動作します。前回と同じ簡単なアプリケーションですので、動作したときの感動はあまりありませんが。
2015-03-06-1

Notes:

  1. 標準のC言語の機能をサポートしているようです。ですので、関数の宣言にexternをつける必要はありません(すべてのブロックの外側で何もつけていない宣言はexternをつけた宣言と同じ意味になります)。
  2. makeの置換機能を使った有名なビルドルールですね。makeの置換機能は大変強力で、上手く使えばソースファイルを自動的に探してコンパイルするなんてこともできます。