
目录
- 一、先明确:状态机到底要描述什么?
- 二、状态机里的东西怎么分类?
- [三、第一类:状态 → 用 enum](#三、第一类:状态 → 用 enum)
- [四、第二类:正反转、速度 → 也是 enum](#四、第二类:正反转、速度 → 也是 enum)
- [五、第三类:多个独立状态 → 用位掩码](#五、第三类:多个独立状态 → 用位掩码)
- [六、第四类:运行数据 → 普通变量](#六、第四类:运行数据 → 普通变量)
- [七、第五类:整个电机 → struct](#七、第五类:整个电机 → struct)
- 八、所以状态机真正的结构是这样的
- 九、那"状态机"本身怎么运行?
- [十、为什么 `OVER_CURR` 是位掩码,而不是 enum?](#十、为什么
OVER_CURR是位掩码,而不是 enum?) - [十一、不要把所有东西都做成 enum](#十一、不要把所有东西都做成 enum)
- 十二、为什么也不能把状态全部做成位掩码?
- 十三、最后把完整设计放在一起
- 十四、最终记住这套方法
-
-
- [① 先找"互斥状态"](#① 先找“互斥状态”)
- [② 再找"可同时成立的条件"](#② 再找“可同时成立的条件”)
- [③ 再找"运行过程中变化的数值"](#③ 再找“运行过程中变化的数值”)
- [④ 最后把属于同一个对象的数据组织起来](#④ 最后把属于同一个对象的数据组织起来)
- [⑤ 固定配置](#⑤ 固定配置)
- [9. 结束语](#9. 结束语)
- 相关文章:
-
一、先明确:状态机到底要描述什么?
假设一个电机需要实现:
- 开机
- 停止
- 正转 / 反转
- 高速 / 中速 / 低速
- 运行次数
- 出现过流、过温、通信异常时能够记录
那么首先要把这些内容分类。
关键不是:
"我要用 enum 还是位掩码?"
而是先问:
这个数据本质上是什么?
二、状态机里的东西怎么分类?
可以把整个电机看成一个对象:
text
电机 Motor
│
┌────────────┼────────────┐
│ │ │
状态 参数 标志
│ │ │
enum 变量 位掩码
│ │ │
STOP 转速 OVER_CURR
RUNNING 次数 OVER_TEMP
ERROR 时间 COMM_ERR
再用 struct 把这些东西组织起来。
所以:
结构体不是用来替代状态的。结构体是用来"装"状态、参数、标志等数据的。
三、第一类:状态 → 用 enum
例如电机的工作状态:
text
STOP
STARTING
RUNNING
ERROR
这些状态在同一时刻通常只能有一个。
所以:
c
typedef enum
{
MOTOR_STATE_STOP = 0,
MOTOR_STATE_STARTING,
MOTOR_STATE_RUNNING,
MOTOR_STATE_ERROR
} motor_state_t;
定义:
c
motor_state_t state;
例如:
c
state = MOTOR_STATE_RUNNING;
表示:
当前电机处于 RUNNING 状态。
这里用 enum 的原因非常简单:
状态是互斥的,只能是其中一种。
四、第二类:正反转、速度 → 也是 enum
电机方向:
text
正转
反转
也是二选一:
c
typedef enum
{
MOTOR_DIRECTION_FORWARD = 0,
MOTOR_DIRECTION_REVERSE
} motor_direction_t;
速度:
text
低速
中速
高速
也是三选一:
c
typedef enum
{
MOTOR_SPEED_LOW = 0,
MOTOR_SPEED_MID,
MOTOR_SPEED_HIGH
} motor_speed_t;
所以这里:
text
工作状态 → enum
方向状态 → enum
速度状态 → enum
为什么?
因为它们分别属于不同的状态维度。
可以同时存在:
text
RUNNING
+
FORWARD
+
HIGH
所以不是把它们塞进一个 enum,而是:
text
state = RUNNING
direction = FORWARD
speed = HIGH
五、第三类:多个独立状态 → 用位掩码
现在有另外一些信息:
text
电机使能
通信正常
过流
过温
这些和前面的状态不一样。
例如完全可能出现:
text
使能 = 1
通信正常 = 1
过流 = 1
过温 = 0
它们可以同时成立。
这时候使用位掩码:
c
#define MOTOR_FLAG_ENABLE (1U << 0)
#define MOTOR_FLAG_COMM_OK (1U << 1)
#define MOTOR_FLAG_OVER_CURR (1U << 2)
#define MOTOR_FLAG_OVER_TEMP (1U << 3)
然后:
c
uint32_t flags;
设置:
c
flags |= MOTOR_FLAG_ENABLE;
flags |= MOTOR_FLAG_COMM_OK;
发生过流:
c
flags |= MOTOR_FLAG_OVER_CURR;
判断:
c
if (flags & MOTOR_FLAG_OVER_CURR)
{
// 过流
}
这里的核心是:
位掩码不是表示"当前是哪一个状态",而是表示"哪些独立条件当前成立"。
六、第四类:运行数据 → 普通变量
例如:
text
当前转速 = 1500 RPM
运行次数 = 20
运行时间 = 5000 ms
这些都是不断变化的数据。
所以:
c
uint16_t current_rpm;
uint32_t run_count;
uint32_t run_time_ms;
它们就是普通变量。
判断标准:
这个东西是一个具体的数值,并且运行过程中会变化 → 变量。
七、第五类:整个电机 → struct
现在我们已经有:
text
工作状态
方向
速度
Flags
当前转速
运行次数
运行时间
这些都属于同一个电机。
所以用结构体:
c
typedef struct
{
motor_state_t state;
motor_direction_t direction;
motor_speed_t speed;
uint32_t flags;
uint16_t current_rpm;
uint32_t run_count;
uint32_t run_time_ms;
} motor_t;
然后:
c
motor_t motor;
这样整个电机就是一个完整对象。
八、所以状态机真正的结构是这样的
text
motor_t
│
┌───────────────┼────────────────┐
│ │ │
状态 参数 标志
│ │ │
enum 变量 位掩码
│ │ │
STOP/RUN RPM/次数 ENABLE/OVER_CURR
ERROR 时间 COMM_OK/OVER_TEMP
│
├── direction
│ └── enum
│
└── speed
└── enum
这就是最重要的关系。
九、那"状态机"本身怎么运行?
状态机核心是:
text
当前状态
↓
判断条件
↓
执行动作
↓
切换到下一个状态
例如:
text
STOP
│
│ 收到启动命令
↓
STARTING
│
│ 启动完成
↓
RUNNING
│
├── 收到停止命令 → STOP
│
├── 过流 → ERROR
│
└── 过温 → ERROR
代码可以写成:
c
switch (motor.state)
{
case MOTOR_STATE_STOP:
if (start_command)
{
motor.state = MOTOR_STATE_STARTING;
}
break;
case MOTOR_STATE_STARTING:
if (motor_start_complete)
{
motor.state = MOTOR_STATE_RUNNING;
}
break;
case MOTOR_STATE_RUNNING:
if (motor.flags & MOTOR_FLAG_OVER_CURR)
{
motor.state = MOTOR_STATE_ERROR;
}
else if (stop_command)
{
motor.state = MOTOR_STATE_STOP;
}
break;
case MOTOR_STATE_ERROR:
motor_stop();
break;
default:
motor.state = MOTOR_STATE_ERROR;
break;
}
这里就能看到 enum 和位掩码是怎么配合工作的。
十、为什么 OVER_CURR 是位掩码,而不是 enum?
看这一段:
c
if (motor.flags & MOTOR_FLAG_OVER_CURR)
{
motor.state = MOTOR_STATE_ERROR;
}
这里实际上是:
text
flags
↓
发现 OVER_CURR
↓
状态机
↓
RUNNING → ERROR
也就是说:
位掩码负责记录"发生了什么"。
enum 负责决定"状态机现在处于什么状态"。
这是两者非常典型的配合。
十一、不要把所有东西都做成 enum
例如错误地设计成:
c
typedef enum
{
MOTOR_STOP,
MOTOR_RUNNING,
MOTOR_OVER_CURRENT,
MOTOR_OVER_TEMP,
MOTOR_COMM_ERROR
} motor_state_t;
这样很快就会出现问题。
假设:
text
过流
+
过温
+
通信异常
同时存在怎么办?
普通 enum 只能:
text
MOTOR_OVER_CURRENT
或者:
text
MOTOR_OVER_TEMP
但无法自然表达多个条件同时成立。
所以应该拆开:
text
状态:
RUNNING
Flag:
OVER_CURR
OVER_TEMP
COMM_ERROR
然后状态机根据这些 Flag 决定下一步。
十二、为什么也不能把状态全部做成位掩码?
例如:
c
#define MOTOR_STOP (1U << 0)
#define MOTOR_RUNNING (1U << 1)
#define MOTOR_ERROR (1U << 2)
然后:
c
motor_state |= MOTOR_RUNNING;
看起来能用,但会产生一个问题:
可能出现:
text
STOP = 1
RUNNING = 1
ERROR = 1
那到底电机是什么状态?
这三个本来应该互斥。
所以:
互斥状态 → enum 更自然。
十三、最后把完整设计放在一起
c
#define MOTOR_LOW_RPM 500U
#define MOTOR_MID_RPM 1000U
#define MOTOR_HIGH_RPM 1500U
#define MOTOR_MAX_RUN_COUNT 100U
typedef enum
{
MOTOR_STATE_STOP = 0,
MOTOR_STATE_STARTING,
MOTOR_STATE_RUNNING,
MOTOR_STATE_ERROR
} motor_state_t;
typedef enum
{
MOTOR_DIRECTION_FORWARD = 0,
MOTOR_DIRECTION_REVERSE
} motor_direction_t;
typedef enum
{
MOTOR_SPEED_LOW = 0,
MOTOR_SPEED_MID,
MOTOR_SPEED_HIGH
} motor_speed_t;
#define MOTOR_FLAG_ENABLE (1U << 0)
#define MOTOR_FLAG_COMM_OK (1U << 1)
#define MOTOR_FLAG_OVER_CURR (1U << 2)
#define MOTOR_FLAG_OVER_TEMP (1U << 3)
typedef struct
{
motor_state_t state;
motor_direction_t direction;
motor_speed_t speed;
uint32_t flags;
uint16_t current_rpm;
uint32_t run_count;
} motor_t;
最终:
text
电机状态机
│
motor_t
│
┌─────────────────┼─────────────────┐
│ │ │
状态 方向 速度
enum enum enum
│ │ │
STOP/START/RUN 正转/反转 高/中/低
ERROR
│
│
flags
│
位掩码
│
┌─────┼─────┬─────┐
↓ ↓ ↓ ↓
使能 通信 过流 过温
+
运行次数
↓
变量
十四、最终记住这套方法
设计状态机时,不要先想:
"我要用 enum、位掩码还是 struct?"
而应该按照这个顺序:
① 先找"互斥状态"
text
STOP / RUNNING / ERROR
正转 / 反转
高速 / 中速 / 低速
→ enum
② 再找"可同时成立的条件"
text
ENABLE
COMM_OK
OVER_CURR
OVER_TEMP
→ 位掩码
③ 再找"运行过程中变化的数值"
text
RPM
次数
时间
→ 变量
④ 最后把属于同一个对象的数据组织起来
text
state
direction
speed
flags
rpm
count
→ struct
⑤ 固定配置
text
最高转速
最低转速
最大次数
→ #define
所以一个比较规范的状态机,本质上是:
enum描述"我是谁/我处于哪种状态",位掩码描述"有哪些条件同时成立",变量保存"当前数据",struct把整个对象组织起来,状态机再根据这些信息进行状态转换。
9. 结束语
- 本节内容已经全部介绍完毕,希望通过这篇文章,大家对C语言有了更深入的理解和认识。
- 感谢各位的阅读和支持,如果觉得这篇文章对你有帮助,请不要吝惜你的点赞和评论 ,这对我们非常重要。再次感谢大家的关注和支持 !点我关注❤️