状态机编程笔记

首页    嵌入式软硬件    状态机编程笔记

什么是状态机?

在程序有一定的复杂度,而且流程图很难描述的时候,需要使用状态机的思想编写程序,借助状态跳转图来描述软件功能,根据状态跳转图让处理器在各个事件与状态之间切换,看上去像在并行工作一样。

数字电路中的时序逻辑分为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