OS  v7.3.3
Documentation
Finite State Machines (FSM)

Overview

The state machine is one of the fundamental programming patterns that are most commonly used. This approach breaks down the design into a series of finite steps called "states" that perform some narrowly defined actions. Every state can change to another as a consequence of incoming stimuli also called events or signals. This elemental mechanism allows designers to solve complex engineering problems in a very straightforward way. Knowing the importance of this approach in the development of embedded applications, the OS adopts this design pattern as a kernel extension.

In an effort to maximize efficiency and minimize complexity, the extension implements the basic features of the Harel statecharts to represent hierarchical state machines. These features form a proper subset that approaches in a very minimalist way, some of the specifications of the UML statecharts, including:

  • Nested states with proper handling of group transitions and group reactions.
  • Guaranteed execution of entry/exit actions upon entering/exiting states.
  • Straightforward transitions and guards.

In addition to this, the provided implementation also features a powerful coding abstraction including transition tables and timeout signals, allowing to build scalable solutions from simple flat state-machines to complex statecharts.

The provided approach

In QuarkTS, a state machine must be instantiated with an object of type qSM_t. States are represented as instances of the qSM_State_t object.

fsmdesign
FSM module design

One important attribute of the qSM_State_t object is the callback function, which is used to describe the behavior specific to the state. Also, there is a pointer to the parent state to define the nesting of the state and its place in the hierarchical topology. As shown in figure above, a state machine consists of a least one state, the "top-level" state. So concrete state machine are built by adding an arbitrary number of states and defining callback functions. The only purpose of the top state is to provide the root of the hierarchy, so that the highest level can return to the top as their parent state.

Setting up a state machine

Like any other OS object, a Finite State Machine (FSM) must be explicitly initialized before it can be used. The qStateMachine_Setup() API initializes the instance, sets the callback for the top state, sets the initial state and the surrounding callback function.

Subscribing states and defining callbacks

State machines are constructed by composition, therefore, the topology of a state machine is determined upon construction. In this FSM implementation, there is no distinction between composite states(states containing substates) and leaf states. All states are potentially composite. The API qStateMachine_StateSubscribe() should be used to initialize the state and define its position in the topology.

A state callback-functions takes a qSM_Handler_t object as input argument and returns a qSM_Status_t value. An example is shown in the following code snippet:

qSM_Status_t ExampleState_Callback( qSM_Handler_t h ) {
/* TODO: State code */
}
qSM_Status_t
This enumeration defines the built-in state-execution status values that can be used as return value ...
Definition: qfsm.h:175
@ qSM_STATUS_EXIT_SUCCESS
Definition: qfsm.h:179
The callback argument to handle the state-machine dynamics and provide execution information....
Definition: qfsm.h:232

The state callback handler: performing transitions and retrieving data

Because callback functions are methods derived from the state-machine object, they have direct access to some attributes via the qSM_Handler_t argument. The usage of this object it's required to make the FSM moves between states and additionally get extra data. The provided attributes are:

  • qSM_Handler_t::NextState : Desired next state. The application writer should change this field to another state to produce a state transition in the next FSM's cycle. Changing this field will only take effect when the state is executed under user custom-defined signals or in the absence of signals QSM_SIGNAL_NONE.
  • qSM_Handler_t::StartState : Desired nested initial state (substate). The application writer should change this field to set the initial transition if the current state is a parent(or composite state). Changing this field attribute only takes effect when the state is executed under the QSM_SIGNAL_START signal.
  • qSM_Handler_t::Signal (read-only) : Received signal. Can have any of the following values:
  • qSM_Handler_t::SignalData (read-only) : Pointer to the data associated with the signal. For internal signals, including timeout signals, its value will be NULL.
  • qSM_Handler_t::TransitionHistory : Use this option if the transition is to a composite state. This attribute defines how the story should be handled. If this field is not established, qSM_TRANSITION_NO_HISTORY is assumed. The possible values for this attribute are:
    • qSM_TRANSITION_NO_HISTORY : History is not preserved. Composite states will start according to their default transition.
    • qSM_TRANSITION_SHALLOW_HISTORY : History will be kept to allow the return to only the top-most sub-state of the most recent state configuration, which is entered using the default entry rule.
    • qSM_TRANSITION_DEEP_HISTORY : History will be kept to allow full state configuration of the most recent visit to the containing region.
  • qSM_Handler_t::Status (read-only) : The exit(or return) status of the last state. Should be used in the Surrounding callback to perform the corresponding actions for every value. On states callback will take the value qSM_STATUS_NULL
  • qSM_Handler_t::machine (read-only) : A generic pointer to the container state machine.
  • qSM_Handler_t::Data (read-only) : State-machine associated data. If the FSM is running as a task, the associated event data can be queried through this field. (here, a cast to qEvent_t is mandatory).
  • qSM_Handler_t::StateData (read-only) : State associated data. Storage-pointer.

Within the callback function of every state, only one level of dispatching (based on the signal) is necessary. Typically this is archived using a single-level switch statement. Callback functions communicate with the state machine engine through the qSM_Handler_t and the return value of type qSM_Status_t.

