Pages

Tuesday, December 4, 2012

Quantum Leaps’ Arduino State Machine Tutorial - Part 1



Quantum Leaps’ Arduino State Machine Framework (QF) hand coded Tutorial

The purpose of this post is to help others that may wish to evaluate the Quantum Leaps state machine interrupt driven framework for an Arduino project. I’m posting this because it took me some concerted effort to understand how the framework could be used from a bottom up hand coding way, rather than top down Quantum Modeling UML way. Those examples are great, but IMHO are too complex for simple understanding. This in no way is intended to reflect poorly on the great job Quantum Leaps has done in presenting this framework.

To begin, I would strongly recommend you spend some time reading the documents that come with the Quantum Platform download. Without a basic understanding of how the framework operates, this tutorial may be of marginal use in understanding the concepts.

As noted, this example does not use the QM modeling tool. This example is a bare bones hand coded event machine example. When you complete this tutorial you will be able to create an event driven blinky sketch. I plan to evaluate this platform as a single “threaded” asynchronous finite state machine, not as a concurrent state machine promoted by Quantum Leaps, however this example can be used for both.

I will preface this post by saying that, as of this writing, I found discrepancies between the supplied functioning example code (in this case Dining Philosopher Problem) and some of the example code found in the available documentation (specifically “7. Coding Hierarchical State Machines” in the "Fly 'n' Shoot" game). I am not an expert on this platform; I’ve only studied it for a few days, however this code in this example compiles and runs on my Arduino Duemilanove board very nicely, and with a slight modification to slow down the time tick calls, it runs well with the VisualMicroVS2010 IDE plugin debugger.

