9.タスク間の排他制御 TOPへ

10.ステートマシン


1.ステートマシン

機器制御では、外部イベントに応じて処理部分を切り替えて動作させことが一般的です。 例えば、ストップウォッチでは、ボタンの押下に応じて、計測待ち、計測、タイム表示を繰り返します。

この図で「計測待ち」「計測中」「表示中」はプログラムの動作状態を表すもので、状態=ステート(state)と言います。 この状態はボタン押下という外部イベントによって切り替わります(変化します)。

この状態を定義して、それによって処理を切り替えるような構造をステートマシーン(state machine)と呼びます。 状態を切り替える方法としては、複数のif文を使って行う方法と、switch case 文を使う方法とがあります。

ステートマシンを実装する場合は上の図の(a)のif 文を使うより、(b) switch case 文を使うのが 一般的です。

if文を使う方法では1回ループで全ての条件判断を行う必要がありますが、switch csee 文では、条件判断を行う部分は一ヶ所だけ なので、その分オーバーヘッドは小さくなります。 しかし、if文を複数並べるのではなく if else 文を連らねれば必ずしも全ての条件判断を行う必要はありません。switch case 文も内部の処理は if else を連ねたものと同じなので、その場合にはオーバーヘッドは変わりません。 ステートマシンの実装で if (else ) より switch case 文が使われる理由は、プログラムの処理構造がより可視化されたものとなるためです。

タイマー割り込みで全ての処理を駆動する場合には、このループを毎ループ回ることになります。その場合一般に、 各状態で行う処理を、その状態に 入った際に行う処理(ENTRY処理)、毎回行う処理(DO処理)、状態から抜ける際に行う処理(EXIT処理)を分ける必要が あります。このうち ENTRY処理とEXIT処理は、状態が遷移した先か元のどちらかで行えばよいので、どちかを省略できます(通常はEXIT処理を省略します)。

例えば、次のようなプログラムになります。

stop_watch.c
#include "kernel.h"
#include "kernel_id.h"
#include "ecrobot_interface.h"

#define NXT_PORT_TOUCH NXT_PORT_S1      //タッチセンサポートの指定

DeclareCounter(SysTimerCnt);            /* SysTimerCntを宣言 */
DeclareTask(Task1);                     /* Task1を宣言 */

void ecrobot_device_initialize(){}      /* OSEK起動時の処理(何もしない)*/

void ecrobot_device_terminate(){}       /* OSEK終了時の処理(何もしない)*/

void user_1ms_isr_type2(void)           /* タイマ割り込み用フック関数 */
{
    SignalCounter(SysTimerCnt); /* カウンタをIncrementする */
}

TASK(Task1)
{
        static int time=0;                             // 変数timeの定義と初期化
        static enum{WAIT,RUN,STOP} state = WAIT;
        static enum{ENTRY,DO,EXIT} eod= ENTRY;

        switch (state){
                case WAIT:
                        if(eod==ENTRY){
                                time=0;
                                display_clear(0); display_goto_xy(0,1);
                                display_string("READY");
                                display_update();
                                eod=DO;
                        }
                        if(eod==DO){
                                if(ecrobot_get_touch_sensor(NXT_PORT_TOUCH)==1){
                                        state=RUN;
                                        eod=ENTRY;
                                }
                        }
                        break;

                case RUN:
                        if(eod==ENTRY){
                                eod=DO;
                        }
                        if(eod==DO){
                                time++;
                                display_clear(0); display_goto_xy(0,1);
                                display_string("RUNNING");
                                //現在のタイムを表示
                                display_update();
                                if(ecrobot_get_touch_sensor(NXT_PORT_TOUCH)==1){
                                        state=STOP;
                                        eod=ENTRY;
                                }
                        }
                        break;

               case STOP:
                        if(eod==ENTRY){
                                display_clear(0); display_goto_xy(0,1);
                                display_string("RESULT_TIME");
                                //最終タイムを表示
                                display_update();
                                eod=DO;
                        }
                        if(eod==DO){
                                if(ecrobot_get_touch_sensor(NXT_PORT_TOUCH)==1){
                                        state=WAIT;
                                        eod=ENTRY;
                                }
                        }
                        break;
        }

        TerminateTask();                        /* 処理終了 */
}


・値を保持すべき変数は static 宣言しておく必要がある。

・このプログラムをコンパイルするためには、OILファイルを実践編の「4.oilファイル」のalarm_test.oilに変更する必要がある。


2.課題1

10msecの時間分解能を持つストップウオッチを作成せよ。


3.課題2

以下の変更を加えたストップウォッチプログラムを作成せよ。