状态机编程笔记
什么是状态机?
在程序有一定的复杂度,而且流程图很难描述的时候,需要使用状态机的思想编写程序,借助状态跳转图来描述软件功能,根据状态跳转图让处理器在各个事件与状态之间切换,看上去像在并行工作一样。
数字电路中的时序逻辑分为Moore型和Mealy型,我们在程序中使用的状态机更像是Mealy型时序电路,即下一状态与当前状态和信号输入量(事件)均有关。下面我们根据实际案例来理解:
图1:状态转移图示例
如图1所示,我们的程序共有两个状态:(1)正常显示;(2)频率设置。箭头上方的字表示:事件/动作。比如F1/显示输入频率:表示按下F1按键后,执行显示输入频率的动作。箭头的方向表示状态的转移,仍以按下F1键为例,此时状态从S1转到S2。
事件与动作
如上所述,事件是指外部的触发,动作则是指在这样的触发下,程序应该做怎样的反应。分清事件和动作之后,要做的就是把动作的代码放到什么地方执行呢?比如,根据图1,按下F1后,程序从S1到S2,这时需要在屏幕上提示用户,请输入频率的字样:
//满洲里国峰电子科技
//微信:GuoFengDianZi
switch(SigGen_Menu_State) //
{
case DISPLAY_NORMAL: //
{
...
break;
}
case DISPLAY_FREQ_SET:
{
...
break;
}
}
//下面这行代码应该放在哪个case语句中呢?
LCD_ReadytoWrite_NewFreq();//在屏幕上显示:“请输入频率”
//下面这行代码应该放在哪个case语句中呢?
LCD_ClearInputNumZone();//输入完毕将“请输入频率”字样从屏幕删除
//下面这行代码应该放在哪个case语句中呢?
LCD_Display_NewFreq(Sig_Freq_Now);//显示新的频率
代码1
这样的代码应该放在哪里呢?放在哪里都可以,只不过我们确定自己的方式后,以后代码升级,或者移植的时候会简单很多。
(1)常态的动作放在常态的状态中执行,比如:
LCD_Display_NewFreq(Sig_Freq_Now);//显示新的频率
屏幕上始终要显示频率,属于常态,故应该放在case1中。
而下面两个函数明显不是需要时时刻刻显示的常态:
LCD_ReadytoWrite_NewFreq();//在屏幕上显示:“请输入频率”
LCD_ClearInputNumZone();//输入完毕将“请输入频率”字样从屏幕删除
因此放在case2中。
(2)非常态的应该放在相应的状态中执行:
LCD_ReadytoWrite_NewFreq();//在屏幕上显示:“请输入频率”
(3)耗时且非常态的不要放在NORMAL状态中执行
LCD_ClearInputNumZone();//输入完毕将“请输入频率”字样从屏幕删除
清屏的动作本来可以放在常态中执行,但由于其太耗时,故放在非常态中执行,这样不用一直清屏了。修改后代码如下:
//满洲里国峰电子科技
//微信:GuoFengDianZi
//以下为状态机的代码框架
switch(SigGen_Menu_State) //
{
case DISPLAY_NORMAL: //
{
Sig_Freq_Now=Sig_Freq_Next;
LCD_Display_NewFreq(Sig_Freq_Now);
if(Key==FREQ_SET_KEY)
SigGen_Menu_State=DISPLAY_FREQ_SET;
...
break;
}
case DISPLAY_FREQ_SET:
{
LCD_ReadytoWrite_NewFreq();//在屏幕上显示:“请输入频率”
if(Key==ENTER_KEY)
{
Sig_Freq_Next=InputValue();
SigGen_Menu_State=DISPLAY_NORMAL;
LCD_ClearInputNumZone();//输入完毕将“请输入频率”字样从屏幕删除
}
...
break;
}
}
代码2
历史变量和当前变量
如上所述,很多数据是要在常态中显示,却在非常态中发生改变,为此,我们需要使用变量在两者之间沟通。很多时候可以使用一个变量就可以,但是为了更好的移植和升级,这里我采用了“历史变量和当前变量”,如代码2中所示,使用了两个变量Sig_Freq_Next和Sig_Freq_Now,其中Sig_Freq_Next只面对修改的需求,Sig_Freq_Now只面对现实的需求,双方互不影响。
在各状态中对数组的操作
在各状态中对数组的操作的时候要使用"写指针”,就是一个变动的角标,每次对数组新增或减少数据后,这个游标都要加1或减1,始终指向下一个空着的元素。
char Num_WrIndex=0;
if(Key==NUMBER)//如果按键按下的是数字键
{
Num[Num_WrIndex]=Key;//将键值存入数组中
Num_WrIndex++;//写指针加1
}
清空数组
清空数组时,将“写指针”归零即可,某些情况下,为了避免歧义,可以讲该数组中的所有元素初始化。
作者:潇洒的电磁波(专业:射频芯片设计、雷达系统、嵌入式。欢迎大家项目合作交流。)
微信:GuoFengDianZi