当前位置:首页 > 嵌入式培训 > 嵌入式学习 > 讲师博文 > 使用状态机轻松编程嵌入式系统

使用状态机轻松编程嵌入式系统 时间:2020-05-07      来源:原创

使用状态机轻松编程嵌入式系统大多数嵌入式系统本质上都是反应性的。他们使用传感器测量环境的某些属性,并对变化做出反应。例如,他们显示一些东西,移动电动机或向其他系统发送通知。反应系统最好用状态机来表示-状态机始终处于一组有限且定义明确的可能状态中。
手动对有限状态机进行编程可能会成为一项繁重的任务,并产生令人费解且难以维护的结果。图形设计工具可帮助您跟踪系统的所有可能状态和操作。本文将向您介绍状态机编程,重点是图形设计工具。此外,您将学习如何将生成的平台无关代码与定制的特定于硬件的代码集成在一起,以与硬件(在本例中为Arduino板)进行交互。
状态机是开发反应系统的理想范例。这些反应系统的最显着特征是它们使用传感器和执行器与环境相互作用。传感器的示例是运动,亮度或温度传感器。常用的执行器包括LED,显示器,阀门和电动机。这些系统的另一个重要特征是它们具有有限的可能状态集,并且始终处于其中的一个精确状态中,可以使用状态机轻松实现这些状态。
用于与实际意义的状态机的可能最简单的例子是光开关控制,如图1所示。不出所料,它有两种状态- 论和关闭。两种状态中只有一种可以同时处于活动状态。当进行所谓的过渡时,会发生从一种状态到下一种状态的改变。在此示例中,每当引发buttonpressed事件时,就会发生这种情况。

图1.灯开关控件的两个状态和过渡。

用所有状态绘制系统可以帮助您提前计划并清楚了解系统在不同情况下的预期行为。然后,您可以将该图用作基础代码并进行测试的蓝图。但是,如果后来更改了代码(通常是这种情况),而该图没有更改,则两者会有所不同。如果有人随后尝试根据现在已过时的图表开发测试,则它们将失败。如果仅将模型用于规范或文档编制,则可能会成为一个巨大的问题。因此,该图不仅应该是代码的蓝图,还应该是代码。
如果您已经绘制了图表,为什么还要自己编写代码?图中已经指定了所有必需的逻辑。用Java或C将图转换为等效的源代码,只是机器可以执行的机械任务。使用图作为单一事实来源并从中自动生成代码的方法称为模型驱动方法。但是,要利用此原理,将无法使用简单的绘图板。
相反,您应该使用诸如YAKINDU Statechart Tools之类的适当建模工具来绘制状态图(statechart)。用这种工具创建的图很容易掌握。它们改善了软件开发人员和领域专家之间的沟通。此外,与在纸上或绘图应用程序中的图不同,建模工具对状态机是什么有一个正式的了解。这使他们(和您)可以模拟和测试他们的行为-甚至无需编写任何代码。这些模型本身是独立于平台的,因此您可以从它们中以任何喜欢的语言生成源代码。工具通常支持C,C ++,Java和Python。
如果您仍然不确定模型驱动的软件开发的工作方式,请不要担心-我们现在将通过示例进行探索。我们将使用状态图和代码生成来开发具有一些输入和输出的非常简单的自动化光源。
我们的示例:自动和运动激活的灯光
自动照明的任务非常简单:只有在天黑时才应该有光,但实际上没有人在周围时就不应浪费能量。为此,大多数楼梯灯都由计时器控制。通过按一个按钮,灯被激活,并在一定时间后自动再次关闭。但是,作为状态图示例,这将很无聊,因此对于本文,我们通过合并由运动传感器驱动的其他模式对其进行了详细说明。
指示灯应具有三种可能的操作模式:
永久关闭
开启–通过时间控制的关闭
自动–带运动传感器
按钮允许用户在这些模式之间循环。两个LED显示当前选择的操作模式。
从这些规范中,您可以很容易地得出状态机的基本结构,如图2所示:

图2.自动和运动激活的灯光。

