嵌入式状态机(三)状态模式状态机
嵌入式状态机的进阶用法——状态模式
前言
在传统的FSM状态机的实现中,常见方法可分为两类:表驱动状态机和状态模式状态机。在资源受限的嵌入式环境中,表驱动状态机因高效、紧凑而备受青睐;然而,随着嵌入式系统所需处理的任务日益复杂,状态模式在提升代码可维护性和扩展性方面的重要性也日益凸显。
注意:想了解表驱动状态机可以看我的上一篇文章嵌入式状态机(二)
了解模式状态机的前置知识:
一、c语言语法
函数指针、指针函数、switch、typedef
二、状态模式
状态都是一个独立的函数,换句话说这些函数分别代表了状态机的一个特定状态。
完整代码
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
typedef enum {
start_key,
stop_key,
suspend_key,
close_the_door,
open_the_door,
EventEnd,
}TackEvent;
typedef struct StateStruct* (*StateFunction)(TackEvent e);
typedef struct StateStruct
{
StateFunction function;
}StateStruct;
StateStruct* Leisure(TackEvent e);
StateStruct* Run(TackEvent e);
StateStruct* Suspend(TackEvent e);
StateStruct* Error(TackEvent e);
StateStruct* Leisure(TackEvent e)
{
switch (e)
{
case start_key:
printf("Leisure->Run \n");
return (StateStruct*)Run;
default:
printf("Leisure->Leisure \n");
return (StateStruct*)Leisure;
}
}
StateStruct* Run(TackEvent e)
{
switch (e)
{
case stop_key:
printf("Run->Leisure \n");
return (StateStruct*)Leisure;
case suspend_key:
printf("Run->Suspend \n");
return (StateStruct*)Suspend;
case open_the_door:
printf("Run->OpenDoor->Error \n");
return (StateStruct*)Error;
default:
printf("Run->Run \n");
return (StateStruct*)Run;
}
}
StateStruct* Suspend(TackEvent e)
{
switch (e)
{
case stop_key:
printf("Suspend->Leisure \n");
return (StateStruct*)Leisure;
case start_key:
printf("Suspend->Run \n");
return (StateStruct*)Run;
default:
printf("Suspend->Suspend \n");
return (StateStruct*)Suspend;
}
}
StateStruct* Error(TackEvent e)
{
switch (e)
{
case stop_key:
printf("Error->Leisure \n");
return (StateStruct*)Leisure;
case close_the_door:
printf("Error->Run \n");
return (StateStruct*)Run;
default:
// printf("Error->Error \n");
return (StateStruct*)Error;
}
}
TackEvent new_event[4]=
{
start_key,
open_the_door,
start_key,
stop_key,
};
StateFunction StateMachineRun=Leisure;
void main(void)
{
for(int i=0;i<4;i++)
{
StateMachineRun =(StateFunction) ((*StateMachineRun)(new_event[i]));
}
}
这是一个关于洗衣机的状态机,小编将洗衣机的状态大致分为四类:空闲leisure、运行run、暂停suspend、错误error。触发事件有五个:开始start_key,停止stop_key,暂停suspend_key,关门close_the_door,开门open_the_door。

