"); //-->
本文将讲解状态机的从入门到“精通”的那些事。有限状态机讲述了有限个状态、以及它们之间相互的动作以及转移的过程。最早接触状态机是在数字电路中的触发器章节,书中清晰的使用了状态图来描述触发器工作机理。
图1 D触发器的状态图
后来在EDA课程中的FPGA设计中,终于有机会将状态机应用于实际电路编程中。在那一刻,状态机概念就像一个烙印刻在心里,无法抹去。
switch-case
相信大多数初学者和我一样,第一次编写状态机是使用switch-case结构来实现的。忆当年,我在C51单片机编程中第一次引入“状态机”,实现功能竟是判断按键是否按下。按键的动作可以分为如下几个状态:等待被按下S_BTN_WAIT_PRESSED、安全被按下S_BTN_PRESSED、等待松开S_BTN_WAIT_RELEASED。其中等待被按下和安全被按下过程之间应该进行按键的消抖操作。
图2 D按键检测状态图
按照上图所示,使用switch-case结构的状态机如下:
switch-case结构实现的状态机,可谓“简单粗暴”。写出来的代码放在一块,一眼就可以看出整个状态机包含了多少个状态,而且每个case就是一个状态。整体容易区分,简单。那“粗暴”从何说起?想象一下,如果这个状态机有10个甚至更多的状态,每个case里面的if结构层数又比较多的时候,总体代码篇幅就会显得很长,臃肿庞大。代码那么“粗”,感觉屏幕都要“暴”炸了。另外也会引入这样的问题,如果case过多,switch的出口就会大大增加(break就会增加),对于阅读代码时就会极其不便。所以switch-case可以应付简单的状态机。那么对于如何解决上述问题呢?
这时候一位因敲代码成仙的仙人指明了方向——函数指针!
函数指针
使用函数指针实现状态机的时候,每一个函数都可以认为一个单独的状态,输入参数可以作为状态转移的触发动作。
现在展示一下初级版本的函数指针状态机,编写一个空调的代码。空调状态机中包含了三个状态:空调关机、空调送风、空调制冷。每个状态下需要辨识的动作是两个按键:开关键按下、送风/制冷按键按下。他们的转移关系图如下:
表1 空调状态转移表
按照上表描述的状态转移过程,空调状态机代码如下:
可以看出,函数指针实现的状态机,就显得“苗条”一点。每个状态通过一个函数实现,函数的输入参数用来传递事件,函数的返回值又作为下一个空调的状态。这种方式的状态机具有更好的可扩展型,比如空调要卖到南方的话,要再增加一个空调除湿模式。这时候,只需要增加一个函数,其他函数保留不动即可。相比switch-case下,函数层次更为分明,功能高度内聚,更加符合“高内聚、低耦合”的软件设计原则。
总结
以上内容简单讲述了switch-case和函数指针实现的状态机以及各自的优缺点。更加复杂的状态机版本可以使用状态转移表来实现。一个二维或者多维的数组存放着所有状态转移的可能性,通过查表的方法即可知道转移的下一个状态。例如:
newState = switchTab[EVT0][EVT1][oldState];
上式表示为:在状态oldState下,如果同时发生了事件为EVT0和EVT1时,那么下一个状态就是newState。这种方式省去了在状态中对事件进行if的逻辑判断,更加适合用于事件有限并且事件类型可用整数表示的情形。
*博客内容为网友个人发布,仅代表博主个人观点,如有侵权请联系工作人员删除。