交流贴-面向状态的嵌入式开发框架

LQ77630   2010-3-14 15:27 楼主
首先声明,以下只是我的个人想法,具体可行性和实现,还需要经过很过拷问

开始了

在做单片机开发的时候,特别是中小型的系统中,常常是  输入-》处理-》输出

即是根据不同的输入来处理,然后生成不输出

那么是不是可以这样来理解
将不系统中所有的状态定义好,在为每一个状态设计一个特定处理程式,比如某个输入为1的时候 执行…………
这就是我的面向状态的开发框架基本思想


本人只会51单片机,就用51单片机+KEIL C来说明了~~

首先,定义我们关注的状态

一个状态的定义大概如下所示

状态一:

 P1==0XF0

 P2==0X00

 P3_2=0

状态二:

 P1==0XF0

 P2==0X00

 P3_2=1

这是我们关注的两个状态

然后定义两个状态对应的处理函数

STATE1()

{

  code……

}

STATE2()

{

  code……

}

然后在定义一个默认状态函数

STATE0()

{

CODE……;

}

将两个状态和状态对应的函数交给框架(系统)

然后启动框架(系统)

框架就能够根据当前的状态来执行对应的状态函数

我们就不用关系程序运行到那里了

只要把状态确定好

把状态对应的函数处理好

以上是昨天的想法

刚才终于把这个框架的原型写出来了

虽然简单点

但是功能按照我预期的实现了

当然也还待很多很多的进一步改进 比如状态优先级,状态的互斥,状态的继承等等~~~

如果这个小小的想法可行

希望高手们都能够站出来,为我们的开源开发框架做贡献~~

以下是keil 51 的【51单片机C语言】代码:  



#include    
#define uchar unsigned char   
#define uint unsigned int   
  
unsigned char state1()   
{   
if(P1_0==0)//修改 返回状态检查结果   
  return 1;   
else return 0;   
}   
unsigned char state2()   
{   
if(P1_0==1)   
  return 1;   
else return 0;   
}   
  
  
void f_state1()   
{   
P2=0xff;   
}   
void f_state2()   
{   
P2=0x00;   
}   
  
/*  
//暂时不能使用的默认状态函数  
void default_state()  
{  
P2=0x0f;  
}  
*/  
  
//***********以下为系统框架的定义   
//函数名StateCreate   
//函数功能 将一个状态和一个状态处理函数对应起来   
//函数参数: statecheck 指向状态检测函数的指针   
//function:指向状态处理函数的地址   
  
#define MAXSTATE 8   
struct State_function   
{   
uchar (*statecheck)();   
void (*function)();   
};   
  
struct State_function SFlist[MAXSTATE];//状态--函数对应表   
uchar nowstate;//当前处理的状态数量   
  
  
void StateCreate(uchar (*statecheck)(),void (*function)())   
{   
//执行绑定 将状态函数和执行函数加入到状态-函数数组中   
SFlist[nowstate].statecheck=statecheck;   
SFlist[nowstate].function=function;   
nowstate++;//增加 状态-函数 对应关系   
}   
  
void soched()   
{   
uchar i;   
//依次寻找满足状态的函数来执行   
for(i=0;i<=nowstate;i++)   
{   
  if(SFlist.statecheck())   
  {   
   SFlist.function();   
  }   
}   
//default_state();//执行默认状态 暂时不能使用   
}   
  
void OSInit()   
{   
   nowstate=0;   
}   
void OSstart()   
{   
  
while(1)   
{   
   soched();   
  P1_2=!P1_2;   
}   
}   
  
  
//用户自己编写的函数   
void main()   
{     
OSInit();   
//先创建的状态优先级高   
StateCreate(state1,f_state1);   
StateCreate(state2,f_state2);   
  OSstart();   
}  


上述代码经过调试 能够正常运行~~

回复评论 (12)

