找回密码
 立即注册
首页 业界区 安全 嵌入式状态机软件实现方式

嵌入式状态机软件实现方式

叟减 3 天前
回想刚开始工作的时候,写代码感觉都会很凌乱,不知道从哪里开始入手,而且写完老是漏掉各种情况处理。只能靠测试来发现缺陷,着实苦恼。直到某天听到办公室某个大佬指导另一个同事:“你搞个状态机去管理啊......你知道什么是状态机不?”于是便抱着好奇的心态去搜索了一下,从此打开了新的大门哈哈哈!发现尤其适合于嵌入式编程,所以十分推荐新手都了解一下。
状态机,这是在嵌入式系统中大量使用的软件模式,简单点说,就是会基于当前的状态去做不同的动作,切换到下一个状态。
下面以交通信号灯控制器作为状态机范例演示一下:(该图来自《嵌入式系统设计与实践》作者:Elecia White)
功能需求很简单:红灯停,绿灯行,黄灯等。
1.png

状态机一共有三个组成部分:状态事件动作
以上范例中,可如下拆解:
状态:红灯状态RedState、绿灯状态GreenState、黄灯状态YellowState
事件:停止命令StopCmd、前进命令GoCmd、超时TimeOut
动作:切换到红灯RedLedOn、切换到绿灯GreenLedOn、切换到黄灯YellowLedOn
在实际编程中,以不同的部分为中心,可以有不同的软件实现方法:
以状态为中心的状态机

我是谁->发生了什么事->我要干什么
每个状态负责自己的行为和迁移逻辑。基本上一个case对应一个状态,可读性比较强,是最常用的风格。
但可扩展性一般,如果状态事件太多,那处理函数很容易膨胀,所以不太适用于事件多的场景。
  1. void TrafficLightSystemStateMachine(event_e event)
  2. {
  3.     static state_e current_state = RedState;
  4.     switch (current_state) {
  5.         case RedState:
  6.             if (event == GoCmd) {
  7.                 GreenLedOn();
  8.                 current_state = GreenState;
  9.             }
  10.             break;
  11.         case GreenState:
  12.             if (event == TimeOut) {
  13.                 YellowLedOn();
  14.                 current_state = YellowState;
  15.             }
  16.             break;
  17.         case YellowState:
  18.             if (event == TimeOut) {
  19.                 RedLedOn();
  20.                 current_state = RedState;
  21.             }
  22.             break;
  23.         default:
  24.             break;
  25.     }
  26. }
复制代码
以隐式状态为中心的状态机

同样是以状态为中心,不过上面那种是显式的。区别就在于把迁移逻辑隐藏到状态函数内部里。
隐藏之后的好处是可以做到模块化处理,每个状态函数独立封装,易于单元测试。
(独立的好处是可以不用太管他人,那坏处就是无法太管得了他人。)
  1. state_e red_state(void)
  2. {
  3.     if (received_event == GoCmd) {
  4.         GreenLedOn();
  5.         return GreenState;
  6.     }
  7.     return RedState;
  8. }
  9. state_e green_state(void)
  10. {
  11.     if (received_event == TimeOut) {
  12.         YellowLedOn();
  13.         return YellowState;
  14.     } else if (received_event == StopCmd) {
  15.         RedLedOn();
  16.         return RedState;
  17.     }
  18.     return GreenState;
  19. }
  20. state_e yellow_state(void)
  21. {
  22.     if (received_event == TimeOut) {
  23.         RedLedOn();
  24.         return RedState;
  25.     }
  26.     return YellowState;
  27. }
  28. state_e (*state_functions[])(void) = {
  29.     red_state, green_state, yellow_state
  30. };
  31. void TrafficLightSystemStateMachine(void)
  32. {
  33.     while (1) {
  34.         current_state = state_functions[current_state]();
  35.     }
  36. }
复制代码
以事件为中心的状态机

发生了什么事->我是谁->我要干什么
比较适用于事件驱动场景,尤其是事件频繁,来源复杂的情况。
  1. void TrafficLightSystemStateMachine(event_e event)
  2. {
  3.     static state_e current_state = RedState;
  4.     switch (event) {
  5.         case StopCmd:
  6.             if (current_state != RedState) {
  7.                 RedLedOn();
  8.                 current_state = RedState;
  9.             }
  10.             break;
  11.         case GoCmd:
  12.             if (current_state == RedState) {
  13.                 GreenLedOn();
  14.                 current_state = GreenState;
  15.             }
  16.             break;
  17.         case TimeOut:
  18.             if (current_state == GreenState) {
  19.                 YellowLedOn();
  20.                 current_state = YellowState;
  21.             } else if (current_state == YellowState) {
  22.                 RedLedOn();
  23.                 current_state = RedState;
  24.             }
  25.             break;
  26.         default:
  27.             break;
  28.     }
  29. }
复制代码
以动作为中心的状态机

现在是什么情况->我要干什么
以状态+事件判断来决定动作和下一个状态。
把迁移逻辑集中在表里,抽象程度高,可扩展性和可移植性强。
很适合状态 + 事件组合很多的场景
  1. typedef struct {
  2.     state_e  current;
  3.     event_e  event;
  4.     state_e  next;
  5.     void (*action)(void);
  6. } fsm_entry_t;
  7. fsm_entry_t fsm_table[] = {
  8.     { RedState,   GoCmd,     GreenState,  GreenLedOn },
  9.     { GreenState, TimeOut,   YellowState, YellowLedOn },
  10.     { YellowState, TimeOut,  RedState,    RedLedOn },
  11.     { GreenState, StopCmd,   RedState,    RedLedOn },
  12. };
  13. void TrafficLightSystemStateMachine(event_e event)
  14. {
  15.     for (int i = 0; i < sizeof(fsm_table)/sizeof(fsm_table[0]); i++) {
  16.         if (fsm_table[i].current == current_state && fsm_table[i].event == event) {
  17.             current_state = fsm_table[i].next;
  18.             fsm_table[i].action();
  19.             break;
  20.         }
  21.     }
  22. }
复制代码
总结:

哪个部分多就适合以哪个部分作为中心。状态多适合以状态为中心,事件多适合以事件为中心,状态和事件都多适合以动作为中心。在实践过程中,其实重要的不是实现方式,而是状态机的设计思想。但是状态机代码往往不利于维护和评审,所以提供状态机图等文档是十分必要的。
练习:
大家可以以电子表作为练习,写在评论区里交流一下,功能需求如下:
1.有液晶显示屏显示年月日时分秒;
2.有4个按键:
a.LIGH键:夜光功能,按下后表灯会亮起,提供夜晚或昏暗环境下的时间显示。
b.MODE键:功能键,用于选择需要调节的闹钟时间等。
c.START键/加键:秒表键,用于开始、停止和继续计时/数字加。
d.RESET键/减键:用于归零计时功能/数字减。
3.可以实现显示时间、调节时间、秒表、设置闹钟、倒计时功能。

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册