打印
[应用相关]

VSF平台之事件驱动的状态机----出来混迟早要还的

[复制链接]
3576|8
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
Simon21ic|  楼主 | 2014-8-3 01:35 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
本帖最后由 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事件)
沙发
xkdwangcs| | 2014-8-3 15:05 | 只看该作者
这个我刚才还很想看看里面什么东西来着。。。。。。

使用特权

评论回复
板凳
Simon21ic|  楼主 | 2014-8-3 18:22 | 只看该作者
xkdwangcs 发表于 2014-8-3 15:05
这个我刚才还很想看看里面什么东西来着。。。。。。

我找外包和兼职用的一些说明的帖子,等有时间的话会写这个,现在只是占个坑

使用特权

评论回复
地板
hqgboy| | 2014-8-4 10:47 | 只看该作者
瓜子花生等讲课。

使用特权

评论回复
5
Simon21ic|  楼主 | 2014-8-7 18:45 | 只看该作者
本帖最后由 Simon21ic 于 2014-8-7 18:51 编辑

讲课不敢当,只是作为我的外包和兼职的一些技术上的要求的说明。
最近看了不少状态机模型,也看了不少调度方法的实现,之后会完善自己的VSF平台中的状态机的实现
看的几个状态机的实现确实非常有意思,可以自己先看一下,比如MSM元状态机,HSM层次状态机
当然,如果有什么其他好玩的东西,也可以推荐我看一下

以前的项目,基本没有对功耗控制有太高的要求,反而对RAD快速应用开发有不小的要求,所以VSF构架中的OOP模块化设计部分,已经开发的方式优化做的不错。现在有一些电池供电的产品开发,所以开始研究各种状态机实现,现在VSF的一些模块虽然使用了状态机轮询,但是还没有形成完整的状态机构架。如果你对状态机构架有自己独到的看法,可以多交流。

使用特权

评论回复
6
电子发烧者| | 2015-1-9 22:53 | 只看该作者
钱工,看了这些让我更加坚定的去投奔您了,年后见

使用特权

评论回复
7
Simon21ic|  楼主 | 2015-1-10 15:57 | 只看该作者
本帖最后由 Simon21ic 于 2015-1-10 15:59 编辑
电子发烧者 发表于 2015-1-9 22:53
钱工,看了这些让我更加坚定的去投奔您了,年后见

这个只是我对嵌入式构架的一些尝试,其实技术上并不复杂的
在各种RTOS大行其道的时候,就不水波逐流了,玩一些不一样的,不过状态机构架,相信论坛里也有不少人玩过的
有人对状态机构架感兴趣的话,欢迎交流
如果你能够看懂,也说明具备了一定的能力了

使用特权

评论回复
8
Serge_Ding| | 2015-1-10 20:49 | 只看该作者
留个记号,值得学习

使用特权

评论回复
9
飞行员| | 2015-1-10 21:47 | 只看该作者
vg

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

266

主题

2597

帖子

104

粉丝