はじめに
ETロボコン2020年に恵庭戦隊トシキングで参加してた人です.
技術的に振り返りたいと思ったので記事を書いてます.また,来年度にむけて設計意図を忘れないよう備忘録としても活用します.
間違っている部分も多いと思いますが,見る方は暖かい目で見ていただけると幸いです.
ここでは,準備からμITRONの使い方を自分なりに説明しています.他はでき次第随時上げていきます・・・
アーキテクチャ編
コード全体はこちら
開発環境
- WSL1 (Windows 10)
- C/C++ (std=c++03)
開発準備の攻略
今年は公式(1)からシミュレータ(2)が配布されていたので,手順通りに構築してます.
去年と違ってほぼ自動だったのでとても楽ですね.
参考文献
(1) ETロボコン2020
(2) etrobo all-in-one installer/builder/launcher environment
μITRON4.0の攻略
μITRON4.0の仕様書(1)と講演会資料(2)を参考にしながら攻略していきます.1
リアルタイムオペレーティングシステム(リアルタイムOS)と聞きなれない用語が出てきますが,肝心な部分はタスク・優先度・周期ハンドラ
あたりを理解できれば使えるかと思います.
次に,この3つを軽く説明していきます.
μITRON4.0におけるタスク・優先度・周期ハンドラ
タスクの状態は(1)の3章2節1項内に記載されている図を抜粋します.
まず,重要なのはタスクを生成(登録)して休止状態にすることです.基本的にETロボコンでは,起動時にすべてのタスクを休止状態にしておくと生成・削除でリソースを気にする必要はなくなります.どうしてもリソース(主にメモリ)が足りないときは未登録状態を活用すればよいかと.
個人的に理想なタスクの状態遷移は休止->実行可能->実行->休止
のサイクルです.複数タスクを定義するとどうしてもこの理想なサイクルにはならなくなりますが,この問題をタスクの優先度を使って解決していきます.
優先度は,実行可能状態から実行状態に移すとき,タスクに持たせることができる変数です.この変数があると,優先度の一番高いタスクは休止->実行可能->実行->休止
のサイクルを保ちやすくなります.2
周期ハンドラでは,各タスクを一定周期で起動しています3.この機能により,タスク内では,いわゆるループ文(for, while)を書くことが少なくなります.
なので,不用意に時間のかかるループ文を書いてしまったり,FILEのI/Oを周期内に書いてしまうと予期せぬエラーや動作が不安定に・・・
周期ハンドラは慣れないとどこがループしているのか気づきずらくなるので公式のサンプル(3)を改造してみるのがいいです.
例示で恵庭戦隊トシキングのコード(4)を載せておくと.
app.cfg
INCLUDE("app_common.cfg");
# include "app.h"
// 休止状態のタスクを定義
DOMAIN(TDOM_APP) {
CRE_TSK( SETUP_GAME_TASK,
{ TA_NULL, 0, setup_game_task, SETUP_GAME_PRIORITY, STACK_SIZE, NULL });
CRE_TSK( GAME_STATE_MANAGER_TASK,
{ TA_NULL, 0, game_state_manager_task, GAME_STATE_MANAGER_PRIORITY, STACK_SIZE*8, NULL });
// 周期ハンドラを定義
CRE_CYC( SETUP_GAME_CYC,
{ TA_STA, { TNFY_ACTTSK, SETUP_GAME_TASK}, 100*1000, 1*1000});
CRE_CYC( GAME_STATE_MANAGER_CYC,
{ TA_NULL, { TNFY_ACTTSK, GAME_STATE_MANAGER_TASK}, 20*1000, 1*1000});
ATT_MOD("app.o");
app.h
# ifdef __cplusplus
extern "C" {
# endif
# include "ev3api.h"
# define SETUP_GAME_PRIORITY (TMIN_APP_TPRI + 1)
# define GAME_STATE_MANAGER_PRIORITY (TMIN_APP_TPRI + 2)
# ifndef STACK_SIZE
# define STACK_SIZE (512)
# endif /* STACK_SIZE */
# ifndef TOPPERS_MACRO_ONLY
extern void setup_game_task(intptr_t exinf);
extern void setup_game_cyc(intptr_t exinf);
extern void game_state_manager_task(intptr_t exinf);
extern void game_state_manager_cyc(intptr_t exinf);
# endif /* TOPPERS_MACRO_ONLY */
# ifdef __cplusplus
}
# endif
app.cpp
// 200 msec loop
void setup_game_task(intptr_t exinf)
{
d.init("Ready");
if (setupGame.isStarted())
{
gameStateManager.init();
sta_cyc(GAME_STATE_MANAGER_CYC);
}
if (ev3_button_is_pressed(LEFT_BUTTON))
{
snprintf(syslogBuf, sizeof(syslogBuf), "push left button:stop ev3");
syslog(LOG_NOTICE, syslogBuf);
stp_cyc(SETUP_GAME_CYC);
stp_cyc(GAME_STATE_MANAGER_CYC);
gameStateManager.terminate();
}
ext_tsk();
}
// 20 msec loop
void game_state_manager_task(intptr_t exinf)
{
gameStateManager.manageGameState();
ext_tsk();
}
まとめると
- リソースに余裕があるなら,最初にタスクは定義して休止状態にしてあげる
- 理想は
休止->実行可能->実行->休止
のサイクル - 実行可能から実行の間には優先度でタスクの順位付けをしてあげる4
- 周期ハンドラは実質ループ(for, while)文とみなす
- タスク内の処理は(for, while)文を少なくして計算量を多くしない
その他タスクの定義方法や使い方についてはTOPPERSのFAQや(1)を見てみると理解が進むかもしれません.
参考文献
(1) ITRON仕様書 ホワイトペーパー
(2) 講演会テキスト
(3) sample_c4
(4) 恵庭戦隊トシキングのコード
さいごに
次は,アーキテクチャの設計意図についてまとめる予定です.
app.cfgの引数についてより知りたい方が多ければスピンオフで作るかもしれないです.