基于ProtoThread的简化状态机
嵌入式系统中必然会存在程序阻塞,阻塞大概有以下两个原因:
l 由于延时产生的阻塞;
l 由于等待某个条件的满足产生的阻塞;
如果不使用一些方法,则程序会在某个断点处等待条件满足才能继续接下来的动作,因此就产生了状态机。下面用一个简单的例子解释C语言中状态机的过程。
首先给出一个状态转换图:
对于状态图的解释如下:
系统启动时处于状态1,也就是St1状态,这时执行函数Func1,然后等待,直到满足条件Cond1,然后转到状态,St2,并执行函数Func2,然后等待条件Cond2满足,转换到状态St3,并执行函数Func3,同时将状态返回St1。
程序实现如下:
首先定义枚举类型:st1、st2、st3。
状态机函数体:
void FSM(void)
{
/* 这里的参数声明必须是静态变量 */
static INT8U State = st1;
switch(State)
{
/* 初始时就为状态1 */
case st1:
/* 执行函数1 */
Func1();
/* 等待Cond1满足 将状态位置为st2
如果条件不满足下次进入时仍然执行Func1,并判断条件Cond1
*/
if(Cond1)
State = st2;
break;
/* 已经到了状态st2 */
case st2:
/* 执行过程和上面相同 */
Func2();
if(Cond2)
State = st3;
break;
/* 已经到了状态st3 */
case st3:
/* 执行函数Func3 */
Func3();
/* 将状态返回st1 */
State = st1;
break;
/* Error */
default:
/* STATE ERROR */
break;
}
}
看过这个程序后应该对状态机的运用很了解。
那么接下来我就将引入一个Protothread的思想。
看了上面的程序,大家可能会注意到。
第一:程序的可读性并不高,只有把状态图画出后,才对整个状态的转换有很明确的认识。
第二:状态的维护性很差,如果后期要在某两个状态间新加一个状态,则需要修改很多东西,直接破坏了整个状态机的流程。
那么ProtoThread是怎样简化状态机的呢?
先看一下Protothread的介绍:
protothread是专为资源有限的系统设计的一种耗费资源特别少并且不使用堆栈的线程模型,相比于嵌入式操作系统,其有如下优点:
1. 以纯C语言实现,无硬件依靠性; 因此不存在移植的困难。
2. 极少的资源需求,每个Protothread仅需要2个额外的字节;
3. 支持阻塞操纵且没有栈的切换。
protothread的阻塞机制: 在每个条件判断前,先将当前地址保存到某个变量中,再判断条件是否成立,若条件成立,则往下运行;若条件不成立,则返回。
接下来就使用Protothread来实现先前的状态机:
首先是头文件:
/**************************************************
File Name: protothread.h
Editor : Tsui XJ
Date : 2014-8-11
Company : ShanghaiQinlu
**************************************************/
#ifndef __protothread_H__
#define __protothread_H__
/* 保持程序的独立性 */
typedef unsigned int PtINT16U;
struct pt
{
PtINT16U lc;
};
#define PT_THREAD_WAITING 0
#define PT_THREAD_EXITED 1
/* 初始化任务变量,只在初始化函数中执行一次 */
#define PT_INIT(pt) (pt)->lc = 0;
/* 启动任务处理,放在函数开始处 */
#define PT_BEGIN(pt) switch((pt)->lc) { case 0:
/* 等待某个条件成立,若条件不成立则直接退出本函数,下次进入本函数就直接跳
到这个地方判断
*__LINE__编译器内置宏,比如:若当前行号为8,则s = __LINE__; case __LINE__:
展开后为s = 8; case 8:
*/
#define PT_WAIT_UNTIL(pt,condition) (pt)->lc = __LINE__; case __LINE__: if(!(condition)) return
/* 结束任务后放在最后 */
#define PT_END(pt) }
#endif
状态机函数:
/* 初始化指针结构体 */
static struct pt pt1;
void FMS(void)
{
/* 开始状态机 */
PT_BEGIN(*pt1);
while(1)
{
/* 当前在状态1 执行Func1 */
Func1();
/* 如果Cond1不满足,则直接返回到系统(退出当前while(1))
如果Cond1满足,则执行下面的函数
*/
PT_WAIT_UNTIL(*pt, Cond1);
/* 当前在状态2 执行Func2 */
Func2();
/* 如果Cond2不满足,则直接返回到系统(退出当前while(1))
如果Cond2满足,则执行下面的函数
*/
PT_WAIT_UNTIL(*pt, Cond2);
/* 当前在状态3 执行Func3
执行完成后 由于处在while(1) 则又返回状态1
*/
Func3();
}
/* 结束Protothread */
PT_END(*pt);
}
由此,程序框架被大大简化,程序结构思路更加明确。
当我们需要在某两个状态间添加一个新的状态时,就可以用很简单的方式实现了。
比如,我们要添加一个状态Add,要执行的函数是Add();当处于状态2,且CondX条件满足时执行,当状态Add满足CondY满足时调到状态3。
/* 初始化指针结构体 */
static struct pt pt1;
void FMS(void)
{
/* 开始状态机 */
PT_BEGIN(*pt1);
while(1)
{
/* 当前在状态1 执行Func1 */
Func1();
/* 如果Cond1不满足,则直接返回到系统(退出当前while(1))
如果Cond1满足,则执行下面的函数
*/
PT_WAIT_UNTIL(*pt, Cond1);
/* 当前在状态2 执行Func2 */
Func2();
/* 如果Cond2不满足,则直接返回到系统(退出当前while(1))
如果Cond2满足,则执行下面的函数
*/
PT_WAIT_UNTIL(*pt, CondX);
Add();
PT_WAIT_UNTIL(*pt, CondY);
/* 当前在状态3 执行Func3
执行完成后 由于处在while(1) 则又返回状态1
*/
Func3();
}
/* 结束Protothread */
PT_END(*pt);
}
和先前的程序相比,只多了两行,改变了两个条件变量名称。可见程序维护性大大增加。
|