The semantic is simple, if a signal is processed, the callback functions returns the status value qSM_STATUS_SIGNAL_HANDLED. Otherwise, it throws the signal for further processing by higher-level states. Also, this returning mechanism can be used to handle exceptions by using the surrounding callback.

Entry/Exit actions and default transitions are also implemented inside the callback function in the response to pre-defined signals. QSM_SIGNAL_ENTRY, QSM_SIGNAL_EXIT and QSM_SIGNAL_START. The state machine generates and dispatches these signals to appropriate handlers upon state transitions.

The example below shows what a status callback should look like including the use of the handler.

qSM_Status_t ExampleState_Callback ( qSM_Handler_t h ) {
switch ( h->Signal ) {
break;
break;
break;
case USER_DEFINED_SIGNAL :
h->NextState = &OtherState; /*transition*/
break;
default:
break;
}
}
#define QSM_SIGNAL_ENTRY
Built-in signal to indicate if the current state has just entered from another state.
Definition: qfsm.h:82
#define QSM_SIGNAL_EXIT
Built-in signal to indicate if the current state has just exit to another state.
Definition: qfsm.h:76
#define QSM_SIGNAL_START
Built-in signal that can be used to set a nested initial-transition (aka default transition) by writi...
Definition: qfsm.h:70
const qSM_SigId_t Signal
Definition: qfsm.h:240
void * NextState
Definition: qfsm.h:234

As shown above, the return value represents the exit status of the state, and it can be handled with an additional surrounding callback function \( S_u \) established at the moment of the FSM setup. The values allowed to return are listed below.

To code initial transitions, the application writer should catch the QSM_SIGNAL_START, perform the required actions and then designate the target sub-state by assigning the StartState attribute of the qSM_Handler_t argument. Regular transitions are coded in a very similar way, except that here, you catch the custom-defined signal and then assign the NextState attribute of the qSM_Handler_t argument. The developer is free to write and control state transitions. Transitions are only allowed under the availability of user custom-defined signals. Regular transitions are not allowed at an entry point (QSM_SIGNAL_ENTRY), exit point (QSM_SIGNAL_EXIT), or a start point (QSM_SIGNAL_START).

Note
User should not target the top state in a transition and use it as transition source either. The only customizable aspect of the top state is the initial transition.

The surrounding callback

It is a checkpoint before and after each state executes its activities through its state callback. The behavior of this surrounding callback must be defined by the programmer.

surrounding
Surrounding callback invocation after and before the current state

The surrounding callback \( S_u \) invocation occurs after and before the current state \( P \). When the surrounding callback is executed, indicates its own checkpoint through the Status attribute of the qSM_Handler_t argument. Unlike a state callback, the surrounding callback should not return anything, thus, the callback should be written as:

void SurroundingCallback_Example( qSM_Handler_t h ) {
switch ( h->Status ) {
/* TODO: before any code */
break;
/* TODO: failure code */
break;
/* TODO: success code */
break;
/* TODO: signal handled code */
break;
case 5: /*user-defined return value*/
/* TODO: used defined*/
break;
default:
/*handle the unexpected*/
break
}
}
@ qSM_STATUS_BEFORE_ANY
Definition: qfsm.h:176
@ qSM_STATUS_EXIT_FAILURE
Definition: qfsm.h:178
@ qSM_STATUS_SIGNAL_HANDLED
Definition: qfsm.h:180
const qSM_Status_t Status
Definition: qfsm.h:241

As you can see in the example below, the surrounding execution case its verified through the FSM handle by reading the Status field.

Adding a state machine as a task

The best strategy to run a FSM is delegating it to a task. For this, the qOS_Add_StateMachineTask() API should be used. Here, the task does not have a specific callback, instead, it will evaluate the active state of the FSM, and later, all the other possible states in response to events that mark their own transition. The task will be scheduled to run every t seconds in qPeriodic mode.

By using this API, the kernel will take care of the FSM by itself, so the usage of qStateMachine_Run() can be omitted.

Now that a task is running a dedicated state machine, the specific task event-information can be obtained in every state callback through the Data field of the qSM_Handler_t argument.

Check the example below:

qSM_Status_t Example_State( qSM_Handler_t h ) {
qEvent_t e = h->Data;
/* Get the event info of the task that owns this state machine*/
switch ( h->Signal ) {
break;
break;
default:
switch ( e->Trigger ) {
/* TODO: Code for this case */
break;
/* TODO: Code for this case */
break;
/* TODO: Code for this case */
break;
default: break;
}
/* TODO: State code */
break;
}
}
@ byQueueCount
When the element-count of the attached queue reaches the specified value. A pointer to the queue will...
Definition: qtasks.h:83
@ byNotificationSimple
When the execution chain does, according to a requirement of asynchronous notification event prompted...
Definition: qtasks.h:65
@ byTimeElapsed
When the time specified for the task elapsed.
Definition: qtasks.h:51
The task argument with all the regarding information of the task execution.
Definition: qtasks.h:162
qTrigger_t Trigger
This member indicates the event source that triggers the task execution. Possible values are describe...
Definition: qtasks.h:180
const void * Data
Definition: qfsm.h:237

A demonstrative example for a FSM

In this example, one press of the button turns on the LED, a second push of the button will make the LED blink and if the button is pressed again, the LED will turn off. Also, our system must turn off the LED after a period of inactivity. If the button hasn't been pressed in the last 10 seconds, the LED will turn off.