原文地址:http://www.untree.net/text.php?id=59
有意交流的朋友情登陆www.untree.net 留言~~
谢谢
点赞  2010-3-14 15:26
量子框架?
点赞  2010-3-14 18:32
理想一般丰满,现实过于骨感,楼主加油哦!
点赞  2010-3-15 06:08
自己UP一个~~~
点赞  2010-3-15 09:47
厉害,,学习一下。。顺便up
点赞  2010-3-15 12:40
状态机的设计方法很常用吧?

反正我在稍复杂的系统里就用状态机,大概在能细分出四个状态以上的系统,我肯定是用状态机,这比IF-ELSE处理要清析很多
点赞  2010-3-15 13:37
基于表驱动状态机方法的DS18B20驱动库设计




lbing7




(四川托普信息技术职业学院 实训中心)


[摘 要]表驱动状态机的方法可以让DS18B20在工作的时候解放MCU,让系统更有效地分配和利用MCU的运算资源。提高应用了DS18B20温度传感器的系统的平均响应时间,拓宽DS18B20温度传感器的应用范围,使DS18B20与系统的接口更加自由、方便。
[关键词]状态机、传感器、DS18B20、微控制器、库



A DS18B20's Driver Library Based on FSMTable




lbing7




(SichuanTOP Vocational Institute of Information Technology)


[ABSTRACT] Table-drivenFSM can free the MCU when DS18B20 is workingand allowing system toutilize MCU resources more effectively. It improves theaverage responsetime for systems using DS18B20, widens the application ofDS18B20, andmakes the interface between DS18B20 and system more freelyandconvenient.
[KEY WORDS]FSM; Sensor; DS18B20;MCU;Library

1 引言
    一个有限状态机(简称状态机)是一个特殊的有向图,它包括一些状态(节点)和连接这些状态的有向弧。每一个有限状态机都有一个启始状态和一个终止状态和若干中间状态。每一条弧上带有从一个状态进入下一个状态的条件[1]。状态机是非常有效地面向对象的建模工具,它把问题看成一个整体(对象),把问题某些特定的时期特性看成状态,把触发这些特性发生改变的因素看成事件。因此,它就能非常紧密地贴近问题,把各种交错复杂的问题清晰地分解开,提供更加漂亮的解决思路。在编译原理、通信协议及游戏设计等领域中状态机都得到非常广泛的应用。将其引入单片机软件开发,必定能为这个领域带来更加广阔的解决思路及优雅的编码。
      DS18B20温度传感器是常见的温度传感器,由于它采用独特的“一线总线”接口,在MCU与之通信的时候仅需要一个数字IO即可。这为MCU系统节省了有限的IO资源,但是由于它完成一次温度转换的时间较长,且当前普遍采用阻塞式的驱动方式(即:MCU被迫停下来等待它完成温度转换),这使得它的应用受到了极大的限制。一般仅用到系统平均响应时间要求较低的场合,比如说:初学者学习,简易电子温度计等。本文结合状态机思想提出一种新的驱动方法,并设计成库以改进DS18B20的驱动方法,不采用阻塞方式,而让DS18B20进行温度转换的时候MCU可以腾出“手”来去做别的事。
2 状态机的驱动方式
    状态机的驱动一般有三种方式:switch-case方式、表驱动方式和状态模式。前两种方式比较常见,状态模式由于完全抽象于面向对象的思想,需要面向对象设计语言的支持,在MCU软件开发领域目前几乎没有面向对象语言的应用,在此不做讨论。
2.1 switch-case方式
      switch-case方式抽象于面向过程设计的思想,贴近状态切换过程,本质上每进行一次switch-case解析都是状态的迁移。在对应的case状态中处理状态中的信息,并依据当时条件迁移到下一个状态。在状态较少以及状态间迁移的边数较少的时候,这种方式比较有效。如果状态过多或迁移的边数较多的情况,则可能需要一个庞大的switch-case代码块,这会给编码、开发带来很大的麻烦。
2.2 表驱动方式
2.2.1伪码
//定义状态机及初始化
FSM Wfsm = START;

