最近阿姨家里的几个表哥都在热烈的讨论着状态机,表弟我心痒,也想来凑凑热闹。要想加入他们的讨论,首先得明白什么是状态机,这是今天的主题之一,不过我会首先给出定义,然后谈谈我的浅薄的理解。另外,前些日子正在学习GOF的《设计模式》,其中的state模式,就是面向对象的状态机,此处我会针对嵌入式的键盘模块,用c++语言写出一个例程,及在写此例程过程中的思考过程,这是主题之二。好了,先哆嗦一下,唉,没办法这是多年的习惯了!请不要不看,请耐心看完,请不要说难,let's go!
状态机是一个有向图形,由一组节点和一组相应的转移函数组成。状态机通过响应一系列事件而“运行”。每个事件都在属于“当前”节点的转移函数的控制范围内,其中函数的范围是节点的一个子集。函数返回“下一个”(也许是同一个)节点。这些节点中至少有一个必须是终态。当到达终态,状态机停止。 这一段抽象的数学定义,或许会晕倒一片人,但是我们能不能换一个说法来更好的理解状态机呢,学习方法中,我最推崇“类比", 因为天地一大宇宙,人身一小宇宙,天地间一切可类比。就像电流与水流,电学与力学一样。在此表弟我不才,也想对状态机类比一下。 其实在我看来,有限状态机就是一个人的悲喜剧,生命不息,状态不止。在父母面前,他是一个好的儿子,他所做的事就是孝敬父母,在妻子面前他是一个好丈夫,呵护着自己的老婆,支撑着这个家,在儿女面前,他是一个好爸爸淳淳教导着自己的子女,在老板面前,他是一个好职员,他所做的是为公司的明天而打拼. 如此环环不息,直至生命的终结,没有人是不死的,状态机也一样(在此不考虑无限状态机). 此类比中的"好儿子“,"好丈夫","好爸爸" 就是一个人的不同的自我,在扮演着不同的角色,演绎着不同的故事,可以称之为“状态”,而“孝敬父母”“呵护老婆”“教导子女”则是在不同状态下的行为。 以下是对以上类比的C伪码抽象.
switch(人.状态) { case "好儿子": 孝敬父母; break; case "好丈夫": 呵护老婆; break; case "好爸爸": 教导子女; break; default: } 以上可能并不准确,因为没考虑其间的状态改变,但是其状态改变是显而易见的,比如到公司上班,就是好员工状态,回到家中,在妻子的面前,你就处于“好丈夫”状态在接儿女放学的路上,你就处在一个“好爸爸”的状态。 接下来,就谈谈FSM(用英文并不是表弟我祟洋媚外,而是少打几个字)在嵌入式键盘模块的应用。并且用C++,及state模块实现之。键盘模块有哪些状态呢,表哥们应都清楚,无非就是,空闲(idle),按下(press),按住(hold),释放(release)状态。每个状态下有不同的行为,也就是要DoSomething,于是用c++抽象一个类如下: 为了简便,在此省略掉hold状态,假设系统仅是有idle,press,release三态 class CKeyState { public: char m_StFlag; //用于键盘模块中的状态转换,state模式中不是这样 CKeyState() { m_StFlag=0; } void virtual DoSth(void)=0; //不同状态下所做的事情,纯虚函数。 void virtual delay(void) //延时函数,虚函数,不同状态可以有不同的延时函数,故声明为虚函数 { for(int i=0;i<100;i++) for(int j=0;j<255;j++); } };
class CIdleSt : public CKeyState //空闲状态 { void virtual DoSth(void) { if(!KEY) //有键按下 { delay(); //去抖 if(!KEY)m_StFlag=1; //仍然有键按下,则去press态 } } };
class CPressSt : public CKeyState //按下状态 { void virtual DoSth(void) { LED=1; //具体的动作,此处为点亮一个LED if(KEY)m_StFlag=1; //按键释放则去Relese态 } };
class CReleaseSt : public CKeyState { void virtual DoSth(void) { delay(); //释放时去抖动 if(KEY) { m_StFlag=0; //释放则回到IDLE态 } else { m_StFlag=0; //是抖动,则仍在press态 } } };
以上就是对状态的简单抽象,以下是键盘模块的类的定义, class CKeyModule { public: CKeyState *st; //键盘模块当然包含一个状态 CKeyModule() { st=new CIdleSt(); //初始化为idle状态 }
void UpdateState(void); //状态的更新和转换 };
void CKeyModule:: UpdateState() { char tmp=st->m_StFlag; //根据m_StFlag的值来转换状态 switch(tmp) { case 0: st = new CIdleSt(); break; case 1: st=new CPressSt(); break; case 2: st=new CReleaseSt(); break; default: st=new CIdleSt(); } }
于是乎在我们的程序中则可应用如下: #include<ioAT89S52.h>
#define KEY P0_bit.P0_0 #define LED P0_bit.P0_1 。。。
CKeyModule pSysKey; //定义一个键盘模块对象
int main(void){ KEY=1; LED=0;
for(;;){ pSysKey.st->DoSth(); //根据不同的状态,做不同的事,此处正是虚函数的精要 pSysKey.UpdateState(); //更新状态 } }
相关代码在IAR中通过编译,也软件仿真过,本打算在cw08中编译,因为我有一个68hc908jl3的小板,但没想到的是,jl3竟提示RAM不够,看来cw08的编译质量或许比iar差一些。这都只是个人的一点浅见,无意中得罪哪位表哥,请不要放在心上。 本来就是信手而写,再加上水平太差,如有不当之外,请随意拍砖,就狠狠心,不要把我当表弟看! 好了,汇报完毕,enjoy it! |