fsmled
Flat FSM example with three states

To start the implementation, let's define the necessary global variables...

qTask_t LED_Task; /*The task node*/
qSM_t LED_FSM; /*The state -machine handle*/
qSM_State_t State_LEDOff, State_LEDOn, State_LEDBlink;
A state object.
Definition: qfsm.h:346
A FSM(Finite State Machine) object.
Definition: qfsm.h:387
A task node object.
Definition: qtasks.h:268

Then, we define our states as the flow diagram shown in the figure above.

qSM_Status_t State_LEDOff_Callback( qSM_Handler_t h ) {
switch ( h->Signal ) {
BSP_LED_OFF();
break;
case QSM_SIGNAL_EXIT: case QSM_SIGNAL_START: /*Ignore*/
break;
default:
if ( BUTTON_PRESSED ) {
h->NextState = &State_LEDOn;
}
break;
}
}
/*---------------------------------------------------------------------*/
qSM_Status_t State_LEDOn_Callback( qSM_Handler_t h ) {
static qSTimer_t timeout;
switch ( h->Signal ) {
qSTimer_Set( &timeout, 10.0f ); /*STimer gets armed*/
BSP_LED_ON();
break;
case QSM_SIGNAL_EXIT: case QSM_SIGNAL_START: /*Ignore*/
break;
default:
if ( qSTimer_Expired( &timeout) ) { /*check if the timeout expired*/
h->NextState = &State_LEDOff;
}
if ( BUTTON_PRESSED ) {
h->NextState = &State_LEDBlink;
}
break;
}
}
/*---------------------------------------------------------------------*/
qSM_Status_t State_LEDBlink_Callback( qSM_Handler_t h ) {
static qSTimer_t timeout;
static qSTimer_t blinktime;
switch ( h->Signal ) {
qSTimer_Set( &timeout, 10.0f );
break;
case QSM_SIGNAL_EXIT: case QSM_SIGNAL_START: /*Ignore*/
break;
default:
if ( qSTimer_Expired( &timeout ) || BUTTON_PRESSED ) {
h->NextState = &State_LEDOff;
}
if ( qSTimer_FreeRun( &blinktime, 0.5f ) ) {
BSP_LED_TOGGLE();
}
break;
}
}
qBool_t qSTimer_Expired(const qSTimer_t *const t)
Non-Blocking STimer check.
Definition: qstimers.c:55
qBool_t qSTimer_Set(qSTimer_t *const t, const qTime_t tTime)
Set the expiration time for a STimer. On success, the STimer gets armed immediately.
Definition: qstimers.c:22
qBool_t qSTimer_FreeRun(qSTimer_t *const t, const qTime_t tTime)
Non-Blocking STimer check with automatic arming.
Definition: qstimers.c:36
A STimer(Software Timer) object.
Definition: qstimers.h:32

Finally, we add the task to the scheduling scheme running the dedicated state machine. Remember that you must set up the scheduler before adding a task to the scheduling scheme.

qStateMachine_Setup( &LED_FSM , NULL , &State_LEDOff , NULL , NULL );
qStateMachine_StateSubscribe( &LED_FSM , &State_LEDOff , NULL ,
State_LEDOff_Callback , NULL , NULL );
qStateMachine_StateSubscribe( &LED_FSM , &State_LEDOn , NULL ,
State_LEDOn_Callback , NULL , NULL );
qStateMachine_StateSubscribe( &LED_FSM , &State_LEDBlink , NULL ,
State_LEDBlink_Callback , NULL , NULL );
qOS_Add_StateMachineTask( &LED_Task , &LED_FSM , qHigh_Priority , 0.1f, qEnabled , NULL );
qBool_t qStateMachine_StateSubscribe(qSM_t *const m, qSM_State_t *const s, qSM_State_t *const parent, qSM_StateCallback_t sFcn, qSM_State_t *const init, void *pData)
This function subscribes the FSM instance to a specific state with an associated callback function.
Definition: qfsm.c:563
qBool_t qStateMachine_Setup(qSM_t *const m, qSM_StateCallback_t topFcn, qSM_State_t *const init, qSM_SurroundingCallback_t sFcn, void *pData)
Initializes a Finite State Machine (FSM).
Definition: qfsm.c:524
#define qHigh_Priority
A macro directive to indicate the highest priority level.
Definition: qkernel.h:77
qBool_t qOS_Add_StateMachineTask(qTask_t *const Task, qSM_t *m, const qPriority_t p, const qTime_t t, const qState_t init, void *arg)
Add a task to the scheduling scheme running a dedicated state-machine. The task is scheduled to run e...
Definition: qkernel.c:485
#define qEnabled
A state value that enables a task.
Definition: qtypes.h:167

Sending signals

To communicate within and between state machines or even other contexts, use signals. A signal is a simple value that can be used to abstract an incoming event. In the receiving state machine, a queue or an exclusion variable receives the signal and holds it until the state machine can evaluate it.

When coding state machines, the application writer can benefit from this simple event-abstraction mechanism. On the one hand, there would be a more uniform programming when writing state callbacks and on the other hand, the communication of the state machine from other contexts becomes easier.

