有限状态机在学习和工作中经常能够遇到,前面的文章也有使用到。但是对于层次状态机网上的学习资源却很少,导致一直不理解这个工作机制,后面偶然在GitHub看到一篇文章,深入学习后发现层次状态机太实用了,如果将其在项目上结合,肯定能够创造出一个比较好的代码框架。
一、HSM状态调度机制
HSM状态间是存在等级关系,状态间至少存在2个等级,并且除根状态外至少存在ENTER和EXTI事件,我认为HSM的状态机结构类似树状结构,HSM状态如图所示:
很明显这里存在四个等级,CAMERA为d0,其余为d1,d2,d3。 CAMERA为根状态(根状态不做任何处理),其余的六种状态,有些的父状态为根状态,有些则作为其他的父状;
1.切换状态-等级不同
例如从CAMERA_StateOnDispPlay状态(d3)切换到CAMERA_StateOnShoot(d2)状态;
首先函数调度会先找到他们共同的父状态,这里即为CAMERA_StateOn,然后依次调用CAMERA_StateOnDispPlay和CAMERA_StateOnDisp状态的EXTI事件,再调用CAMERA_StateOnShoot的ENTER事件,即可切换到目标状态,方向如图:
2.切换状态-等级相同
例如从CAMERA_StateOnDisp状态(d2)切换到CAMERA_StateOnShoot(d2)状态;
他们父状态是相同的,所以只需要调用当前状态的EXTI事件后,再进入目标状态的ENTER事件即可。
状态调度函数源码如下
// Func: void HSM_Tran(HSM *This, HSM_STATE *nextState, void *param, void (*method)(HSM *This, void *param))
// Desc: Transition to another HSM STATE
// This: Pointer to HSM instance
// nextState: Pointer to next HSM STATE
// param: Optional Parameter associated with HSME_ENTRY and HSME_EXIT event
// method: Optional function hook between the HSME_ENTRY and HSME_EXIT event handling
void HSM_Tran(HSM *This, HSM_STATE *nextState, void *param, void (*method)(HSM *This, void *param))
{
#if HSM_FEATURE_SAFETY_CHECK
// [optional] Check for illegal call to HSM_Tran in HSME_ENTRY or HSME_EXIT
if (This->hsmTran)
{
HSM_DEBUG("!!!!Illegal call of HSM_Tran[%s -> %s] in HSME_ENTRY or HSME_EXIT Handler!!!!",
This->curState->name, nextState->name);
return;
}
// Guard HSM_Tran() from certain recursive calls
This->hsmTran = 1;
#endif // HSM_FEATURE_SAFETY_CHECK
HSM_STATE *list_exit[HSM_MAX_DEPTH];
HSM_STATE *list_entry[HSM_MAX_DEPTH];
uint8_t cnt_exit = 0;
uint8_t cnt_entry = 0;
uint8_t idx;
// This performs the state transition with calls of exit, entry and init
// Bulk of the work handles the exit and entry event during transitions
HSM_DEBUGC2("Tran %s[%s -> %s]", This->name, This->curState->name, nextState->name);
// 1) Find the lowest common parent state
HSM_STATE *src = This->curState;
HSM_STATE *dst = nextState;
// 1a) Equalize the levels
while (src->level != dst->level)
{
if (src->level > dst->level)
{
// source is deeper
list_exit[cnt_exit++] = src;
src = src->parent;
}
else
{
// destination is deeper
list_entry[cnt_entry++] = dst;
dst = dst->parent;
}
}
// 1b) find the common parent
while (src != dst)
{
list_exit[cnt_exit++] = src;
src = src->parent;
list_entry[cnt_entry++] = dst;
dst = dst->parent;
}
// 2) Process all the exit events
for (idx = 0; idx < cnt_exit; idx++)
{
src = list_exit[idx];
HSM_DEBUGC3(" %s[%s](EXIT)", This->name, src->name);
src->handler(This, HSME_EXIT, param);
}
// 3) Call the transitional method hook
if (method)
{
method(This, param);
}
// 4) Process all the entry events
for (idx = 0; idx < cnt_entry; idx++)
{
dst = list_entry[cnt_entry - idx - 1];
HSM_DEBUGC3(" %s[%s](ENTRY)", This->name, dst->name);
dst->handler(This, HSME_ENTRY, param);
}
// 5) Now we can set the destination state
This->curState = nextState;
#if HSM_FEATURE_SAFETY_CHECK
This->hsmTran = 0;
#endif // HSM_FEATURE_SAFETY_CHECK
#if HSM_FEATURE_INIT
// 6) Invoke INIT signal, NOTE: Only HSME_INIT can recursively call HSM_Tran()
HSM_DEBUGC3(" %s[%s](INIT)", This->name, nextState->name);
This->curState->handler(This, HSME_INIT, param);
#endif // HSM_FEATURE_INIT
}
二、HSM事件调度机制
HSM事件的调度比较简单,HSM状态事件如图所示:
这里的状态关系和上述的一致,可以看到不同状态均会存在ENTER和EXTI事件,并且每个状态也有自己可能触发的事件类型;
1.切换事件-目标状态不存在事件
例如在CAMERA_StateOnDispPlay状态中触发LOWBATT事件;
调度函数HSM_Run首先会进入回调函数,如果没有该事件则会返回LOWBATT事件类型,HSM_Run接着进入父状态CAMERA_StateOnDisp回调函数查看是否存在LOWBATT事件,很明显没有,最后回到CAMERA_StateOn状态回调函数,触发LOWBATT事件进行关机处理。
2.切换事件-目标状态存在事件
例如在CAMERA_StateOn状态中触发LOWBATT事件,立即执行关机处理后退出HSM_Run函数
事件调度函数源码如下
void HSM_Run(HSM *This, HSM_EVENT event, void *param)
{
#if HSM_FEATURE_DEBUG_ENABLE && HSM_FEATURE_DEBUG_NESTED_CALL
// Increment the nesting count
gucHsmNestLevel++;
#endif // HSM_FEATURE_DEBUG_ENABLE && HSM_FEATURE_DEBUG_NESTED_CALL
// This runs the state's event handler and forwards unhandled events to
// the parent state
HSM_STATE *state = This->curState;
#ifdef HSM_DEBUG_EVT2STR
HSM_DEBUGC1("Run %s[%s](evt:%s, param:%08lx)", This->name, state->name, HSM_DEBUG_EVT2STR(event), (unsigned long)param);
#else
HSM_DEBUGC1("Run %s[%s](evt:%lx, param:%08lx)", This->name, state->name, (unsigned long)event, (unsigned long)param);
#endif // HSM_DEBUG_EVT2STR
while (event)
{
event = state->handler(This, event, param);
state = state->parent;
if (event)
{
#ifdef HSM_DEBUG_EVT2STR
HSM_DEBUGC1(" evt:%s unhandled, passing to %s[%s]", HSM_DEBUG_EVT2STR(event), This->name, state->name);
#else
HSM_DEBUGC1(" evt:%lx unhandled, passing to %s[%s]", (unsigned long)event, This->name, state->name);
#endif // HSM_DEBUG_EVT2STR
}
}
#if HSM_FEATURE_DEBUG_ENABLE
// Restore debug back to the configured debug
This->hsmDebug = This->hsmDebugCfg;
#if HSM_FEATURE_DEBUG_NESTED_CALL
if (gucHsmNestLevel)
{
// Decrement the nesting count
gucHsmNestLevel--;
}
#endif // HSM_FEATURE_DEBUG_NESTED_CALL
#endif // HSM_FEATURE_DEBUG_ENABLE
}
三、HSM实例和状态创建
1.HSM状态结构体
在创建HSM状态函数前,需要了解状态结构体里面的成员;
struct HSM_STATE_T为HSM状态结构体,包含指向父状态的指针,状态的回调函数,状态的字符串名称,状态的等级;
结构体源码如下:
//----Structure declaration----
typedef uint32_t HSM_EVENT;
typedef struct HSM_STATE_T HSM_STATE;
typedef struct HSM_T HSM;
typedef HSM_EVENT (* HSM_FN)(HSM *This, HSM_EVENT event, void *param);
struct HSM_STATE_T
{
HSM_STATE *parent; // parent state
HSM_FN handler; // associated event handler for state
const char *name; // name of state
uint8_t level; // depth level of the state
};
2.HSM状态创建
使用HSM_STATE_Create函数创建HSM状态,首先会判断该结构体是否添加父状态,如果没有默认会将根状态作为该结构体节点的父状态,依次将字符串名称、回调函数、状态等级、父状态等注册到输入的结构体内;
HSM_EVENT HSM_RootHandler(HSM *This, HSM_EVENT event, void *param)
{
#ifdef HSM_DEBUG_EVT2STR
HSM_DEBUG("\tEvent:%s dropped, No Parent handling of %s[%s] param %lx",
HSM_DEBUG_EVT2STR(event), This->name, This->curState->name, (unsigned long)param);
#else
HSM_DEBUG("\tEvent:%lx dropped, No Parent handling of %s[%s] param %lx",
(unsigned long)event, This->name, This->curState->name, (unsigned long)param);
#endif // HSM_DEBUG_EVT2STR
return HSME_NULL;
}
HSM_STATE const HSM_ROOT =
{
.parent = ((void *)0),
.handler = HSM_RootHandler,
.name = ":ROOT:",
.level = 0
};
void HSM_STATE_Create(HSM_STATE *This, const char *name, HSM_FN handler, HSM_STATE *parent)
{
if (((void *)0) == parent)
{
parent = (HSM_STATE *)&HSM_ROOT;
}
This->name = name;
This->handler = handler;
This->parent = parent;
This->level = parent->level + 1;
if (This->level >= HSM_MAX_DEPTH)
{
HSM_DEBUG("Please increase HSM_MAX_DEPTH > %d", This->level);
// assert(0, "Please increase HSM_MAX_DEPTH");
while(1);
}
}
3.HSM状态获取
通过HSM_GetState函数可以获取到当前最新的状态
HSM_STATE *HSM_GetState(HSM *This)
{
// This returns the current HSM state
return This->curState;
}
uint8_t HSM_IsInState(HSM *This, HSM_STATE *state)
{
HSM_STATE *curState;
// Traverse the parents to find the matching state.
for (curState = This->curState; curState; curState = curState->parent)
{
if (state == curState)
{
// Match found, HSM is in state or parent state
return 1;
}
}
// This HSM is not in state or parent state
return 0;
}
4.HSM实例结构体
struct HSM_T为HSM实例结构体,该类型里面包含一个struct HSM_STATE_T状态结构体成员指针,相当于在HSM状态结构体的基础上进行封装,在创建HSM实例后,该指针会一直记录当前的最新的HSM状态。
结构体源码如下:
struct HSM_T
{
HSM_STATE *curState; // Current HSM State
#if HSM_FEATURE_DEBUG_ENABLE
const char *name; // Name of HSM Machine
const char *prefix; // Prefix for debugging (e.g. grep)
uint8_t hsmDebugCfg; // HSM debug configuration flag
uint8_t hsmDebug; // HSM run-time debug flag
#endif // HSM_FEATURE_DEBUG_ENABLE
#if HSM_FEATURE_SAFETY_CHECK
uint8_t hsmTran; // HSM Transition Flag
#endif // HSM_FEATURE_SAFETY_CHECK
};
5.HSM实例创建
通过HSM_Create函数可以创建一个HSM实例,并且初始化当前的状态
void HSM_Create(HSM *This, const char *name, HSM_STATE *initState)
{
// Setup debug
#if HSM_FEATURE_DEBUG_ENABLE
This->name = name;
This->prefix = "";
This->hsmDebugCfg = 0;
This->hsmDebug = 0;
#endif // HSM_FEATURE_DEBUG_ENABLE
// Supress warning for unused variable if HSM_FEATURE_DEBUG_ENABLE is not defined
(void)name;
// Initialize state
This->curState = initState;
// Invoke ENTRY and INIT event
HSM_DEBUGC1(" %s[%s](ENTRY)", This->name, initState->name);
This->curState->handler(This, HSME_ENTRY, 0);
HSM_DEBUGC1(" %s[%s](INIT)", This->name, initState->name);
This->curState->handler(This, HSME_INIT, 0);
}
四、总结
HSM层次状态两个关键因素为状态的切换和事件的切换,前面状态和事件的调度是重中之重,必须得理解HSM类似树结构的运行方式。至此HSM层次状态机的核心代码解析完成;
早在2022年12月就应该写这篇文章,因为工作刚好上了项目比较紧急一直没有时间,也是当时没有完全理解调度机制,导致这个文章在四个月后在编写完成。本文章只是介绍HSM层次状态机的核心代码,下一篇文章会继续把GitHub文章HSM应用例子写完。