フェーズ0

E0-06 状態遷移とエラー判別:INIT / IDLE / RUN / ERROR をログで追えるようにする

シリーズ:実験シリーズ(フェーズ0)
対応ロードマップ:フェーズ0 / E0-06
この記事で扱う範囲:状態とエラー番号を定義し、ログで「今どの状態か」「なぜ止まったか」を追えるようにする。

1. 目的

前回までで、UARTログを出せる状態になりました。
ただ、ログが出るだけでは、動かないときに原因を追いにくいです。

たとえば、ただ error とだけ出ても、

  • 初期化で失敗したのか
  • 動作中に異常になったのか
  • 何の異常なのか

が分かりません。
そこで今回は、プログラムを次の4つの状態に分けます。

  • INIT
  • IDLE
  • RUN
  • ERROR

さらに、異常時の原因を表す エラー番号 も決めます。
今回のゴールは、UARTログを見て、

  • 今どの状態か
  • どのエラーで止まったか

を分かるようにすることです。

2. 回路・配線

今回は新しい外付け回路は使いません。
E0-05 のUARTログ出力ができる配線のまま 行います。

今回使うもの

ポイント

今回の主役は配線ではなく、ソフト側で状態とエラーを定義することです。
そのため、ハード構成は最低限で構いません。

3. 手順

3-1. 状態を決める

今回は次の4状態を使います。

  • INIT:初期化中
  • IDLE:待機中
  • RUN:動作中
  • ERROR:異常状態

3-2. エラー番号を決める

今回は例として次を使います。

  • 0:エラーなし
  • 1:初期化失敗
  • 2:テスト用異常
  • 3:想定外の状態

※エラー番号の詳しい決め方や分類方法は、この記事では扱いません。

3-3. 状態番号とエラー番号をUARTログに出せるようにする

UARTログで確認しやすいように、状態ごとに送信する文字列と、エラーごとに送信する文字列を用意します。
これにより、PC側のTeraTermで INITIDLERUNERROR や、エラー内容を確認できるようにします。
文字列と送信処理の内容は、4章のコード(最小構成)で示します。

3-4. 正常系と異常系を確認する

まずは、そのままデバッグ実行または書き込み後に動作させ、UARTログで
INIT → IDLE → RUN
と進むことを確認します。

次に、異常系の確認として、4章の int error_test = 0;01 に変更します。

これにより、RUN中にテスト用異常が発生するようになります。
この状態で再ビルドして実行し、UARTログで
RUN → ERROR
へ遷移し、エラー番号が出ることを確認します。
※確認後は、int error_test = 0; に戻します。

e² studioを使用したデバッグ方法は次を参照してください。
⇒「初心者のためのデバッグ入門:ブレークポイントと変数を見る

4. コード(最小構成)

最小構成の例です。
hal_entry.cにあるhal_entry関数を以下のように修正します。

void hal_entry(void)
{
    fsp_err_t err;

    int state_no = 0;   /* 0=INIT, 1=IDLE, 2=RUN, 3=ERROR */
    int error_no = 0;   /* 0=なし, 1=UART_OPEN_FAILED, 2=TEST_ERROR */
    int error_test = 0; /* 異常確認したいときだけ 1 にする */

    static const uint8_t msg_init[]   = "[STATE] INIT\r\n";
    static const uint8_t msg_idle[]   = "[STATE] IDLE\r\n";
    static const uint8_t msg_run[]    = "[STATE] RUN\r\n";
    static const uint8_t msg_error[]  = "[STATE] ERROR\r\n";
    static const uint8_t msg_err1[]   = "[ERROR] 1 : UART_OPEN_FAILED\r\n";
    static const uint8_t msg_err2[]   = "[ERROR] 2 : TEST_ERROR\r\n";

    /* UART Open */
    err = R_SCI_B_UART_Open(&g_uart0_ctrl, &g_uart0_cfg);
    if (FSP_SUCCESS != err)
    {
        while (1) {;}
    }

    while (1)
    {
        if (state_no == 0)
        {
            err = R_SCI_B_UART_Write(&g_uart0_ctrl, msg_init, (uint32_t)(sizeof(msg_init) - 1));
            if (FSP_SUCCESS != err)
            {
                error_no = 1;
                state_no = 3;
            }
            else
            {
                for (volatile uint32_t i = 0; i < 1000000U; i++)
                {
                    __asm volatile ("nop");
                }

                error_no = 0;
                state_no = 1;
            }
        }

        if (state_no == 1)
        {
            err = R_SCI_B_UART_Write(&g_uart0_ctrl, msg_idle, (uint32_t)(sizeof(msg_idle) - 1));
            if (FSP_SUCCESS != err)
            {
                error_no = 1;
                state_no = 3;
            }
            else
            {
                for (volatile uint32_t i = 0; i < 1000000U; i++)
                {
                    __asm volatile ("nop");
                }

                state_no = 2;
            }
        }

        if (state_no == 2)
        {
            err = R_SCI_B_UART_Write(&g_uart0_ctrl, msg_run, (uint32_t)(sizeof(msg_run) - 1));
            if (FSP_SUCCESS != err)
            {
                error_no = 1;
                state_no = 3;
            }
            else
            {
                for (volatile uint32_t i = 0; i < 1000000U; i++)
                {
                    __asm volatile ("nop");
                }

                if (error_test == 1)
                {
                    error_no = 2;
                    state_no = 3;
                }
                else
                {
                    while (1) {;}
                }
            }
        }

        if (state_no == 3)
        {
            err = R_SCI_B_UART_Write(&g_uart0_ctrl, msg_error, (uint32_t)(sizeof(msg_error) - 1));
            if (FSP_SUCCESS != err)
            {
                while (1) {;}
            }

            for (volatile uint32_t i = 0; i < 1000000U; i++)
            {
                __asm volatile ("nop");
            }

            if (error_no == 1)
            {
                err = R_SCI_B_UART_Write(&g_uart0_ctrl, msg_err1, (uint32_t)(sizeof(msg_err1) - 1));
                if (FSP_SUCCESS != err)
                {
                    while (1) {;}
                }
            }

            if (error_no == 2)
            {
                err = R_SCI_B_UART_Write(&g_uart0_ctrl, msg_err2, (uint32_t)(sizeof(msg_err2) - 1));
                if (FSP_SUCCESS != err)
                {
                    while (1) {;}
                }
            }

            for (volatile uint32_t i = 0; i < 1000000U; i++)
            {
                __asm volatile ("nop");
            }

            while (1) {;}
        }
    }
}

