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
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
nice article, thanks for sharing!
ReplyDeleteQP is overcomplicated.
ReplyDeleteI'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 =(
ReplyDeletenow 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!