Channels ▼
RSS

Parallel

UML Statecharts at $10.99


Declaring State Machine Objects

While state handler functions specify the state machine behavior, and as such are represented in code only (ROM), they require a state machine object (RAM) to remember the current active state and the current event. These state machine objects are represented in QP-nano as C structures derived from the QActive structure, which is provided in QP-nano header file qpn.h. The "derivation of structures" means simply, that you need to literally embed the QActive structure as the first member of the derived structure. Listing Two shows the declaration of the Pelican structure. By a convention, I always name the parent structure member super_.


typedef struct PelicanTag Pelican; /* type definition for Pelican */
    struct PelicanTag {
        QActive super_;              /* derived from QActive */
        uint8_t pedFlashCtr__;    /* private pedestrian flash counter */
    };

Listing Two: Declaration of the Pelican state machine "derived from" QActive structure.

Looking at Listing Two, you should convince yourself that the "derivation of structures" simply means aligning the QActive object at the beginning of every Pelican object in memory. Such alignment allows treating every pointer to Pelican as a pointer to QActive at the same time, so any function designed to work with a pointer to QActive will work correctly if you pass to it a pointer to Pelican. In other words, all functions that QP-nano provides for QActive objects will work just fine for the derived Pelican (or Pedestrian) objects. You can think of this mechanism as single inheritance implemented in C.

Actually, when you look at the declaration of the QActive structure in the qpn.h header file, you will notice, that QActive itself is also derived from another structure QHsm. The QHsm structure represents a Hierarchical State Machine (HSM) and stores the current active state and the current event. QActive adds to this an event queue and a timer, which are both necessary elements of an independently executing state machine. In UML, such independently executing entities are called active objects, which explains the origin of the name QActive. The memory cost of QActive object is 6 to 10 bytes of RAM, depending on the size of the pointer-to-function and the configured size of the timer counter.

Of course, it doesn't matter what else you add in the derived structure after the super_ member. In Listing Two, I've declared additionally the pedFlashCtr__ counter, which the Pelican state machine uses for counting the number of flashes of the "don't walk" signal in the pedsFlash state (Figure 2).

Initializing State Machine Objects

Initialization of hierarchical state machines requires some attention because you need to execute the top-most initial transition, which in general case can be quite involved. For example, the top-most initial transition in the PELICAN crossing statechart (Figure 2) consists of the following steps: (1) entry to operational, (2) initial transition in operational, (3) entry to carsEnabled, (4) initial transition in carsEnabled, (5) entry to carsGreen, (6) initial transition in "carsGreen", and finally (7) entry to carsGreenNoPed. Of course, you want QP-nano to deal with this complexity, which it can actually do, but in order to reuse the already implemented mechanisms, you need to execute the top-most initial transition as a regular transition.

Figure 3 illustrates how you do it. You need to provide a special state initial that handles the top-most initial transition as a regular state transition. The initial state is nested directly in the top state, which is the UML concept that denotes the ultimate root of the state hierarchy. QP-nano defines the top state handler function QHsm_top, which by default "handles" all events by returning NULL pointer. ("Handling" events in the top state means really silently discarding them, per the UML semantics.)

Figure 3: The top-most initial transition for the PELICAN state machine.

Configuring and Starting the Application

After you've coded all state machines, you need to tell QP-nano about them, so that it can start managing the state machines (or actually active objects) as components of the application.

QP-nano executes all active objects in the system in a run-to-completion (RTC) fashion, meaning that each active object completely handles the current event before processing the next one. After each RTC step, QP-nano engages a simple scheduler to decide which active object to execute next. The scheduler makes this decision based on the priority statically assigned to each active object upon the system startup (priority-based scheduling). The scheduler always executes the highest-priority active object that has some events in its event queue.

Listing Three shows how to configure and start the application. You customize QP-nano in the qpn_port.h header file, which contains extensive comments explaining all the options (1). Next, you statically allocate all active objects (2-3) as well as correctly sized event queues for them (4-5). Please note, that the highest-priority active object in the system, such as Pedestrian, might not need an event queue buffer at all, because the single event stored inside the state machine itself might be sufficient.

(1) #include "qpn_port.h"        /* QP-nano port */
    #include "bsp.h"            /* Board Support Package (BSP) */
    #include "pelican.h"       /* application header file */

    /*................................................................*/
(2) static Pelican l_pelican;   /* statically allocate PELICAN object */
(3) static Ped     l_ped;    /* statically allocate Pedestrian object */

(4) static QEvent  l_pelicanQueue[1];        /* PELICAN's event queue */
 /* as the highest-priority task, Ped does not need event queue    */

    /*................................................................*/
    /* CAUTION: the QF_active[] array must be initialized consistently
    * with the priority assignement in pelican.h
    */
(5) QActiveCB const Q_ROM QF_active[] = {
(6)     { (QActive *)0,          (QEvent *)0,    0                    },
(7)     { (QActive *)&l_pelican, l_pelicanQueue, Q_DIM(l_pelicanQueue)},
(8)     { (QActive *)&l_ped,     (QEvent *)0,    0 /* no queue */     }
    };
(9) uint8_t const Q_ROM QF_activeNum =
        (sizeof(QF_active)/sizeof(QF_active[0])) - 1;

    /*................................................................*/
    void main (void) {
(11)    BSP_init();                           /* initialize the board */

(12)    Pelican_init(&l_pelican);  /* take the top-most initial tran. */
(13)    Ped_init(&l_ped, 15);      /* take the top-most initial tran. */

(14)    QF_run();                   /* start executing state machines */
    }
Listing Three: Definition of the state machine objects and the main() function.

Next, at label (5) of Listing Three, you define and initialize a constant array QF_active[], in which you configure all the active objects in the system in the order of their relative priority. QP-nano has been carefully designed not to waste the precious RAM for any information available at compile time. The QF_active[] array is an example of such compile-time information and is allocated in the code-space by the Q_ROM modifier. Q_ROM is a macro that for the IAR 8051 compiler is defined as __code in qpn_port.h. Other Harvard-architecture processors can benefit from this scheme as well. Similarly, at label (9) of Listing Three, you define and initialize another compile-time variable QF_activeNum, which is the total number of active objects actually used in the system. Please note that the zero-element of the QF_active[] is unused, so the number of active objects in the application is the dimension of the QF_active[] array minus 1.

The main() function is remarkably simple. You call the board initialization (11), trigger all initial transitions in the active objects (12-13), and finally transfer the control to the QF_run() function, which implements the QP-nano scheduler running in an endless loop.


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.
 

Video