QP/C++  7.1.1
Real-Time Embedded Framework
Loading...
Searching...
No Matches
State Machines

The behavior of each active object in QP Framework is specified by means of a hierarchical state machine (UML statechart), which is the most effective and elegant technique of describing event-driven behavior. The most important innovation of UML state machines over classical finite state machines (FSMs) is the hierarchical state nesting. The value of state nesting lies in avoiding repetitions, which are inevitable in the traditional "flat" FSM formalism and are the main reason for the "state-transition explosion" in FSMs. The semantics of state nesting allow substates to define only the differences of behavior from the superstates, thus promoting sharing and reusing behavior.

Note
The Quantum Leaps Application Note A Crash Course in UML State Machines introduces the main state machine concepts backed up by examples.
Application Note: A Crash Course in UML State Machines
The hallmark of the QP Framework implementation of UML state machines is traceability, which is direct, precise, and unambiguous mapping of every state machine element to human-readable, portable, MISRA-compliant C code. Preserving the traceability from requirements through design to code is essential for mission-critical systems, such as medical devices or avionic systems.

This section describes how to implement hierarchical state machines with the QP™/C++ real-time embedded framework, which is quite a mechanical process consisting of just a few simple rules. (In fact, the process of coding state machines in QP™/C++ has been automated by the QM model-based design and code-generating tool.)

To focus this discussion, this section uses the Calculator example, located in the directory qpcpp/examples/workstation/calc. This example has been used in the PSiCC2 book (Section 4.6 "Summary of Steps for Implementing HSMs with QEP")

This section explains how to code the following (marked) elements of a hierarchical state machine:

Fragment of the Calculator hierarchical state machine
Note
This section describes the QHsm state machine implementation strategy, suitable for manual coding of hierarchical state machines in QP™/C++. The alternative QMsm state machine implementation strategy, which QP™/C++ also supports, is not covered in this section, as the code needs to be generated automatically by the QM modeling tool.
Historical Notes
The previous implementation (from QP™/C++ version 3.x through 6.4.x) used state-handlers that were static members of the state machine class, which don't have the this pointer and therefore needed to use the specially supplied "me-> pointer" to access the members of the state machine class. That previous "me-> pointer" state machine implementation style is still supported in QP™/C++ for backwards compatibility with the existing code, but it is not recommended for new designs.

An even earlier QP™/C++ state machine implementation strategy, used in QP™/C++ version 2.x and published in the first edition of the PSiCC book, also implemented state handlers as regular members of the state machine class. However, this earlier implementation relied internally on pointers to member functions, which turned out to be a problematic in practice, because some embedded C++ compilers at the time didn't implement pointers to member functions efficiently. Therefore, the implementation published in the second edition of the PSiCC2 book, switched to static state-handler functions (without the this pointer), which allowed it to use internally the simple pointers to functions that are very efficient.

This latest "no me-> pointer" state machine implementation style in QP™/C++ applies a hybrid approach. Internally, it represents states as simple and efficient pointers to "state-caller" functions. But these "state-caller" functions call "state-handlers" as regular members of the state machine class, which have the this pointer and therefore can access all members of the state machine class naturally (without the need for the "me-> pointer").

State Machine Declaration

Hierarchical state machines are represented in QP™/C++ as subclasses of the QHsm abstract base class, which is defined in the header file qpcpp\include\qep.h. Please note that abstract classes like QP::QMsm, QP::QActive and QP::QMActive are also subclasses of QP::QHsm, so their subclasses also can have state machines.

