目录

前言

一、状态

二、事件

三、动作

四、c语言知识

状态机——switch代码

代码详解

一、状态机的状态与事件

二、状态机函数的实现

三、状态机模拟

结语


前言

        在学习状态机之前首先需要掌握一些必要几个概念与知识,这可以帮助我们阅读代码,快速掌握状态机的核心思路

一、状态

凡是实体的(物体的)状态都可以被状态机纳入状态机的状态,如:

灯的状态有亮、灭

机器的状态有待机(空闲)、运行、关机

二、事件

凡是使状态发生改变的原因(条件)都可以称之为事件,如:

要开灯时拨动开关,要关灯时拨回开关

要使机器开机时按下开机键,使机器运行时需要打开进程(下达运行指令),使机器待机时需要关闭运行进程

三、动作

在该状态下的必须要执行动作都可以称之为动作,如:

灯泡亮起灯泡电源接通、电流经过灯泡,灯泡熄灭灯泡电源断开

电脑开机时接通电源、启动屏幕驱动等,电脑运行游戏时屏幕根据游戏调整屏幕刷新率、散热功率,电脑关机时保存数据并按顺序关闭硬件功能

四、c语言知识

        除此之外还需要掌握必要的c语言语法如:枚举类型、for循环、宏定义、typedef关键字、生命周期的概念与用法

注:在传统的状态机教程中还有一个重要的概念——转移,在这里我们可以简单的理解为:旧状态-发生事件-到达新状态的过程,当然其中也伴随者旧动作转变为新动作的过程。这些我们可以在之后实际的代码中深刻的体会到。

状态机——switch代码

我们先来看一下完整的代码

#include <stdio.h>
#include <stdbool.h>

//状态机状态
typedef enum  {
    state_A,
    state_B,
    state_C,
    state_D,
    state_end,
}state_enum;
//状态机事件
typedef enum{
    event_1,
    event_2,
    event_3,
    event_4,
    event_5,
    event_6,
    event_end
}event_enum;

//状态机存储状态的实体
static state_enum s_buff= state_A;
//状态机存储事件的实体
static event_enum e_buff= event_end;

//宏定义,用于快速打印状态,在纯C环环境下模拟
#define PRINTF_STA(a) printf("-->当前状态"#a"\n")


/**
 * 状态机处理函数
 * 根据当前状态和事件转换到下一个状态
 */
void stata_m(void)
{
    switch (s_buff)    // 根据当前状态进行判断
    {
    case  state_A:     // 状态A的处理逻辑
    PRINTF_STA(state_A);  // 打印当前状态A
    // 检查事件1,如果发生则转换到状态C
    if (e_buff == event_1)s_buff=state_C;
    // 检查事件2,如果发生则转换到状态B
    if (e_buff == event_2)s_buff=state_B;
    e_buff= event_end;    // 重置事件为结束状态
        break;
    case  state_B:     // 状态B的处理逻辑
    PRINTF_STA(state_B);  // 打印当前状态B
    // 检查事件3,如果发生则转换到状态C
    if (e_buff == event_3)s_buff=state_C;
    // 检查事件5,如果发生则转换到状态D
    if (e_buff == event_5)s_buff=state_D;
    e_buff= event_end;    // 重置事件为结束状态
        break;
    case  state_C:     // 状态C的处理逻辑
    PRINTF_STA(state_C);  // 打印当前状态C
    // 检查事件4,如果发生则转换到状态D
    if (e_buff == event_4)s_buff=state_D;
    e_buff= event_end;    // 重置事件为结束状态
        break;
    case  state_D:     // 状态D的处理逻辑
    PRINTF_STA(state_D);  // 打印当前状态D
    // 检查事件6,如果发生则转换到状态A
    if (e_buff == event_6)s_buff=state_A;
    e_buff= event_end;    // 重置事件为结束状态
        break;
    default:           // 默认情况,不做任何处理
        break;
    }
}

//用于模拟事件的发生
event_enum e_[7]={
    event_1,
    event_4,
    event_6,
    event_2,
    event_3,
    event_4,
    event_6,
};


/**
 * 主函数
 * 执行一个循环,遍历数组元素并执行状态机函数
 */
void main(void)
{
    // 循环7次,i从0到6
    for (char i = 0; i < 7; i++)
    {
        // 将数组e_的第i个元素赋值给e_buff
        e_buff=e_[i];
        // 打印当前步骤数
        printf("第%d步",i);
        // 调用状态机函数stata_m()
        stata_m();
    }
}

