C言語でステートマシン図のパターン化


ステートマシン図のC言語へのマッピング

共通言語であるUMLからステートマシン図に対して、非オブジェクト指向であるC言語にマッピングするためのテンプレートコードを紹介する。

モデルのテンプレートコードにより組織やチーム内で共有できることが目標。(C++のデザインパターンみたいなやつを目指してみた?)

例題となるステートマシン図

ステートマシン図自体の説明は下記またはgoogleで検索して調べてもらいたい。
ウィキペディア 状態遷移図

サンプルのステートマシン図

サンプルステートマシンのCソースコード

モデル図に対してソースコードは一定回数状態遷移を繰り返し終了するように実装している。また、各状態内のアクションをprintf文で表示するようにもしている。

ソースファイル前段の定義

main.c

#include "stdio.h"

/** アクション定数 */
#define  FSM_ENTRY     ((unsigned char)0x01)
#define  FSM_DO        ((unsigned char)0x02)
#define  FSM_EXIT      ((unsigned char)0x03)

/** 状態定数 */
#define STATE_XXXXX    ((unsigned char)0x00)
#define STATE_YYYYY    ((unsigned char)0x01)
#define STATE_ZZZZZ    ((unsigned char)0x02)
#define STATE_NUM      ((unsigned char)0x03)  


static unsigned char   temp_cnt;

/**
* @brief 状態変数型定義
*/
typedef struct
{
    unsigned char  crreStat;   /* 現在の状態 */
    unsigned char  newStat;    /* 遷移先の状態 */
}ST_FMS_STATE_INFO;

ステートマシンのイベントとアクション関数およびそのテーブル定義

main.c

/**
* @brief イベント関数
*/
static unsigned char    XXXXX_Fsm_Event(void);
static unsigned char    YYYYY_Fsm_Event(void);
static unsigned char    ZZZZZ_Fsm_Event(void);

/**
* @brief 状態関数(アクション)
*/
static void  XXXXX_Fsm_State(unsigned char cmd);
static void  YYYYY_Fsm_State(unsigned char cmd);
static void  ZZZZZ_Fsm_State(unsigned char cmd);

/**
* @brief 状態制御関数のテーブル型定義
*/
typedef struct
{
    unsigned char(*fmc_Event)(void);
             void(*fmc_state)(unsigned char cmd);
}ST_FMS_TYPE;



/**
* @brief ステートマシン関数テーブル
*/
const static ST_FMS_TYPE sts_fms_stateInst[STATE_NUM] =
{
    { &XXXXX_Fsm_Event, &XXXXX_Fsm_State},
    { &YYYYY_Fsm_Event, &YYYYY_Fsm_State},
    { &ZZZZZ_Fsm_Event, &ZZZZZ_Fsm_State}
};
main.c

/**
* @brief 状態XXXXXから呼ばれるイベント処理を実施
* @param NONE
* @return  遷移先の状態
*/
static unsigned char  XXXXX_Fsm_Event(void)
{
    unsigned char  ret = STATE_XXXXX;

    if (temp_cnt >= 1)
    {
        ret = STATE_YYYYY;
    }

    return ret;
}

/**
* @brief 状態YYYYYから呼ばれるイベント処理を実施
* @param NONE
* @return  遷移先の状態
*/
static unsigned char  YYYYY_Fsm_Event(void)
{
    unsigned char  ret = STATE_YYYYY;

    if (temp_cnt >= 1)
    {
        ret = STATE_ZZZZZ;
    }

    return ret;
}

/**
* @brief 状態ZZZZZから呼ばれるイベント処理を実施
* @param NONE
* @return  遷移先の状態
*/
static unsigned char  ZZZZZ_Fsm_Event(void)
{
    unsigned char ret = STATE_ZZZZZ;

    if (temp_cnt >= 1)
    {
        ret = STATE_XXXXX;
    }

    return ret;
}
main.c