//状态1
void STATE1(void)
{
    //状态处理
   
    //状态迁移
        Wfsm = NEXT;
}

//状态1(2)
void STATE2(void)
{
    //状态处理
   
    //状态迁移
        Wfsm = START;
}

//建立状态表
void (*STATETABLE[])(void)= {STATE1, STATE2};
while (1){
        //表驱动状态机
        (*STATETABLE[Wfsm])();
}

2.2.2 简述
     表驱动方式抽象于面向对象的设计的思想,它把每一个状态(包括它的迁移边)看成一个对象,用一个函数去表达这个“对象”。然后把这些“对象”放到一个函数表里,以状态机作为函数表的索引。通过修改索引去调用不同的函数,达到状态迁移的目的。
2.3 表驱动方式的优势
1.switch-case方式源于面向过程设计的思想,它关注状态的迁移,使程序员花费较多的精力于状态机的转换逻辑,而分散其于状态机功能实现这个本质要解决的问题上的精力。表驱动方式源于面向对象设计的思想,它把各个状态都看成了“对象”。把状态的迁移也封装到了“对象”里面。让程序员更加关注状态本身的功能实现。
2.switch-case方式在进行状态解析的时候会用到一组switch-case语句,若状态机中状态较多,迁移的边数较多的情况,可能需要一个庞大的switch-case代码块,这会给编码、开发带来很大的麻烦。表驱动方式仅仅需要更改状态机以决定去调用哪个状态的函数,在状态机处理的地方仅需要1行代码即可,代码非常简洁。
3.switch-case方式的代码会被编译器编译成一系列地比较、跳转指令,这些指令会造成比较大的CPU运算资源花销。表驱动状态机方式仅需要通过下标在函数表里索引找到对应的函数进行调用即可,几乎没有太多地系统资源浪费。因此,它更适合用在系统资源更弱的MCU上。
4.switch-case方式如果要进行维护(如:添加或删除状态)那必须在switch-case代码块中删除或添加相应的状态,以及到其它的状态处理的case代码里去删除或添加相应的状态迁移条件,这给修改代码带来了很大的麻烦,很容易让程序员淹没在茫茫的代码海洋里。表驱动状态机方式仅需要在状态机类型中添加一个新的状态,然后实现新的状态的处理函数,然后到相关的状态处理函数里修改迁移条件即可。由于各种迁移条件都被封装到状态处理函数里,在修改起来就很清晰,不会出现switch-case那样的问题。
3 表驱动状态机方法于MSP430单片机驱动DS18B20上的应用
3.1 DS18B20简介
    DS18B20由美国DALLAS半导体公司的数字化温度传感器,它是世界上第一片支持“一线总线”接口的温度传感器,主要特性:



独特的“一线总线”接口,仅需一个端口引脚进行通讯




3.0V到5.5V宽电压工作




测温范围-55℃至+125℃,在-10℃到+85℃间误差为:±0.5℃。




温度可选以9位至12位数字量分辨率




温度数字量最长转换时间750毫秒




应用于包括温度控制、工业系统、消费品、温度计或任何热感测系统


3.2 对DS18B20温度转换过程建模



下载 (19.8 KB)
3 小时前




图表 1:DS18B20状态图


