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

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

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

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

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

ステートマシン図自体の説明は下記またはgoogleで検索して調べてもらいたい。
[ウィキペディア 状態遷移図](https://ja.wikipedia.org/wiki/%E7%8A%B6%E6%85%8B%E9%81%B7%E7%A7%BB%E5%9B%B3)


#サンプルのステートマシン図
![スクリーンショット 2021-03-01 5.13.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1141527/170472e6-8fad-bcd8-6dce-9bdd7ffe80ab.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処理
```