To send a signal to a state machine, use the qStateMachine_SendSignal() API. This API can manage their delivery to one of these possible destinations: an exclusion variable or a signal queue:

  • An exclusion variable its a variable with an important distinction, it can only be written if it is empty. The empty situation only happens, if the engine has already propagated the signal within the state machine. If the signal has not yet propagated, the signal sending cannot be carried out.
  • When a signal queue is used, the signal is put into a FIFO structure and the engine takes care of dispatching the signal in an orderly manner. The only situation where the signal cannot be delivered is if the queue is full. This is the preferred destination, as long as there is a previously installed signal queue.
Note
If the signal-queue its available, the qStateMachine_SendSignal() will always select it as destination.
Warning
If a state machine, a task, or another context sends a signal to a full queue, a queue overflow occurs. The result of the queue overflow it that the state machine drops the new signal.

Installing a signal queue

A state machine can have a FIFO queue to allow the delivery of signals from another contexts. If the signal queue is installed, the state-machine engine constantly monitors the queue for available signals. The engine then propagates the signal through the hierarchy until it is processed. To enable this functionality in your state machine, the queue must be installed by using the qStateMachine_InstallSignalQueue() API.

The install operation should be performed after both, the queue and the FSM are correctly initialized by using qQueue_Setup() and qStateMachine_Setup() respectively.

Note
Make sure that queues are enabled in the qconfig.h header file
Attention
When configuring a signal queue with qQueue_Setup(), remember to size it based on the type qSM_Signal_t.
Note
If the state machines its delegated to a task, make sure to install the queue prior to setting up the task. In this way, a kernel connection can be performed between the FSM signal queue and the FSM-task, allowing the OS to catch signals to produce a task event, this prevents the wait of the task for the specified period, resulting in faster handling of incoming signals.

Using a transition table

In this approach, the FSM is coded in tables with the outgoing transitions of every state, where each entry relates signals, actions and the target state. This is an elegant method to translate the FSM to actual implementation as the handling for every state and event combination is encapsulated in the table.

Here, the application writer gets a quick picture of the FSM and the embedded software maintenance is also much more under control. A transition table should be explicitly installed in the target state with the corresponding entries, an n-sized array of qSM_Transition_t elements following the layout described below:

The API qStateMachine_Set_StateTransitions(), should be used to perform the transition table installation to a specific state.

Transition table layout for a state
Signal Id Signal action/guard Target state History mode Signal data
Signal1 NULL StateB 0 NULL
Signal3 DoOnSignal3 StateD 0 &sig3data
... ... ... ... ...
Signal6 NULL StateA 0 NULL

Caveats:

  • State transitions are not limited to the specification of the transition table. A state callback owns the higher precedence to change a state. The application writer can use both, a transition table and direct NextState field manipulation in state callbacks to perform a transition to the FSM.
  • Special care is required when the table grows very large, that is, when there are many invalid state/event combinations, leading to a waste of memory. There is also a memory penalty as the number of states and events grows. The application writer needs to accurately account for this during the initial design. A statechart pattern can be used to improve the design and reduce the number of transition entries.
  • The user is responsible for defining the transitions according to the topology of the state machine. Undefined behaviors can occur if the topology is broken with poorly defined transitions.

Signal actions and guards

Transition tables allow the usage of this feature. When an event-signal is received from the queue, the signal-action, if available, is evaluated before the transition is triggered. This action is user-defined and should be coded as a function that takes a qSM_Handler_t object and returns a value of type qBool_t.

qBool_t Signal_Action( qSM_Handler_t h ) {
/* TODO : Event -signal action*/
return qTrue; /*allow the state transition*/
}
qUINT8_t qBool_t
A type to instantiate an OS boolean variable.
Definition: qtypes.h:139
#define qTrue
A boolean value that represents true/success/On or High.
Definition: qtypes.h:162

The return value is checked after to allow or reject the state transition. The application writer can code a boolean expression to implement statechart guards or perform some pre-transition procedure.

Remarks
If a signal-action returns qFalse, the event-signal is rejected, preventing the state transition to be performed in the calling FSM.
Note
When a transition entry is defined. the signal-action should be located as the third parameter of the entry. Please see the transition layout. A NULL value will act as a NOT-defined, always allowing the state-transition.

FSM Timeout specification

A timeout specification is a mechanism to simplify the notion of time passage inside states. The basic usage model of the timeout signals is as follows:

A timeout specification allocates one or more timer objects. The user relates in a table each specific timeout operation within the state where are they going to operate. So, according to the table, when a state needs to arrange for a timeout, the engine can set or reset the given timer. When the FSM engine detects that the appropriate moment has arrived (a timer expiration occurs), it inserts the timeout signal directly into the recipient's event queue. The recipient then processes the timeout signal just like any other signal.

Given the above explanation, it is evident that for its operation, the state machine requires an installed signal queue. A timeout specification is referenced by an object of type qSM_TimeoutSpec_t and must be installed inside the state machine using the API qStateMachine_InstallTimeoutSpec(). Then, timeout operations can be defined in a table for each state by using the qStateMachine_Set_StateTimeouts().

A timeout specification element is defined as a structure of type qSM_TimeoutStateDefinition_t and should follow this layout:

Timeout specification layout
Timeout value Options

