本文已参与「新人创作礼」活动,一起开启掘金创作之路。
General
Nowadays many applications either small or complex use the finite state machine (FSM). A finite state machine in C is one of the popular design patterns for the embedded system. A finite state machine makes the development easy and smooth.
There are a lot of devices which use event base states, like coffee machine, vending machine, POS devices, door lock system, etc. Some POS devices are used the event table in which events are registered with an event handler. This event handler executes when the relevant events come.
A finite state machine can have multiple states, it can switch from one state to another state on the basis of internal or external input. This input could be timer expiry signal, hardware or software interrupt .. etc. In the finite state machine, the procedure to change one state to another state is called transition.
In this article, I will describe some approaches for implementing a state machine in C.
For example, I am considering an ATM machine and creating its sample state machine in C. The state of the ATM machine could be changed through the coming events. I have mentioned below the sample stats of the ATM machine.
如今,许多小型或复杂的应用程序都使用有限状态机(FSM)。用C语言编写的有限状态机是嵌入式系统的流行设计模式之一。有限状态机使开发变得轻松顺畅。
有许多使用事件基本状态的设备,例如咖啡机,自动售货机,POS设备,门锁系统等。某些POS设备用于事件表,在该表中向事件处理程序注册了事件。相关事件到来时,将执行此事件处理程序。
有限状态机可以具有多个状态,它可以根据内部或外部输入从一种状态切换到另一种状态。此输入可以是计时器到期信号,硬件或软件中断等。在有限状态机中,将一种状态更改为另一种状态的过程称为转换。
在本文中,我将介绍一些在C中实现状态机的方法。
例如,我正在考虑使用ATM机并在C语言中创建其示例状态机。可以通过即将发生的事件来更改ATM机的状态。我在下面提到了ATM机的统计数据示例。
The Sample States of the ATM machine.
- Idle State
- Card Inserted State
- Pin entered State
- Option Selected State
- Amount Entered State
Initially, the ATM machine would be in the Idle state, When a user inserts the card then it change their state and processes the card. After the card processing, ATM again changes their state and ask the user to enter the pin number. When the user entered the pin then it asks for choice ( Balance inquiry, withdrawal, Deposit) and after that change the state and ask to enter the amount and dispatch the entered amount.
- 空闲状态
- 卡插入状态
- 密码输入状态
- 选项选择状态
- 金额输入状态
最初,ATM机将处于空闲状态。当用户插入卡时,它会更改其状态并处理卡。 卡处理后,ATM再次更改其状态,并要求用户输入密码。 当用户输入密码时,它会要求您选择(余额查询,提款,存款),然后更改状态并要求输入金额并分发输入的金额。
【TO-DO:插入图片】
Above figure describe the states of the ATM machine.
上图描述了ATM机的状态。
Recommended steps to create the state machine
- Gather the information which the user wants.
- Analyze the all gather information and sketch the state transition diagram.
- create a code skeleton of the state machine.
- Make sure the transition (changing state) work properly
- Implement all the required information in the code skeleton of the state machine.
- Test the implemented state machine.
- 收集用户想要的信息。
- 分析所有收集的信息并绘制状态转换图。
- 创建状态机的代码框架。
- 确保过渡(状态更改)正常工作
- 在状态机的代码框架中实现所有必需的信息。
- 测试已实现的状态机。
There are two most popular approaches for implementing an event-based state machine in C. The selection of both approaches depends on the requirement and situations.
在C中实现基于事件的状态机的最流行方法有两种。两种方法的选择取决于需求和情况。
- Using the conditional statement (nested switch or nested if-else).
- Using the lookup table
- 使用条件语句(嵌套开关或嵌套if-else)。
- 使用查询表
Using the conditional statement
This is the simplest way to implement the state machine. We have used if-else or the switch case to check the states and triggered the event. If the combination of states and triggered an event match, execute the event handler to serve the service and update the next state. It depends on a requirement that checks first states or the event.
这是实现状态机的最简单方法。 我们使用了if-else或switch case来检查状态并触发了事件。 如果状态组合和触发的事件匹配,则执行事件处理程序以服务该服务并更新下一个状态。 这取决于检查第一状态或事件的要求。
In the below sample code, I am verifying the states first and after that checks the triggered event. If you want you can reverse the procedure that means you can check the event first and after that checks the states.
在下面的示例代码中,我首先验证状态,然后检查触发的事件。 如果需要,可以撤消该过程,这意味着可以先检查事件,然后再检查状态。
#include <stdio.h>
//Different state of ATM machine
typedef enum
{
Idle_State,
Card_Inserted_State,
Pin_Eentered_State,
Option_Selected_State,
Amount_Entered_State
} eSystemState;
//Different type events
typedef enum
{
Card_Insert_Event,
Pin_Enter_Event,
Option_Selection_Event,
Amount_Enter_Event,
Amount_Dispatch_Event
} eSystemEvent;
//Prototype of eventhandlers
eSystemState AmountDispatchHandler(void)
{
return Idle_State;
}
eSystemState EnterAmountHandler(void)
{
return Amount_Entered_State;
}
eSystemState OptionSelectionHandler(void)
{
return Option_Selected_State;
}
eSystemState EnterPinHandler(void)
{
return Pin_Eentered_State;
}
eSystemState InsertCardHandler(void)
{
return Card_Inserted_State;
}
int main(int argc, char *argv[])
{
eSystemState eNextState = Idle_State;
eSystemEvent eNewEvent;
while(1)
{
//Read system Events
eSystemEvent eNewEvent = ReadEvent();
switch(eNextState)
{
case Idle_State:
{
if(Card_Insert_Event == eNewEvent)
{
eNextState = InsertCardHandler();
}
}
break;
case Card_Inserted_State:
{
if(Pin_Enter_Event == eNewEvent)
{
eNextState = EnterPinHandler();
}
}
break;
case Pin_Eentered_State:
{
if(Option_Selection_Event == eNewEvent)
{
eNextState = OptionSelectionHandler();
}
}
break;
case Option_Selected_State:
{
if(Amount_Enter_Event == eNewEvent)
{
eNextState = EnterAmountHandler();
}
}
break;
case Amount_Entered_State:
{
if(Amount_Dispatch_Event == eNewEvent)
{
eNextState = AmountDispatchHandler();
}
}
break;
default:
break;
}
}
return 0;
}
Using the lookup table
A lookup table is also a very good technique to implement the state machine. Using the c language we can implement a lookup table in many ways. In the below section, I am describing some ways to implement the state machine using the function pointer and lookup table.
查表也是实现状态机的一种非常好的技术。 使用c语言,我们可以通过多种方式实现查找表。 在下一节中,我将描述一些使用函数指针和查找表来实现状态机的方法。
A state machine in c using a 2D array
We will create a 2D array containing the function pointers. In which rows and columns represented by the states and events of the finite state machine. This 2D array initializes using the designated initializer.
It is the simplest way to implement the state machine, using this technique we can reduce the length of the code. The most important feature of this technique in the future if you want to add any new states or events, we can easily integrate with it without any huge hurdle.
我们将创建一个包含函数指针的2D数组。 其中行和列由有限状态机的状态和事件表示。 该2D数组使用指定的初始化程序进行初始化。
这是实现状态机的最简单方法,使用这种技术,我们可以减少代码的长度。 将来,如果要添加任何新状态或事件,则此技术的最重要功能是,我们可以轻松地与其集成,而不会遇到任何大障碍。
Let’s see an example,
#include <stdio.h>
//Different state of ATM machine
typedef enum
{
Idle_State,
Card_Inserted_State,
Pin_Eentered_State,
Option_Selected_State,
Amount_Entered_State,
last_State
} eSystemState;
//Different type events
typedef enum
{
Card_Insert_Event,
Pin_Enter_Event,
Option_Selection_Event,
Amount_Enter_Event,
Amount_Dispatch_Event,
last_Event
} eSystemEvent;
//typedef of 2d array
typedef eSystemState (*const afEventHandler[last_State][last_Event])(void);
//typedef of function pointer
typedef eSystemState (*pfEventHandler)(void);
//function call to dispatch the amount and return the ideal state
eSystemState AmountDispatchHandler(void)
{
return Idle_State;
}
//function call to Enter amount and return amount enetered state
eSystemState EnterAmountHandler(void)
{
return Amount_Entered_State;
}
//function call to option select and return the option selected state
eSystemState OptionSelectionHandler(void)
{
return Option_Selected_State;
}
//function call to enter the pin and return pin entered state
eSystemState EnterPinHandler(void)
{
return Pin_Eentered_State;
}
//function call to processing track data and return card inserted state
eSystemState InsertCardHandler(void)
{
return Card_Inserted_State;
}
int main(int argc, char *argv[])
{
eSystemState eNextState = Idle_State;
eSystemEvent eNewEvent;
// Table to define valid states and event of finite state machine
static afEventHandler StateMachine =
{
[Idle_State] ={[Card_Insert_Event]= InsertCardHandler },
[Card_Inserted_State] ={[Pin_Enter_Event] = EnterPinHandler },
[Pin_Eentered_State] ={[Option_Selection_Event] = OptionSelectionHandler},
[Option_Selected_State] ={[Amount_Enter_Event] = EnterAmountHandler},
[Amount_Entered_State] ={[Amount_Dispatch_Event] = AmountDispatchHandler},
};
while(1)
{
// assume api to read the next event
eSystemEvent eNewEvent = ReadEvent();
//Check NULL pointer and array boundary
if( ( eNextState < last_State) && (eNewEvent < last_Event) && StateMachine[eNextState][eNewEvent]!= NULL)
{
// function call as per the state and event and return the next state of the finite state machine
eNextState = (*StateMachine[eNextState][eNewEvent])();
}
else
{
//Invalid
}
}
return 0;
}
One thing needs to remember, here table is sparse, if the states and events are increasing, this technique increases the wastage of the memory. So before creating the state machine diagram we need to account all the things very precisely at the beginning of the design.
需要记住的一件事,这里的表是稀疏的,如果状态和事件在增加,这种技术会增加内存的浪费。 因此,在创建状态机图之前,我们需要在设计开始时就非常准确地说明所有事情。
State machine using an array of structure
This is an elegant way to create a finite state machine. The states and events of the state machine are encapsulated in a structure with a function pointer (Event handler) call at the proper state and event.
这是创建有限状态机的一种优雅方法。 状态机的状态和事件封装在结构中,并在适当的状态和事件处调用函数指针(事件处理程序)。
#include <stdio.h>
//Different state of ATM machine
typedef enum
{
Idle_State,
Card_Inserted_State,
Pin_Eentered_State,
Option_Selected_State,
Amount_Entered_State,
last_State
} eSystemState;
//Different type events
typedef enum
{
Card_Insert_Event,
Pin_Enter_Event,
Option_Selection_Event,
Amount_Enter_Event,
Amount_Dispatch_Event,
last_Event
} eSystemEvent;
//typedef of function pointer
typedef eSystemState (*pfEventHandler)(void);
//structure of state and event with event handler
typedef struct
{
eSystemState eStateMachine;
eSystemEvent eStateMachineEvent;
pfEventHandler pfStateMachineEvnentHandler;
} sStateMachine;
//function call to dispatch the amount and return the ideal state
eSystemState AmountDispatchHandler(void)
{
return Idle_State;
}
//function call to Enter amount and return amount entered state
eSystemState EnterAmountHandler(void)
{
return Amount_Entered_State;
}
//function call to option select and return the option selected state
eSystemState OptionSelectionHandler(void)
{
return Option_Selected_State;
}
//function call to enter the pin and return pin entered state
eSystemState EnterPinHandler(void)
{
return Pin_Eentered_State;
}
//function call to processing track data and return card inserted state
eSystemState InsertCardHandler(void)
{
return Card_Inserted_State;
}
//Initialize array of structure with states and event with proper handler
sStateMachine asStateMachine [] =
{
{Idle_State,Card_Insert_Event,InsertCardHandler},
{Card_Inserted_State,Pin_Enter_Event,EnterPinHandler},
{Pin_Eentered_State,Option_Selection_Event,OptionSelectionHandler},
{Option_Selected_State,Amount_Enter_Event,EnterAmountHandler},
{Amount_Entered_State,Amount_Dispatch_Event,AmountDispatchHandler}
};
//main function
int main(int argc, char *argv[])
{
eSystemState eNextState = Idle_State;
while(1)
{
//Api read the event
eSystemEvent eNewEvent = read_event();
if((eNextState < last_State) && (eNewEvent < last_Event)&& (asStateMachine[eNextState].eStateMachineEvent == eNewEvent) && (asStateMachine[eNextState].pfStateMachineEvnentHandler != NULL))
{
// function call as per the state and event and return the next state of the finite state machine
eNextState = (*asStateMachine[eNextState].pfStateMachineEvnentHandler)();
}
else
{
//Invalid
}
}
return 0;
}