1234下一页
返回列表 发新帖我要提问本帖赏金: 50.00元(功能说明)

[学习资料] 什么是状态机?怎么设计MCU状态机?

[复制链接]
 楼主| dffzh 发表于 2025-5-29 15:17 | 显示全部楼层 |阅读模式
<
本帖最后由 dffzh 于 2025-5-29 15:25 编辑

#申请原创#
@21小跑堂

1、什么是状态机?
状态机(State Machine, SM),也称为有限状态机(Finite State Machine, FSM),是嵌入式系统软件设计中一种非常重要的用来描述一个系统的行为模型和编程模式。
状态机由一组状态、一组输入事件和一组转换规则组成,系统在任何时刻都处于一种状态,当输入事件发生时,根据当前状态和转换规则,系统可能会转移到另一种状态。
因此,状态机主要包括以下几个基本要素:
状态(State): 系统所处的不同模式或阶段,一个状态机至少要包含两个状态;
事件(Event): 触发状态转换的条件或输入;
转换(Transition): 从一个状态到另一个状态的变化;
动作(Action): 状态转换时执行的操作。
状态机广泛应用于嵌入式系统软件设计中,特别是在需要明确不同状态行为和事件处理的应用场景,包括协议开发、系统界面启动和按键操作等等。
所以,通过使用状态机,开发者可以更容易地构建出可靠、易维护且高效的嵌入式软件系统。

2、怎么设计MCU状态机?
状态机的特性非常适合用于MCU(微控制器)的程序设计,因此接下来作者就通过实际代码向大家阐述一下如何设计MCU状态机。
状态机设计的一般步骤如下所示:
先明确系统状态:
你首先需要分析和明确系统需要哪些状态,常见的但不仅限于以下状态:
初始化状态(INIT)
空闲状态(IDLE)
等待状态(WAIT)
运行状态(RUN)
错误状态(ERROR)
休眠状态(SLEEP)
写状态(WRITE)
读状态(READ)
……
再定义状态转换条件:
当触发什么事件时进行状态切换,常见的但不仅限于以下事件:
定时器超时
外部中断
数据接收完成
等待超时
延时时间到
计数器计数值到
错误、报警和故障发生
……
最后选择状态机实现方式:
常见的MCU状态机实现方式主要包括两种:
第一种方式:switch-case结构
这种方式应该是大家比较熟悉的一种吧,一般在头文件里面通过宏定义或者枚举方式定义好需要的状态机值,然后在源文件里用switch-case结构实现状态机的代码逻辑,示例代码如下:
  1. //header file
  2. /*types of master commands*/
  3. #define FALLBACK              0x5A
  4. #define MASTERIDENT          0x95
  5. #define DEVICEIDENT           0x96
  6. #define DEVICE_STARTUP        0x97
  7. #define PD_OUTOUT_OPERATE   0x98
  8. #define DEVICE_OPERATE        0x99
  9. #define DEVICE_PREOPERATE    0x9A

  10. //source file
  11. switch(slave_state)
  12.         {
  13.                 case FALLBACK:
  14.                 {
  15.                         //to add your code
  16.                         break;
  17.                 }
  18.                 case MASTERIDENT:
  19.                 {
  20.                         break;
  21.                 }
  22.                 case DEVICEIDENT:
  23.                 {
  24.                         break;
  25.                 }
  26.                 case DEVICE_STARTUP:
  27.                 {
  28.                         break;
  29.                 }
  30.                 case PD_OUTOUT_OPERATE:
  31.                 {
  32.                         break;
  33.                 }
  34.                 case DEVICE_PREOPERATE:
  35.                 {
  36.                         break;
  37.                 }
  38.                 case DEVICE_OPERATE:
  39.                 {

  40.                         break;
  41.                 }
  42.                 default:
  43.                 {
  44.                         break;
  45.                 }
  46.         }
这种结构实现的状态机代码看上去比较简单明了易懂,但是如果状态机数量比较多的话,代码看上去就比较臃肿和繁琐。此时,你就可以考虑使用第二种方式来实现状态机。
第二种方式:状态表驱动结构
表驱动方法是一种让你可以在表中查找信息,而不必使用很多的逻辑语句(if-else或switch-case)来把它们找出来的方法。在简单的情况下,逻辑语句确实更简单更直接,但是随着逻辑链的复杂,表驱动法就变得越来越富有吸引力了。21ic论坛上也有专门介绍表驱动法的比较好的文章,比如链接https://bbs.21ic.com/icview-3441826-1-1.html。
那到底怎么用表驱动法实现状态机代码呢?请看代码结构:
  1. //define communication command
  2. #define COMM_SERVER_SCAN 0x0001
  3. #define COMM_SERVER_INFO 0x0002
  4. #define COMM_SERVER_DATA 0x0003
  5. #define COMM_SERVER_DONE 0x0004

  6. //define struct
  7. typedef struct
  8. {
  9.     uint16_t uiCmd; //communication command
  10.     void (*pHandler)(CommServerPack_t *pStr); //function pointer
  11. } CommServerHandler_t;

  12. //define struct array
  13. const CommServerHandler_t g_strCommServerHandler[] =
  14. {
  15.         { COMM_SERVER_SCAN, fCommServerVendorInfo},
  16.         {COMM_SERVER_INFO, fCommServerVendorInfo},
  17.         {COMM_SERVER_DATA, fCommServerVendorInfo},
  18.         {COMM_SERVER_DONE, fCommServerVendorInfo},
  19.         {0, 0},
  20. };

  21. // define communication info handle
  22. void fCommServerVendorInfo(CommServerPack_t *pStr)
  23. {
  24.     //add your code
  25. }

  26. //state machine application
  27. for (i = 0;; i++)
  28.     {
  29.         if (g_strCommServerHandler[i].uiCmd == 0x00)
  30. {
  31.             break;
  32. }
  33.         else if (strRecvPack.uiCmd == g_strCommServerHandler[i].uiCmd)
  34.         {
  35.             g_strCommServerHandler[i].pHandler(&strRecvPack);
  36.             break;
  37.         }
  38.     }
