一、介绍

很多人说,嵌入式编程就是写状态机。这句话说得虽然有点绝对,但是也反映了状态机设计在嵌入式编程中的重要地位。举例来说,

  1. 在嵌入式系统中,如果是单线程编程,主函数的行为通过状态机进行调整;如果是RTOS,主任务的行为也可以通过状态机调整。
  2. 在GUI中,主界面的设计和行为、某些控件的显示风格和行为,以及整个界面系统的使用逻辑都通过状态机实现。
  3. 前面所述的单件模式和策略模式,有的时候也离不开状态机来调整单件的行为和策略的选择。
  4. 很多细节算法,如按键去抖,都可以通过状态机达到很好的效果。

所以,可以说在嵌入式系统中状态机的设计比比皆是。本文将结合设计模式、UML和嵌入式C语言探讨状态机。阐述结合设计模式和UML建模的状态机设计可以实现高效、规范且复杂的状态机设计。

二、浅谈状态机模式

在设计模式中,有关于状态机模式的定义(虽然原文用的是状态模式,而非状态机模式。但是道理是一样的)。

The State Pattern allows an object to alter its behavior when its internal state changes. The object will appear to change its class. —— 《Head First: Design Patterns》
状态模式允许对象在内部状态改变时改变它的行为,对象看起来好像改变了它的类。——《Head First 设计模式》

对比策略模式,似乎有些令人感到混淆。因为根据设计模式的说法,策略模式是围绕可以互换的算法来创建成功业务,而状态机是通过改变对象内部的状态来帮助对象控制自己的行为。这一番话听起来说了很清楚,可是针对下面这段伪代码,其实很难真正说是哪种模式。

/****************这是个简单而纠结的例子****************
文件名:Test.c
定义一个函数列表,根据外部消息动态调整函数指针指向来改变行为
*****************************************************/#include <stdint.h>
#include <stdbool.h>extern void user_init(void);          /*外部定义的初始化函数*//*外部定义的检测到用户按下start按键的函数,按下则输出true*/
extern bool user_pressed_start(void);   /*外部定义的检测到用户按下stop按键的函数,按下则输出true*/
extern bool user_pressed_stop(void);    #define MONITOR_STOP    0
#define MONITOR_RUNNING 1uint8_t void _monitor_stop(uint8_t);
uint8_t void _monitor_running(uint8_t);
uint8_t void (*monitor_beh)(void) = _monitor_stop;void main(void){uint8_t beh = MONITOR_STOP;/****initialisation***/user_init();while(1){beh = monitor_beh(beh);}
}uint8_t void _monitor_stop(uint8_t beh){if(user_pressed_start() == true){ /*The user pressed the start button*/monitor_beh = _monitor_running;return MONITOR_RUNNING ;}else{return MONITOR_STOP;}
}
uint8_t void _monitor_running(uint8_t beh){if(user_pressed_stop() == true){/*The user pressed the stop button*/monitor_beh = _monitor_stop;return MONITOR_STOP;}else{return MONITOR_RUNNING;}
};

我们可以说,定义了两个状态,MONITOR_STOP和MOINTOR_RUNNING两个状态,分别对应了_monitor_stop(uint8_t)和_monitor_running(uint8_t)两个活动,这是个典型的状态机模式;也可以说,定义了一个接口,叫monitor_beh,对应了两个实现,分别是_monitor_stop(uint8_t)和_monitor_running(uint8_t)两个实现这是典型的策略模式。

也许会有一些所谓的实用主义者会说,你纠结这些个语法和词法的问题没有意义,这些都是形式的东西,是过时的,要看具体的应用,要把任务做好。其实说这种话的人,大都是没有怎么学习过,甚至是思考过我们为什么会有这些语法和词法。没有认真学习过现代汉语和古代汉语语法词法的人怎么会写出好文章?同样,没有思考和学习过建模语言和编程语言的人,自然不会了解设计模式和嵌入式软件建模的力量和价值。

不过这里可以看出。在一些很简单的应用中,策略和状态机看起来就是一回事。但是这两个设计模式其实对应了不同的建模思路和应用场景。本文将探讨状态机模式的应用。关于策略模式的探讨我们放在其他的文章里。

三、状态机的UML建模

如果说策略模式中,我们在建模上谈论的主要是interface和realisation,那么状态机模式中,我们谈论的就是state, state activity和 state transition。由于状态机建模以及状态机思维方式的广泛使用,在UML中有对状态机的系统性阐述。

我们在建模中常用的这种状态机在UML中的定义是行为状态机(behavior StateMachines)。在UML 2.5中,行为状态机已经有关的状态、状态转移等的定义如下图所述。