针对DS18B20的特性,结合实际系统应用将其工作状态如上图所划分:
开始状态:这个状态用于DS18B20上电后的复位,重新配置寄存器,进入就绪状态。等用户给一个开始转换指令。
开始温度转换状态:原本这只是一个很快的过程,只是MCU给DS18B20发一个开始转换指令而已,按理是应该作为一个事件看待,但是,为了让DS18B20连续地工作,保证循环工作地逻辑连接的持续性及语义的正确性。也把它独立成一个状态了,这让这整个状态机更加切实丰满。
转换中状态:这个是此设计的闪光点,一般来说DS18B20在接到开始温度转换指令之后,它就开始温度转换。这个过程它是阻塞式的,也就是说在它完成转换之前,对它的任何操作都是没有意义的。在此状态中,利用定时器中断中计数,以达到等待其相应延时的目的。转换结束后自动进入读取温度值状态。相当于在中断服务中以状态机的方式开启了一个智能的“进程”,由它自动地去启动、等待和读取DS18B20。解放了MCU,更合理地分配MCU资源。
读取温度值状态:把传感器转换完成的温度值从传感器里面读回MCU中,再根据用户的指令,来确定是进入开始温度转换状态还是结束状态。
结束状态:DS18B20停止工作。
3.3 DS18B20驱动库3.3.1
    DS18B20驱动库结构DS18B20的单总线接口,需要在IO上加一个1千欧姆的上拉电阻。利用定时器中断提供的“死循环”,为驱动状态机提供服务。在状态机的各个状态中,运用到了单总线接口协议去操作DS18B20按要求完成其温度转换的功能。在用户接口上,采用类似“消息”的机制去传递完成转换后的温度值。由于没有实时操作系统的支持,因此,只能让用户去查询消息标志。另外,向用户开发了控制状态机启停的两个控制标志,以方便用户更加灵活地使用DS18B20。



下载 (27 KB)
3 小时前




图表 2DS18B20驱动库结构图


3.3.2 DS18B20驱动库的核心及接口
3.3.2.1 DS18B20状态实现
    DS18B20具体三个核心的状态,用一个枚举类型表示,如下:
/*
DS18B20操作时序状态机
START,DELAY和READDATA分别对应开始温度转换,转换中和读取温度值状态。
*/
typedef enum {
    START = 0,
    DELAY,
    READDATA,
}tmpsensorFSM;
//定义状态机及初始化状态机
tmpsensorFSMDS18B20FSM = START;
/*
DS18B20启动温度转换
*/
voidDS18B20START(void){
    PowerOnDs18b20();//复位DS18B20
    WriteDS18B20(0xcc);//跳过ROM指令
    WriteDS18B20(0x44);//开始转换
}
//等待计数器
unsigned intWaitDS18B20Times = 0;
//最长等待时间
#defineMICROMINITE750 7
/*
DS18B20转换中状态
*/
voidDS18B20Delay(void){
    if (WaitDS18B20Times < MICROMINITE750)
    {
        WaitDS18B20Times++;//步进计数
        DS18B20FSM--;//把状态机拉回来
    }
    else
    {
        WaitDS18B20Times = 0;
    }
}
/*
DS18B20读取温度
*/
voidDS18B20ReadData(void){
    unsigned char H = 0;
    unsigned char L = 0;

    PowerOnDs18b20();//复位DS18B20

    WriteDS18B20(0xCC); //跳过ROM指令

    WriteDS18B20(0xBE); //读数据

    L = ReadDS18B20();
    H = ReadDS18B20();

    TmpCTLState.TmpDecimalH = DecimalH[L &0x0f];//处理数据
    TmpCTLState.TmpDecimalL = DecimalL[L &0x0f];

    TmpCTLState.TmpInteger = ((H << 4) | (L>> 4));

    TmpCTLState.Begin = 0;
    TmpCTLState.DataReady = 1;
}
//建立状态表:
void (*P[])(void) ={DS18B20START, DS18B20Delay, DS18B20ReadData};

3.3.2.2驱动库接口
    按常理,应该对用户有所隐藏,把必要的控制变量用函数进行封装起来。但为了不占用更多的MCU本就有限的处理器资源,因此,不过多地进行封装,直接向用户开放控制接口所用到的控制结构。
/*
TmpCTLState传感器控制结构
*/
typedef struct {
    //最近一次转换完成的温度值
    unsigned char TmpInteger;
    unsigned char TmpDecimalH;
    unsigned char TmpDecimalL;

    unsigned char Stop;//stop:停止转换,值为1停止,反之为0。
    unsigned char Begin;// begin:开始转换
    unsigned char DataReady; //缓冲区中数据状态,数据准备好值为1,反之为0
}CTLstate;
CTLstateTmpCTLState = {0};