当然,以上的代码结构只是一个比较简单的框架,你可以在此基础上对控制状态机的结构体进行扩展,以满足你的应用需求。
因此,使用表驱动法能够得到代码简洁、执行效率高、扩展性好和可维护性好等好处。
如果系统有多个任务,需要注意以下几点:
为每个功能模块设计独立的状态机;
使用优先级调度确保关键状态及时处理;
注意状态机间的通信和同步。
文章最后,提供一些MCU状态机的设计技巧:
保持状态机简洁:每个状态只做必要的事情;
明确状态转换条件:避免模糊的转换逻辑;
添加默认处理:处理未预料到的事件;
考虑错误恢复:设计错误状态和恢复路径;
记录状态历史:有助于调试复杂问题;
使用状态图工具:如UML状态图辅助设计。
通过合理设计和使用MCU状态机,将会让你的MCU程序结构更加清晰、易于维护和扩展,同时也会提高整套系统的可靠性和响实时性。

打赏榜单

21小跑堂 打赏了 50.00 元 2025-06-19
理由:恭喜通过原创审核!期待您更多的原创作品~~

评论

不错  发表于 2025-6-26 09:10
讲解状态机的概念,以及如何设计状态机,并通过文字和代码讲解了两种状态机的实现。  发表于 2025-6-19 14:10
xch
炒图灵机90年的冷饭。  发表于 2025-5-30 12:13
if-else if-else结构就不考虑在状态机的实现方式上了。  发表于 2025-5-29 15:21
xinxianshi 发表于 2025-5-29 15:50 | 显示全部楼层
是不是也可以用goto
 楼主| dffzh 发表于 2025-5-29 15:56 | 显示全部楼层
本帖最后由 dffzh 于 2025-5-29 16:02 编辑
xinxianshi 发表于 2025-5-29 15:50
是不是也可以用goto
goto语句主要用来实现控制流程的跳转,也可以用来跳转状态机;不过我一般很少用,主要是考虑到goto可能会降低代码的可读性和可维护性。
zjsx8192 发表于 2025-5-30 08:14 | 显示全部楼层
有源码配合就更好了
 楼主| dffzh 发表于 2025-5-30 08:51 | 显示全部楼层
一起学习进步
 楼主| dffzh 发表于 2025-5-30 08:53 | 显示全部楼层
zjsx8192 发表于 2025-5-30 08:14
有源码配合就更好了
关于表驱动法的更详细的说明和源代码,可以看这篇非常优秀的文章:
https://bbs.21ic.com/icview-3441826-1-1.html
chenjun89 发表于 2025-6-4 22:09 来自手机 | 显示全部楼层
状态机设计还是有难度的,还有商业版的状态机软件厂商。
weifeng90 发表于 2025-6-5 08:12 来自手机 | 显示全部楼层
可以参考成熟的状态机软件
 楼主| dffzh 发表于 2025-6-5 08:34 | 显示全部楼层
chenjun89 发表于 2025-6-4 22:09
状态机设计还是有难度的,还有商业版的状态机软件厂商。
是的,真正设计好得有些硬功夫才行。
 楼主| dffzh 发表于 2025-6-5 08:35 | 显示全部楼层
weifeng90 发表于 2025-6-5 08:12
可以参考成熟的状态机软件

是的,可以参考设计。
星空魔法师 发表于 2025-6-20 21:07 | 显示全部楼层
状态机是描述系统行为的模型,由状态、事件和转换规则组成。在MCU设计中,它有助于构建清晰、可靠的程序。
keer_zu 发表于 2025-6-24 07:10 | 显示全部楼层
这两种都不好,还有更好的,可以参考设计模式里的状态机模式
 楼主| dffzh 发表于 2025-6-24 08:48 | 显示全部楼层
keer_zu 发表于 2025-6-24 07:10
这两种都不好,还有更好的,可以参考设计模式里的状态机模式

嗯,没有最好,只有更好。
keer_zu 发表于 2025-6-24 09:24 | 显示全部楼层
 楼主| dffzh 发表于 2025-6-24 10:55 | 显示全部楼层
keer_zu 发表于 2025-6-24 09:24
https://bbs.21ic.com/icview-3464170-1-1.html

好的,值得学习一下,很有用。
hp860629 发表于 2025-6-27 17:31 | 显示全部楼层
状态机是不是只是用在裸奔单片机上啊?跑系统的单片机不用状态机吧?
 楼主| dffzh 发表于 2025-6-28 07:32 | 显示全部楼层
hp860629 发表于 2025-6-27 17:31
状态机是不是只是用在裸奔单片机上啊?跑系统的单片机不用状态机吧?

肯定更需要呢。
ZenithSeeker 发表于 2025-6-29 16:05 | 显示全部楼层
裸奔前后台系统,状态机或者时间片都是不错的方式
hp860629 发表于 2025-6-30 08:39 | 显示全部楼层
ZenithSeeker 发表于 2025-6-29 16:05
裸奔前后台系统,状态机或者时间片都是不错的方式

裸奔一般最多可以做几个任务啊?
mikewalpole 发表于 2025-7-2 11:38 | 显示全部楼层
嵌入式系统软件设计中一种非常重要的用来描述一个系统的行为模型和编程模式。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

109

主题

1164

帖子

22

粉丝
快速回复 在线客服 返回列表 返回顶部

109

主题

1164

帖子

22

粉丝
快速回复 在线客服 返回列表 返回顶部