C语言状态机演示 开关机

发布于:2025-04-01 ⋅ 阅读:(14) ⋅ 点赞:(0)

在嵌入式系统、游戏开发和协议解析等领域,状态机是一种常用且高效的设计模式。它通过将系统的行为划分为不同的状态,并根据输入事件在这些状态之间进行转换,简化了复杂系统的设计和维护。本文将介绍一个由 happyDom 开发的 C 语言状态机框架,并演示如何使用它来构建一个简单的状态机。

框架概述

happyDom/stateMachineC 是一个用 C 语言编写的轻量级状态机框架。它提供了定义状态、事件和状态转换的功能,支持有限状态机(FSM)的创建和管理。该框架的主要特点包括:

  • 简洁易用:API 设计直观,易于集成和使用。

  • 高性能:经过优化,适合资源受限的嵌入式环境。

  • 可扩展性:支持动态添加状态和事件,灵活应对复杂场景。

快速入门

  1. 设计状态跳转图

    作为演示,我们假设我们要实现的状态机如下👇
    Snipaste_2025-03-29_11-23-03

  2. 克隆仓库

    首先,使用 Git 克隆状态机框架的源代码:

    git clone git@gitee.com:DyyYq/stateMachineC.git
    
  3. 包含头文件

    在您的项目中,包含状态机框架的头文件:

    # include "stateMachine.h"
    
  4. 定义状态

    定义一个枚举变量,来表示你的状态机的各个状态,此处我们有 开机状态关机状态 两种状态;为了在开机和状态前处理一些预处理事件,我们增加了 预开机预开机 两个状态;一共是 4 个状态。

    enum myStates{
        // 状态机状态值从 0 开始
        stPreOff = 0,   //预关机状态
        stOff,          //关机状态
        stPreOn,        //预开机状态
        stOn,           //开机状态
    
        stCnt           //这不是一个状态机的状态,但是这个值表征了状态机状态的的数量
    };
    
  5. 定义跳转事件

    状态机需要根据不同的事件进行状态的跳转,我们这个演示中,我们只有一个事件,就是用户的按键事件。我们假充用户按下按键 s 时,事件发生。则我们定义事件函数如下:

    // 状态机跳转事件,这个事件用来检测是否发生了指定的事件,如果发生,返回go,否则返回 await
    smEventResult_t keyPressed(smUnit_t *pSt){
        // 确保开机或者关机完成后,再响应用户的按键事件
        if (pSt->roundCounter > 3 && _kbhit()) {
            if( 's' == _getch()){
                return go;
            }
        }
        return aWait;
    }
    

    为了检测 预关机预开机 两个状态的动作是否完成,我们需要添加一个事件判定。如下👇:

    // 判定预开机和预关机动作是否完成,此处我们简化处理,3s后我们直接判定动作完成
    smEventResult_t preActionCmplt(smUnit_t *pSt){
        if(pSt->roundCounter >= 3){
            return go;
        }
        return aWait;
    }
    
  6. 定义状态动作

    我们对每个状态下的动作,做如下的要求:

    • stOff 状态时,什么也不作。
    • stPreOn 状态时,打印显示读取用户配置的过程。
    • stPreOn 状态时,开机过程持续3s
    • stOn 状态时,持续显示开机的时长。
    • stPreOn 状态时,打印显示保存用户配置的过程。
    • stPreOn 状态时,关机过程持续3s

    为满足以上设计要求,我们定义状态动作如下👇

    //定义 preOff 状态下的 Entry 动作
    void actionEntry_preOff(smUnit_t *pSt){
        printf("保存用户配置中,请稍候!\n");
    }
    
    //定义 preOff 状态下的 Do 动作
    void actionDo_preOff(smUnit_t *pSt){
        if(pSt->roundCounter < 3){
            printf("%d\n", 3 - pSt->roundCounter);
        }else if(pSt->roundCounter == 3){
            printf("用户配置保存完成\n");
        }
    }
    
    // 定义 preOn 状态下的 entry 动作
    void actionEntry_preOn(smUnit_t *pSt){
        printf("加载用户配置中,请稍候!\n");
    }
    
    // 定义 preOn 状态下的 do 动作
    void actionDo_preOn(smUnit_t *pSt){
        if(pSt->roundCounter < 3){
            printf("%d\n", 3 - pSt->roundCounter);
        }else if(pSt->roundCounter == 3){
            printf("用户配置加载完成\n");
        }
    }
    
    // 定义 on 状态下的 Do 动作
    void actionDo_on(smUnit_t *pSt){
        printf("已经开机 %ds\n", pSt->roundCounter);
    }
    
  7. 定义状态机变量,并初始化之,然后将状态动作和事件注册到状态机变量中

    以下代码,我们先定义了一个状态机变量,然后初始化该状态机,然后并我们定义的状态动作和跳转事件注册到状态机中。最后,再以1s为周期轮询运行状态机。

    int main(int argc, char const *argv[]){
        // 定义状态机变量
        stateMachine_t demoSM;
    
        // 初始化状态机,指定状态数量,指定默认状态
        fsm_init(&demoSM, stCnt, stOff);
    
        // 为各状态注册状态动作
        demoSM.actionSignUp(&demoSM, stPreOff, actionEntry_preOff, actionDo_preOff, NULL);
        demoSM.actionSignUp(&demoSM, stPreOn, actionEntry_preOn, actionDo_preOn, NULL);
        demoSM.actionSignUp(&demoSM, stOn, NULL, actionDo_on, NULL);
    
        // 注册状态跳转事件
        demoSM.eventSingUp(&demoSM, stOff, stPreOn, keyPressed);
        demoSM.eventSingUp(&demoSM, stPreOn, stOn, preActionCmplt);
        demoSM.eventSingUp(&demoSM, stOn, stPreOff, keyPressed);
        demoSM.eventSingUp(&demoSM, stPreOff, stOff, preActionCmplt);
    
        while (1){
            demoSM.run(&demoSM);
    
            Sleep(1000);
        }
    }
    

运行效果

本状态机的运行效果如下👇
20250329142854_rec_