ステートマシン図の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処理
