OpenEdv-开源电子网

 找回密码
 立即注册
正点原子全套STM32/Linux/FPGA开发资料,上千讲STM32视频教程免费下载...
查看: 10914|回复: 5

基于Protothread的简化状态机

[复制链接]

6

主题

17

帖子

0

精华

初级会员

Rank: 2

积分
65
金钱
65
注册时间
2014-5-6
在线时间
1 小时
发表于 2014-8-12 10:30:43 | 显示全部楼层 |阅读模式
 

基于ProtoThread的简化状态机

      

       嵌入式系统中必然会存在程序阻塞,阻塞大概有以下两个原因:

l         由于延时产生的阻塞;

l         由于等待某个条件的满足产生的阻塞;

如果不使用一些方法,则程序会在某个断点处等待条件满足才能继续接下来的动作,因此就产生了状态机。下面用一个简单的例子解释C语言中状态机的过程。

首先给出一个状态转换图:


 

对于状态图的解释如下:  

系统启动时处于状态1,也就是St1状态,这时执行函数Func1,然后等待,直到满足条件Cond1,然后转到状态,St2,并执行函数Func2,然后等待条件Cond2满足,转换到状态St3,并执行函数Func3,同时将状态返回St1

程序实现如下:

首先定义枚举类型:st1st2st3

enum{

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

}

    和先前的程序相比,只多了两行,改变了两个条件变量名称。可见程序维护性大大增加。

正点原子逻辑分析仪DL16劲爆上市
回复

使用道具 举报

13

主题

185

帖子

0

精华

中级会员

Rank: 3Rank: 3

积分
272
金钱
272
注册时间
2014-4-9
在线时间
9 小时
发表于 2014-8-12 10:51:12 | 显示全部楼层
大海啊,全是水; 骏马啊,四条腿。
回复 支持 反对

使用道具 举报

38

主题

2061

帖子

6

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
3273
金钱
3273
注册时间
2012-1-16
在线时间
37 小时
发表于 2014-8-12 17:31:08 | 显示全部楼层
谢谢分享。。。
站在巨人的肩膀上不断的前进。。。
回复 支持 反对

使用道具 举报

0

主题

4

帖子

0

精华

新手入门

积分
24
金钱
24
注册时间
2014-12-9
在线时间
0 小时
发表于 2014-12-15 10:53:04 | 显示全部楼层
谢谢分享,,简洁~
回复 支持 反对

使用道具 举报

0

主题

3

帖子

0

精华

新手入门

积分
19
金钱
19
注册时间
2019-1-15
在线时间
4 小时
发表于 2020-6-10 19:35:12 | 显示全部楼层
使用K51,v9.6编译出错;protothread.c(9): error C195: '*' illegal indirection,出错位置是“PT_BEGIN(*pt1);”把“*”去掉又报:protothread.c(9): error C200: left side of '->' requires struct/union pointer
回复 支持 反对

使用道具 举报

0

主题

1

帖子

0

精华

新手入门

积分
5
金钱
5
注册时间
2020-12-3
在线时间
1 小时
发表于 2020-12-3 16:27:09 | 显示全部楼层
fllfqj 发表于 2020-6-10 19:35
使用K51,v9.6编译出错;protothread.c(9): error C195: '*' illegal indirection,出错位置是“PT_BEGIN(*p ...

代码都是错的,你要实例化一个指针,函数要传实参,不能是形参。还有它这种功能和上面的不符合,一旦pt->lc被赋新值下次就会直接跳转到那一行,如果条件还是不满足,直接就退出了,啥球都没执行!并不是像上面的那种方法如果条件不满足执行上一次的状态函数,代码不亲测也敢贴出来。你要想用就得自己再改一改。
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则



关闭

原子哥极力推荐上一条 /2 下一条

正点原子公众号

QQ|手机版|OpenEdv-开源电子网 ( 粤ICP备12000418号-1 )

GMT+8, 2025-6-23 07:13

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

快速回复 返回顶部 返回列表