The options for every timeout its a bitwise value that indicates which timeout should be used and the operations than should be performed internally by the state-machine engine. These options can be combined with a bitwise OR and are detailed as follows:

Note
Data associated with timeout signals should be set to NULL. Any other value will be ignored and will be passed as NULL to the FSM handler.
Attention
The user is responsible for writing timeout specifications correctly. Care must be taken that the specifications do not collide between hierarchical states to avoid overwriting operations.
Note
You can increase the number of available timeouts instances by changing the Q_FSM_MAX_TIMEOUTS configuration macro inside qconfig.h.

Demonstrative example using transition tables

The following example shows the implementation of the led-button FSM presented above by using the transition table approach with signal-queue and a timeout specification.

Before getting started, the required variables should be defined:

/*define the FSM application event-signals*/
#define SIGNAL_BUTTON_PRESSED ( (qSM_SigId_t)1 )
#define SIGNAL_TIMEOUT ( QSM_SIGNAL_TIMEOUT( 0 ) )
#define SIGNAL_BLINK ( QSM_SIGNAL_TIMEOUT( 1 ) )
qTask_t LED_Task; /*The task node*/
qSM_t LED_FSM; /*The state-machine handler*/
qSM_State_t State_LEDOff, State_LEDOn, State_LEDBlink;
qQueue_t LEDsigqueue; /*the signal-queue*/
qSM_Signal_t led_sig_stack[ 5 ]; /*the signal-queue storage area*/
qSM_TimeoutSpec_t tm_spectimeout;
/*create the transition tables for every state*/
qSM_Transition_t LEDOff_transitions[] = {
{ SIGNAL_BUTTON_PRESSED, NULL, &State_LEDOn , 0, NULL}
};
qSM_Transition_t LEDOn_transitions[] = {
{ SIGNAL_TIMEOUT, NULL, &State_LEDOff , 0, NULL},
{ SIGNAL_BUTTON_PRESSED, NULL, &State_LEDBlink , 0, NULL}
};
qSM_Transition_t LEDBlink_transitions[] = {
{ SIGNAL_TIMEOUT, NULL, &State_LEDOff , 0, NULL},
{ SIGNAL_BUTTON_PRESSED, NULL, &State_LEDOff , 0, NULL}
};
/*define the timeout specifications */
qSM_TimeoutStateDefinition_t LedOn_Timeouts[] = {
};
qSM_TimeoutStateDefinition_t LEDBlink_timeouts[] = {
};
#define QSM_TSOPT_RST_EXIT
This timeout-specification option its used to specify that the engine should reset the timeout when t...
Definition: qfsm.h:126
#define QSM_TSOPT_INDEX(index)
Timeout-specification option. Should be used to specify the timeout index.
Definition: qfsm.h:100
#define QSM_TSOPT_PERIODIC
This timeout-specification option its used setup the timeout in periodic mode.
Definition: qfsm.h:138
#define QSM_TSOPT_SET_ENTRY
This timeout-specification option its used to specify that the engine should set the timeout when the...
Definition: qfsm.h:108
A Queue object.
Definition: qqueues.h:53
The type to be used as a container variable for a signal.
Definition: qfsm.h:166
A FSM Timeout-specification object.
Definition: qfsm.h:368
This structure should be used to define an item for a timeout-specification table.
Definition: qfsm.h:319
This structure should be used to define an item for a state transition table.
Definition: qfsm.h:413

Then, we define the callback for the states.

qSM_Status_t State_LEDOff_Callback( qSM_Handler_t h ) {
switch ( h->Signal ) {
BSP_LED_OFF();
break;
default:
break;
}
}
/*---------------------------------------------------------------------*/
qSM_Status_t State_LEDOn_Callback( qSM_Handler_t h ) {
switch ( h->Signal ) {
BSP_LED_ON();
break;
default:
break;
}
}
/*---------------------------------------------------------------------*/
qSM_Status_t State_LEDBlink_Callback( qSM_Handler_t h ) {
switch ( h->Signal ) {
case SIGNAL_BLINK:
BSP_LED_TOGGLE();
break;
default:
break;
}
}

In the previous code snippet, we assumed that SIGNAL_BUTTON_PRESSED can be delivered from either the interrupt context or another task.

To finish the setup, a task is added to handle the FSM and then, the transition table can be installed with the other required objects.