有关行状态机的描述如下文引用。

A behavior StateMachine comprises one or more Regions, each Region containing a graph (possibly hierarchical) comprising a set of Vertices interconnected by arcs representing Transitions. State machine execution is triggered by appropriate Event occurrences. A particular execution of a StateMachine is represented by a set of valid path traversals through one or more Region graphs, triggered by the dispatching of an Event occurrence that match active Triggers in these graphs. The rules for matching Triggers are described below. In the course of such a traversal, a StateMachine instance may execute a potentially complex sequence of Behaviors associated with the particular elements of the graphs that are being traversed (transition effects, state entry and state exit Behaviors, etc.) —— 《OMG® Unified Modeling Language® (OMG UML®)》

简单地说,

  1. 状态机里面包含有至少一个域(region)。每个域包含了若干顶点(vertex)。这些顶点可以是状态,可以是伪状态。
  2. 每个状态有三种活动,分别是entry、exit和doActivity。这三种活动可以被建模也可以被忽略。
  3. 每个状态有若干触发条件(trigger),并引起状态转移。
  4. 伪状态(Pseudostate)不涉及到活动。
  5. 状态可以关联最多1个子状态机。

当然,如果从这个图上还可以挖出更多的词法解说,但是那些主要都是给UML建模软件开发商看的,比如Enterprise Archtect的开发商;但对于我们来说,挖出这些已经可以做很多事情了。我们在建模的时候如果先参考一下UML关于状态机的定义,在自己建模的时候就可以把很多东西考虑到,就可以把模型做的完备一些。

至于具体的状态机相关的UML语法,读者可以去参考标准,或者《UML in a Nutshell》等书。

四、状态机的代码实现

关于状态机的代码实现,一种常见的实现方法是如下所示。

int main(void){int state = 0;extern int get_state(void);    extern void state_0_handler(void);extern void state_1_handler(void);extern void state_2_handler(void);while(1){state = get_state();switch(state){case STATE_0: state_0_handler();break;case STATE_1: state_1_handler();break;case STATE_2: state_2_handler();break;default:    break;}}return 0;
}

这种写法对应于简单的状态机还是可以应对的。但是对于复杂一点的,如下面若干图所示。这个系统




中有很多嵌套,每级状态机也有4个状态,有些状态还需要对活动进行建模。大部分时候,系统会驻留在某个状态上。所以如果写成switch…case…的方法,难免会有很多无效的判断。这里笔者考虑使用函数指针的方式进行建模实现,以AppMonitor_Running运行状态为例进行伪代码实现。