状态流程图
代码详解
一、状态函数与接口
typedef struct StateStruct* (*StateFunction)(TackEvent e);
typedef struct StateStruct
{
StateFunction function;
}StateStruct;
StateStruct* Leisure(TackEvent e);
StateStruct* Run(TackEvent e);
StateStruct* Suspend(TackEvent e);
StateStruct* Error(TackEvent e);
StateStruct* Leisure(TackEvent e)
{
switch (e)
{
case start_key:
printf("Leisure->Run \n");
return (StateStruct*)Run;
default:
printf("Leisure->Leisure \n");
return (StateStruct*)Leisure;
}
}
StateStruct* Run(TackEvent e)
{
switch (e)
{
case stop_key:
printf("Run->Leisure \n");
return (StateStruct*)Leisure;
case suspend_key:
printf("Run->Suspend \n");
return (StateStruct*)Suspend;
case open_the_door:
printf("Run->OpenDoor->Error \n");
return (StateStruct*)Error;
default:
printf("Run->Run \n");
return (StateStruct*)Run;
}
}
StateStruct* Suspend(TackEvent e)
{
switch (e)
{
case stop_key:
printf("Suspend->Leisure \n");
return (StateStruct*)Leisure;
case start_key:
printf("Suspend->Run \n");
return (StateStruct*)Run;
default:
printf("Suspend->Suspend \n");
return (StateStruct*)Suspend;
}
}
StateStruct* Error(TackEvent e)
{
switch (e)
{
case stop_key:
printf("Error->Leisure \n");
return (StateStruct*)Leisure;
case close_the_door:
printf("Error->Run \n");
return (StateStruct*)Run;
default:
// printf("Error->Error \n");
return (StateStruct*)Error;
}
}
在状态模式状态机中最核心的方法就是状态即函数。
以运行状态run为例:在StateStruct* Run(TackEvent e)函数中使用switch来分解当前状态的不同事件,可将状态和该状态下的行为捆绑在一起。在Run函数中包含了所有状态run的处理逻辑,当你想要知道“运行状态”遇到“暂停事件”会发生什么时不要查表,而是直接去看 Run 函数里的 case suspend_key 分支即可。
typedef struct StateStruct* (*StateFunction)(TackEvent e); 这条语句定义了一个状态函数的统一接口,是状态之间能够互相转换的前提。它保证了无论当前是哪个状态,我们都可以用同样的方式去调用它:next_state = current_state(event):
整个状态函数的转换核心就是可以通过不同的事件返回不同的状态函数指针。
二、状态机的驱动
//初始化
StateFunction StateMachineRun=Leisure;
//核心驱动
StateMachineRun =(StateFunction) ((*StateMachineRun)(new_event[i]));
整个状态机的驱动只有这一句话,其中new_event数组是用来模拟事件的发生的。
整个过程就是同当前状态函数的返回值(状态函数的指针)来决定下一次运行哪个状态(状态函数)
详细过程:
- 获取当前状态:
StateMachineRun指针指向谁,当前就是什么状态(例如,最初指向Leisure)。 - 接收事件:从模拟事件队列(这里是数组
new_event)中取出一个事件。 - 处理并转换:调用当前状态函数,并传入事件。这个函数内部会根据事件执行相应动作(如打印)并返回下一个状态是谁。
- 状态更新:将返回的下一个状态函数赋值给
StateMachineRun指针,完成状态切换。
三、总结
看到这里的朋友们可能会有一个疑惑,为什么要在StateFunction外套一个StateMachineRun 类型的外壳呢?这不是多此一举吗!其原因很简单,就是为了扩展状态机使其可以传递状态机的上下文信息!
// 定义状态机上下文
typedef struct {
uint32_t run_time_seconds; // 运行时间
uint8_t door_open_count; // 开门次数
bool is_motor_overheated; // 电机是否过热
// ... 其他任何需要跨状态共享的数据
} StateMachineContext;
// 重新定义状态结构体,使其包含上下文
typedef struct StateStruct {
StateFunction function; // 状态函数指针
StateMachineContext* context; // 【核心】指向共享上下文的指针
} StateStruct;
// 状态函数的签名也需要改变,现在它需要接收一个“真正”的StateStruct*
typedef struct StateStruct* (*StateFunction)(struct StateStruct* current_state, TackEvent e);
这个例子很好的向我们展现了这个功能,的具体如何使用还是因人而异。
之所以这样设计是为了留下一个可扩展的接口。
结语
在小编看来这种状态模式状态机(函数指针实现)通常比状态转移表状态机更符合人类的思考模式,尤其是当状态逻辑变得复杂时。且在编程时应该遵循哪种抽象更符合人类直觉的(最好实现)软件设计哲学。不要过早优化。首先使用最直观、最不易出错的方式(状态函数)实现功能正确性。
当然,在嵌入式系统中状态转移表状态机依然是效率最高、消耗的资源最少的方法。在嵌入式系统中我们需要正确是认识它们的特性。
openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。
更多推荐



所有评论(0)