[FSM]状态机实践入门——程咬金只要三斧头厉害
本帖最后由 Gorgon_Meducer 于 2012-7-23 21:12 编辑不用怀疑,单片机的万能语言就是状态机。还希望大家不要条件反射式的看到状态机就
以为我要讲什么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. 赶紧MARK。 以为是沙发 原来是板凳..
正在学习状态机 沙了个发? 讲的不错 搬个板凳来学习。 mark,学习 哈哈,我的程序结构都是这样的,
不过,今天才知道这种结构叫状态机。
楼主真是一针见血。看来找到师傅了。
继续关注,看看我的程序哪里还可以改进。 very good 标记 状态机对于单个 顺序流程的任务来讲 非常适合
之前在项目上应用过状态机的构架, 驱动型任务 都写成单独的状态机
每一个状态机 都有不同的触发条件
系统使用了状态机的结构之后 明显的优势
1、CPU利用率大大提高了
2、可以多任务 “并行执行” 提高了系统响应速度
但随着状态机的增多 和系统结构分层 出现的问题:
1、各个状态机 之间究竟如何管理?(之前用的是上层状态机管理下层多个状态机的)
2、各状态机 之间同步 如何做到更可靠
一不小心就会出现 锁死 状态
3、产品代码 以后维护比较困难
后来状态机就淡淡的 退出了我的考虑范围内说实话 有点心不甘毕竟当时是 自己一个人独立完成的系统
当时公司都很不理解花那么大精力去做这件事情 但我仍然坚持了下来
Gorgon Meducer 老师多次讲到状态机 希望能解决我之前的一些问题 mark, 状态机。 to 【10楼】 3466756555
状态机是一个语言模式,可以说增加了我们的语言表达能力。它太灵活了
所以,就好比我们已经能随意用文字去写文章了,但是如何写好文章却是一个
和状态机本身关系不大的事情。
你的疑问来自于对系统概念的模糊和缺失。不单纯是学习操作系统概念能
够根本解决的。必须要有一种类似哲学的东西,比如面向对象的开发习惯,比
如黑盒子的开发行为模式,比如如何解决实际问题的分析能力,如何打破已有
的惯性思维,根据实际情况创造新结构的勇气、洞察力和创造力。
简单来说,这些都是需要时间和经验积累的。如果你迷惑了,说明你需要
坚持下去,因为你已经上路了。 学习状态机。 初学单片机的人就应该立即学习文中列出的那种八股文开发模式:
1、画(状态)流程图
2、翻译状态流程图为状态机
3、优化、调试
形成习惯以后,能大大加快学习嵌入式系统的速度。不会被诸如
按键扫描程序和数码管扫描程序无法很好协调工作的问题困惑,
甚至在缺乏引导的情况下,养成非常不好的习惯,对由于这些习
惯本身容易导致的栈溢出问题,存储器使用不当(指针类问题),
产生恐惧心理。
状态机是一种计算机软件开发的万能语言模式,他让我们能随心
所欲的表达自己的思想。在这种基础下,自己的想法能够得到充
分的表达,从而自己的思想也能得到实践的充分验证。于是思维
和实践能够得到充分的交互,尽早的形成良性循环。这个时候,
书本上那些理论,只要能够理解,你就有足够的语言能力去检验
于是也就能很快的体会其中的好坏。 感谢分享! mark一下 谢谢分享 MARK 如果能把状态机和socket编程结合讲解下就太好了,期待! 有限状态机 可是一个好东东哈。
最难的地方在于 状态的划分和状态迁移图的设计。
嘎嘎,很好很强大,一直都用它。 mark 一直在期待 傻孩子的大作一定要MARK! 还好,我的人机界面显示菜单也是这种结构。 有没有相关的书,系统一些的。 学习 非常感谢 状态在心中,无态也变态 !
顶这句 请教楼主一个问题,如果在一个应用中有多个事件任务,我希望MCU能够以时间片的方式轮流处理每个事件,而不是要等一个事件响应程序结束后才执行其它的事件响应程序.有什么方法吗?谢谢! 以前发过一个状态机图的entry\do\exit的简单实现方法
http://www.ourdev.cn/bbs/bbs_content.jsp?bbs_sn=4520667&bbs_page_no=1&search_mode=3&search_text=syuanwang&bbs_id=9999
如果想写状态机的C程序,推荐这种方法,开销很小
应该比LZ的代码风格写起来简单,维护起来也容易
不过得有先去搜搜UML或者SYSML中关于状态机图的定义
简单来说:
entry是进入状态是执行的行为,do是状态中执行的行为,exit是退出状态是执行的行为 晕用了这么久才知道叫状态鸡啊⊙﹏⊙b汗。。。 学习。 出手标。。。个。。。。。。。。。。。。。。记。 mark 上路啰~上路啰~各位吃饱了、喝足了,好上路啰~~咯咯~ 好!!! 顶起 期待下篇 mark,顶楼主~~ mark,相当好 惭愧,用了半天竟然不知是状态机,知识匮乏阿! mark有时间学 mark
同样使用状态机写程序。
这东西,就是个思想。没有固定的代码格式!!!
我个人任务,这种方法最能体现对代码的绝对驾驭性:) to 【29楼】 syuanwang 昙花公公
to 【42楼】 zy473551 饺子
对的。状态机是一种编程思维模式,与具体的语言和实现方式无关。
但是,一个明确提出的格式,配上相应的使用说明,还是很有参考价值的。
举例来说,我以前很多放出来的代码都是状态机的,但是很多人看了以后
仍然不知道状态机应该怎么写;如果我今天不在这里把自己的习惯风格作
一个提炼和总结后汇报给大家,很多人就仍然无法立即体会其中很多东西
来——至少要花费一定的时间,通过一定的代码积累才能总结出来。 mark 明白! to 【28楼】 keepworking
对于你说的这种情况,如果你的应用环境,或者你不想用抢占式的调度器(操作系统)
那么你就要仔细审视目标任务的执行时间,重复执行的时间间隔;有多少个这样的任务,
之类的问题。然后将具体任务细化成状态机,并将这些任务以规定的时间间隔触发即可。
说起来似乎就是这样,但是实际应用还有很多细节需要注意。没有更进一步的应用环境信息
我没有能力在这里三言两语给你说明白——如果说明白了,基本上又可以发一篇文章了……
不过你可以先参考一下我一个帖子里面关于状态机平面的描述。对你应该有启发。 思想不错 傻孩子的授课计划定了吗? 打算几节课花多少时候什么时候把FSM讲完? mark~ 不错 LZ 状态机 打算出书吗?期待... 出书必买!赶紧学习! 正在用visualstate写菜单的飘过 mark状态机 搬个马扎来听听 MARK下。。。 本人才学疏浅,最近着力看 外国人写的关于时间调度的 书,就是看不太懂
傻孩子如果 出书,一定 买一本 学习了,好贴! mark 学习 mark 看到傻孩子,就得赶紧搬凳子 mark 上学的时候一直以为是状态机一种很高级算法,后来无聊在网上浏览了下究竟是啥子东西,才发觉原来自己编出来的程序实质也是状态机的一种,从此不再觉得没什么稀奇了,只是这名字取得有点好。其实这所谓的“状态机”每个人都能自己摸索出来的,编个比较多任务的单片机程序,在不断的追求完美的过程中就会走“状态机”这种方式。楼主似乎比较喜欢把程序模块化,很重视基础性的东西,精益求精,不错。看得出楼主应该是个学校老师吧,呵呵 终于等到傻孩子关于状态机的入门文章了 工业采集+控制+协议传输就很需要这样的状态机来协调,谢谢楼主 学习 MARK MARK mark 来学习的。 <font color=red>MARK; MARK 我一开始学单片机(汇编)就在想怎么能让单片机不要傻等,呵呵,看来我一直有这思想。 mark 状态机 学习学习 先看一点 今天刚用状态机写了个键盘接口。 mark 学习学习 mark MARK 好文,以后借鉴 无态也变态 !犀利~~~ 郑重mark 个人感觉模仿QT中的信号-槽机制来编写更加简洁一点,一个信号可以触发多个槽,多个信号也可以对应一个槽。通过一个函数将信号与槽连接起来。 mark 晚了 前段时间刚捣鼓过Verilog 的状态机,51没用过。回头试试。
thx mark mark mark 其实:学习状态机最好的入门应该是写LCD的菜单程序。
一环扣一环,逐级递进,逐级退出,而且每级里面显示的内容不同,对应的按键效果不同
等等。 mark 学习了 mark 回复【楼主位】Gorgon Meducer傻孩子
-----------------------------------------------------------------------
很好的資料啊 mark!!!!!!!!为啥早点没看到这篇文章~~ MARK 大家好好回忆一下。
当年学数电的时候。
是不是当时就有过这种概念…… mark