FSM有限状态机其二

我们在上篇博客中简单探索了一下表函数法实现的FSM,我们通过函数指针跟结构体的配合来消除了巨大的 switch(current_state),每个状态独立成函数,易于扩展。状态处理函数 = δ 函数的分段实现。 state_table[s](fsm, e) 执行 δ(s, e)

局限

  • 没有“动作”的概念。转移时无法自动执行硬件初始化/清理。

  • 没有“层次”。多个状态共享行为时,代码重复。

我们在这篇博客中,我们要给它实现”动作”这个概念


1
2
3
4
5
6
7
struct StateMachine {
State current_state;
StateHandler state_table[STATE_COUNT];
// 进入和退出动作表
void (*entry_actions[STATE_COUNT])(void); // 函数指针数组
void (*exit_actions[STATE_COUNT])(void);
};

观察我们全新的StateMachine结构体的声明,可以发现它的内部多了两个新的成员,这两个成员其实也是指针数组。存储的数据类型为void ()(void)

顺便一提,C语言有时候这个语法真的是有些逆天,就算我们有所谓的右左法则,但是我觉得用typedef简化复杂类型的声明也是合理的

函数如其名,我们为每一个状态新增了对应的进入函数跟退出函数。

1
2
3
static void entry_closed(void) {
printf(" [Action] entry Closed: motor off\n");
}

当然,在这个简单的demo中,它们也仅仅就只是打印了一些我们需要的文本而已

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void fsm_init(StateMachine *fsm) {
fsm->current_state = STATE_CLOSED; // 初始状态
fsm->state_table[STATE_CLOSED] = state_closed;
fsm->state_table[STATE_OPENED] = state_opened;

// 注册进入/退出动作
fsm->entry_actions[STATE_CLOSED] = entry_closed;
fsm->exit_actions[STATE_CLOSED] = exit_closed;
fsm->entry_actions[STATE_OPENED] = entry_opened;
fsm->exit_actions[STATE_OPENED] = exit_opened;

// 手动执行初始状态的进入动作(因为初始状态没有“转移进入”)
if (fsm->entry_actions[fsm->current_state]) {
fsm->entry_actions[fsm->current_state]();
}
}

上篇博客好像没有讲解这个fsm_init函数,这个函数基本上就是初始化了一下我们所要用到的状态机结构体,我们在这个版本的代码中,仅仅只是增加了对于每个状态进入/退出动作的注册。以及对于初始状态的进入动作的手动执行。

我们在这个版本中最重要的变动就是新增了一个函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
static void fsm_transition_to(StateMachine *fsm, State next_state) {
if (fsm->current_state == next_state) {
// do someting here,自转移通常也需要执行exit/entry,但是按需决定
return;
}

// 1.执行当前状态的退出动作
if (fsm->exit_actions[fsm->current_state]) {
fsm->exit_actions[fsm->current_state]();
}
// 2.更新状态
fsm->current_state = next_state;
// 3.执行新状态的进入动作
if (fsm->entry_actions[fsm->current_state]) {
fsm->entry_actions[fsm->current_state]();
}
}

这个函数专门负责状态机的状态切换,把原本放在状态处理函数里的状态跳转逻辑抽离出来,统一由它管理。

它会自动执行旧状态的退出动作更新当前状态、再执行新状态的进入动作

fsm_dispatch 思想类似,把进入 / 退出动作封装成函数指针调用,大幅减少新增状态时的重复代码。

透过这种改动,我们现在拥有了转移时自动执行硬件初始化/清理的功能,并且将硬件操作与状态绑定,而非与事件绑定。

我们在下一篇博客中,将会给这个框架新增层次状态的功能。