【嵌入式】STM32 状态机设计:枚举、位掩码、结构体、变量与宏定义的合理选择

目录

一、先明确:状态机到底要描述什么?

假设一个电机需要实现:

  • 开机
  • 停止
  • 正转 / 反转
  • 高速 / 中速 / 低速
  • 运行次数
  • 出现过流、过温、通信异常时能够记录

那么首先要把这些内容分类。

关键不是:

"我要用 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. 结束语

  1. 本节内容已经全部介绍完毕,希望通过这篇文章,大家对C语言有了更深入的理解和认识。
  2. 感谢各位的阅读和支持,如果觉得这篇文章对你有帮助,请不要吝惜你的点赞和评论 ,这对我们非常重要。再次感谢大家的关注和支持点我关注❤️

相关文章:

相关推荐
AOI小白新手上路8 分钟前
韦东山IMX6ULL快速入门路线
嵌入式硬件
糖糖单片机设计20 分钟前
基于 STM32 的智能净水器系统
stm32·单片机·嵌入式硬件
zcmodeltech24 分钟前
火电厂模型能演示冷态启动和故障处理吗?——基于STM32与Modbus RTU的集控实训控制系统设计
数据结构·stm32·单片机·嵌入式硬件·能源
llilian_1632 分钟前
标准时间间隔发生器应用解决方案 脉冲发生器 时间测量仪
大数据·网络·人工智能·功能测试·单片机·嵌入式硬件·51单片机
桀人1 小时前
STM32之GPIO四种输出模式——P-MOS和N-MOS管的工作原理——双二极管钳位保护电路——上拉电阻
stm32·单片机·嵌入式硬件
HRTOS1 小时前
HRTOS 4.0 DEBUG:面向 8051 实时系统运行状态的轻量级调试组件
单片机·嵌入式硬件·51单片机
LCG元1 小时前
STM32F407 I2S驱动WM8978音频播放实战:PLLI2S时钟整定、DMA双缓冲防爆音与WAV采样率实测
stm32·嵌入式硬件·音视频
ZLG_zhiyuan2 小时前
明明加了隔离,电源为啥还是烧了?
单片机
不怕犯错,就怕不做2 小时前
RK linux在buildroot中如何添加linux命令
linux·驱动开发·嵌入式硬件
咖丨喱2 小时前
【MMC驱动分析】
嵌入式硬件