/*appMonitor_running.h*/
#ifndef _APPMONITOR_RUNNING_H_
#define _APPMONITOR_RUNNING_H_#include <stdint.h>typedef struct _AppMonitor_Running_States_TypeDef{void (*entry)(void);void (*normal)(void);void (*decrease)(void);void (*increase)(void);void (*exit2waiting)(void);
}AppMonitor_Running_States_TypeDef;extern void (*appMonitor_running)(void);
extern const AppMonitor_Running_States_TypeDef appMonitor_Running_States;
#endif
/*appMonitor_running.c*/
#include "appMonitor_running.h"extern void (*appMonitor_running)(void);
extern const AppMonitor_Running_States_TypeDef appMonitor_Running_States;
extern void (*appMonitor)(void);extern void appMonitor_Running_Normal(void);
extern void appMonitor_Running_Decrease(void);
extern void appMonitor_Running_Increase(void);static void _act_entry(void);
static void _exit2waiting(void);const AppMonitor_Running_States_TypeDef appMonitor_Running_States = {.entry        =  _act_entry                      ,   //  对entry伪状态或者说entry活动建模.normal        =  appMonitor_Running_Normal       ,   //  对normal状态建模(do活动).decrease    =  appMonitor_Running_Decrease     ,   //  对decrease状态建模(do活动).increase  =  appMonitor_Running_Increase     ,   //  对increase状态建模(do活动).exit2waiting  =  _exit2waiting               ,   //  对退出running到waiting建模(exit活动)
};
void (*appMonitor_running)(void) = _act_entry;
/* 这里不能直接指向结构提内部成员*/void _act_entry(void){/*Initialisation*/appMonitor_running = appMonitor_Running_States.normal;
}static void _exit2waiting(void){appMonitor_running = appMonitor_Running_States.entry;appMonitor   =  appMoniot_States.waiting; //引用上级函数指针,running状态转移为waiting状态
}
/*appMonitor_running_normal.c*/
#include "appMonitor_running.h"
static void _act_entry(void);   //entry 活动
static void _act_exit(void);    //exit 活动
static void _act_do(void);      //do    活动
static void _act_trans2inc(void);   //跳转到inc状态
static void _act_trans2dec(void);   //跳转到dec状态
static void _act_trans2exit2waiting(void); //跳转到exit2waiting状态
static void *_pAct(void) = _act_entry; //指向各个活动的指针void appMonitor_Running_Normal(void){_pAct();
}
void _act_entry(void){/*做一些前置的有必要的工作*/_pAct = _act_do;
}
void _act_exit(void){/*做一些前置的有必要的工作*/_pAct = _act_entry;
}
void _act_do(void){/*正常工作时候的事情*/if(发生了需要向inc状态跳转的事件)_pAct = _act_trans2inc;if(发生了需要向dec状态跳转的事件)_pAct = _act_trans2dec;if(发生了需要向exit2waiting状态跳转的事件)_pAct = exit2waiting;
}   void _act_trans2inc(void){;   //跳转到inc状态appMonitor_running = appMonitor_Running_States.increase;_act_exit();
}void _act_trans2dec(void){;   //跳转到dec状态  appMonitor_running = appMonitor_Running_States.decrease;_act_exit();
}void _act_trans2exit2waiting(void){appMonitor_running  =  appMonitor_Running_States.exit2waiting;_act_exit();
}
/*appMonitor_running_increase.c*/
#include "appMonitor_running.h"
static void _act_entry(void);   //entry 活动
static void _act_exit(void);    //exit 活动
static void _act_do(void);      //do    活动static void *_pAct(void) = _act_entry;   //指向各个活动的指针void appMonitor_running_increase(void){_pAct();
}
void _act_entry(void){/*做一些前置的有必要的工作*/_pAct = _act_do;
}
void _act_exit(void){/*做一些前置的有必要的工作*/_pAct = _act_entry;/*因为只会往normal状态跳转*/appMonitor_running = appMonitor_Running_States.normal;
}
void _act_do(void){/*正常工作时候的事情*/if(发生了需要向normal状态跳转的事件)_pAct = _act_exit;
}   
/*appMonitor_running_decrease.c*/
#include "appMonitor_running.h"
static void _act_entry(void);   //entry 活动
static void _act_exit(void);    //exit 活动
static void _act_do(void);      //do    活动static void *_pAct(void) = _act_entry;   //指向各个活动的指针void appMonitor_running_decrease(void){_pAct();
}
void _act_entry(void){/*做一些前置的有必要的工作*/_pAct = _act_do;
}
void _act_exit(void){/*做一些前置的有必要的工作*/_pAct = _act_entry;/*因为只会往normal状态跳转*/appMonitor_running = appMonitor_Running_States.normal;
}
void _act_do(void){/*正常工作时候的事情*/if(发生了需要向normal状态跳转的事件)_pAct = _act_exit;
}   

以上便是利用函数指针和结构体实现了一个活动和状态转移的稍微复杂的状态机。关于这几段代码有一下几点有必要说明一下。

  1. 创建AppMonitor_Running_States_TypeDef结构体的目的仅仅就是为了被引用起来比较方便。也可以做这个结构体,单纯用函数指针。
  2. running状态底下还有3个状态和2个伪状态。根据需要对他们分别建模。
  3. 每个状态都有活动(activity)和状态转移(transition),根据需要对他们进行建模。
  4. 有些状态涉及到两级状态机的行为。那么在这种状态的活动中,是可以直接影响两级状态机的行为的。这个回顾行为状态机的描述图,可以看到状态可以被域包含,也可以关联一个子状态机。
  5. 状态转移也会伴随这退出状态。所以虽然在UML图中,exit活动是被单独建模描述的。但是在代码上exit活动和其他的状态转移被建模成函数时,exit活动的函数是被状态转移的函数调用。

五、结论

经过上面的阐述,可以看出参考UML中对状态机的定义,深入的了解要实现的需求并对其建模,再加以实现会得到很好的效果。本文仅讨论建模的方法,不探讨具体的编程细节,所以代码主要是用来辅助说明问题。