事件触发了Off,Timer和Motion_Automatic状态之间的变化–通过按一个按钮或在计时器到期后。如果用户按下按钮一次,则计时器模式被激活,指示灯点亮,并在30秒后自动关闭。如果用户在30秒用完之前再次按下按钮,则将激活运动传感器模式。每当运动传感器检测到有人(或某物)在移动时,如果需要,灯会再打开30秒钟。每次检测到运动都会重置计时器。当进入或退出各自的状态时,指示当前模式的两个LED会根据需要激活和停用。这样,整个控制器逻辑就完全封装在状态机(也称为自动机)中。
如果自动机打算在嵌入式系统上运行,我们现在可以直接从图中生成C或C ++代码。生成的代码包含模型中的所有逻辑。只有与实际硬件相接的代码才需要手动编写。在此示例中,这包括在按下实际按钮时引发按钮事件,控制实际楼梯灯以及控制状态LED。需要此手动编程,因为生成的代码独立于目标平台。计时器也是如此-在不同的目标平台上,时间的处理方式非常不同。
有许多可能的方法来实现状态机。最常用的方法是状态表,基于切换案例的构造或状态模式(通常在面向对象的编程语言中使用)。如果您想阅读有关此主题的更多详细信息,可以在本白皮书中找到广泛的比较。默认情况下,YAKINDU Statechart Tools使用switch-case语句生成状态机代码。这样可以确保良好的性能,同时仍保持源代码的良好可读性。

生成的代码如何工作
如上所述,状态机代码被实现为switch-case语句。执行的主要部分将在runCycle函数中处理:

无效的Lightswitch :: runCycle()

{

   

   clearOutEvents();

   对于(stateConfVectorPosition = 0;

      stateConfVectorPosition <maxOrthogonalStates;

      stateConfVectorPosition ++)

      {

         

      开关(stateConfVector [stateConfVectorPosition])

      {

      案例lightswitch_Off:

      {

         lightswitch_Off_react(true);

         打破;

      }

      案例lightswitch_Timer:

      {

         lightswitch_Timer_react(true);

         打破;

      }

      case lightswitch_Motion_Automatic_motion_Motion:

      {

         lightswitch_Motion_Automatic_motion_Motion_react(true);

         打破;

      }

      case lightswitch_Motion_Automatic_motion_No_Motion:

      {

         lightswitch_Motion_Automatic_motion_No_Motion_react(true);

         打破;

      }

      默认:

         打破;

      }

   }

   clearInEvents();

}

该runCycle每当一个事件引发函数将被调用。它遍历所有正交状态以执行在那里要做的任何事情。switch-case语句决定要调用哪个函数来执行相应的状态响应。例如,关闭状态有一个进入反应,将light变量设置为false,仅在进入该状态时才执行。它具有一个传出和一个传入的过渡。如果引发按钮事件,则将退出状态。此行为在lightswitch_Off_react函数中处理:

sc_boolean Lightswitch :: lightswitch_Off_react(const sc_boolean try_transition){

   / *状态为Off的反应。* /

   sc_boolean did_transition = try_transition;

   如果(try_transition)

   { 

      如果(iface.button_raised)

      { 

         exseq_lightswitch_Off();

         enseq_lightswitch_Timer_default();

         react();

      }其他

      {

         did_transition = false;

      }

   } 

   如果((did_transition)==(false))

   { 

      did_transition = react();

   } 

   返回did_transition;

}

因此,假设已经进入“关闭”状态。每次调用runCycle函数时,都必须检查是否已引发按钮事件。这是在lightswitch_Off_react函数中完成的。如果确实发生了按钮事件,则必须完成两件事:执行当前状态的退出序列和执行目标状态的输入序列:

如果(iface.button_raised)

   exseq_lightswitch_Off();

   enseq_lightswitch_Timer_default();      

react();

}

在Arduino Uno上的实现

图3. Arduino原理图。

