问题描述
我正在尝试使用 C 中的函数指针来实现 FSM。
虽然我已经尝试过我的版本,我想知道是否还有其他优化的方法可以实现这一点?请让我知道你的方法。请注意,逻辑中可能存在一些错误。使用 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;
}
编辑:我的代码是可编译和测试的。如果你也分享你的代码,请你分享编译好的代码。原因是我想比较可以删除或纠正重复代码的部分,并检查可以以任何方式优化代码的程度。我会把这当作我的学习之一。
解决方法
我快速浏览了您的代码,发现一些可以改进的地方。
-
您定义了
driveState_t
,然后对targetState
使用幻数(0、1、3 等)。使用枚举,它将使代码更加清晰,并且还可以自我记录。例如,您将不再需要此行的注释:while(targetState != 0) // not PARK
行
currentState = targetState;
重复了 10 次或更多,当然有更好的方法来解决这个问题,这样你就不会有太多重复的代码(即使它是一个简单的赋值)。-
这也适用于您的代码打印的消息 - 有很多重复,请考虑以某种参数化方式组合它们。对涉及
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
。让每个状态自然地决定下一个状态是什么,不需要额外的信息来做到这一点。
您还可以考虑添加一个单独的功能,在一个地方管理所有用户交互。您可以在调用下一个状态例程之前在主事件循环中调用它。
再说一次,总的来说,我认为您的开端非常好。