嵌入式状态机软件实现方式
回想刚开始工作的时候,写代码感觉都会很凌乱,不知道从哪里开始入手,而且写完老是漏掉各种情况处理。只能靠测试来发现缺陷,着实苦恼。直到某天听到办公室某个大佬指导另一个同事:“你搞个状态机去管理啊......你知道什么是状态机不?”于是便抱着好奇的心态去搜索了一下,从此打开了新的大门哈哈哈!发现尤其适合于嵌入式编程,所以十分推荐新手都了解一下。状态机,这是在嵌入式系统中大量使用的软件模式,简单点说,就是会基于当前的状态去做不同的动作,切换到下一个状态。
下面以交通信号灯控制器作为状态机范例演示一下:(该图来自《嵌入式系统设计与实践》作者:Elecia White)
功能需求很简单:红灯停,绿灯行,黄灯等。
状态机一共有三个组成部分:状态、事件、动作;
以上范例中,可如下拆解:
状态:红灯状态RedState、绿灯状态GreenState、黄灯状态YellowState;
事件:停止命令StopCmd、前进命令GoCmd、超时TimeOut;
动作:切换到红灯RedLedOn、切换到绿灯GreenLedOn、切换到黄灯YellowLedOn;
在实际编程中,以不同的部分为中心,可以有不同的软件实现方法:
以状态为中心的状态机
我是谁->发生了什么事->我要干什么
每个状态负责自己的行为和迁移逻辑。基本上一个case对应一个状态,可读性比较强,是最常用的风格。
但可扩展性一般,如果状态事件太多,那处理函数很容易膨胀,所以不太适用于事件多的场景。
void TrafficLightSystemStateMachine(event_e event)
{
static state_e current_state = RedState;
switch (current_state) {
case RedState:
if (event == GoCmd) {
GreenLedOn();
current_state = GreenState;
}
break;
case GreenState:
if (event == TimeOut) {
YellowLedOn();
current_state = YellowState;
}
break;
case YellowState:
if (event == TimeOut) {
RedLedOn();
current_state = RedState;
}
break;
default:
break;
}
}以隐式状态为中心的状态机
同样是以状态为中心,不过上面那种是显式的。区别就在于把迁移逻辑隐藏到状态函数内部里。
隐藏之后的好处是可以做到模块化处理,每个状态函数独立封装,易于单元测试。
(独立的好处是可以不用太管他人,那坏处就是无法太管得了他人。)
state_e red_state(void)
{
if (received_event == GoCmd) {
GreenLedOn();
return GreenState;
}
return RedState;
}
state_e green_state(void)
{
if (received_event == TimeOut) {
YellowLedOn();
return YellowState;
} else if (received_event == StopCmd) {
RedLedOn();
return RedState;
}
return GreenState;
}
state_e yellow_state(void)
{
if (received_event == TimeOut) {
RedLedOn();
return RedState;
}
return YellowState;
}
state_e (*state_functions[])(void) = {
red_state, green_state, yellow_state
};
void TrafficLightSystemStateMachine(void)
{
while (1) {
current_state = state_functions();
}
}以事件为中心的状态机
发生了什么事->我是谁->我要干什么
比较适用于事件驱动场景,尤其是事件频繁,来源复杂的情况。
void TrafficLightSystemStateMachine(event_e event)
{
static state_e current_state = RedState;
switch (event) {
case StopCmd:
if (current_state != RedState) {
RedLedOn();
current_state = RedState;
}
break;
case GoCmd:
if (current_state == RedState) {
GreenLedOn();
current_state = GreenState;
}
break;
case TimeOut:
if (current_state == GreenState) {
YellowLedOn();
current_state = YellowState;
} else if (current_state == YellowState) {
RedLedOn();
current_state = RedState;
}
break;
default:
break;
}
}以动作为中心的状态机
现在是什么情况->我要干什么
以状态+事件判断来决定动作和下一个状态。
把迁移逻辑集中在表里,抽象程度高,可扩展性和可移植性强。
很适合状态 + 事件组合很多的场景
typedef struct {
state_ecurrent;
event_eevent;
state_enext;
void (*action)(void);
} fsm_entry_t;
fsm_entry_t fsm_table[] = {
{ RedState, GoCmd, GreenState,GreenLedOn },
{ GreenState, TimeOut, YellowState, YellowLedOn },
{ YellowState, TimeOut,RedState, RedLedOn },
{ GreenState, StopCmd, RedState, RedLedOn },
};
void TrafficLightSystemStateMachine(event_e event)
{
for (int i = 0; i < sizeof(fsm_table)/sizeof(fsm_table); i++) {
if (fsm_table.current == current_state && fsm_table.event == event) {
current_state = fsm_table.next;
fsm_table.action();
break;
}
}
}总结:
哪个部分多就适合以哪个部分作为中心。状态多适合以状态为中心,事件多适合以事件为中心,状态和事件都多适合以动作为中心。在实践过程中,其实重要的不是实现方式,而是状态机的设计思想。但是状态机代码往往不利于维护和评审,所以提供状态机图等文档是十分必要的。
练习:
大家可以以电子表作为练习,写在评论区里交流一下,功能需求如下:
1.有液晶显示屏显示年月日时分秒;
2.有4个按键:
a.LIGH键:夜光功能,按下后表灯会亮起,提供夜晚或昏暗环境下的时间显示。
b.MODE键:功能键,用于选择需要调节的闹钟时间等。
c.START键/加键:秒表键,用于开始、停止和继续计时/数字加。
d.RESET键/减键:用于归零计时功能/数字减。
3.可以实现显示时间、调节时间、秒表、设置闹钟、倒计时功能。
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
页:
[1]