[1] class Calc : public QP::QHsm {
private:
[2] double m_operand1;
uint8_t m_operator;
public:
[3] Calc() // constructor
[4] : QHsm(&initial) // superclass' constructor
{}
protected:
[5] Q_STATE_DECL(initial);
[6] Q_STATE_DECL(on);
Q_STATE_DECL(ready);
Q_STATE_DECL(result);
Q_STATE_DECL(begin);
. . .
Hierarchical State Machine abstract base class (ABC)
Definition: qep.hpp:296
#define Q_STATE_DECL(state_)
Macro to generate a declaration of a state-handler, state-caller and a state-object for a given state...
Definition: qep.hpp:846

The Q_STATE_DECL() Macro

The Q_STATE_DECL() macro declares two functions for every state: the "state-handler" regular member function and the "state-caller" static member function. So, for example, the declaration of the "on" state Q_STATE_DECL(on) expands to the following two declarations within the Calc class:

[1] QP::QState on_h(QP::QEvt const * const e); // "state-handler"
[2] static QP::QState on(void * const me, QP::QEvt const * const e); // "state-caller"
Event class.
Definition: qep.hpp:163
std::uint_fast8_t QState
Type returned from state-handler functions.
Definition: qep.hpp:212

The two functions have each a different purpose.

See also
State-Handler Call Overhead
Remarks
Because the state-handler functions are regular members of the state machine class, they can be virtual, which allows them to be overridden in the subclasses of a given state machine class. Such inheritance of entire sate machines is an advanced concept, which should be used only in very special circumstances and with great caution. To declare a virtual state-handler, you simply prepend virtual in front of the Q_STATE_DECL() macro, as in the following examples:
virtual Q_STATE_DECL(on);
virtual Q_STATE_DECL(ready);
. . .

State Machine Definition

The definition of the state machine class is the actual code for your state machine. You need to define (i.e., write the code for) all "state-handler" member functions you declared in the state machine class declaration. You don't need to explicitly define the "state-caller" static functions, because they are synthesized implicitly in the macro Q_STATE_DEF()).

One important aspect to realize about coding "state-handler" functions is that they are always called from the QEP event processor. The purpose of the "state-handlers" is to perform your specific actions and then to tell the event processor what needs to be done with the state machine. For example, if your "state-handler" performs a state transition, it executes some actions and then it calls the special tran(<target>) function, where it specifies the <target> state of this state transition. The state-handler then returns the status from the tran() function, and through this return value it informs the QEP event processor what needs to be done with the state machine. Based on this information, the event-processor might decide to call this or other state-handler functions to process the same current event. The following code examples should make all this clearer.

The Q_STATE_DEF() Macro

Every state that has been declared with the Q_STATE_DECL() macro in the state machine class needs to be defined with the Q_STATE_DEF() macro. For example, the state "ready" in the Calculator state machines, the Q_STATE_DEF(Calc, ready) macro expands into the following code:

[1] QP::QState Calc::ready(void * const me, QP::QEvt const * const e) {
return static_cast<Calc *>(me)->ready_h(e);
}
[2] QP::QState Calc::ready_h(QP::QEvt const * const e)

Top-Most Initial Pseudostate

Every state machine must have exactly one top-most initial pseudo-state, which is assumed when the state machine is instantiated in the constructor of the state machine class. By convention, the initial pseudo-state should be always called initial.

This top-most initial pseudo-state has one transition, which points to the state that will become active after the state machine is initialized (through the QHsm::init() function). The following code the definition of the initial pseudo-state for the Calc class:

[1] Q_STATE_DEF(Calc, initial) {
[2] . . . // initialize the members of the state machine class
[3] (void)e; // unused parameter
[4] return tran(&on);
}
#define Q_STATE_DEF(subclass_, state_)
Macro to generate a declaration of a state-handler, state-caller and a state-object for a given state...
Definition: qep.hpp:853

State-Handler Member Functions

Every regular state (including states nested in other states) is also coded with the Q_STATE_DEF() macro. The function body, following the macro, consists of the switch statement that discriminates based on the event signal (e->sig). The following code shows the complete definition of the Calculator "on" state. The explanation section below the code clarifies the main points.

[1] Q_STATE_DEF(Calc, ready) {
[2] QP::QState status_;
[3] switch (e->sig) {
[4] case Q_ENTRY_SIG: {
[5] BSP_message("ready-ENTRY;");
[6] status_ = Q_RET_HANDLED;
[7] break;
}
[8] case Q_EXIT_SIG: {
BSP_message("ready-EXIT;");
[9] status_ = Q_RET_HANDLED;
break;
}
[10] case Q_INIT_SIG: {
BSP_message("ready-INIT;");
[11] status_ = tran(&begin);
break;
}
case DIGIT_0_SIG: {
BSP_clear();
status_ = tran(&zero1);
break;
}
[12] case DIGIT_1_9_SIG: {
BSP_clear();
[13] BSP_insert(Q_EVT_CAST(CalcEvt)->key_code);
[14] status_ = tran(&int1);
break;
}
case POINT_SIG: {
BSP_clear();
BSP_insert((int)'0');
BSP_insert((int)'.');
status_ = tran(&frac1);
break;
}
case OPER_SIG: {
[15] m_operand1 = BSP_get_value();
[16] m_operator = Q_EVT_CAST(CalcEvt)->key_code;
status_ = tran(&opEntered);
break;
}
case CE_SIG: {
BSP_clear();
status_ = tran(&ready);
break;
}
[17] default: {
[18] status_ = super(&on);
break;
}
}
[19] return status_;
}
QSignal sig
signal of the event instance
Definition: qep.hpp:168
#define Q_EVT_CAST(subclass_)
Perform downcast of an event onto a subclass of QEvt class_
Definition: qep.hpp:874
Note
The switch statement code and the single return from the state-handler function is compliant with the MISRA standards.

State-Handler Call Overhead

For embedded system applications, it is always interesting to know the overhead of the implementation used. It turns out that the chosen "state-caller"/"state-handler" implementation is very efficient. The following dis-assembly listing shows the code generated for invocation of a state-handler from the QEP code. The compiler used is IAR C/C++ EWARM 8.32 with Cortex-M target CPU and Medium level of optimization.

r = (*s)(this, e); // invoke state handler s
[1] MOV R1, R7 // place `e` pointer in R1
[2] MOV R0, R6 // place `this` pointer in R0
[3] BLX R4 // branch to the state-caller
state-caller:
[4] B.W MyHsm::state_h() ; branch to state-handler member

The machine code instructions [1-3] are the minimum code to call a function with two parameters via a function pointer (in R4). The single branch instruction [4] represents the only overhead of using the "state-caller" indirection layer. This instruction takes about 4 CPU clock cycles, which is minuscule and typically much better than using a pointer to a C++ member function.


Design