5. 実行結果

5-1. 正常時のログ

[STATE] INIT
[STATE] IDLE
[STATE] RUN

これで、

  • 初期化が完了した
  • 待機に入れた
  • 動作中まで進んだ

と分かります。

5-2. 異常時のログ

int error_test = 1; にすると、次のようになります。

[STATE] INIT
[STATE] IDLE
[STATE] RUN
[STATE] ERROR
[ERROR] 2 : TEST_ERROR

これで、

  • RUN中に
  • エラー2が発生して
  • ERRORへ遷移した

と読めます。

5-3. 今回確認できればよいこと

今回の段階では、次が確認できれば十分です。

  • 状態名がログに出る
  • 正常時に INIT → IDLE → RUN と進む
  • 異常時に ERROR に入る
  • エラー番号とエラー名が出る

6. トラブルと対処方法

6-1. TeraTermに何も表示されない

原因:UART設定や配線が正しくない可能性があります。
対処方法:先に UARTの設定や接続を見直し、次を確認します。

  • COM番号が正しいか
  • TeraTermの通信条件が一致しているか
  • GND / TX / RX の配線が正しいか
  • FSP Configurator の UART 設定と Pins 設定が正しいか

※UARTログの設定については、次の記事を参考にしてください。
⇒E0-05 ログ出力(UART):HelloログをTeraTermに出す(EK-RA8M2)(リンク)

6-2. INITは出るが、その先が出ない

原因:状態番号が次の値に更新されていない、またはUART送信でエラーになっている可能性があります。
対処方法state_no0 → 1 → 2 と切り替わるコードになっているか確認します。あわせて、R_SCI_B_UART_Write() の戻り値チェックがあるか確認します。

6-3. IDLEまでは出るが、RUNまで進まない

原因state_no1 から 2 に切り替わっていない可能性があります。
対処方法:コードを最初から順に見て、state_no = 2; の処理があるか確認します。

6-4. ERRORに入らない

原因error_test0 のままの可能性があります。
対処方法:異常系を確認したいときは、error_test = 1; に変更して再ビルドします。確認後は 0 に戻します。

6-5. ERRORに入ったが、どのエラーか分からない

原因error_no に値を入れる前に、[STATE] ERROR だけを出している可能性があります。
対処方法:先に error_no を設定してから、ERRORのログを出すようにします。

6-6. 同じログが何度も出る

原因:同じ状態で何度も UART 送信している可能性があります。
対処方法:状態を進めたら次の state_no に切り替えるようにし、同じ状態のログを繰り返し送らないようにします。

6-7. ビルドは通るのに期待したログにならない

原因:文字列や状態番号の対応が、自分で決めた内容とずれている可能性があります。
対処方法:記事内で決めた対応と、コード内の値が一致しているか確認します。
状態番号

  • 0 = INIT
  • 1 = IDLE
  • 2 = RUN
  • 3 = ERROR

エラー番号

  • 0 = エラーなし
  • 1 = UART_OPEN_FAILED
  • 2 = TEST_ERROR

7. 今回わかったこと

今回の実験で大事なのは、高度な状態機械を作ることではありません。
大事なのは次の3つです。

  • 状態を名前で分ける
  • エラーを番号で分ける
  • それをログで見えるようにする

これを先にやっておくと、後の実験で
「動かない」ではなく「どこで」「なぜ」止まったか を追えるようになります。

8. 次回へのメモ

次回以降は、今回の状態とエラーの考え方を土台にして、
タイマや入力やセンサの処理にも広げていきます。
たとえば次のような発展ができます。

  • ボタン入力で IDLE → RUN を切り替える
  • タイマ周期で状態を更新する
  • センサ異常で ERROR へ入る
  • エラー番号を増やして切り分けしやすくする

フェーズ0では、まず
状態とエラーをログで見えるようにする ができれば十分です。

関連記事

-フェーズ0