LoginSignup
4
5

More than 3 years have passed since last update.

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

Last updated at Posted at 2021-02-28

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

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

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

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

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

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

スクリーンショット 2021-03-01 5.13.02.png

サンプルステートマシンの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処理
4
5
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
5