/**
* @brief 状態Xのアクション処理を実施
* @param cmd=>アクション定義(ENTRY,DO,EXIT)
* @return  遷移先の状態
*/
static void  XXXXX_Fsm_State(unsigned char cmd)
{
    switch (cmd)
    {
    case FSM_ENTRY:
        printf("XXXXXのENTRY処理\n");
        temp_cnt = 0;
        break;
    case FSM_DO:
        printf(">>XXXXXのDO処理\n");
        temp_cnt++;
        break;
    case FSM_EXIT:
        printf(">>>>XXXXXのEXIT処理\n\n");
        break;
    default:
        /* 処理なし */
        break;
    }
}


/**
* @brief 状態YYYYYのアクション処理を実施
* @param cmd=>アクション定義(ENTRY,DO,EXIT)
* @return  遷移先の状態
*/
static void  YYYYY_Fsm_State(unsigned char cmd)
{
    switch (cmd)
    {
    case FSM_ENTRY:
        printf("YYYYYのENTRY処理\n");
        temp_cnt = 0;
        break;
    case FSM_DO:
        printf(">>YYYYYのDO処理\n");
        temp_cnt++;
        break;
    case FSM_EXIT:
        printf(">>>>YYYYYのEXIT処理\n\n");
        break;
    default:
        /* 処理なし */
        break;
    }
}


/**
* @brief 状態ZZZZZのアクション処理を実施
* @param cmd=>アクション定義(ENTRY,DO,EXIT)
* @return  遷移先の状態
*/
static void  ZZZZZ_Fsm_State(unsigned char cmd)
{
    switch (cmd)
    {
    case FSM_ENTRY:
        printf("ZZZZZのENTRY処理\n");
        temp_cnt = 0;
        break;
    case FSM_DO:
        printf(">>ZZZZZのDO処理\n");
        temp_cnt++;
        break;
    case FSM_EXIT:
        printf(">>>>ZZZZZのEXIT処理\n\n");
        break;
    default:
        /* 処理なし */
        break;

    }
}

最後に上記ステートマシンコードをコントロールするメイン処理

main.c

/**
* @brief main処理
* @param NONE
* @return  NONE
* @details メインの処理(ステートマシンコントローラー)
*/
void main(void)
{
    ST_FMS_STATE_INFO sts_state;
    unsigned char cnt;

    /* イニシャル処理 */
    sts_state.crreStat = STATE_XXXXX;
    sts_state.newStat = STATE_XXXXX;

    cnt = 13;
    temp_cnt = 0;

    /* 状態遷移処理 */
    while (cnt--)
    {
        printf("%d周期目\n", (13 - cnt));
        /* イベント判定 */
        sts_state.newStat = sts_fms_stateInst[sts_state.crreStat].fmc_Event();

        if (sts_state.crreStat != sts_state.newStat)
        {
            /* exit処理 */
            sts_fms_stateInst[sts_state.crreStat].fmc_state(FSM_EXIT);
            /* entry処理 */
            sts_fms_stateInst[sts_state.newStat].fmc_state(FSM_ENTRY);
        }
        else
        {
            /*do処理 */
            sts_fms_stateInst[sts_state.newStat].fmc_state(FSM_DO);
        }

        sts_state.crreStat = sts_state.newStat;
    }
}

上記ソフトの実行結果↓

テンプレートコードでは終了する状態のExit処理と同一周期にEntry処理を実行するようにしている。
ここに関しては各自の都合に合わせて設計するといいと思う。

1周期目
>>XXXXXのDO処理
2周期目
>>>>XXXXXのEXIT処理

YYYYYのENTRY処理
3周期目
>>YYYYYのDO処理
4周期目
>>>>YYYYYのEXIT処理

ZZZZZのENTRY処理
5周期目
>>ZZZZZのDO処理
6周期目
>>>>ZZZZZのEXIT処理

XXXXXのENTRY処理
7周期目
>>XXXXXのDO処理
8周期目
>>>>XXXXXのEXIT処理

YYYYYのENTRY処理
9周期目
>>YYYYYのDO処理
10周期目
>>>>YYYYYのEXIT処理

ZZZZZのENTRY処理
11周期目
>>ZZZZZのDO処理
12周期目
>>>>ZZZZZのEXIT処理

XXXXXのENTRY処理
13周期目
>>XXXXXのDO処理