Arduino Uno的实现示意图如图3所示。实际的楼梯灯由板载LED表示,以保持电路简单。两个模式显示LED分别连接到引脚9和10,运动传感器连接到引脚7。如果需要,可以更改这些引脚号。该按钮必须连接到引脚2或3,因为只有这些可以触发中断。LED与220Ω电阻串联,按钮连接到22kΩ下拉电阻。
该软件由两个核心组件组成:从状态图生成的C ++代码和用于将独立于平台的状态机逻辑与硬件连接的手写胶合代码。
代码生成器根据模型中定义的事件和变量创建状态机的接口:void raise_button(); void raise_motion(); sc_boolean get_light()const; sc_boolean get_led_timer()const; sc_boolean get_led_motion()const; 无效的init(); void enter();
为了连接状态机,必须定义特定状态机类型的对象,在这里:Lightswitch。该对象代表实际的状态机,可用于与后者进行编程交互。例如:

灯开关灯开关;

int main(){

   lightswitch.init();

   lightswitch.enter();

   lightswitch.raise_button();

}

通过这种简单的实现,将初始化,输入灯光开关状态机,并引发按钮事件。这当然不是要走的路。目标是将硬件(在本例中为具有连接的LED,传感器和按钮的Arduino)连接到状态机。为此,我们将以非常简单的输入过程输出模式使用状态机。这描述了一个简单的循环,如下所示:

检查硬件和传感器是否有变化

将此信息传输到状态机的输入中

让状态机处理这些输入

检查状态机的输出并对它们作出反应。

首先,使用当前时间刷新计时器。在Arduino上,我们使用millis函数获取自系统启动以来经过的毫秒数。如果需要,计时器将触发状态机中的时间事件。

很久以前= millis();

  if(现在-time_ms> 0){

    timerInterface-> proceed(现在-time_ms);

    time_ms = millis();

  }

基于其他输入,例如按钮按下或运动检测,我们可以引发状态机的“输入”事件。在这里,我们不必关心状态机当前所处的模式–生成的状态机代码封装了所有逻辑。我们只是引发事件,然后将其留给状态机来决定是否要对此作出反应。

 //从ISR处理按钮

  if(buttonPressed){

    lightswitch.raise_button();

    buttonPressed = false;

  }

  //读出运动传感器

  if(digitalRead(7)){

    lightswitch.raise_motion();

  }

处理完所有“输入”事件后,状态机已正确设置了布尔变量。我们可以使用它们来控制“楼梯灯”和指示灯LED。

//设置灯光

  digitalWrite(13,lightswitch.get_light());

  //设置模式LED

  digitalWrite(9,lightswitch.get_led_timer());

  digitalWrite(10,lightswitch.get_led_motion());

最后,如果Arduino处于关闭状态,我们将使其进入睡眠模式,以节省能量。如果用户按下该按钮,则将调用其中断服务例程,然后Arduino再次唤醒。请注意,在睡眠模式下不会更新由millis返回的值的计时器。因此,在睡眠模式下不会更新依赖于毫秒的软件计时器。在此示例中,当“ 关闭”状态处于活动状态时,没有计时器在运行,因此我们可以安全地进入睡眠状态。

 //如果处于关闭状态,请入睡(由ISR唤醒)

  if(lightswitch.isStateActive(Lightswitch :: lightswitch_Off)){

    enterSleep();

  }

Arduino的刷新是通过常规的Arduino IDE完成的。为此,我们将包含状态机的项目作为库导入,并仅在Arduino IDE中手动编写上面显示的特定于Arduino的代码。

 

结论

这个例子清楚地表明了在软件开发中使用模型(如状态图)的优势。主要优点是:

状态机足够正式,可以执行。

状态图是图形化的,易于理解。

设备的执行逻辑与相关的硬件相关代码已完美分离。

硬件和设备逻辑的分离提高了可移植性,并减少了更改或其他版本所需的工作。

它们可以彼此分开开发。

上一篇:用SoC进行嵌入式系统的验证

下一篇:嵌入式平台上进行自动化音频接口测试

热点文章推荐
华清学员就业榜单
高薪学员经验分享
热点新闻推荐
前台专线:010-82525158 企业培训洽谈专线:010-82525379 院校合作洽谈专线:010-82525379 Copyright © 2004-2022 北京华清远见科技集团有限公司 版权所有 ,京ICP备16055225号-5京公海网安备11010802025203号

回到顶部