本帖最后由 Simon21ic 于 2014-8-19 15:38 编辑
终于稍微有些时间看了几种状态机的实现方式,其实很久以前就有人和我提过全状态机的系统,只是那个时候没有迫切需求,所以那时只是实现非阻塞的轮训系统(虽然也是基于简单的有限状态机)。
出来混迟早要还的,该要做的东西,总是逃不掉的。而且,我的性格就是在我的能力下花时间把东西做到最好,而不是花时间一遍一遍做同样的东西。
这个帖子不是要推广我的代码构架,只是为了让给我做外包的人,前期能够先简单了解一下,所以我就不顶贴了,希望大家看到的话,也让他沉下去吧。
目前的系统构架是vsf平台构架中,新增加的一部分,大概花了1周时间不到,基本定型,以后还可能会有更新,当然,如果有好的建议的话,可以联系我。
状态机的基本概念我就不多说了,自己去网上搜索就是了。
先介绍一下目前我看过的几种事件驱动的状态机以及UML的状态图:
1. MSM(MSM在搜索引擎搜索的话不一定找得到,可以搜索meta state machine),Boost library中使用的状态机,传说中最快的状态机
直接上文档
http://www.boost.org/doc/libs/1_55_0/libs/msm/doc/HTML/index.html
2. HSM,QP平台使用的状态机
Practical UML statecharts in C/C++, Second Edition: Event-Driven Programming for Embedded Systems
这本书写的不错,值得看一下,虽然网上能够下载到中文版的PDF,不过建议看英文的,你懂得。。。。
3. UML状态图
http://www.sparxsystems.com/resources/uml2_tutorial/uml2_statediagram.html
事件驱动的状态机:
状态机有以下几个元素组成:
1. 状态,可以有entry和exit行为
2. 事件,发送给某个状态机的事件队列,由改状态机的当前状态处理,在层次状态机中,如果当前当前状态处理不了的话,可以由超状态处理
3. 动作,某个事件触发后,状态机执行的动作
4. 转换,有一个状态到另一个状态
5. 条件,某个状态下,如果触发某个事件,只有当条件满足才会进行状态转换
所以,简单的将,状态机由有限个状态组成,当触发某个事件后,执行相应的动作,并且,如果满足一定条件,这需要做状态转换。
层次状态机
UML定义的状态图中,状态是有层次关系的,除了top状态外,其他状态都有超状态,这个按照QP量子平台的说明,用GUI更加容易解释。
这里随便用GUI举个例子,当然,并不一定是实际情况。
GUI的串口中,可以有各个空间,也可以有一个container空间,比如groupbox等。
当前的事件是发送给当前的活动控件的,比如是一个按键,按钮只处理按钮能够处理的时间,比如click,enter等等。
那么其他所有处理不了的事件,按钮可以发送他的超状态处理,比如是一个groupbox,其他空间也一样,处理不了的事件发给超状态,这个类似面向对象的继承关系。
所以,对于ctrl+F4的时间处理,只需要放在窗口的状态机里处理就行,因为即使里面的控件收到了,会层层网上传递,知道找到能够处理的状态。
VSF平台中的事件驱动的层次状态机构架:
首先,不推荐大家使用,刚刚完成,距离完善还需要经过一些项目的试炼。而且,也有其他更加成熟的层次状态机可以使用。
VSF平台的状态机构架,只是给我自己的产品使用的。看到这里,如果觉得误入歧途,现在逃还来得及。
VSF的状态机构架和QP的不同,没有用到底层的调度内核。
系统构架简单的说,就是中断只做紧急的处理,然后发送事件给状态机,状态机在收到事件后,在非中断状态下,处理事件。并且,当系统没有待处理的状态的话,就可以休眠。
当然,除了中断可以发送事件外,非中断代码也同样可以发送事件,甚至状态而已给自己发送事件。当然,也可以模拟中断发送的事件。
不过事件类型是有区分的,可以通过代码实现事件的互斥,也就是说,发送了A事件就不发送B事件了,而且,可以实现负责的事件互斥关系。
状态内部可以包含复合状态机,并且可以支持多个复合状态机同时运行。VSF的状态机构架实现了timer组件,可以根据设定的时间发送用户指定的事件。
状态机由于是并行处理,VSF的状态机构架也实现了信号量,可以用于一些共享资源的保护。
当然,由于所有状态机都运行在同一任务平面上,所以,目前不支持状态机的优先级。目前也还没有去支持紧急事件,也没有实现元状态机的构架。
这里就只是简单介绍一下一些基本元素的数据结构,数据结构确定后,代码其实也就能大概猜到是怎么实现的了。
状态结构:
struct vsfsm_t;
struct vsfsm_state_t
{
// return NULL means the event is handled, and no transition
// return a vsfsm_state_t pointer means transition to the state
// return -1 means the event is not handled, should redirect to superstate
struct vsfsm_state_t * (*evt_handler)(struct vsfsm_t *sm, vsfsm_evt_t evt);
// sub state machine list
// for subsm, user need to call vsfsm_init on VSFSM_EVT_ENTER
// if the subsm is historical, vsfsm_init should only be called once on
// first VSFSM_EVT_ENTER
// for initialized historical subsm, vsfsm_set_active should be called to
// set the subsm active(means ready to accept events) on
// VSFSM_EVT_ENTER, and set the subsm inactive on VSFSM_EVT_EXIT
struct vsfsm_t *subsm;
#if VSFSM_CFG_HSM_EN
// for top state, super is NULL; other super points to the superstate
struct vsfsm_state_t *super;
#endif
};
简单明了,一个超状态,一个事件处理接口,一个子状态列表。
消息以及消息队列:
enum
{
VSFSM_EVT_INVALID = -1,
VSFSM_EVT_SYSTEM = 0,
VSFSM_EVT_DUMMY = VSFSM_EVT_SYSTEM + 0,
VSFSM_EVT_INIT = VSFSM_EVT_SYSTEM + 1,
VSFSM_EVT_USER = VSFSM_EVT_SYSTEM + 0x10,
// instant message CANNOT be but in the event queue and
// can not be sent in interrupt
VSFSM_EVT_INSTANT = VSFSM_EVT_SYSTEM + 0x2000,
VSFSM_EVT_USER_INSTANT = VSFSM_EVT_INSTANT,
VSFSM_EVT_INSTANT_END = VSFSM_EVT_INSTANT + 0x2000 - 1,
// local event can not transmit or be passed to superstate
VSFSM_EVT_LOCAL = VSFSM_EVT_INSTANT_END + 1,
VSFSM_EVT_USER_LOCAL = VSFSM_EVT_LOCAL,
// local instant message CANNOT be but in the event queue and
// can not be sent in interrupt
VSFSM_EVT_LOCAL_INSTANT = VSFSM_EVT_LOCAL + 0x2000,
// VSFSM_EVT_ENTER and VSFSM_EVT_EXIT are local instant events
VSFSM_EVT_ENTER = VSFSM_EVT_LOCAL_INSTANT + 0,
VSFSM_EVT_EXIT = VSFSM_EVT_LOCAL_INSTANT + 1,
VSFSM_EVT_USER_LOCAL_INSTANT = VSFSM_EVT_LOCAL_INSTANT + 2,
VSFSM_EVT_LOCAL_INSTANT_END = VSFSM_EVT_LOCAL_INSTANT + 0x2000 - 1,
};
typedef int vsfsm_evt_t;
struct vsfsm_evtqueue_t
{
vsfsm_evt_t *evt_buffer;
uint16_t evt_buffer_num;
// private
uint16_t head;
uint16_t tail;
uint16_t count;
};
这里有一个local事件的概念,是指这个事件只能状态内部处理,不能像高层传递。事件队列的实现也非常简单,看了结构就知道代码。
然后,还有instant事件的概念,就是不能放到事件列表,需要状态立即处理的时间。
通过instant事件,可以实现多个时间的复杂互斥关系。
比如,代码申请A资源的访问许可,发现不可立即访问,那么再申请了一个50ms的timer,访问许可的事件以及timer的事件都设置为立即时间。
在任何一个时间到达后,都会关闭另一个事件,由于事件需要立即执行,执行的时候,不会有另一个互斥时间等在事件列表,这样就实现了事件的互斥。
不过需要注意的是,instant事件不能在中断中发送。
目前事件只是一个int类型的signal,以后更具需要,可能会改成一个结构体,发送信号的时候,还可以带参数。
事件处理接口,可以返回下一个状态,如果是NULL,那就不需要转换;如果是自己,那就退出后再进入;如果是其他状态,那就进入其他状态;如果是-1那就提交给超状态处理(如果不是LOCAL事件的话)。
状态机接口也呼之欲出了:
struct vsfsm_t
{
struct vsfsm_evtqueue_t evtq;
// initial state
struct vsfsm_state_t init_state;
// user_data point to the user specified data for the sm
void *user_data;
// sm_extra is used for specific sm type
// for MSM, sm_extra point to the transmit table
void *sm_extra;
// private
struct vsfsm_state_t *cur_state;
#if VSFSM_CFG_SEM_EN
// pending_next is used for vsfsm_sem_t
struct vsfsm_t *pending_next;
#endif
volatile bool active;
struct vsfsm_t *next;
};
事件队列,初始状态,用户数据以及状态机数据(暂时没用到,以后如果支持元状态机的话,可以放状态转换表),其他的都是内部处理的变量。
应用部分,在定义好状态机后,代码其实就非常简单了:
int main(void)
{
interfaces->core.init(NULL);
interfaces->tickclk.init();
interfaces->tickclk.start();
vsftimer_init();
vsfsm_init(&app.sm);
while (1)
{
vsftimer_poll();
vsfsm_poll(&app.sm);
if (!vsfsm_get_event_pending())
{
// sleep
interfaces->core.sleep(SLEEP_WFI);
}
}
}
当然,这个构架有一个问题,就是在vsfsm_get_event_pending查询是否有等待的事件和interfaces->core.sleep(SLEEP_WFI);休眠之间,如果有中断发生,并且发送了事件,那要等到下一次唤醒才能处理,求解决方案(Cortex的ARM已经找到解决方法)。
最后,提一下目前VSF构架下,状态机设计的几个原则。有一些是别人推荐的。
大部分原则是给事件触发的状态机增加一些限制,使得复杂度更加容易控制。
1. 层次状态机的灵活度非常大,复杂富很难控制,建议能不使用层次状态机就不使用。
目前的VSF状态机构架,即使应用不使用层次状态机,但是也可以和层次状态机兼容,所以不需要担心需要实现2种状态机(一种层次的,一种非层次的)。
2. 模块的代码,状态机不继承其他状态机,也就是说,模块的代码中的状态机,就是顶层的。
层次状态机的理念是消息处理的继承性,模块化设计的理念是模块与外部隔离,当2这有冲突的时候,优先考虑模块化的要求。
3. 中断中,发送事件使用pending的方式(发送到状态机的事件队列),而且,不能使用instant event(不发送到事件队列,需要立即处理的事件)。
普通代码中,发送的事件尽量使用instant事件,特别是状态机可以同时处理超过1个事件,并且处理代码互斥
4. 尽量不要同时给状态机发送多个事件,状态机尽量每次只处理一个事件(就是第3条中,尽量使用instant事件)
|