万事万物都有其状态
状态是人或事物表现出来的形态。是指现实(或虚拟)事物处于生成、生存、发展、消亡时期或各转化临界点时的形态或事物态势。
通过上面那句话,我们知道了状态就是一个对象在不同情况下对应的各种形态
做产品的时候,如果我们如果要对这个对象所有的形态进行描述,在一些对象复杂的逻辑状态下,比较复杂的逻辑问题,普通的流程图,或时序图对于对象和状态的解读缺乏直观的描述。
这个时候就需要状态机来对对象的各个形态进行描述,将对象的全部工作方式,分成几个场景,这些场景的工作方式不同,然后将这些场景通过数学模型表示出来
比方说一个小灯泡的开关,就是一个最基本的小型状态机
这里就是两个状态:①打开开关,灯泡亮,②关闭开关,灯泡灭
对应的状态机图:
状态机方便的地方是,如果现在我们只有两种状态,这两个状态在 打开开关/关闭开关
两个条件之间跳转,如果我们要加上一个新的条件,比方说我们设定了定时开关灯,多了一个跳转条件,如果灯泡开了超过8个小时就自动关闭,除非再次做打开开关,否则灯泡一致处于关闭状态,这个状态图如下:
是不是很方便
再举个最简单的例子。人有三个状态健康,感冒,康复中。
触发的条件有淋雨(t1),吃药(t2),打针(t3),休息(t4)。
所以状态机就是健康->(t4)->健康;健康->(t1)->感冒;感冒->(t3)->健康;感冒->(t2)->康复中;康复中->(t4)->健康,等等。就是这样状态在不同的条件下跳转到自己或不同状态的情况,就叫做状态机。
通过上面的举例,我们可以讲状态机可归纳为4个要素,即现态、条件、动作、次态。这样的归纳,主要是出于对状态机的内在因果关系的考虑。“现态”和“条件”是因,“动作”和“次态”是果。详解如下:
①现态:是指当前所处的状态。
②条件:又称为“事件”,当一个条件被满足,将会触发一个动作,或者执行一次状态的迁移。
③动作:条件满足后执行的动作。动作执行完毕后,可以迁移到新的状态,也可以仍旧保持原状态。动作不是必需的,当条件满足后,也可以不执行任何动作,直接迁移到新状态。
④次态:条件满足后要迁往的新状态。“次态”是相对于“现态”而言的,“次态”一旦被激活,就转变成新的“现态”了。
状态机是一种编程思路。是现实事物运行规则抽象而成的一个数学模型。
有限状态机简称就是状态机,因为一般的状态机的状态都是离散和可举的,即为有限,所以后面的介绍都不加有限二字**。状态机表示有限个状态以及在这些状态之间的转移和动作等行为的数学模型**。通俗的描述状态机就是定义了一套状态変更的流程:
状态机包含一个状态集合,定义当状态机处于某一个状态的时候它所能接收的事件以及可执行的行为,执行完成后,状态机所处的状态。
举个简单的例子:就按键处理来说,按键动作本身也可以看做一个状态机。一个细小的击键动作包含了:释放、抖动、按下、抖动和重新释放等状态。 当我们打开思路,把状态机作为一种思想导入到程序中去时,就会找到处理疑问的一条有效的捷径。有时候用状态机的思维去思考程序该干什么,比用控制流程的思维去思考,可能会更有效。这样一来状态机便有了更实际的功用。废话不多说,实践才是检验真理的唯一标准。
也许有人觉得状态机把问题复杂化了,其实我们在编写代码的时候在无形之中已经使用了状态机的思想,比方说我们的if else 判断
if 条件1
else if 条件2
else if 条件3
...
else 条件n
我们知道C语言的if else if语句是从第一条开始判断的,如果符合条件的那一行永远在后面几行,那么就要每次多执行很多次的if …而if是判断语句,括号内的判断是要执行运算的,即使是单周期指令的MCU,在进行乘除运算等都需要消耗多个时钟周期,因此,每次多执行1次判断至少浪费一个时钟周期甚至更多,因此,这样子的状态机无疑是效率低下的,系统软件设计会很复杂。
switch()。
case1:。
if(not反复执行状态1)。
进入1状态前要做的准备。
进入1状态的过程。
if(not反复执行状态1)。
离开状态1的过程。
case2:。
...。
使用Switch case 相较于if else的好处就是可以清楚的看到所有的状态,然后代码架构更清楚点,但是实际的运行效率还是没有提高
再说一下使用Switch编写状态机的两种写法
我们假设状态机的状态转换由下表所示:当处于State0时发生event0 则执行action0并将状态变成state1,当state1状态下发生event2则执行action6并将状态变成state2。以此类推。
下面描述下实现上述状态机的两种不同的写法:
1)竖着写:在状态中判断事件,并执行相应的操作,完成相应的状态转换。
2)横着写:在事件中根据当前的状态,执行相应的操作,完成相应的状态转换。
//竖着写
switch(cur_state)
{
case State0:
if(event1)
{
action0;
cur_state = State1;
}
else if(event2)
{
action4;
cur_state = State1;
}
else if(event3)
{
action5;
cur_state = State2;
}
break;
case State1:
if(event1)
{
action1;
cur_state = State2;
}
else if(event3)
{
action6;
cur_state = State0;
}
break;
case State2:
if(event1)
{
action3;
cur_state = State0;
}
break;
default:break;
}
//横着写
void event0func(void)
{
switch(cur_state)
{
case State0:
action0;
cur_state = State1;
break;
case State1:
action1;
cur_state = State2;
break;
case State2:
action1;
cur_state = State0;
break;
default:break;
}
}
void event1func(void)
{
switch(cur_state)
{
case State0:
action4;
cur_state = State1;
break;
default:break;
}
}
void event2func(void)
{
switch(cur_state)
{
case State0:
action5;
cur_state = State2;
break;
case State1:
action6;
cur_state = State0;
break;
default:break;
}
}
上述两种写法实现的功能完全相同,对比两种写法:
1)写法1(竖着写)使用了if -else if语句隐含了优先级,破坏可事件间的原有关系(各个时间应该同优先级)
2)写法1(竖着写)在结构上是顺序查询方式(查询事件),浪费大量的时间,而且时间不可估算。
写法2(横着写)因为在某个时间点上状态是唯一确定的,在时间处理函数中通过switch语句可直接定位到相同状态,执行时间也可以估算。
3)写法2(横着写)比较直观,程序执行效率较高。
总体来说:写法2要优于写法1。
但是不论是在事件中判断状态,在状态中判断事件,横竖两种写法的代码都比较冗长,看起来呢也不大好,一旦增减,就又要动脑子重新梳理一遍,实际编写我们并不采用这两种写法
那么下一节我们将用STM32实现LED的状态机编程,用状态机的4个要素,让您了解下状态机编程的方便之处。