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