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

一、介绍

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

  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 
#include 

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	1

uint8_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中,行为状态机已经有关的状态、状态转移等的定义如下图所述。

嵌入式C编程中的设计模式之二——状态机模式_第1张图片
有关行状态机的描述如下文引用。

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;
}

这种写法对应于简单的状态机还是可以应对的。但是对于复杂一点的,如下面若干图所示。这个系统
嵌入式C编程中的设计模式之二——状态机模式_第2张图片
嵌入式C编程中的设计模式之二——状态机模式_第3张图片
嵌入式C编程中的设计模式之二——状态机模式_第4张图片
嵌入式C编程中的设计模式之二——状态机模式_第5张图片
中有很多嵌套,每级状态机也有4个状态,有些状态还需要对活动进行建模。大部分时候,系统会驻留在某个状态上。所以如果写成switch…case…的方法,难免会有很多无效的判断。这里笔者考虑使用函数指针的方式进行建模实现,以AppMonitor_Running运行状态为例进行伪代码实现。

/*appMonitor_running.h*/
#ifndef _APPMONITOR_RUNNING_H_
#define _APPMONITOR_RUNNING_H_

#include 

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语言,UML与设计模式,c语言,状态模式,开发语言)