状态机 - 用于保存状态、事件和pFuncs的结构体

4
如果我制作了一个状态机并想要使用这样的接口:
AddState ( state1, state2, Key_UP );
AddEvent ( Key_UP );
AddEventFunction ( Key_UP, &UP_Function);
AddStateFunction ( state1, &State1_In_Function, &State1_Out_Function);
AddStateFunction ( state2, &State2_In_Function, &State2_Out_Function);

State1_In_Function  ( void ) { printf ( "In #1 \n" ); }
State1_Out_Function ( void ) { printf ( "Out #1 \n" ); }
State2_In_Function  ( void ) { printf ( "In #2 \n" ); }
State2_Out_Function ( void ) { printf ( "Out #2 \n" ); }
UP_Function         ( void ) { printf ( "Goin UP \n" ); }

那么当我处于state1状态时,FSM接收到Key_UP时,程序将打印:
Out #1
Goin UP
In #2

问题是如何在类内部存储状态和转换信息,而不需要程序员改变数组大小。我考虑使用2D数组并将其制作成状态表,然后为了使其更加便携,我将使用向量类型来根据需要调整大小以处理事件和状态的添加。向量的问题在于许多嵌入式设备无法使用内存分配调用。我的第二个选择是调用一个构造函数,并将状态机的大小传递给它,但是如果我添加任何新的状态或事件,则也需要更改这些值...那么我应该如何存储我的状态、事件和函数指针呢?

基于Matthieu的下面的示例,我已经完成了一个完全符合我的要求的工作系统!如果你感兴趣,请查看完整的程序HERE,只需执行“g++ file.cpp”,它就可以编译而没有任何错误! - uMinded
3个回答

3

虽然有点困难,但你可以简单地将它们存储在堆栈上 :)

不过,这仍然是一个有趣的解决方案,所以这里给出一个示例。其基本原理是利用装饰器和可变性。以下是代码示例:

State state1, state2; // creates a state
Event KEY_UP;
Event KEY_DOWN;

Transition t0(state1, KEY_UP, state2);
Transition t1(state2, KEY_DOWN, state1);

它是如何工作的?

与其说state1是一个“简单”的对象,不如说它稍微有些复杂。可能像这样:

struct State;

struct StateImpl {
  StateImpl(char const* n): name(n) {}
  char const* name;
};

struct StateNode {
  StateNode(Event e, State const& s, StateNode const* n):
    event(e), state(s), next(n) {}

  Event event;
  State const& destination;
  StateNode const* next;
};

struct State {
  State(char const* name):
    me(0), impl(name) {}

  StateNode const* me;
  StateImpl impl;
};

然后我们定义一个Transition

struct Transition {
  Transition(State& origin, Event e, State const& destination):
    node(e, destination, origin.me)
  {
    origin.me = node;
  }
  StateNode node;
};

简单来说,我们正在构建一个单向链表,其中头部位于State。每次添加转换时,我们更新头部。

在事件发生时,需要遍历此列表,直到遇到事件并适当地分派,或者到达空指针,表示该状态不应接收该事件。


嗯... 这确实非常有趣。通过这种方式,可以轻松地将指针添加到任何结构的输入/输出函数中! - uMinded
我添加了一些调试来跟踪程序逻辑,您可以在此处查看完整的程序。 - uMinded
哇,我正准备睡觉,突然意识到链表就像一个FIFO(先进先出)……它不是递归调用自身,而是最后一个结构成员是下一个地址。哇,我犯了个错误。 - uMinded
我目前使用一种已经被废弃8年的开源FSM生成器,因为它能够创建最小化的C代码并且易于阅读。它将所有内容都放置在if/else和switch语句中结合__inline,C代码很长但是汇编代码非常紧凑。随着我进入100MHz+芯片领域,我希望能有自己的C++ FSM,因为那里有足够的空间容纳额外的占用以及需要易于阅读的大量代码。我正在阅读一个链接列表教程,以了解如何使用你的代码,但现在我意识到完全错过了重点,哈哈,希望我可以让它发挥作用,并将其发布到pastebin上,如果你想要查看的话。谢谢! - uMinded
谢谢您的所有帮助,我学到了很多关于链表的知识。我以前从未使用过它们,但它们非常方便。我已经用我的新代码更新了原始问题,希望其他人也能从中受益。祝您度过愉快的假期! - uMinded
显示剩余3条评论

0

我建议你看一下Boost.StateChart,如果需要的话,可以用你想要的API构建一个薄层。

这应该能解决你设计适当数据结构的需求。


2
我不确定Boost.StateChart是否适用于嵌入式设备。 - Matthieu M.
1
OP明确提到他所关心的平台上没有堆分配内存可用。这意味着他正在使用C++的子集,而不是完整的标准。就我个人而言,我不了解Boost.StateChart内在机制是否可行...因此这是一种评论。你知道它们吗? - Matthieu M.
我必须说我不知道,但是尝试一下也许会有所收获。 - Benoît
在嵌入式设备上获取Boost库并不是不可能,但它们会增加大量的存储空间。我曾经尝试在一个73MHz的ARM上添加Boost库,但它使我的代码大小超出了闪存空间。因此,我正在尝试编写自己的嵌入式状态机实现。 - uMinded

0

很遗憾,这有点过于简单了。我的大多数状态机都有约30个状态和5个事件。 - uMinded

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接