qStateMachine_Setup( &LED_FSM, NULL, &State_LEDOff, NULL, NULL );
qStateMachine_StateSubscribe( &LED_FSM, &State_LEDOff, QSM_STATE_TOP, State_LEDOff_Callback, NULL, NULL );
qStateMachine_StateSubscribe( &LED_FSM, &State_LEDOn, QSM_STATE_TOP, State_LEDOn_Callback, NULL, NULL );
qStateMachine_StateSubscribe( &LED_FSM, &State_LEDBlink, QSM_STATE_TOP, State_LEDBlink_Callback, NULL, NULL );
qQueue_Setup( &LEDsigqueue, led_sig_stack, sizeof(qSM_Signal_t), qFLM_ArraySize(led_sig_stack) );
qStateMachine_InstallSignalQueue( &LED_FSM, &LEDsigqueue );
qStateMachine_InstallTimeoutSpec( &LED_FSM, &tm_spectimeout );
qStateMachine_Set_StateTimeouts( &State_LEDOn, LedOn_Timeouts, qFLM_ArraySize(LedOn_Timeouts) );
qStateMachine_Set_StateTimeouts( &State_LEDBlink, LEDBlink_timeouts, qFLM_ArraySize(LEDBlink_timeouts) );
qStateMachine_Set_StateTransitions( &State_LEDOff, LEDOff_transitions, qFLM_ArraySize(LEDOff_transitions) );
qStateMachine_Set_StateTransitions( &State_LEDOn, LEDOn_transitions, qFLM_ArraySize(LEDOn_transitions) );
qStateMachine_Set_StateTransitions( &State_LEDBlink, LEDBlink_transitions, qFLM_ArraySize(LEDBlink_transitions) );
qOS_Add_StateMachineTask( &LED_Task, &LED_FSM, qMedium_Priority, 0.1f, qEnabled, NULL );
#define qFLM_ArraySize(x)
Returns the number of elements in an array x.
Definition: qflm.h:234
#define QSM_STATE_TOP
This macro can be used to reference the top state when using the qStateMachine_StateSubscribe() API.
Definition: qfsm.h:57
qBool_t qStateMachine_InstallSignalQueue(qSM_t *const m, qQueue_t *q)
Install a signal queue to the provided Finite State Machine (FSM).
Definition: qfsm.c:604
qBool_t qStateMachine_Set_StateTransitions(qSM_State_t *const s, qSM_Transition_t *const table, const size_t n)
Installs a table with the outgoing transitions for the supplied state.
Definition: qfsm.c:589
qBool_t qStateMachine_InstallTimeoutSpec(qSM_t *const m, qSM_TimeoutSpec_t *const ts)
Install the Timeout-specification object to target FSM to allow timed signals within states ( See the...
Definition: qfsm.c:794
qBool_t qStateMachine_Set_StateTimeouts(qSM_State_t *const s, qSM_TimeoutStateDefinition_t *tdef, const size_t n)
Setup fixed timeouts for the specified state using a lookup-table.
Definition: qfsm.c:813
qBool_t qQueue_Setup(qQueue_t *const q, void *pData, const size_t itemSize, const size_t itemsCount)
Configures a Queue. Here, the RAM used to hold the queue data pData is statically allocated at compil...
Definition: qqueues.c:32
#define qMedium_Priority
A macro directive to indicate the medium priority level.
Definition: qkernel.h:74

Using the hierarchical approach

In conventional state machine designs, all states are considered at the same level. The design does not capture the commonality that exists among states. In real life, many states handle most transitions in similar fashion and differ only in a few key components. Even when the actual handling differs, there is still some commonality. It is in these situations where the hierarchical designs make the most sense.

A hierarchical state-machine is characterized by having compound states. A composite state is defined as state that has inner states and can be used as a decomposition mechanism that allows factoring of common behaviors and their reuse. And this is the biggest advantage of this design, because it captures the commonality by organizing the states as a hierarchy. The states at the higher level in the hierarchy perform the common handling, while the lower level states inherit the commonality from higher level ones and perform the state specific functions.

Example using a hierarchical FSM

This example takes the "Cruise Control" study case, a real-time system that manages the speed of an automobile based on inputs from the driver.

hsm
Cruise control FSM example

The behavior of this system is state-dependent in that the executed actions correspond not only to the driver input, but also to the current state of the system and with the status of the engine and the brake. The figure above illustrates the modeling of this system with the "Automated Control" state acting as composite.

Before getting started, the required user-defined signals, variables, and entries of the transition table should be defined:

#define SIGNAL_ENGINE_ON ( (qSM_SigId_t)(1) )
#define SIGNAL_ACCEL ( (qSM_SigId_t)(2) )
#define SIGNAL_RESUME ( (qSM_SigId_t)(3) )
#define SIGNAL_OFF ( (qSM_SigId_t)(4) )
#define SIGNAL_BRAKE_PRESSED ( (qSM_SigId_t)(5) )
#define SIGNAL_CRUISE ( (qSM_SigId_t)(6) )
#define SIGNAL_REACHED_CRUISING ( (qSM_SigId_t)(7) )
#define SIGNAL_ENGINE_OFF ( (qSM_SigId_t)(8) )
qTask_t CruiseControlTask;
qSM_t Top_SM;
/*highest level states*/
qSM_State_t state_idle, state_initial, state_cruisingoff, state_automatedcontrol;
/*states inside the state_automatedcontrol*/
qSM_State_t state_accelerating, state_cruising, state_resuming;
qQueue_t top_sigqueue;
qSM_Signal_t topsm_sig_stack[ 10 ];
/*=======================================================================*/
/* TRANSITION TABLES */
/*=======================================================================*/
qSM_Transition_t idle_transitions[] =
{
{ SIGNAL_ENGINE_ON, SigAct_ClearDesiredSpeed, &state_initial , 0, NULL }
};
qSM_Transition_t initial_transitions[] =
{
{ SIGNAL_ACCEL, SigAct_BrakeOff, &state_accelerating , 0, NULL }
};
qSM_Transition_t accel_transitions[] =
{
{ SIGNAL_CRUISE, NULL, &state_cruising , 0, NULL }
};
qSM_Transition_t cruising_transitions[] =
{
{ SIGNAL_OFF, NULL, &state_cruisingoff , 0, NULL },
{ SIGNAL_ACCEL, NULL, &state_accelerating , 0, NULL }
};
qSM_Transition_t resuming_transitions[] =
{
{ SIGNAL_ACCEL, NULL, &state_accelerating , 0, NULL }
};
qSM_Transition_t cruisingoff_transitions[] =
{
{ SIGNAL_ACCEL, SigAct_BrakeOff, &state_accelerating , 0, NULL },
{ SIGNAL_RESUME, SigAct_BrakeOff, &state_resuming , 0, NULL },
{ SIGNAL_ENGINE_OFF, NULL, &state_idle , 0, NULL }
};
qSM_Transition_t automated_transitions[] =
{
{ SIGNAL_BRAKE_PRESSED, NULL, &state_cruisingoff , 0, NULL }
};
/*---------------------------------------------------------------------*/

Then, signal-actions and state callbacks are later defined

/*=======================================================================*/
/* EVENT-SIGNAL ACTIONS AND GUARDS */
/*=======================================================================*/
qBool_t SigAct_ClearDesiredSpeed( qSM_Handler_t h ) {
(void)h;
Speed_ClearDesired();
return qTrue;
}
/*---------------------------------------------------------------------*/
qBool_t SigAct_BrakeOff( qSM_Handler_t h ) {
(void)h; /*unused*/
return ( BSP_BREAK_READ() == OFF ) ? qTrue : qFalse; /*check guard*/
}
/*=======================================================================*/
/* STATE CALLBACK FOR THE TOP FSM */
/*=======================================================================*/
qSM_Status_t state_top_callback( qSM_Handler_t h ) {
switch ( h->Signal ) {
break;
break;
}
return RetVal;
}
/*=======================================================================*/
/* CALLBACKS FOR THE STATES ABOVE TOP */
/*=======================================================================*/
qSM_Status_t state_idle_callback( qSM_Handler_t h ) {
/*TODO : state activities*/
return qSM_EXIT_SUCCESS;
}
/*---------------------------------------------------------------------*/
qSM_Status_t state_initial_callback( qSM_Handler_t h ) {
/*TODO : state activities*/
return qSM_EXIT_SUCCESS;
}
/*---------------------------------------------------------------------*/
qSM_Status_t state_cruisingoff_callback( qSM_Handler_t h ) {
/*TODO : state activities*/
return qSM_EXIT_SUCCESS;
}
/*---------------------------------------------------------------------*/
qSM_Status_t state_automatedcontrol_callback( qSM_Handler_t h ) {
/*TODO : state activities*/
return qSM_EXIT_SUCCESS;
}
/*=======================================================================*/
/* STATE CALLBACKS FOR THE AUTOMATED CONTROL FSM */
/*=======================================================================*/
qSM_Status_t state_accelerating_callback( qSM_Handler_t h ) {
switch ( h->Signal ) {
Speed_SelectDesired();
break;
default:
Speed_Increase();
break;
}
return qSM_EXIT_SUCCESS;
}
/*---------------------------------------------------------------------*/
qSM_Status_t state_resuming_callback( qSM_Handler_t h ) {
Cruising_Resume();
return qSM_EXIT_SUCCESS;
}
/*---------------------------------------------------------------------*/
qSM_Status_t state_cruising_callback( qSM_Handler_t h ) {
Speed_Maintain();
return qSM_EXIT_SUCCESS;
}
#define qFalse
A boolean value that represents false/failure/Off or Low.
Definition: qtypes.h:157

Finally, the dedicated task for the FSM and related objects are configured.

qStateMachine_Setup( &Top_SM, state_top_callback, &state_idle, NULL, NULL );
/*subscribe to the highest level states*/
qStateMachine_StateSubscribe( &Top_SM, &state_idle, QSM_STATE_TOP, state_idle_callback, NULL, NULL );
qStateMachine_StateSubscribe( &Top_SM, &state_initial, QSM_STATE_TOP, state_initial_callback, NULL, NULL );
qStateMachine_StateSubscribe( &Top_SM, &state_cruisingoff, QSM_STATE_TOP, state_cruisingoff_callback, NULL, NULL );
qStateMachine_StateSubscribe( &Top_SM, &state_automatedcontrol, QSM_STATE_TOP, state_automatedcontrol_callback, NULL, NULL );
/*subscribe to the states within the state_automatedcontrol*/
qStateMachine_StateSubscribe( &Top_SM, &state_accelerating, &state_automatedcontrol, state_accelerating_callback, NULL, NULL );
qStateMachine_StateSubscribe( &Top_SM, &state_resuming, &state_automatedcontrol, state_resuming_callback, NULL, NULL );
qStateMachine_StateSubscribe( &Top_SM, &state_cruising, &state_automatedcontrol, state_cruising_callback, NULL, NULL );
qQueue_Setup( &top_sigqueue, topsm_sig_stack, sizeof(qSM_Signal_t), qFLM_ArraySize(topsm_sig_stack) );
qStateMachine_InstallSignalQueue( &Top_SM, &top_sigqueue );
qStateMachine_Set_StateTransitions( &state_idle, idle_transitions, qFLM_ArraySize(idle_transitions) );
qStateMachine_Set_StateTransitions( &state_initial, initial_transitions, qFLM_ArraySize(initial_transitions) );
qStateMachine_Set_StateTransitions( &state_cruisingoff, cruisingoff_transitions, qFLM_ArraySize(cruisingoff_transitions) );
qStateMachine_Set_StateTransitions( &state_automatedcontrol, automated_transitions, qFLM_ArraySize(automated_transitions) );
qStateMachine_Set_StateTransitions( &state_accelerating, accel_transitions, qFLM_ArraySize(accel_transitions) );
qStateMachine_Set_StateTransitions( &state_resuming, resuming_transitions, qFLM_ArraySize(resuming_transitions) );
qStateMachine_Set_StateTransitions( &state_cruising, cruising_transitions, qFLM_ArraySize(cruising_transitions) );
qOS_Add_StateMachineTask( &CruiseControlTask, &Top_SM, qMedium_Priority, 0.1f, qEnabled, NULL );

Example with history pseudo-states

State transitions defined in high-level composite states often deal with events that require immediate attention; however, after handling them, the system should return to the most recent substate of the given composite state. UML statecharts address this situation with two kinds of history pseudostates: "shallow history" and "deep history"( denoted as the circled H and H* icon respectively in the figure).

fsmhist
Example with history pseudo-states
  • Shallow history : A transition to the shallow history state in a composite state invokes the last state that was active, at the same depth as the history state itself, prior to the most recent exit of the composite state.
  • Deep history : A transition to the deep history state within a composite state invokes the state that was active, immediately before the most recent exit of the composite state. The last active state can be nested at any depth.

Here, the way to specify this type of transition in QuarkTS is very straightforward, you only need to assign the history-mode in the last entry of the transition as shown below:

#define SIGNAL_A ( (qSM_SigId_t)(1) )
#define SIGNAL_B ( (qSM_SigId_t)(2) )
#define SIGNAL_C ( (qSM_SigId_t)(3) )
#define SIGNAL_D ( (qSM_SigId_t)(4) )
#define SIGNAL_E ( (qSM_SigId_t)(5) )
#define SIGNAL_F ( (qSM_SigId_t)(6) )
qQueue_t sigqueue;
qSM_Signal_t topsm_sig_stack[ 10 ];
qSM_t super;
qSM_State_t state1 , state2 , state3 , state4 , state5 , state6;
qSM_Transition_t state1_transitions [] = {
{ SIGNAL_A , NULL , &state2 , qSM_TRANSITION_SHALLOW_HISTORY, NULL },
{ SIGNAL_B , NULL , &state2 , qSM_TRANSITION_DEEP_HISTORY, NULL },
{ SIGNAL_C , NULL , &state2 , qSM_TRANSITION_NO_HISTORY, NULL }
};
qSM_Transition_t state2_transitions [] = {
{ SIGNAL_D , NULL , &state1 , 0, NULL }
};
qSM_Transition_t state3_transitions [] = {
{ SIGNAL_E , NULL , &state4 , 0, NULL }
};
qSM_Transition_t state5_transitions [] = {
{ SIGNAL_F , NULL , &state6 , 0, NULL }
};
@ qSM_TRANSITION_DEEP_HISTORY
Definition: qfsm.h:191
@ qSM_TRANSITION_SHALLOW_HISTORY
Definition: qfsm.h:190
@ qSM_TRANSITION_NO_HISTORY
Definition: qfsm.h:189

Next, the configuration and topology of the state-machine is presented, including the default transitions (the small circles filled with black). Please do not forget to define the callbacks for each state.

qStateMachine_Setup( &super , state_top_callback , &state1 , NULL , NULL );
qStateMachine_StateSubscribe( &super , &state1 , QSM_STATE_TOP , state1_callback , NULL , NULL );
qStateMachine_StateSubscribe( &super , &state2 , QSM_STATE_TOP , state2_callback , &state3 , NULL );
qStateMachine_StateSubscribe( &super , &state3 , &state2 , state3_callback , NULL , NULL );
qStateMachine_StateSubscribe( &super , &state4 , &state2 , state4_callback , &state5 , NULL );
qStateMachine_StateSubscribe( &super , &state5 , &state4 , state5_callback , NULL , NULL );
qStateMachine_StateSubscribe( &super , &state6 , &state4 , state6_callback , NULL , NULL );
qQueue_Setup( &sigqueue , topsm_sig_stack , sizeof(qSM_Signal_t), qFLM_ArraySize(topsm_sig_stack ) );
qStateMachine_InstallSignalQueue( &super , &sigqueue );
qStateMachine_Set_StateTransitions( &state1 , state1_transitions , qFLM_ArraySize( state1_transitions ) );
qStateMachine_Set_StateTransitions( &state2 , state2_transitions , qFLM_ArraySize( state2_transitions ) );
qStateMachine_Set_StateTransitions( &state3 , state3_transitions , qFLM_ArraySize( state3_transitions ) );
qStateMachine_Set_StateTransitions( &state5 , state5_transitions , qFLM_ArraySize( state5_transitions ) );
qOS_Add_StateMachineTask( &SMTask , &super , qMedium_Priority , 0.1f, qEnabled , NULL );