单片机的状态机编程思路

发布时间:2023-04-06  

不知道大家有没有这样一种感觉,就是感觉自己玩单片机还可以,各个功能模块也都会驱动,但如果让你完整地写一套代码,却无逻辑与框架可言,上来就是开始写!东抄抄西抄抄,说明编程还处于比较低的水平。那么,如何才能提高自己的编程水平呢?


学会一种好的编程框架,或者一种编程思想,可能会受用终生!比如模块化编程、框架式编程、状态机编程等,都是一种好的框架。

今天说的就是状态机编程,由于篇幅较长,大家慢慢欣赏。那么,状态机是一个什么样的东东?

状态机(state machine)有5个要素,分别是状态(state)、迁移(transition)、事件(event)、动作(action)、条件(guard)。


什么是状态机?

状态机是一个这样的东东:状态机(state machine)有 5 个要素,分别是状态(state)、迁移(transition)、事件(event)、动作(action)、条件(guard)。


状态:一个系统在某一时刻所存在的稳定的工作情况,系统在整个工作周期中可能有多个状态。例如一部电动机共有正转、反转、停转这 3 种状态。


一个状态机需要在状态集合中选取一个状态作为初始状态。


迁移:系统从一个状态转移到另一个状态的过程称作迁移,迁移不是自动发生的,需要外界对系统施加影响。停转的电动机自己不会转起来,让它转起来必须上电。


事件:某一时刻发生的对系统有意义的事情,状态机之所以发生状态迁移,就是因为出现了事件。对电动机来讲,加正电压、加负电压、断电就是事件。


动作:在状态机的迁移过程中,状态机会做出一些其它的行为,这些行为就是动作,动作是状态机对事件的响应。给停转的电动机加正电压,电动机由停转状态迁移到正转状态,同时会启动电机,这个启动过程可以看做是动作,也就是对上电事件的响应。


条件:状态机对事件并不是有求必应的,有了事件,状态机还要满足一定的条件才能发生状态迁移。还是以停转状态的电动机为例,虽然合闸上电了,但是如果供电线路有问题的话,电动机还是不能转起来。


只谈概念太空洞了,上一个小例子:一单片机、一按键、俩 LED 灯(记为L1和L2)、一人, 足矣!


规则描述:

1、L1L2状态转换顺序OFF/OFF--->ON/OFF--->ON/ON--->OFF/ON--->OFF/OFF

2、通过按键控制L1L2的状态,每次状态转换需连续按键5次

3、L1L2的初始状态OFF/OFF

图1

下面这段程序是根据功能要求写成的代码。

程序清单List1:

void main(void)

{

 sys_init();

 led_off(LED1);

 led_off(LED2);

 g_stFSM.u8LedStat = LS_OFFOFF;

 g_stFSM.u8KeyCnt = 0;

 while(1)

 {

  if(test_key()==TRUE)

  {

   fsm_active();

  }

  else

  {

   ; /*idle code*/

  }

 }

}

void fsm_active(void)

{

 if(g_stFSM.u8KeyCnt > 3) /*击键是否满 5 次*/

 {

  switch(g_stFSM.u8LedStat)

  {

   case LS_OFFOFF:

    led_on(LED1); /*输出动作*/

    g_stFSM.u8KeyCnt = 0;

    g_stFSM.u8LedStat = LS_ONOFF; /*状态迁移*/

    break;

   case LS_ONOFF:

    led_on(LED2); /*输出动作*/

    g_stFSM.u8KeyCnt = 0;

    g_stFSM.u8LedStat = LS_ONON; /*状态迁移*/

    break;

   case LS_ONON:

    led_off(LED1); /*输出动作*/

    g_stFSM.u8KeyCnt = 0;

    g_stFSM.u8LedStat = LS_OFFON; /*状态迁移*/

    break;

   case LS_OFFON:

    led_off(LED2); /*输出动作*/

    g_stFSM.u8KeyCnt = 0;

    g_stFSM.u8LedStat = LS_OFFOFF; /*状态迁移*/

    break;

   default: /*非法状态*/

    led_off(LED1);

    led_off(LED2);

    g_stFSM.u8KeyCnt = 0;

    g_stFSM.u8LedStat = LS_OFFOFF; /*恢复初始状态*/

    break;

  }

 }

 else

 {

  g_stFSM.u8KeyCnt++; /*状态不迁移,仅记录击键次数*/

 }

}


实际上在状态机编程中,正确的顺序应该是先有状态转换图,后有程序,程序应该是根据设计好的状态图写出来的。不过考虑到有些童鞋会觉得代码要比转换图来得亲切,我就先把程序放在前头了。


这张状态转换图是用UML(统一建模语言)的语法元素画出来的,语法不是很标准,但拿来解释问题足够了。

图2按键控制流水灯状态转换图

圆角矩形代表状态机的各个状态,里面标注着状态的名称。

带箭头的直线或弧线代表状态迁移,起于初态,止于次态。

图中的文字内容是对迁移的说明,格式是:事件[条件]/动作列表(后两项可选)。


