回想刚开始工作的时候,写代码感觉都会很凌乱,不知道从哪里开始入手,而且写完老是漏掉各种情况处理。只能靠测试来发现缺陷,着实苦恼。直到某天听到办公室某个大佬指导另一个同事:“你搞个状态机去管理啊......你知道什么是状态机不?”于是便抱着好奇的心态去搜索了一下,从此打开了新的大门哈哈哈!发现尤其适合于嵌入式编程,所以十分推荐新手都了解一下。
状态机,这是在嵌入式系统中大量使用的软件模式,简单点说,就是会基于当前的状态去做不同的动作,切换到下一个状态。
下面以交通信号灯控制器作为状态机范例演示一下:(该图来自《嵌入式系统设计与实践》作者: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[current_state]();
- }
- }
复制代码 以事件为中心的状态机
发生了什么事->我是谁->我要干什么
比较适用于事件驱动场景,尤其是事件频繁,来源复杂的情况。- 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_e current;
- event_e event;
- state_e next;
- 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[0]); i++) {
- if (fsm_table[i].current == current_state && fsm_table[i].event == event) {
- current_state = fsm_table[i].next;
- fsm_table[i].action();
- break;
- }
- }
- }
复制代码 总结:
哪个部分多就适合以哪个部分作为中心。状态多适合以状态为中心,事件多适合以事件为中心,状态和事件都多适合以动作为中心。在实践过程中,其实重要的不是实现方式,而是状态机的设计思想。但是状态机代码往往不利于维护和评审,所以提供状态机图等文档是十分必要的。
练习:
大家可以以电子表作为练习,写在评论区里交流一下,功能需求如下:
1.有液晶显示屏显示年月日时分秒;
2.有4个按键:
a.LIGH键:夜光功能,按下后表灯会亮起,提供夜晚或昏暗环境下的时间显示。
b.MODE键:功能键,用于选择需要调节的闹钟时间等。
c.START键/加键:秒表键,用于开始、停止和继续计时/数字加。
d.RESET键/减键:用于归零计时功能/数字减。
3.可以实现显示时间、调节时间、秒表、设置闹钟、倒计时功能。
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |