|
戏法人人会变,各有巧妙不同--用有限状态机对键盘模块的另类实现
[复制链接]
最近阿姨家里的几个表哥都在热烈的讨论着状态机,表弟我心痒,也想来凑凑热闹。要想加入他们的讨论,首先得明白什么是状态机,这是今天的主题之一,不过我会首先给出定义,然后谈谈我的浅薄的理解。另外,前些日子正在学习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!
|
|