使用 C 中的函数指针实现有限状态机

问题描述

我正在尝试使用 C 中的函数指针来实现 FSM

This is the state machine which I have to implement

  1. 状态机必须使用函数指针。
  2. 当系统进入某种状态时,例如:Drive,它可以打印当前状态正在驾驶。
  3. 主程序将有一个 API 来设置当前状态,如 SetState(DriveModeEnum)

虽然我已经尝试过我的版本,我想知道是否还有其他优化的方法可以实现这一点?请让我知道你的方法。请注意,逻辑中可能存在一些错误。使用 scanf 假设输入可以来自任何地方,例如 CAN 消息或嵌入式系统中的某些全局变量

试用代码

#include <stdio.h>
#include <conio.h>

enum driveState_t
{
    PARK = 0,NEUTRAL = 1,DRIVE = 2,REVERSE = 3,SIZE
};

enum driveState_t park_to_xxx(enum driveState_t targetState);
enum driveState_t neutral_to_xxx(enum driveState_t targetState);
enum driveState_t drive_to_xxx(enum driveState_t targetState);
enum driveState_t reverse_to_xxx(enum driveState_t targetState);

enum driveState_t (*nextState[])(enum driveState_t) = 
{
    park_to_xxx,neutral_to_xxx,drive_to_xxx,reverse_to_xxx
};

enum driveState_t currentState;

int main()
{
    static enum driveState_t targetState;    
    currentState = PARK;
    targetState = NEUTRAL;
    printf("\n Starting State Machine at currentState %d Target State %d",currentState,targetState);
    while(targetState != 0) // not PARK
    {
        targetState = (*nextState[currentState])(targetState);
    }
    printf("\nExiting State Machine at Currnet State %d\n",currentState);
    return 0;
}


enum driveState_t park_to_xxx(enum driveState_t targetState) // Park is 0
{
    if(targetState == 1)
    {
        printf("\n Park to xxx - Current State is %d,Target State is %d\n",(int)currentState,(int)targetState);
        currentState = targetState;
    }
    else if(targetState == 0)
    {
        printf("\n Park to xxx - Current State will remain %d",(int)targetState);
        currentState = targetState;
    }
    else
    {
        printf("\n Park to xxx - Invalid State,Current State will remain %d",(int)currentState);
        currentState = currentState;
    }
    
    printf("\nEnter next state :");
    scanf("%d",&targetState);
    return targetState;
}

enum driveState_t neutral_to_xxx(enum driveState_t targetState) // Neutral is 1
{
    if((targetState == 0) || (targetState == 2) || (targetState == 3))
    {
        printf("\n Neutral to xxx - Current State is %d,(int)targetState);
        currentState = targetState;
    }
    else if(targetState == 1)
    {
        printf("\n Neutral to xxx - Current State will remain %d",(int)targetState);
        currentState = targetState;
    }
    else
    {
        printf("\n Neutral to xxx - Invalid State,(int)currentState);
        currentState = currentState;
    }
        
    printf("\nEnter next state :");
    scanf("%d",&targetState);
    return targetState;
}

enum driveState_t drive_to_xxx(enum driveState_t targetState) // Drive is 2
{
    if(targetState == 1)
    {
        printf("\n Drive to xxx - Current State is %d,(int)targetState);
        currentState = targetState;
    }
    else if(targetState == 2)
    {
        printf("\n Drive to xxx - Current State will remain %d",(int)targetState);
        currentState = targetState;
    }
    else
    {
        printf("\n Drive to xxx - Invalid State,(int)currentState);
        currentState = currentState;
    }    
    printf("\nEnter next state :");
    scanf("%d",&targetState);
    return targetState;
}

enum driveState_t reverse_to_xxx(enum driveState_t targetState)
{
    if(targetState == 1)
    {
        printf("\n Reverse to xxx - Current State is %d,(int)targetState);
        currentState = targetState;
    }
    else if(targetState == 3)
    {
        printf("\n Reverse to xxx - Current State will remain %d",(int)targetState);
        currentState = targetState;
    }
    else
    {
        printf("\n Reverse to xxx - Invalid State,(int)currentState);
        currentState = currentState;
    }     
    printf("\nEnter next state :");
    scanf("%d",&targetState);
    return targetState;
}

编辑:我的代码是可编译和测试的。如果你也分享你的代码,请你分享编译好的代码。原因是我想比较可以删除或纠正重复代码的部分,并检查可以以任何方式优化代码的程度。我会把这当作我的学习之一。

解决方法

我快速浏览了您的代码,发现一些可以改进的地方。

  1. 您定义了 driveState_t,然后对 targetState 使用幻数(0、1、3 等)。使用枚举,它将使代码更加清晰,并且还可以自我记录。例如,您将不再需要此行的注释:while(targetState != 0) // not PARK

  2. currentState = targetState; 重复了 10 次或更多,当然有更好的方法来解决这个问题,这样你就不会有太多重复的代码(即使它是一个简单的赋值)。

  3. 这也适用于您的代码打印的消息 - 有很多重复,请考虑以某种参数化方式组合它们。对涉及 scanf 调用的用户输入序列执行相同操作。

,

这接近于我认为的 FSM 使用函数指针的规范示例。这是我最喜欢的嵌入式 C 模式之一。

一个建议:

我喜欢做的是使用 state 定义 typedef 枚举并创建一个 struct 来保存状态和指向它的服务例程的指针。然后“StateMachine”变成了这些结构的数组,当您管理状态之间的转换时,代码变得更加清晰:

typedef enum {
  DRIVE,NEUTRAL,REVERSE
} MachineState;

typedef struct {
  MachineState stateName;
  void (*stateFunc)(void);
} StateNode;

void drive();
void neutral();
void reverse();

StateNode* stateMachine = (StateNode*)malloc(NUM_STATES * sizeof(StateNode));
stateMachine[0] = {DRIVE,drive};
stateMachine[1] = {NEUTRAL,neutral};
stateMachine[2] = {REVERSE,reverse};

MachineState currentState = NEUTRAL;

void main() {
  while(1) {
    (*stateMachine[currentState].stateFunc)();
  }
}

void drive() {
  if(something) {
    currentState = NEUTRAL;
  } else if(something else) {
    currentState = DRIVE;
  } else {
    currentState = REVERSE;
  }
}

此外,我认为您可以完全摆脱 targetState。让每个状态自然地决定下一个状态是什么,不需要额外的信息来做到这一点。

您还可以考虑添加一个单独的功能,在一个地方管理所有用户交互。您可以在调用下一个状态例程之前在主事件循环中调用它。

再说一次,总的来说,我认为您的开端非常好。