有限状态机在学习和工作中经常能够遇到,前面的文章也有使用到。但是对于层次状态机网上的学习资源却很少,导致一直不理解这个工作机制,后面偶然在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_EXITif (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 callsThis->hsmTran = 1;
#endif // HSM_FEATURE_SAFETY_CHECKHSM_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 transitionsHSM_DEBUGC2("Tran %s[%s -> %s]", This->name, This->curState->name, nextState->name);// 1) Find the lowest common parent stateHSM_STATE *src = This->curState;HSM_STATE *dst = nextState;// 1a) Equalize the levelswhile (src->level != dst->level){if (src->level > dst->level){// source is deeperlist_exit[cnt_exit++] = src;src = src->parent;}else{// destination is deeperlist_entry[cnt_entry++] = dst;dst = dst->parent;}}// 1b) find the common parentwhile (src != dst){list_exit[cnt_exit++] = src;src = src->parent;list_entry[cnt_entry++] = dst;dst = dst->parent;}// 2) Process all the exit eventsfor (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 hookif (method){method(This, param);}// 4) Process all the entry eventsfor (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 stateThis->curState = nextState;
#if HSM_FEATURE_SAFETY_CHECKThis->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 countgucHsmNestLevel++;
#endif // HSM_FEATURE_DEBUG_ENABLE && HSM_FEATURE_DEBUG_NESTED_CALL// This runs the state's event handler and forwards unhandled events to// the parent stateHSM_STATE *state = This->curState;
#ifdef HSM_DEBUG_EVT2STRHSM_DEBUGC1("Run %s[%s](evt:%s, param:%08lx)", This->name, state->name, HSM_DEBUG_EVT2STR(event), (unsigned long)param);
#elseHSM_DEBUGC1("Run %s[%s](evt:%lx, param:%08lx)", This->name, state->name, (unsigned long)event, (unsigned long)param);
#endif // HSM_DEBUG_EVT2STRwhile (event){event = state->handler(This, event, param);state = state->parent;if (event){
#ifdef HSM_DEBUG_EVT2STRHSM_DEBUGC1(" evt:%s unhandled, passing to %s[%s]", HSM_DEBUG_EVT2STR(event), This->name, state->name);
#elseHSM_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 debugThis->hsmDebug = This->hsmDebugCfg;
#if HSM_FEATURE_DEBUG_NESTED_CALLif (gucHsmNestLevel){// Decrement the nesting countgucHsmNestLevel--;}
#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 stateHSM_FN handler; // associated event handler for stateconst char *name; // name of stateuint8_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_EVT2STRHSM_DEBUG("\tEvent:%s dropped, No Parent handling of %s[%s] param %lx",HSM_DEBUG_EVT2STR(event), This->name, This->curState->name, (unsigned long)param);
#elseHSM_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_EVT2STRreturn 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 statereturn 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 statereturn 1;}}// This HSM is not in state or parent statereturn 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_ENABLEconst char *name; // Name of HSM Machineconst char *prefix; // Prefix for debugging (e.g. grep)uint8_t hsmDebugCfg; // HSM debug configuration flaguint8_t hsmDebug; // HSM run-time debug flag
#endif // HSM_FEATURE_DEBUG_ENABLE
#if HSM_FEATURE_SAFETY_CHECKuint8_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_ENABLEThis->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 stateThis->curState = initState;// Invoke ENTRY and INIT eventHSM_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应用例子写完。