注:这里可能有人会有疑惑——明明是嵌入式状态机为什么没有一点嵌入式代码的影子?原因有两点:一、绝大多数嵌入式编程都是用C/C++实现的,可以在纯C环境下运行就一定可以在嵌入式芯片上运行。二、状态机的本质是处理复杂事件的逻辑,是一个事件的抽象过程,所以我希望在设计状态机时我们可以忽略硬件实现的过程专注于逻辑的实现与复杂事件的处理

状态机——switch代码流程图

代码详解

一、状态机的状态与事件

//状态机状态
typedef enum  {
    state_A,
    state_B,
    state_C,
    state_D,
    state_end,
}state_enum;
//状态机事件
typedef enum{
    event_1,
    event_2,
    event_3,
    event_4,
    event_5,
    event_6,
    event_end
}event_enum;

//状态机存储状态的实体
static state_enum s_buff= state_A;
//状态机存储事件的实体
static event_enum e_buff= event_end;

在这个代码中event_enum、state_enum都是我为了搭建状态机创建的变量,这很好理解,是必要的步骤。s_buff、e_buff是静态全局变量,其中s_buff是专门用来存储当前状态的缓存,是状态转换的凭证;e_buff是外部传递事件给状态机,触发状态转换的载体。

二、状态机函数的实现
/**
 * 状态机处理函数
 * 根据当前状态和事件转换到下一个状态
 */
void stata_m(void)
{
    switch (s_buff)    // 根据当前状态进行判断
    {
    case  state_A:     // 状态A的处理逻辑
    PRINTF_STA(state_A);  // 打印当前状态A
    // 检查事件1,如果发生则转换到状态C
    if (e_buff == event_1)s_buff=state_C;
    // 检查事件2,如果发生则转换到状态B
    if (e_buff == event_2)s_buff=state_B;
    e_buff= event_end;    // 重置事件为结束状态
        break;
    case  state_B:     // 状态B的处理逻辑
    PRINTF_STA(state_B);  // 打印当前状态B
    // 检查事件3,如果发生则转换到状态C
    if (e_buff == event_3)s_buff=state_C;
    // 检查事件5,如果发生则转换到状态D
    if (e_buff == event_5)s_buff=state_D;
    e_buff= event_end;    // 重置事件为结束状态
        break;
    case  state_C:     // 状态C的处理逻辑
    PRINTF_STA(state_C);  // 打印当前状态C
    // 检查事件4,如果发生则转换到状态D
    if (e_buff == event_4)s_buff=state_D;
    e_buff= event_end;    // 重置事件为结束状态
        break;
    case  state_D:     // 状态D的处理逻辑
    PRINTF_STA(state_D);  // 打印当前状态D
    // 检查事件6,如果发生则转换到状态A
    if (e_buff == event_6)s_buff=state_A;
    e_buff= event_end;    // 重置事件为结束状态
        break;
    default:           // 默认情况,不做任何处理
        break;
    }
}

这个状态机转换状态的主体是c语言的语法switch分支语句,其中 PRINTF_STA(state_A);便是当前状态的动作。case  state_A:表示为A状态的处理代码的入口。if (e_buff==event_1)s_buff=state_C; if (e_buff == event_2)s_buff=state_B;表示在A状态下发生了事件1则s_buff变量指向状态C、A状态下发生了事2则s_buff变量指向状态B,这是状态A的转换过程,也被称之为转移。

状态机转换状态的主体、状态的动作、状态的处理代码入口、状态的转换过程都是状态机的重要组成部分,搞定了这些就基本上解决了状态机设计。

在代码中还有这样一句e_buff= event_end;,是用来重置e_buff的,本质是为了只让外部触发的事件只生效一次。可以把它删除,那么在状态发生改变之前,状态机会一直执行当前事件指向的状态。

注:剩下的B、C、D状态都是类似的,可以根据代码自己画出状态流程图,或者画一个UML图

三、状态机模拟
//用于模拟事件的发生
event_enum e_[7]={
    event_1,
    event_4,
    event_6,
    event_2,
    event_3,
    event_4,
    event_6,
};


/**
 * 主函数
 * 执行一个循环,遍历数组元素并执行状态机函数
 */
void main(void)
{
    // 循环7次,i从0到6
    for (char i = 0; i < 7; i++)
    {
        // 将数组e_的第i个元素赋值给e_buff
        e_buff=e_[i];
        // 打印当前步骤数
        printf("第%d步",i);
        // 调用状态机函数stata_m()
        stata_m();
    }
}

用于在纯C环境下模拟状态机的方法,你可以尝试修改e_中的事件

结语

使用switch来搭建状态机是一种简单、直接的方法,非常适合初学者,足够实现简单的事件处理。但它的缺点也很明显,状态之间不易拆分(耦合性高),状态的转移规则不明显。既不方便阅读代码也不好扩展功能。是一个特点鲜明的状态机方法

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