3.3.2.3库的使用示例
#pragma vector =TIMERA0_VECTOR__interrupt voidTIMER0ISR(void){
    //模糊定时器中断
    //如果传感器结构被启动
    if (TmpCTLState.Stop == 0)
    {

        //若上次数据没有处理
        if (TmpCTLState.DataReady == 1)
        {
             return;
        }
        //若没有开始
        if (TmpCTLState.Begin == 0)
        {
            DS18B20FSM = START;
            TmpCTLState.Begin = 1;
        }
        //运行状态机(表驱动方式)
        (*P[DS18B20FSM++])();
    }
}

int main()
{
    ...//其它代码


    //启动传感器控制状态机
    TmpCTLState.Stop= 0;

    if(TmpCTLState.DataReady == 1)
    {
        //操作温度
        DrawTemperatrue(TmpCTLState.TmpInteger);

        //更新温度控制状态机
        TmpCTLState.DataReady = 0;
    }

    //关闭温度控制状态机
    TmpCTLState.Stop= 1;
    ...//其它代码

    return 0;
}

4 结束语
   这套库引入了状态机设计思想,解决了传统方法在驱动DS18B20时必须花费高昂代价的问题,扩展了DS18B20的应用范围。但它也有不足的地方,比如,要占用一个时钟中断,由于要在中断中处理各种状态,会造成定时中断的不确定,因此,它不能用于一个精确定时的时钟。就于目前所有的驱动DS18B20的方式来说,这个库不论是从用户接口,资源占用还是可移植性都算上乘。本文也表达了这样的一种思想:在MCU资源越来越充足的今天,如果能很好地利用高层次软件开发的思想改进传统MCU底层的软件开发,将能获得更好的系统性能,更低的系统维护代价以及更优雅的代码和设计。

参考文献:
[1] 发表者:吴军,Google 研究员
数学之美 系列十
有限状态机和地址识别
http://www.googlechinablog.com/2006/07/blog-post.html
2006年7月5日上午 09:09:00
[2] http://datasheets.maxim-ic.com/en/ds/DS18B20.pdf
[作者简介]lbing7,四川托普信息技术职业学院实训中心实验室管理员。
点赞  2010-3-15 13:41
  好长,帮顶一个。
点赞  2010-3-15 19:49
感谢lbing7提供的资料
现在的关键问题有一下几点:
1、状态的定义方式,目前我是使用函数返回值来定义的,这样效率低下
2、状态的判断,目前只是简单的函数调用,说白了只是将IF语句换个地方而已
3、实现状态的的互斥,状态之间的依赖,这样可以大大提高框架效率,从而让出更多的CPU给用户程序
4、状态优先级,
等等好多问题~~
等待的高见~~~
点赞  2010-3-15 20:08
没有太深入的去理解LZ的意思,给点个人看法:
1.状态机,个人觉得最好是一个全局变量,而且专门定义一个新的类型,以防止这个变量乱跑。
2.一定要在程序里管好状态机,并且一定要写异常状态的处理。
3.可以建立专门的访问状态的接口,并封装好提供给用户。以防用户直接看到全局变量乱碰。(PS:不过我经常不做这个工作,我都是直接操作状态机变量,呵呵)
4.个人觉得:不要枉想去实现一个通用的状态机模型(最多就统一一下访问接口即可,就是第三条)因为,状态机可以说是一种建模体系。它不专属于某一个领域,几乎所有的过程都可以用状态机来表达,而且越复杂的过程越适用。再有就是:系统状态的划分有很大的主观性,同一个东西,不同的程序员,不同知识结构背景的人得到的状态划分结果是不一样的。去实现这样的一个能用模型代价太大
点赞  2010-3-17 08:07
好厉害,学习了
点赞  2010-3-17 08:48
建议楼主看看QP-nano
点赞  2010-3-17 08:59
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 京公网安备 11010802033920号
    写回复