状态机学习
2016-06-14 21:45
302 查看
出处:http://baike.baidu.com/link?url=q7t9k9sdXvomlsPjtVMdy9ngWhaIr6p_4uBBgyVp4XHf_XYUPNkXJQSof8xwOlSbB3lFNN4KWQfKv0Nqtd2_G_
锁定
本词条由“科普中国”百科科学词条编写与应用工作项目 审核
。
状态机由状态寄存器和组合逻辑电路构成,能够根据控制信号按照预先设定的状态进行状态转移,是协调相关信号动作、完成特定操作的控制中心。状态机简写为FSM(Finite State Machine),主要分为2大类:
第一类,若输出只和状态有关而与输入无关,则称为Moore状态机
第二类,输出不仅和状态有关而且和输入有关系,则称为Mealy状态机
中文名
状态机
外文名
Finite State Machine)
缩 写
FSM
又 名
状态转移图
1 基本信息
2 状态机综述
3 两种写法
▪ 有限状态机FSM
▪ 竖着写C代码片段
4 其它
▪ Moore状态机
▪ 范例
▪ Mealy状态机
状态机由状态寄存器和组合逻辑电路构成,能够根据控制信号按照预先设定的状态进行状态转移,是协调相关信号动作,完成特定操作的控制中心。状态机分为摩尔(Moore)型状态机和米莉(Mealy)型状态机。[1]
就是状态转移图。举个最简单的例子。人有三个状态健康,感冒,康复中。触发的条件有淋雨(t1),吃药(t2),打针(t3),休息(t4)。所以状态机就是健康-(t4)->健康;健康-(t1)->感冒;感冒-(t3)->健康;感冒-(t2)->康复中;康复中-(t4)->健康,等等。就是这样状态在不同的条件下跳转到自己或不同状态的图。
关于状态机的一个极度确切的描述是它是一个有向图形,由一组节点和一组相应的转移函数组成。状态机通过响应一系列事件而“运行”。每个事件都在属于“当前” 节点的转移函数的控制范围内,其中函数的范围是节点的一个子集。函数返回“下一个”(也许是同一个)节点。这些节点中至少有一个必须是终态。当到达终态, 状态机停止。
包含一组状态集(states)、一个起始状态(start state)、一组输入符号集(alphabet)、一个映射输入符号和当前状态到下一状态的转换函数(transition
function)的计算模型。当输入符号串,模型随即进入起始状态。它要改变到新的状态,依赖于转换函数。在有限状态机中,会有有许多变量,例如,状态 机有很多与动作(actions)转换(Mealy机)或状态(摩尔机)关联的动作,多重起始状态,基于没有输入符号的转换,或者指定符号和状态(非定有
限状态机)的多个转换,指派给接收状态(识别者)的一个或多个状态,等等。
传统应用程序的控制流程基本是顺序的:遵循事先设定的逻辑,从头到尾地执行。很少有事件能改变标准执行流程;而且这些事件主要涉及异常情况。“命令行实用程序”是这种传统应用程序的典型例子。
另一类应用程序由外部发生的事件来驱动——换言之,事件在应用程序之外生成,无法由应用程序或程序员来控制。具体需要执行的代码取决于接收到的事件,或者它 相对于其他事件的抵达时间。所以,控制流程既不能是顺序的,也不能是事先设定好的,因为它要依赖于外部事件。事件驱动的GUI应用程序是这种应用程序的典
型例子,它们由命令和选择(也就是用户造成的事件)来驱动。
Web应用程序由提交的表单和用户请求的网页来驱动,它们也可划归到上述类别。但是,GUI应用程序对于接收到的事件仍有一定程度的控制,因为这些事件要依赖于向用户显示的窗口和控件,而窗口和控件是由程序员控制的。Web应用
程序则不然,因为一旦用户采取不在预料之中的操作(比如使用浏览器的历史记录、手工输入链接以及模拟一次表单提交等等),就很容易打乱设计好的应用程序逻辑。
显然,必须采取不同的技术来处理这些情况。它能处理任何顺序的事件,并能提供有意义的响应——即使这些事件发生的顺序和预计的不同。有限状态机正是为了满足这方面的要求而设计的。
有限状态机是一种概念性机器,它能采取某种操作来响应一个外部事件。具体采取的操作不仅能取决于接收到的事件,还能取决于各个事件的相对发生顺序。之所以能 做到这一点,是因为机器能跟踪一个内部状态,它会在收到事件后进行更新。为一个事件而响应的行动不仅取决于事件本身,还取决于机器的内部状态。另外,采取 的行动还会决定并更新机器的状态。这样一来,任何逻辑都可建模成一系列事件/状态组合。
[2] 状态机可归纳为4个要素,即现态、条件、动作、次态。这样的归纳,主要是出于对状态机的内在因果关系的考虑。“现态”和“条件”是因,“动作”和“次态”是果。详解如下:
①现态:是指当前所处的状态。
②条件:又称为“事件”,当一个条件被满足,将会触发一个动作,或者执行一次状态的迁移。
③动作:条件满足后执行的动作。动作执行完毕后,可以迁移到新的状态,也可以仍旧保持原状态。动作不是必需的,当条件满足后,也可以不执行任何动作,直接迁移到新状态。
④次态:条件满足后要迁往的新状态。“次态”是相对于“现态”而言的,“次态”一旦被激活,就转变成新的“现态”了。
思想广泛应用于硬件控制电路设计,也是软件上常用的一种处理方法(软 件上称为FMM--有限消息机)。它把复杂的控制逻辑分解成有限个稳定状态,在每个状态上判断事件,变连续处理为离散数字处理,符合计算机的工作特点。同时,因为有限状态机具有有限个状态,所以可以在实际的工程上实现。但这并不意味着其只能进行有限次的处理,相反,有限状态机是闭环系统,有限无穷,可以用有限的状态,处理无穷的事务。
有限状态机的工作原理如图1所示,发生事件(event)后,根据当前状态(cur_state) ,决定执行的动作(action),并设置下一个状态号(nxt_state)。
-------------
| |-------->执行动作action
发生事件event ----->| cur_state |
| |-------->设置下一状态号nxt_state
-------------
当前状态
图1 有限状态机工作原理
e0/a0
--->--
| |
-------->----------
e0/a0 | | S0 |-----
| -<------------ | e1/a1
| | e2/a2 V
---------- ----------
| S2 |-----<-----| S1 |
---------- e2/a2 ----------
图2 一个有限状态机实例
--------------------------------------------
当前状态 s0 s1 s2 | 事件
--------------------------------------------
a0/s0 -- a0/s0 | e0
--------------------------------------------
a1/s1 -- -- | e1
--------------------------------------------
a2/s2 a2/s2 -- | e2
--------------------------------------------
表1 图2状态机实例的二维表格表示(动作/下一状态)
图2为一个状态机实例的状态转移图,它的含义是:
在s0状态,如果发生e0事件,那么就执行a0动作,并保持状态不变;
如果发生e1事件,那么就执行a1动作,并将状态转移到s1态;
如果发生e2事件,那么就执行a2动作,并将状态转移到s2态;
在s1状态,如果发生e2事件,那么就执行a2动作,并将状态转移到s2态;
在s2状态,如果发生e0事件,那么就执行a0动作,并将状态转移到s0态;
有限状态机不仅能够用状态转移图表示,还可以用二维的表格代表。一般将当前状 态号写在横行上,将事件写在纵列上,如表1所示。其中“--”表示空(不执行动作,也 不进行状态转移),“an/sn”表示执行动作an,同时将下一状态设置为sn。表1和图2表示 的含义是完全相同的。
观察表1可知,状态机可以用两种方法实现:竖着写(在状态中判断事件)和横着写( 在事件中判断状态)。这两种实现在本质上是完全等效的,但在实际操作中,效果却截然 不同。
cur_state = nxt_state;
switch(cur_state){ //在当前状态中判断事件
case s0: //在s0状态
if(e0_event){ //如果发生e0事件,那么就执行a0动作,
并保持状态不变;
执行a0动作;
//nxt_state = s0; //因为状态号是自身,所以可以删除此句
,以提高运行速度。
}
else if(e1_event){ //如果发生e1事件,那么就执行a1动作,
并将状态转移到s1态;
执行a1动作;
nxt_state = s1;
}
else if(e2_event){ //如果发生e2事件,那么就执行a2动作,
并将状态转移到s2态;
执行a2动作;
nxt_state = s2;
}
break;
case s1: //在s1状态
if(e2_event){ //如果发生e2事件,那么就执行a2动作,
并将状态转移到s2态;
执行a2动作;
nxt_state = s2;
}
break;
case s2: //在s2状态
if(e0_event){ //如果发生e0事件,那么就执行a0动作,
并将状态转移到s0态;
执行a0动作;
nxt_state = s0;
}
}
横着写(在事件中判断状态)C代码片段
//e0事件发生时,执行的函数
void e0_event_function(int * nxt_state)
{
int cur_state;
cur_state = *nxt_state;
switch(cur_state){
case s0: //观察表1,在e0事件发生时,s1处为空
case s2:
执行a0动作;
*nxt_state = s0;
}
}
//e1事件发生时,执行的函数
void e1_event_function(int * nxt_state)
{
int cur_state;
cur_state = *nxt_state;
switch(cur_state){
case s0: //观察表1,在e1事件发生时,s1和s2处为
空
执行a1动作;
*nxt_state = s1;
}
}
//e2事件发生时,执行的函数
void e2_event_function(int * nxt_state)
{
int cur_state;
cur_state = *nxt_state;
switch(cur_state){
case s0: //观察表1,在e2事件发生时,s2处为空
case s1:
执行a2动作;
*nxt_state = s2;
}
}
其Moore状态图如图1所示。
S0/0S1/1S3/0S2/100110011
其中S0/0所代表的意思为现在是状态S0且输出为0,状态图最主要是将每个状态都给予一个编号,详细描述如下:
1) 在某状态时,列出所有的输出条件。
2) 在某状态时,当输入信号是什么则会跳至哪一个状态。
3) 在某状态时,当输入信号是什么则会维持原状态不变。
可以将图1的Moore状态机写成状态表如表1.
表1 Moore状态表
状态表主要描述它与状态图的关系,再设计状态机电路是,需要先定义状态机的变量,定义状态机的变量时使用枚举类型来定义,如下范例所示:
Type State is (S0,S1,S2,S3)
接下来,状态会被加以编码。其状态编码方式如下:
(1) 时序编码(Sequential)
将每个状态以二进制来做编码。
(2)格雷码(Gray)
也是将四个State以二进制来编码,不过不同的是每次编码只会差一个位,其主要缺点是状态改变是要依序改变才可以,若状态不是依序是,则Gray编码不适用。
(3) 独热码(One hot)
独热码状态编码的特色为每一个状态均有自己的触发器,所以若有N个状态就也存在有N个触发器,在任一时刻只会有一组状态编码,缺点是会产生较大的电路,但是相对的使用独热码状态编码对帧错相当有帮助。
三种格式之状态编码如表2所示。
从状态编码表可以看出时序编码和Gray编码均是用二个位来做编码,而以独热码作为编码方式则编码位增加至四个位,所以电路比其他两种编码方式都大一些。
所以可以使用属性来定义编码方式,若要编码成独热码编码,则可加上:
Type State is (S0,S1,S2,S3);
Attribute encoding of state;
Type is “0001 0010 0100 1000”;
在设计状态机时,通常使用进程语句来描述状态机,其中进程语句又可以分为三种方式:
n 一个进程
利用一个进程来描述状态的转换及输出信号的定义。
n 两个进程
一个为时序电路主要负责状态变量的更新,此进程为同步电路,而另一个进程语句主要是描述下次态变量和输出的更新。
n 三个进程
第一个进程主要负责状态变量的更新,第二个进程语句负责描述次态变量,而最后一个则是负责输出信号的更新。
有了以上的初步观念,可以设计图1四个状态的Moore状态机。
首先根据之前的状态表编写VHDL程序如下所示:
Library ieee;
Use ieee.std_logic_1164.all;
Use ieee.numeric_std.all;
Entity moore_fsm is
Port(
clk : in std_logic;
rstn : in std_logic;
x : in std_logic;
output : out std_logic
);
End moore_fsm;
Architecture rtl of moore_fsm is
Type state is (s0,s1,s2,s3); ---状态定义
Signal current_state : state; ---现态
Signal next_state : state; ---次态
Begin
Statefsm: process(rstn, x, current_state)
Begin
If rstn = ‘0’ then --异步reset
next_state <= s0;
output <= ‘0’;
else
case current_state is
when s0 =>
if x =’0’ then
next_state <= s0;
else
next_state <= s1;
end if;
output <= ‘0’;
when s1 =>
if x =’0’ then
next_state <= s1;
else
next_state <= s2;
end if;
output <= ‘1’;
when s2 =>
if x =’0’ then
next_state <= s3;
else
next_state <= s0;
end if;
output <= ‘0’;
when s3 =>
if x =’0’ then
next_state <= s0;
else
next_state <= s3;
end if;
output <= ‘0’;
end case;
end if;
end process statefsm;
stat: process(clk) --current_stateànext_state
begin
if rising_edge (clk) then
current_state <=next_state;
end if;
end process stat;
end rtl;
1) 编码方式预设为时序编码
2) 使用两个进程语句来设计状态机
其综合电路如图2 所示。
其状态图如图3 所示。
Moore FSM 模拟波形如图4 所示。
模拟结果说明:
(1) 由于reset 为异步reset ,所以当reset在150ns~200ns为0时,则状态图会从s1回到s0.
(2) 在50 ns时输入x为0且现态为1,在70 ns 时clk上升沿触发且x为1,则current_state会变成next_state s2;
接下来我们要介绍Mealy状态机,它和输入、输出、状态皆有关。它的状态图、状态表与Moore状态机都有所不同,输出会随输入变化而变化。如图5
所示。
图5:
S0S1S3S20/11/10/10/01/11/0 1/0
若现态为s0输入为0时,则次态为s0且输出为0;若现态为s0输入为1时,则次态为s1且输出为1。
根据这个规则,列出Mealy状态机的状态表如表3。
其Mealy状态机的VHDL如下所示:
Library ieee;
Use ieee.std_logic_1164.all;
Use ieee.numeric_std.all;
Entity melay_fsm is
Port(
clk: : in std_logic;
rstn : in std_logic;
x : in std_logic;
output : out std_logic
);
End moore_fsm;
Architecture rtl of moore_fsm is
Type state is (s0,s1,s2,s3); ---状态定义
Signal current_state : state; ---现态
Signal next_state : state; ---次态
Begin
Statefsm: process(rstn, x, current_state)
Begin
If rstn = ‘0’ then --异步reset
next_state <= s0;
output <= ‘0’;
else
case current_state is
when s0 =>
if x =’0’ then
next_state <= s0;
else
next_state <= s1;
end if;
if x= ‘0’ then
output <= ‘0’;
else
output <= ‘1’;
end if;
when s1 =>
if x =’0’ then
next_state <= s2;
else
next_state <= s1;
end if;
if x= ‘0’ then
output <= ‘1’;
else
output <= ‘0’;
end if;
when s2 =>
if x =’0’ then
next_state <= s3;
else
next_state <= s0;
end if;
if x= ‘0’ then
output <= ‘0’;
else
output <= ‘1’;
end if;
when s3 =>
if x =’0’ then
next_state <= s0;
else
next_state <= s3;
end if;
if x= ‘0’ then
output <= ‘1’;
else
output <= ‘0’;
end if;
end case;
end if;
end process statefsm;
stat: process(clk) --current_stateànext_state
begin
if prsing_edge (clk) then
current_state <=next_state;
end if;
end process stat;
end rtl;
Mealy状态机综合电路如图6 所示。
参考资料
1._1906565] 状态机设计研究 .中国知网[引用日期2016-04-10]
2._1906565] 状态机思路在单片机程序设计中的应用
出处:http://www.amobbs.com/thread-4585393-1-1.html
不用怀疑,单片机的万能语言就是状态机。还希望大家不要条件反射式的看到状态机就
以为我要讲什么VHDL的东西——状态机是一种思维模式,是计算机理论的立足之本(不
相信请参考清华大学出版社的《自动机理论与应用》)——因此状态机的实现与语言本
身关系并不是绝对的。本文要讨论的状态机,从实现方式上更类似于Java中常用的那种
思维模式,而与VHDL相去甚远。
路要一步一步走,饭要一口一口吃,为了不把后来人吓跑,状态机理论中更多复杂的部
分,我会在以后专门写文章讨论,这里我先找一个切入点,从我常用的2种状态机编写
方式为大家慢慢展开。
首先,关于几个问题,比如:什么地方用状态机?状态机究竟有几种写法?状态机效率
到底高不高?是不是把简单问题弄复杂了?这类问题统统不在本文讨论之列,简而言之
——谁用谁知道。其实,还不能简单的就这么下了结论,套八股文而不求甚解的也大有
人在————因此我要说:关于状态机的各种问题“谁思考谁实践谁坚持谁知道”。
状态机入门第一式:switch case一线到底
要点: 用switch结构配合一个状态变量,通过修改状态变量的值来切换状态。
范例:
//! 定义状态名称与状态值之间的关系,增加可读性
#define FSM_START 0x00
#define FSM_STATE_A 0x01
#define FSM_STATE_B 0x02
…
#define FSM_RESET 0xFF
bool fsm_example_A( <</span>形参列表> ) {
static uint8_t s_chFSMState = FSM_START; //!< 定义状态变量
…
switch ( s_chFSMState ) {
case FSM_START:
//! 这里添加状态机初始化代码
…
s_chFSMState = FSM_STATE_A; //!< 进入下一状态
break;
case FSM_STATE_A:
//! 这里添加状态机A进入下一状态的检测代码
if (<</span>某某条件>) {
//! 这里做一些进入下一状态时要做的准备工作
s_chFSMState = FSM_STATE_B; //!< 进入下一状态
}
break;
case FSM_STATE_B:
//! 这里添加状态机A进入下一状态的检测代码
if (<</span>某某条件>) {
//! 这里做一些进入下一状态时要做的准备工作
s_chFSMState = FSM_STATE_A; //!< 进入下一状态
} else if (<</span>某某条件>) {
} else if (<</span>某某条件>) {
…
} else {
}
break;
…
case FSM_STOP:
case FSM_RESET:
default:
//! 这里添加状态机复位相关的代码
…
chFSMState = FSM_START; //!< 状态机复位
//! 返回false表示状态机已经不需要继续运行了
return false;
}
//! 返回true表示状态机正在运行
return true;
}
总结: 从范例可知,这种状态机就是一根筋……并不是说他走不出什么分支来,而
是说通常他没有办法让多个状态同时处于激活状态。
状态机入门第二式:if 判断变化无穷
要点: 用if else…else if结构的组合来描述状态流程图。
什么是状态流程图?我不想多解释,因为就那么个简单的东西,说多了反而神秘兮兮的。
状态流程图你可以简单粗暴的认为,他就是流程图,等你用得多了,你就渐渐明白为啥
多了“状态”二字;如果你后来或者先前学过状态图,那么很快你就会明白状态流程图
比状态图“高级”了多少。
1、 不管怎么说,你可以先为你要处理的事物画一个流程图。如果流程图都不会画,就
不用凑热闹了。
2、 接下来,把流程图上每一个方框或者判断筐都“简单粗暴”地看成一个状态。
3、 将每一个状态用if结构表示出来
if (<</span>状态标志>) {
//! 状态代码
…
}
4、 自己看着办,合并多余的状态,优化优化代码。
范例:
//! 首先将布尔量的状态标志压缩在一个字节里面以节省内存开支
typedef union {
uint8_t Value;
uint8_t Byte;
struct {
unsigned BIT0:1;
unsigned BIT1:1;
unsigned BIT2:1;
unsigned BIT3:1;
unsigned BIT4:1;
unsigned BIT5:1;
unsigned BIT6:1;
unsigned BIT7:1;
} Bits;
}byte_t;
#define FSM_ACTION_FLAG s_tbState.Bits
#define FSM_STOP_ALL_ACTIONS() do {s_tbState.Value = 0;}while(0)
#define FSM_START (0 == s_tbState.Value)
#define FSM_STATE_A FSM_ACTION_FLAG.BIT0
#define FSM_STATE_B FSM_ACTION_FLAG.BIT1
…
#define FSM_STATE_H FSM_ACTION_FLAG.BIT7
bool fsm_example_B( <</span>形参列表> ) {
static byte_t s_tbState = {0}; //!< 定义状态变量
if (FSM_START) { //!< 起始状态
//! 这里放置状态机初始化的代码
…
FSM_STATE_A = true; //!< 进入状态B,start装台自动结束
}
if (FSM_STATE_A) { //!< 一个典型的简单状态
//! 这里放置状态A的代码或者
…
//! 这里放置某些条件以开启别的状态
if (<</span>某些条件>) {
//! 这里做一些“进入”下一个状态之前的准备工作
FSM_STATE_B = true; //!< 开启下一个状态
FSM_STATE_A = false; //!< 结束当前状态
}
}
if (FSM_STATE_B) { //!< 一个典型的监视状态
…
//! 这里检测某些条件
if (<</span>某些条件>) {
//! 这里做一些“开启”某个状态的准备工作
FSM_STATE_C = true; //!< 开启某一个状态而不结束当前状态
FSM_STATE_D = true; //!< 你当然可以一次触发多个状态
…
} else if (<</span>某些条件>) {
//! 满足某些条件以后关闭当前状态
FSM_STATE_B = false;
}
}
…
if (FSM_STATE_F) { //!< 一个典型的子状态机调用
if (!fsm_example_a(<</span>实参列表>)) { //!< 等待子状态机返回false
//!子状态机运行完成,进入下一状态
…
FSM_STATE_F = false; //!< 结束当前状态
FSM_STATE_x = true; //!< 进入下一状态x代表某个字母
}
}
if (FSM_STATE_H) { //!< 一个典型的中止状态
//!< 某些状态机的操作,比如释放某些资源
…
FSM_STOP_ALL_ACTIONS(); //!< 复位状态机
return false; //!< 返回false表示状态机结束
}
return true; //!< 返回true表示状态机保持运行
}
总结: 从范例可知,这种状态机非常灵活,通过布尔变量的开启和关闭,你可以自
由的控制某些状态的开启。同一时刻可能有多个状态是激活的。这种结构几乎可以翻译
任何流程图。具体还有很多好处,可以在使用中体会。
状态机入门第三式:状态在心中,无态也变态
要点: 所有的函数都可以看作是状态机,只不过普通的函数是一个只有单一状态的
状态机。如果函数有返回值,且这个返回值能表征至少两种以上不同的状态(比如返回
是一个指针,那么NULL和非NULL就是两种状态;比如返回是一个布尔变量,那么true和
false就是两种状态;比如返回的是一个整数,并且整数的某些特征可以被分类,那么
这些不同分类就是几种不同的状态),那么这些返回值就可以被用作指示当前状态机的
运行情况。状态机可以调用子状态机。所有的状态都应该是none-block的,简单说就是
不会把系统定死在某一个状态里面很久都出不来,比如while(1)或者循环次数较大的for
结构;否则状态机的存在意义就大打折扣——直接按照流程图写代码不就好了,干吗非
要翻译成状态机?状态机中,状态的功能应该是等待某一个事件的发生(或者说条件的
满足);某些情况下,一些一次性执行完的流程也可以独立成一个状态——它当然没有
等待任何条件的满足,你可以认为他是无条件进行状态转移的。
总体说来,状态机是一个万能的计算机语言表述方式,与具体的载体语言关系不大。心
中有状态,代码怎会无状态?状态机是裸机条件下多任务的廉价实现方案。在状态机多
任务条件下,操作系统牵涉的几乎所有概念都会有所涉及,比如任务的同步,临界区的
保护,任务间的通讯,任务的优先级,资源的动态分配等等。你可以这么理解,每一个
状态机都是一个进程,每一个状态都是一个线程,因为进程有自己的资源,而同一个进
程里面的多个线程是公用同一片资源的。你甚至可以在有抢占式操作系统的情况下用状
态机,这个时候,操作系统的每一个任务都是个内核,那么整个系统开发就可以登小于
多核系统开发了。
是不是很有意思?没意思就别看了!状态机的种种,以后再表。
Have a good time.
状态机
锁定本词条由“科普中国”百科科学词条编写与应用工作项目 审核
。
状态机由状态寄存器和组合逻辑电路构成,能够根据控制信号按照预先设定的状态进行状态转移,是协调相关信号动作、完成特定操作的控制中心。状态机简写为FSM(Finite State Machine),主要分为2大类:
第一类,若输出只和状态有关而与输入无关,则称为Moore状态机
第二类,输出不仅和状态有关而且和输入有关系,则称为Mealy状态机
中文名
状态机
外文名
Finite State Machine)
缩 写
FSM
又 名
状态转移图
目录
1 基本信息2 状态机综述
3 两种写法
▪ 有限状态机FSM
▪ 竖着写C代码片段
4 其它
▪ Moore状态机
▪ 范例
▪ Mealy状态机
基本信息
状态机由状态寄存器和组合逻辑电路构成,能够根据控制信号按照预先设定的状态进行状态转移,是协调相关信号动作,完成特定操作的控制中心。状态机分为摩尔(Moore)型状态机和米莉(Mealy)型状态机。[1]就是状态转移图。举个最简单的例子。人有三个状态健康,感冒,康复中。触发的条件有淋雨(t1),吃药(t2),打针(t3),休息(t4)。所以状态机就是健康-(t4)->健康;健康-(t1)->感冒;感冒-(t3)->健康;感冒-(t2)->康复中;康复中-(t4)->健康,等等。就是这样状态在不同的条件下跳转到自己或不同状态的图。
状态机综述
关于状态机的一个极度确切的描述是它是一个有向图形,由一组节点和一组相应的转移函数组成。状态机通过响应一系列事件而“运行”。每个事件都在属于“当前” 节点的转移函数的控制范围内,其中函数的范围是节点的一个子集。函数返回“下一个”(也许是同一个)节点。这些节点中至少有一个必须是终态。当到达终态, 状态机停止。包含一组状态集(states)、一个起始状态(start state)、一组输入符号集(alphabet)、一个映射输入符号和当前状态到下一状态的转换函数(transition
function)的计算模型。当输入符号串,模型随即进入起始状态。它要改变到新的状态,依赖于转换函数。在有限状态机中,会有有许多变量,例如,状态 机有很多与动作(actions)转换(Mealy机)或状态(摩尔机)关联的动作,多重起始状态,基于没有输入符号的转换,或者指定符号和状态(非定有
限状态机)的多个转换,指派给接收状态(识别者)的一个或多个状态,等等。
传统应用程序的控制流程基本是顺序的:遵循事先设定的逻辑,从头到尾地执行。很少有事件能改变标准执行流程;而且这些事件主要涉及异常情况。“命令行实用程序”是这种传统应用程序的典型例子。
另一类应用程序由外部发生的事件来驱动——换言之,事件在应用程序之外生成,无法由应用程序或程序员来控制。具体需要执行的代码取决于接收到的事件,或者它 相对于其他事件的抵达时间。所以,控制流程既不能是顺序的,也不能是事先设定好的,因为它要依赖于外部事件。事件驱动的GUI应用程序是这种应用程序的典
型例子,它们由命令和选择(也就是用户造成的事件)来驱动。
Web应用程序由提交的表单和用户请求的网页来驱动,它们也可划归到上述类别。但是,GUI应用程序对于接收到的事件仍有一定程度的控制,因为这些事件要依赖于向用户显示的窗口和控件,而窗口和控件是由程序员控制的。Web应用
程序则不然,因为一旦用户采取不在预料之中的操作(比如使用浏览器的历史记录、手工输入链接以及模拟一次表单提交等等),就很容易打乱设计好的应用程序逻辑。
显然,必须采取不同的技术来处理这些情况。它能处理任何顺序的事件,并能提供有意义的响应——即使这些事件发生的顺序和预计的不同。有限状态机正是为了满足这方面的要求而设计的。
有限状态机是一种概念性机器,它能采取某种操作来响应一个外部事件。具体采取的操作不仅能取决于接收到的事件,还能取决于各个事件的相对发生顺序。之所以能 做到这一点,是因为机器能跟踪一个内部状态,它会在收到事件后进行更新。为一个事件而响应的行动不仅取决于事件本身,还取决于机器的内部状态。另外,采取 的行动还会决定并更新机器的状态。这样一来,任何逻辑都可建模成一系列事件/状态组合。
[2] 状态机可归纳为4个要素,即现态、条件、动作、次态。这样的归纳,主要是出于对状态机的内在因果关系的考虑。“现态”和“条件”是因,“动作”和“次态”是果。详解如下:
①现态:是指当前所处的状态。
②条件:又称为“事件”,当一个条件被满足,将会触发一个动作,或者执行一次状态的迁移。
③动作:条件满足后执行的动作。动作执行完毕后,可以迁移到新的状态,也可以仍旧保持原状态。动作不是必需的,当条件满足后,也可以不执行任何动作,直接迁移到新状态。
④次态:条件满足后要迁往的新状态。“次态”是相对于“现态”而言的,“次态”一旦被激活,就转变成新的“现态”了。
两种写法
有限状态机FSM
思想广泛应用于硬件控制电路设计,也是软件上常用的一种处理方法(软 件上称为FMM--有限消息机)。它把复杂的控制逻辑分解成有限个稳定状态,在每个状态上判断事件,变连续处理为离散数字处理,符合计算机的工作特点。同时,因为有限状态机具有有限个状态,所以可以在实际的工程上实现。但这并不意味着其只能进行有限次的处理,相反,有限状态机是闭环系统,有限无穷,可以用有限的状态,处理无穷的事务。有限状态机的工作原理如图1所示,发生事件(event)后,根据当前状态(cur_state) ,决定执行的动作(action),并设置下一个状态号(nxt_state)。
-------------
| |-------->执行动作action
发生事件event ----->| cur_state |
| |-------->设置下一状态号nxt_state
-------------
当前状态
图1 有限状态机工作原理
e0/a0
--->--
| |
-------->----------
e0/a0 | | S0 |-----
| -<------------ | e1/a1
| | e2/a2 V
---------- ----------
| S2 |-----<-----| S1 |
---------- e2/a2 ----------
图2 一个有限状态机实例
--------------------------------------------
当前状态 s0 s1 s2 | 事件
--------------------------------------------
a0/s0 -- a0/s0 | e0
--------------------------------------------
a1/s1 -- -- | e1
--------------------------------------------
a2/s2 a2/s2 -- | e2
--------------------------------------------
表1 图2状态机实例的二维表格表示(动作/下一状态)
图2为一个状态机实例的状态转移图,它的含义是:
在s0状态,如果发生e0事件,那么就执行a0动作,并保持状态不变;
如果发生e1事件,那么就执行a1动作,并将状态转移到s1态;
如果发生e2事件,那么就执行a2动作,并将状态转移到s2态;
在s1状态,如果发生e2事件,那么就执行a2动作,并将状态转移到s2态;
在s2状态,如果发生e0事件,那么就执行a0动作,并将状态转移到s0态;
有限状态机不仅能够用状态转移图表示,还可以用二维的表格代表。一般将当前状 态号写在横行上,将事件写在纵列上,如表1所示。其中“--”表示空(不执行动作,也 不进行状态转移),“an/sn”表示执行动作an,同时将下一状态设置为sn。表1和图2表示 的含义是完全相同的。
观察表1可知,状态机可以用两种方法实现:竖着写(在状态中判断事件)和横着写( 在事件中判断状态)。这两种实现在本质上是完全等效的,但在实际操作中,效果却截然 不同。
竖着写C代码片段
cur_state = nxt_state;switch(cur_state){ //在当前状态中判断事件
case s0: //在s0状态
if(e0_event){ //如果发生e0事件,那么就执行a0动作,
并保持状态不变;
执行a0动作;
//nxt_state = s0; //因为状态号是自身,所以可以删除此句
,以提高运行速度。
}
else if(e1_event){ //如果发生e1事件,那么就执行a1动作,
并将状态转移到s1态;
执行a1动作;
nxt_state = s1;
}
else if(e2_event){ //如果发生e2事件,那么就执行a2动作,
并将状态转移到s2态;
执行a2动作;
nxt_state = s2;
}
break;
case s1: //在s1状态
if(e2_event){ //如果发生e2事件,那么就执行a2动作,
并将状态转移到s2态;
执行a2动作;
nxt_state = s2;
}
break;
case s2: //在s2状态
if(e0_event){ //如果发生e0事件,那么就执行a0动作,
并将状态转移到s0态;
执行a0动作;
nxt_state = s0;
}
}
横着写(在事件中判断状态)C代码片段
//e0事件发生时,执行的函数
void e0_event_function(int * nxt_state)
{
int cur_state;
cur_state = *nxt_state;
switch(cur_state){
case s0: //观察表1,在e0事件发生时,s1处为空
case s2:
执行a0动作;
*nxt_state = s0;
}
}
//e1事件发生时,执行的函数
void e1_event_function(int * nxt_state)
{
int cur_state;
cur_state = *nxt_state;
switch(cur_state){
case s0: //观察表1,在e1事件发生时,s1和s2处为
空
执行a1动作;
*nxt_state = s1;
}
}
//e2事件发生时,执行的函数
void e2_event_function(int * nxt_state)
{
int cur_state;
cur_state = *nxt_state;
switch(cur_state){
case s0: //观察表1,在e2事件发生时,s2处为空
case s1:
执行a2动作;
*nxt_state = s2;
}
}
其它
Moore状态机
其Moore状态图如图1所示。S0/0S1/1S3/0S2/100110011
其中S0/0所代表的意思为现在是状态S0且输出为0,状态图最主要是将每个状态都给予一个编号,详细描述如下:
1) 在某状态时,列出所有的输出条件。
2) 在某状态时,当输入信号是什么则会跳至哪一个状态。
3) 在某状态时,当输入信号是什么则会维持原状态不变。
可以将图1的Moore状态机写成状态表如表1.
表1 Moore状态表
现态 | 次态 | 输出 | ||
输入 | X=0 | X=1 | X=0 | X=1 |
S0 | S0 | S1 | 0 | 0 |
S1 | S1 | S2 | 1 | 1 |
S2 | S3 | S0 | 0 | 0 |
S3 | S0 | S3 | 0 | 0 |
Type State is (S0,S1,S2,S3)
接下来,状态会被加以编码。其状态编码方式如下:
(1) 时序编码(Sequential)
将每个状态以二进制来做编码。
(2)格雷码(Gray)
也是将四个State以二进制来编码,不过不同的是每次编码只会差一个位,其主要缺点是状态改变是要依序改变才可以,若状态不是依序是,则Gray编码不适用。
(3) 独热码(One hot)
独热码状态编码的特色为每一个状态均有自己的触发器,所以若有N个状态就也存在有N个触发器,在任一时刻只会有一组状态编码,缺点是会产生较大的电路,但是相对的使用独热码状态编码对帧错相当有帮助。
三种格式之状态编码如表2所示。
状态 | 时序编码 | Gray编码 | One hot编码 |
S0 | 00 | 00 | 0001 |
S1 | 01 | 01 | 0010 |
S2 | 10 | 11 | 0100 |
S3 | 11 | 10 | 1000 |
所以可以使用属性来定义编码方式,若要编码成独热码编码,则可加上:
Type State is (S0,S1,S2,S3);
Attribute encoding of state;
Type is “0001 0010 0100 1000”;
在设计状态机时,通常使用进程语句来描述状态机,其中进程语句又可以分为三种方式:
n 一个进程
利用一个进程来描述状态的转换及输出信号的定义。
n 两个进程
一个为时序电路主要负责状态变量的更新,此进程为同步电路,而另一个进程语句主要是描述下次态变量和输出的更新。
n 三个进程
第一个进程主要负责状态变量的更新,第二个进程语句负责描述次态变量,而最后一个则是负责输出信号的更新。
有了以上的初步观念,可以设计图1四个状态的Moore状态机。
范例
首先根据之前的状态表编写VHDL程序如下所示:Library ieee;
Use ieee.std_logic_1164.all;
Use ieee.numeric_std.all;
Entity moore_fsm is
Port(
clk : in std_logic;
rstn : in std_logic;
x : in std_logic;
output : out std_logic
);
End moore_fsm;
Architecture rtl of moore_fsm is
Type state is (s0,s1,s2,s3); ---状态定义
Signal current_state : state; ---现态
Signal next_state : state; ---次态
Begin
Statefsm: process(rstn, x, current_state)
Begin
If rstn = ‘0’ then --异步reset
next_state <= s0;
output <= ‘0’;
else
case current_state is
when s0 =>
if x =’0’ then
next_state <= s0;
else
next_state <= s1;
end if;
output <= ‘0’;
when s1 =>
if x =’0’ then
next_state <= s1;
else
next_state <= s2;
end if;
output <= ‘1’;
when s2 =>
if x =’0’ then
next_state <= s3;
else
next_state <= s0;
end if;
output <= ‘0’;
when s3 =>
if x =’0’ then
next_state <= s0;
else
next_state <= s3;
end if;
output <= ‘0’;
end case;
end if;
end process statefsm;
stat: process(clk) --current_stateànext_state
begin
if rising_edge (clk) then
current_state <=next_state;
end if;
end process stat;
end rtl;
1) 编码方式预设为时序编码
2) 使用两个进程语句来设计状态机
其综合电路如图2 所示。
其状态图如图3 所示。
Moore FSM 模拟波形如图4 所示。
模拟结果说明:
(1) 由于reset 为异步reset ,所以当reset在150ns~200ns为0时,则状态图会从s1回到s0.
(2) 在50 ns时输入x为0且现态为1,在70 ns 时clk上升沿触发且x为1,则current_state会变成next_state s2;
Mealy状态机
接下来我们要介绍Mealy状态机,它和输入、输出、状态皆有关。它的状态图、状态表与Moore状态机都有所不同,输出会随输入变化而变化。如图5所示。
0/0 |
S0S1S3S20/11/10/10/01/11/0 1/0
若现态为s0输入为0时,则次态为s0且输出为0;若现态为s0输入为1时,则次态为s1且输出为1。
根据这个规则,列出Mealy状态机的状态表如表3。
现太 | 次态 | 输出 | ||
输入 | X=0 | X=1 | X=0 | X=1 |
S0 | S0 | S1 | 0 | 1 |
S1 | S2 | S1 | 1 | 0 |
S2 | S3 | S0 | 0 | 1 |
S3 | S0 | S3 | 1 | 0 |
Library ieee;
Use ieee.std_logic_1164.all;
Use ieee.numeric_std.all;
Entity melay_fsm is
Port(
clk: : in std_logic;
rstn : in std_logic;
x : in std_logic;
output : out std_logic
);
End moore_fsm;
Architecture rtl of moore_fsm is
Type state is (s0,s1,s2,s3); ---状态定义
Signal current_state : state; ---现态
Signal next_state : state; ---次态
Begin
Statefsm: process(rstn, x, current_state)
Begin
If rstn = ‘0’ then --异步reset
next_state <= s0;
output <= ‘0’;
else
case current_state is
when s0 =>
if x =’0’ then
next_state <= s0;
else
next_state <= s1;
end if;
if x= ‘0’ then
output <= ‘0’;
else
output <= ‘1’;
end if;
when s1 =>
if x =’0’ then
next_state <= s2;
else
next_state <= s1;
end if;
if x= ‘0’ then
output <= ‘1’;
else
output <= ‘0’;
end if;
when s2 =>
if x =’0’ then
next_state <= s3;
else
next_state <= s0;
end if;
if x= ‘0’ then
output <= ‘0’;
else
output <= ‘1’;
end if;
when s3 =>
if x =’0’ then
next_state <= s0;
else
next_state <= s3;
end if;
if x= ‘0’ then
output <= ‘1’;
else
output <= ‘0’;
end if;
end case;
end if;
end process statefsm;
stat: process(clk) --current_stateànext_state
begin
if prsing_edge (clk) then
current_state <=next_state;
end if;
end process stat;
end rtl;
Mealy状态机综合电路如图6 所示。
参考资料
1._1906565] 状态机设计研究 .中国知网[引用日期2016-04-10]
2._1906565] 状态机思路在单片机程序设计中的应用
出处:http://www.amobbs.com/thread-4585393-1-1.html
不用怀疑,单片机的万能语言就是状态机。还希望大家不要条件反射式的看到状态机就
以为我要讲什么VHDL的东西——状态机是一种思维模式,是计算机理论的立足之本(不
相信请参考清华大学出版社的《自动机理论与应用》)——因此状态机的实现与语言本
身关系并不是绝对的。本文要讨论的状态机,从实现方式上更类似于Java中常用的那种
思维模式,而与VHDL相去甚远。
路要一步一步走,饭要一口一口吃,为了不把后来人吓跑,状态机理论中更多复杂的部
分,我会在以后专门写文章讨论,这里我先找一个切入点,从我常用的2种状态机编写
方式为大家慢慢展开。
首先,关于几个问题,比如:什么地方用状态机?状态机究竟有几种写法?状态机效率
到底高不高?是不是把简单问题弄复杂了?这类问题统统不在本文讨论之列,简而言之
——谁用谁知道。其实,还不能简单的就这么下了结论,套八股文而不求甚解的也大有
人在————因此我要说:关于状态机的各种问题“谁思考谁实践谁坚持谁知道”。
状态机入门第一式:switch case一线到底
要点: 用switch结构配合一个状态变量,通过修改状态变量的值来切换状态。
范例:
//! 定义状态名称与状态值之间的关系,增加可读性
#define FSM_START 0x00
#define FSM_STATE_A 0x01
#define FSM_STATE_B 0x02
…
#define FSM_RESET 0xFF
bool fsm_example_A( <</span>形参列表> ) {
static uint8_t s_chFSMState = FSM_START; //!< 定义状态变量
…
switch ( s_chFSMState ) {
case FSM_START:
//! 这里添加状态机初始化代码
…
s_chFSMState = FSM_STATE_A; //!< 进入下一状态
break;
case FSM_STATE_A:
//! 这里添加状态机A进入下一状态的检测代码
if (<</span>某某条件>) {
//! 这里做一些进入下一状态时要做的准备工作
s_chFSMState = FSM_STATE_B; //!< 进入下一状态
}
break;
case FSM_STATE_B:
//! 这里添加状态机A进入下一状态的检测代码
if (<</span>某某条件>) {
//! 这里做一些进入下一状态时要做的准备工作
s_chFSMState = FSM_STATE_A; //!< 进入下一状态
} else if (<</span>某某条件>) {
} else if (<</span>某某条件>) {
…
} else {
}
break;
…
case FSM_STOP:
case FSM_RESET:
default:
//! 这里添加状态机复位相关的代码
…
chFSMState = FSM_START; //!< 状态机复位
//! 返回false表示状态机已经不需要继续运行了
return false;
}
//! 返回true表示状态机正在运行
return true;
}
总结: 从范例可知,这种状态机就是一根筋……并不是说他走不出什么分支来,而
是说通常他没有办法让多个状态同时处于激活状态。
状态机入门第二式:if 判断变化无穷
要点: 用if else…else if结构的组合来描述状态流程图。
什么是状态流程图?我不想多解释,因为就那么个简单的东西,说多了反而神秘兮兮的。
状态流程图你可以简单粗暴的认为,他就是流程图,等你用得多了,你就渐渐明白为啥
多了“状态”二字;如果你后来或者先前学过状态图,那么很快你就会明白状态流程图
比状态图“高级”了多少。
1、 不管怎么说,你可以先为你要处理的事物画一个流程图。如果流程图都不会画,就
不用凑热闹了。
2、 接下来,把流程图上每一个方框或者判断筐都“简单粗暴”地看成一个状态。
3、 将每一个状态用if结构表示出来
if (<</span>状态标志>) {
//! 状态代码
…
}
4、 自己看着办,合并多余的状态,优化优化代码。
范例:
//! 首先将布尔量的状态标志压缩在一个字节里面以节省内存开支
typedef union {
uint8_t Value;
uint8_t Byte;
struct {
unsigned BIT0:1;
unsigned BIT1:1;
unsigned BIT2:1;
unsigned BIT3:1;
unsigned BIT4:1;
unsigned BIT5:1;
unsigned BIT6:1;
unsigned BIT7:1;
} Bits;
}byte_t;
#define FSM_ACTION_FLAG s_tbState.Bits
#define FSM_STOP_ALL_ACTIONS() do {s_tbState.Value = 0;}while(0)
#define FSM_START (0 == s_tbState.Value)
#define FSM_STATE_A FSM_ACTION_FLAG.BIT0
#define FSM_STATE_B FSM_ACTION_FLAG.BIT1
…
#define FSM_STATE_H FSM_ACTION_FLAG.BIT7
bool fsm_example_B( <</span>形参列表> ) {
static byte_t s_tbState = {0}; //!< 定义状态变量
if (FSM_START) { //!< 起始状态
//! 这里放置状态机初始化的代码
…
FSM_STATE_A = true; //!< 进入状态B,start装台自动结束
}
if (FSM_STATE_A) { //!< 一个典型的简单状态
//! 这里放置状态A的代码或者
…
//! 这里放置某些条件以开启别的状态
if (<</span>某些条件>) {
//! 这里做一些“进入”下一个状态之前的准备工作
FSM_STATE_B = true; //!< 开启下一个状态
FSM_STATE_A = false; //!< 结束当前状态
}
}
if (FSM_STATE_B) { //!< 一个典型的监视状态
…
//! 这里检测某些条件
if (<</span>某些条件>) {
//! 这里做一些“开启”某个状态的准备工作
FSM_STATE_C = true; //!< 开启某一个状态而不结束当前状态
FSM_STATE_D = true; //!< 你当然可以一次触发多个状态
…
} else if (<</span>某些条件>) {
//! 满足某些条件以后关闭当前状态
FSM_STATE_B = false;
}
}
…
if (FSM_STATE_F) { //!< 一个典型的子状态机调用
if (!fsm_example_a(<</span>实参列表>)) { //!< 等待子状态机返回false
//!子状态机运行完成,进入下一状态
…
FSM_STATE_F = false; //!< 结束当前状态
FSM_STATE_x = true; //!< 进入下一状态x代表某个字母
}
}
if (FSM_STATE_H) { //!< 一个典型的中止状态
//!< 某些状态机的操作,比如释放某些资源
…
FSM_STOP_ALL_ACTIONS(); //!< 复位状态机
return false; //!< 返回false表示状态机结束
}
return true; //!< 返回true表示状态机保持运行
}
总结: 从范例可知,这种状态机非常灵活,通过布尔变量的开启和关闭,你可以自
由的控制某些状态的开启。同一时刻可能有多个状态是激活的。这种结构几乎可以翻译
任何流程图。具体还有很多好处,可以在使用中体会。
状态机入门第三式:状态在心中,无态也变态
要点: 所有的函数都可以看作是状态机,只不过普通的函数是一个只有单一状态的
状态机。如果函数有返回值,且这个返回值能表征至少两种以上不同的状态(比如返回
是一个指针,那么NULL和非NULL就是两种状态;比如返回是一个布尔变量,那么true和
false就是两种状态;比如返回的是一个整数,并且整数的某些特征可以被分类,那么
这些不同分类就是几种不同的状态),那么这些返回值就可以被用作指示当前状态机的
运行情况。状态机可以调用子状态机。所有的状态都应该是none-block的,简单说就是
不会把系统定死在某一个状态里面很久都出不来,比如while(1)或者循环次数较大的for
结构;否则状态机的存在意义就大打折扣——直接按照流程图写代码不就好了,干吗非
要翻译成状态机?状态机中,状态的功能应该是等待某一个事件的发生(或者说条件的
满足);某些情况下,一些一次性执行完的流程也可以独立成一个状态——它当然没有
等待任何条件的满足,你可以认为他是无条件进行状态转移的。
总体说来,状态机是一个万能的计算机语言表述方式,与具体的载体语言关系不大。心
中有状态,代码怎会无状态?状态机是裸机条件下多任务的廉价实现方案。在状态机多
任务条件下,操作系统牵涉的几乎所有概念都会有所涉及,比如任务的同步,临界区的
保护,任务间的通讯,任务的优先级,资源的动态分配等等。你可以这么理解,每一个
状态机都是一个进程,每一个状态都是一个线程,因为进程有自己的资源,而同一个进
程里面的多个线程是公用同一片资源的。你甚至可以在有抢占式操作系统的情况下用状
态机,这个时候,操作系统的每一个任务都是个内核,那么整个系统开发就可以登小于
多核系统开发了。
是不是很有意思?没意思就别看了!状态机的种种,以后再表。
Have a good time.
相关文章推荐
- Python 文档处理doctest
- c++ 需要用拷贝构造函数的情况
- PHP面向对象——访问修饰符
- (OK) running two Android-x86 in VirtualBox, they connect to "ethernet bridge"
- 源码阅读器的安装
- Effective C++--条款20:适当地用pass-by-reference-to-const代替pass-by-value
- NSLayoutConstraint万能约束公式
- Asp.net WebApi + EF 单元测试架构 DbContext一站到底
- 【HTML5】input类型
- Android 中LayoutInflater的使用(转)
- LeetCode Binary Search Tree Iterator
- 点 圆柱类的设计3
- 工具的使用 —— 搜狗输入法(二)
- 关系数据库设计范式介绍
- Oracle数据库语句大全
- 基础入门之堆栈
- HDU2602Bone Collector 简单0-1背包
- xcode快捷键
- Android的setTag
- 点 圆柱类的设计2