嵌入式C编程中的设计模式之二——状态机模式相关推荐

  1. 函数式编程中的战斗机(二) --运用elm语言MUV设计模式做一个简单的应用实例

    @函数式编程中的战斗机(二) -运用elm语言MUV设计模式做一个简单的应用实例 1 elm语言设计模式的特点 1.1 面向对象设计模式的特点 每种编程语言都有其独特的语法和优缺点,从而导致与众不同的 ...

  2. 如何在类图中标注设计模式(二)

    接文章<如何在类图中标注设计模式(一)>. 本文姗姗来迟,见谅!        5.  基于标记的模式标注 美国德克萨斯大学达拉斯分校的Jing Dong等人提出了一种基于标记的模式标注方 ...

  3. 嵌入式C编程中错误异常该如何统一处理?

    关注.星标公众号,直达精彩内容 来源:网络素材 本文主要总结嵌入式系统C语言编程中,主要的错误处理方式. 一.错误概念 1.1 错误分类 从严重性而言,程序错误可分为致命性和非致命性两类.对于致命性错 ...

  4. 乱砍设计模式之二 -- STATE模式

    转自 : http://blog.csdn.net/wishfly/archive/2008/01/22/2060026.aspx STATE模式的中文名称是状态模式.在<设计模式>一书中 ...

  5. 虚无空间java下载_Java进阶篇设计模式之二 ----- 工厂模式

    前言 在上一篇中我们学习了单例模式,介绍了单例模式创建的几种方法以及最优的方法.本篇则介绍设计模式中的工厂模式,主要分为简单工厂模式.工厂方法和抽象工厂模式. 简单工厂模式 简单工厂模式是属于创建型模 ...

  6. 设计模式之二——工厂模式

    1.工厂模式简介 1.1定义 简单工厂模式(Simple Factory Pattern):专门定义一个类(工厂类)来负责创建其他类的实例.可以根据创建方法的参数来返回不同类的实例,被创建的实例通常都 ...

  7. 支付系统中的设计模式03:模板方法模式

    在上一节末尾,留了一个需求问题,就是老板提出的「支付前锁定账户,支付后增加积分」这个需求「3」没有解决.有些文章写得比较好的人其实会有一些固定的结构格式,比如总分总.总分.分总.并列.对照.递进等等. ...

  8. elm具体实现过程_函数式编程中的战斗机(二)---elm语言MUV设计模式应用实例...

    1 elm语言设计模式的特点 1.1 面向对象设计模式的特点 每种编程语言都有其独特的语法和优缺点,从而导致与众不同的设计模式和固定架构.面向对象编程因其竭力接近和模拟现实世界的多态和继承,导致面向对 ...

  9. 向量几何在游戏编程中的使用系列二之2-D物体间的碰撞响应

    2019独角兽企业重金招聘Python工程师标准>>> 2-D物体间的碰撞响应 这次我要分析两个球体之间的碰撞响应,这样我们就可以结合以前的知识来编写一款最基本的2-D台球游戏了,虽 ...

  10. 一天一种设计模式之二-----备忘录模式

    2019独角兽企业重金招聘Python工程师标准>>> 一.备忘录模式 备忘录模式属于三种设计模式中的行为型模式(另外两种是创建型模式和结构型模式). 定义:在不破坏封闭性的前提下, ...

最新文章

  1. 零基础学习java,这些书一定要看!
  2. mysql font zhushi_关于在mysql front中使用注释符报错的问题
  3. 服务没有报告任何错误。 请键入 NET HELPMSG 3534 以获得更多的帮助。
  4. socket python 收 发 队列 线程_对于Python中socket.listen()与多线程结合的困惑?
  5. 笔记本控制机器人方法
  6. jpa之PagingAndSortingRepository带分页查询
  7. 【Ids4实战】分模块保护资源API
  8. 第一个Sprint冲刺成果
  9. ssm指的是什么_什么是RESTful?RESTfule风格又是啥?
  10. 一招教你使用注解处理幂等问题 8种方案解决重复提交
  11. Scala 专题指南
  12. 软件开发需求文档案例_第2部分:开发软件需求,一个案例研究
  13. Javaweb 九大内置对象
  14. win10重置计算机网络设置,为你解答win10下如何重置网络
  15. Python爬取上交所年报下载并转成TXT
  16. MyGUI_Orge官网教程_5.窗口部件控制
  17. 湖南大学计算机学院陈浩,杨圣洪-湖大信息科学与工程学院
  18. 慕课Java第三季学习及笔记整理
  19. [LnOI2019]东京夏日相会
  20. 老男孩python全栈9期全套视频_[全套视频] 老男孩Python全栈7期:Flask全套组件及原理剖析视频教程,全套视频教程学习资料通过百度云网盘下载...

热门文章

  1. CAD必备插件分享 快速提高工作效率
  2. python 微服务 网关_关于API微服务网关
  3. 京东18年管培生—技术方向笔试有感
  4. windows系统TLQ8安装时提示载入java vm时windows出现错误
  5. opencv codebook
  6. 【科研学习】Demo3D2015安装包以及图文安装步骤
  7. 微信修改运动步数卡密源码 每日自助修改
  8. 模糊综合评价法的R语言实现
  9. acrobat支持日文粘贴复制
  10. 增值税下调对财务软件的影响