To begin, install the Quantum Platform library linked here (http://arduino.cc/playground/Code/QP), follow the installation directions and create a new Arduino sketch.

Add the QP library to your project and delete the Loop() method. The Loop() routine is implemented by the framework so you will get a compile error if it remains in your ino file. You will also need to add the Board Support Package files (bsp.h and bsp.cpp) to your project. I started by copying these files from the Dining Philosopher Problem (qp_dpp) example project found in the download.

You will need to remove the reference to dpp.h from the bsp.cpp file, however removing this reference also removes the namespace directive so add “using namespace QP;” under the Arduino.h include reference.  If you compile this you will get an undeclared reference “PAUSE_SIG” error in the ISR(TIMER2_COMPA_vect), so remove the entire if… conditional and leave this method as:
 ----------------------------------------
ISR(TIMER2_COMPA_vect)
{
    QF::TICK(&l_TIMER2_COMPA);  // process all armed time events
}
---------------------------------------- 
The project should compile and show about 3500 bytes of used code. This means the QF engine takes about 3K of code space, which is very efficient for the features it offers!

Next create a global include file for the entire project, I’ll call it globals.h, and add the following section:
----------------------------------------
#ifndef globals_h
#define globals_h

#include "Arduino.h"

using namespace QP;

enum APPSignals
{
    TIME_TICK_SIG = Q_USER_SIG, // time tick for all classes that sign up
    MAX_PUB_SIG,                // the last published signal
};

const uint8_t  LEDPin = 4;

#endif  // globals_h
 ----------------------------------------
Here we are defining a time tick TIME_TICK_SIGnal as an extension of the QP built-in signals by post pending our set onto the end of framework Q_USER_SIG. Also note the “size” definition of MAX_PUB_SIG. We will code the time tick to be sent to all subscribers every time the interrupt runs. Also note that I’ve set the LED pin to 4. Note that QP uses pin 13 LED. While running normally the Arduino board LED glows, but if there is an assertion failure this LED turns full on so it shows brightly. This is a good debug aid.

To code up the time tick, go back to the bsp.cpp file and add the globals.h include ”#include "globals.h"”.  Then add a call to publish the time event on each timer interrupt to any classes that have subscribed to the event. I put this before the “QF::TICK” process but it works equally well, for now, after it.
---------------------------------------- 
ISR(TIMER2_COMPA_vect)
{
    QF::PUBLISH(Q_NEW(QEvt, TIME_TICK_SIG), &l_TIMER2_COMPA);
    QF::TICK(&l_TIMER2_COMPA);   // process all armed time events
}
----------------------------------------
Next build an LED blink class and subscribe to the time tick event. The BlinkLED.h file:

---------------------------------------- 
#include "qp_port.h"
#include "bsp.h"
#include "globals.h"

using namespace QP;

class BlinkLED :
      public QActive
{
      public:
            BlinkLED(void) : QActive(Q_STATE_CAST(&BlinkLED::initial)){}

      protected:
            static QState initial (BlinkLED *me, QEvent const *e);
            static QState active  (BlinkLED *me, QEvent const *e);
};

      Static BlinkLED l_BlinkLED; // sole instance of the active object
      QActive * const AO_BlinkLED = &l_BlinkLED; // ptr to AO_BlinkLED

      static      uint16_t    BLED_tickCount    =     0;
      static      uint16_t    BLED_togglePoint  =     66;
      static      uint8_t     BLED_ledState     =     LOW;

#endif  // BlinkLED_h
---------------------------------------- 

This class was modeled from the QP documentation. The constructor initializes the base class QActive with a starting pointer to “initial()”. I designed this class to only have two methods, initial and active, and both methods must return a type of QActive and must take a pointer to this instance and to an event argument pointer (*e). Although not required for this example, the event arg has to be cast to the type we are expecting before it can be used.

The instance is a static global “l_BlinkLED and the const active object is this reference. This is used by the setup method in ino. The 3 static storage variables are used to count the number of times this object is called during the timer tick interrupt and toggles the LED every “togglePoint” times it is called. I’m not advocating this method for tracking time, it is just for this exercise, and that is, the LED doesn’t blink unless it is called multiple times.

The BlinkLED.cpp
---------------------------------------- 
#include "BlinkLED.h"

QState BlinkLED::initial(BlinkLED *me, QEvent const *)
{
       me->subscribe(TIME_TICK_SIG);
       return Q_TRAN(&BlinkLED::active); // initial transition
}

QState BlinkLED::active(BlinkLED *me, QEvent const *e)
{
      switch (e->sig)
      {
            case Q_INIT_SIG:
                  pinMode(LEDPin, OUTPUT);
                  digitalWrite(LEDPin,BLED_ledState);
                  break;
           
            case TIME_TICK_SIG:
                  if(BLED_tickCount++ > BLED_togglePoint)
                  {
                     BLED_ledState = BLED_ledState == LOW ? HIGH : LOW;
                     BLED_tickCount = 0;
                     digitalWrite(LEDPin,BLED_ledState);
                  }
                  break;
     
            default:
                  break;
     }
     return Q_SUPER(&QHsm::top);
}
---------------------------------------- 

The framework will initialize the QActive base class which in turn sets up the call to initial(…). This methods in turn subscribes to the TIME_TICK_SIGNAL and returns a pointer to the a method the framework is to call on the next subscribed event, which is the active(…) method.

During startup, the active(…) method will be called with Q_ENTRY_SIG, Q_EXIT_SIG and Q_INIT_SIG. We are only interested in the INIT signal event, and use it to initialize class variables etc. After that, each subsequent call, in this example, will be the time tick event. The init calls are made during the Setup(…) routine, whereas the time tick event is initiated in the interrupt routine.

 The last step is getting the framework initialized and running in the ino setup() routine.
----------------------------------------
#include <qp_port.h>
#include "bsp.h"
#include "globals.h"

#include "BlinkLED.h"

static QF_MPOOL_EL(QEvt) l_smlPoolSto[5];         // small event pool
static QSubscrList       l_subscrSto[MAX_PUB_SIG];// subscriber store
static QEvent const     *l_BlinkLEDQueueSto[1];   // One LED blinker


void setup()
{
    BSP_init();
    QF::init();     // initialize the framework and underlying RT kernel

    QF::poolInit(l_smlPoolSto, sizeof(l_smlPoolSto), 
                              sizeof(l_smlPoolSto[0]));

    QF::psInit(l_subscrSto, Q_DIM(l_subscrSto)); 
                                // init publish-subscribe

    uint8_t priority = 1;

    AO_BlinkLED->start
    (
       priority++,      // object priority, 63 is highest no two alike
       l_BlinkLEDQueueSto, // storage queue used by this object
       Q_DIM(l_BlinkLEDQueueSto),  // size of object in queue
       (void *)0, // the stack storage in bytes.
        0U        // the stack size in bytes.
     );
}
----------------------------------------  

Some notes and caveats.

The event (QEvt) pool size is based on two factors, the [size] of the array and the size of the structure referenced. For example the QEvt class is small, so if you derive from this class and add members you need to change the event argument reference, for example QF_MPOOL_EL(MyEventArgStructure). I discovered that when I changed the QEvt arg reference structure and did not reference it in this allocation my application crashed. I do not know yet if this allocation should be the largest of the structures you will use if you define multiple event arg classes (I will attempt to cover this in a subsequent blog post). In this example we don't send anything but the default event so the simple allocation in this example works fine.

The QSubscriberList storage is self-evident, but the queue size for the Blink LED is not. There is a reference to the Quantum Leaps manual that you can purchase that describes how to determine the required storage. Perhaps I can figure this out through trial and error or additional reading but I suspect it might have to do with a preemptive interrupt configuration. The model that this example is running on is the non-preemptive RTC style Run-To-Completion model (a.k.a. Simple Non-Preemptive "Vanilla" Kernel).

The BSP init and QF init are standard platform setup calls as well as the poolInit and psInit.

The active object start(…) call registers the class. What I found is that the priority of the object is not like a thread priority similar to low medium and high, but rather, it must be a unique priority, so each object should have a different priority from 1 the lowest, to 63 the highest. For convenience in adding other classes I have used priority++ to set and increment the priority, but in this example it could have been set to 1. If the priority for two objects is identical, the initialization fails and the framework appears to get into an endless startup loop so it never runs.

One last note, the framework Run(..) method call is not required in the Startup method even though it appears in some QP documentation. It is called when the framework runs it's predefined loop() method.

So hopefully your LED should be blinking while pin 13 LED glows. I followed these exact steps while creating this post so I hope I didn’t forget to write something down.

I plan to build on this project by adding a sensor to demonstrate an object that posts an event to registered subscriber.

Project files can be downloaded here:

"Simple Event Driven Blinky Sketch - Arduino IDE version"
http://code.google.com/p/quantum-leaps-arduino-light/downloads/list





3 comments:

  1. nice article, thanks for sharing!

    ReplyDelete
  2. I'm sorry, but I want to ask about the diagram model (statechart?) where you put it? =( I have run examples program from state-machine.com (till it works good, need about 2 weeks for me, and spending another 2 weeks studying basic OOP and UML), but when I try to build a new project, it always failed to compile =(

    now I see your tutorial, and it seems great! But I don't have any idea about statechart. Is it the same statechart like in Blinky examples? Thankyou!

    ReplyDelete