“事件[条件]/动作列表”要说明的意思是:如果在某个状态下发生了“事件”,并且状态机满足“[条件]”,那么就要执行此次状态转移,同时要产生一系列“动作”,以响应事件。在这个例子里,我用“KEY”表示击键事件。


图中有一个黑色实心圆点,表示状态机在工作之前所处的一种不可知的状态,在运行之前状态机必须强制地由这个状态迁移到初始状态,这个迁移可以有动作列表(如图1所示),但不需要事件触发。


图中还有一个包含黑色实心圆点的圆圈,表示状态机生命周期的结束,这个例子中的状态机生生不息,所以没有状态指向该圆圈。


关于这个状态转换图就不多说了,相信大家结合着上面的代码能很容易看明白。现在我们再聊一聊程序清单List1。


先看一下fsm_active()这个函数,g_stFSM.u8KeyCnt = 0;这个语句在switch—case里共出现了 5 次,前 4 次是作为各个状态迁移的动作出现的。从代码简化提高效率的角度来看,我们完全可以把这 5 次合并为 1 次放在 switch—case 语句之前,两者的效果是完全一样的,代码里之所以这样啰嗦,是为了清晰地表明每次状态迁移中所有的动作细节,这种方式和图2的状态转换图所要表达的意图是完全一致的。


再看一下g_stFSM这个状态机结构体变量,它有两个成员:u8LedStat和 u8KeyCnt。用这个结构体来做状态机好像有点儿啰嗦,我们能不能只用一个像 u8LedStat 这样的整型变量来做状态机呢?


当然可以!我们把图 2中的这 4 个状态各自拆分成 5 个小状态,这样用 20 个状态同样能实现这个状态机,而且只需要一个 unsigned char 型的变量就足够了,每次击键都会引发状态迁移, 每迁移 5 次就能改变一次 LED 灯的状态,从外面看两种方法的效果完全一样。


假设我把功能要求改一下,把连续击键5次改变L1L2的状态改为连续击键100次才能改变L1L2的状态。这样的话第二种方法需要4X100=400个状态!而且函数fsm_active()中的switch—case语句里要有400个case,这样的程序还有法儿写么?!


同样的功能改动,如果用g_stFSM这个结构体来实现状态机的话,函数fsm_active()只需要将if(g_stFSM.u8KeyCnt>3)改为if(g_stFSM.u8KeyCnt > 98)就可以了!

g_stFSM结构体的两个成员中,u8LedStat可以看作是质变因子,相当于主变量;u8KeyCnt可以看作是量变因子,相当于辅助变量。量变因子的逐步积累会引发质变因子的变化。


像g_stFSM这样的状态机被称作Extended State Machine,我不知道业内正规的中文术语怎么讲,只好把英文词组搬过来了。


2、状态机编程的优点

说了这么多,大家大概明白状态机到底是个什么东西了,也知道状态机化的程序大体怎么写了,那么单片机的程序用状态机的方法来写有什么好处呢?


(1)提高CPU使用效率

话说我只要见到满篇都是delay_ms()的程序就会蛋疼,动辄十几个ms几十个ms的软件延时是对CPU资源的巨大浪费,宝贵的CPU机时都浪费在了NOP指令上。那种为了等待一个管脚电平跳变或者一个串口数据而岿然不动的程序也让我非常纠结,如果事件一直不发生,你要等到世界末日么?


把程序状态机化,这种情况就会明显改观,程序只需要用全局变量记录下工作状态,就可以转头去干别的工作了。当然,忙完那些活儿之后,要再看看工作状态有没有变化。只要目标事件(定时未到、电平没跳变、串口数据没收完)还没发生,工作状态就不会改变,程序就一直重复着“查询—干别的—查询—干别的”这样的循环,这样CPU就闲不下来了。


在程序清单 List3 中,if{}else{}语句里else下的内容(代码中没有添加,只是加了一条/*idle code*/的注释示意)就是上文所说的“别的工作” 。


这种处理方法的实质,就是在程序等待事件的过程中间隔性地插入一些有意义的工作,好让CPU不是一直无谓地等待。


(2) 逻辑完备性

我觉得逻辑完备性是状态机编程最大的优点。

不知道大家有没有用C语言写过计算器的小程序,我很早以前写过,写出来一测试,那个惨不忍睹啊!当我规规矩矩的输入算式的时候,程序可以得到正确的计算结果,但要是故意输入数字和运算符号的随意组合,程序总是得出莫名其妙的结果。


后来我试着思维模拟一下程序的工作过程,正确的算式思路清晰,流程顺畅,可要碰上了不规矩的式子,走着走着我就晕菜了,那么多的标志位,那么多的变量,变来变去,最后直接分析不下去了。


很久之后我认识了状态机,才恍然明白,当时的程序是有逻辑漏洞的。如果把这个计算器程序当做是一个反应式系统,那么一个数字或者运算符就可以看做一个事件,一个算式就是一组事件组合。


对于一个逻辑完备的反应式系统,不管什么样的事件组合,系统都能正确处理事件,而且系统自身的工作状态也一直处在可知可控的状态中。反过来,如果一个系统的逻辑功能不完备,在某些特定事件组合的驱动下,系统就会进入一个不可知不可控的状态,与设计者的意图相悖。


状态机就能解决逻辑完备性的问题。

状态机是一种以系统状态为中心,以事件为变量的设计方法,它专注于各个状态的特点以及状态之间相互转换的关系。状态的转换恰恰是事件引起的,那么在研究某个具体状态的时候,我们自然而然地会考虑任何一个事件对这个状态有什么样的影响。这样,每一个状态中发生的每一个事件都会在我们的考虑之中,也就不会留下逻辑漏洞。


这样说也许大家会觉得太空洞,实践出真知,某天如果你真的要设计一个逻辑复杂的程序,我保证你会说:哇!状态机真的很好用哎!


文章来源于:电子工程世界    原文链接
本站所有转载文章系出于传递更多信息之目的,且明确注明来源,不希望被转载的媒体或个人可与我们联系,我们将立即进行删除处理。

相关文章

    以STC8H8K64U单片机兼容STC89C52开发板的设计;本文以STC8H8K64U单片机兼容STC89C52传统开发板及发挥引脚最大化资源配置设计核心板,结合与Keil C兼容的图形化编程......
    stm32单片机用什么语言 编程 stm32各种型号;  stm32单片机用什么语言编程   STM32单片机可以使用多种编程语言进行编程,包括C语言、汇编语言以及基于图形化编程的工具,如ST的......
    、编译和调试。 STM32CubeIDE:STM32CubeIDE是STMicroelectronics提供的官方集成开发环境,可以使用C/C++语言编程,支持多种STM32单片机系列,提供了一系列的图形化......
    优异的性能而广泛应用于自动控制、通信等领域,并逐步成为各种仪器仪表的首选控制芯片之一。虚拟仪器(labview)是美国NI公司推出的图形化编程软件,包含了丰富的处理函数和各种算法。目前大部分虚拟仪器要求配以不同总线标准的NI......
    较高的抗干扰、抗振动和抗电磁干扰能力。   2. 稳定性好:PLC用于控制系统时,其稳定性和可靠性都得到了较好的保证。   3. 编程简单:PLC的编程方式通常基于图形化编程,易于理解和掌握,而且......
    的语言不是复杂的c语言,而是更简单图形化编程。 也不需要开发者去学习复杂的电路就能快速地做出应用。 如果想转行的话,我建议还是走单片机方向发展的前景更广阔一些。 做PLC的工资没做单片机......
    . 基本方法:根据功能模块图,画出各个输入、输出及逻辑操作之间的连接关系,逐一确定逻辑元件的状态,从而实现程序功能。   2. 软件辅助编程:可以使用各种PLC编程软件,利用图形化编程界面,直接......
    、AVRStudio等等,还有针对arduino等芯片的图形化编程引擎如mixly等,现在还有针对51单片机的图形化编程引擎。 图形化编程引擎的使用降低了编写代码的成本,人们只需拖动几个积木样的图形......
    前面板设计UI界面,之后后面板通过图形化编程完成软件开发,放上一个上位机前面板的截图: 后面板截图如下: ......
    源蜂鸣器来实现。 ☞ APP: App采用MIT研发的图形化编程app:app inventor2,通过蓝牙模块连接至单片机,从而去控制我们的“家电”。 ☞ OLED屏幕显示: 用来显示各个模块的工作状态,大致......

我们与500+贴片厂合作,完美满足客户的定制需求。为品牌提供定制化的推广方案、专属产品特色页,多渠道推广,SEM/SEO精准营销以及与公众号的联合推广...详细>>

利用葫芦芯平台的卓越技术服务和新产品推广能力,原厂代理能轻松打入消费物联网(IOT)、信息与通信(ICT)、汽车及新能源汽车、工业自动化及工业物联网、装备及功率电子...详细>>

充分利用其强大的电子元器件采购流量,创新性地为这些物料提供了一个全新的窗口。我们的高效数字营销技术,不仅可以助你轻松识别与连接到需求方,更能够极大地提高“闲置物料”的处理能力,通过葫芦芯平台...详细>>

我们的目标很明确:构建一个全方位的半导体产业生态系统。成为一家全球领先的半导体互联网生态公司。目前,我们已成功打造了智能汽车、智能家居、大健康医疗、机器人和材料等五大生态领域。更为重要的是...详细>>

我们深知加工与定制类服务商的价值和重要性,因此,我们倾力为您提供最顶尖的营销资源。在我们的平台上,您可以直接接触到100万的研发工程师和采购工程师,以及10万的活跃客户群体...详细>>

凭借我们强大的专业流量和尖端的互联网数字营销技术,我们承诺为原厂提供免费的产品资料推广服务。无论是最新的资讯、技术动态还是创新产品,都可以通过我们的平台迅速传达给目标客户...详细>>

我们不止于将线索转化为潜在客户。葫芦芯平台致力于形成业务闭环,从引流、宣传到最终销售,全程跟进,确保每一个potential lead都得到妥善处理,从而大幅提高转化率。不仅如此...详细>>