Wednesday, December 5, 2012

Quantum Leaps’ Arduino State Machine Tutorial - Part 2

Next I'll demonstrate a simple input device publishing its state to all subscribers. I've added a class and modified the code published in Part 1. The entire project can be downloaded from http://code.google.com/p/quantum-leaps-arduino-light/downloads/list as the qp_SimplePublish.zip file.

To begin, I added a new input pin definition, event type and event arg to the globals.h file:

enum APPSignals
{
    TIME_TICK_SIG = Q_USER_SIG,
    PIR1_SIG,                    // motion detection
    MAX_PUB_SIG
};
...
const uint8_t    PIRPin        =    2;
...
class PirEvtArgs : public QEvt
{        
    public:
        bool    isTrue;
};


A passive infrared component on pin 2 will send out a PIR1_SIG signal. The event will be sent with a PirEventArgs status of isTrue = true if it senses movement and will transition to false when it no longer senses that movement.

 Then I added a PIR01 class to the project.

--- Pir01.h

#ifndef Pir01_h
#define Pir01_h

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

using namespace QP;

class Pir01 :
    public QActive
{
    uint8_t m_Pir01Value;

    public:
        Pir01(void) : 
               QActive(Q_STATE_CAST (&Pir01::initial)),
               m_Pir01Value(0) {}

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

    static Pir01 l_Pir01;
    QActive * const AO_Pir01 = &l_Pir01;

#endif  // Pir01_h

---

Note that I'm using class member variable m_PirValue and during construction it is being set to 0. This is slightly different than the Blink class definition. Note that this member variable will use a few more bytes than a static reference, but appears "cleaner" in implementation.

--- Pir01.cpp

#include "Pir01.h"
#include "BlinkLED.h"


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


QState Pir01::active(Pir01 *me, QEvent const *e)
{
    uint8_t temp = 0;

    switch (e->sig)
    {
        case Q_INIT_SIG:
            pinMode(PIRPin, INPUT);
            me->m_Pir01Value = digitalRead(PIRPin);
            break;
       
        case TIME_TICK_SIG:
            temp = digitalRead(PIRPin);
            if( temp != me->m_Pir01Value )
            {
                PirEvtArgs *ea = Q_NEW(PirEvtArgs, PIR1_SIG);
                ea->isTrue = temp > 0 ? true : false;
                QF::PUBLISH(ea, PIR1_SIG);
                me->m_Pir01Value = temp;
            }
            break;
   
        default:
            break;
     }
     return Q_SUPER(&QHsm::top);
}
---

The operation for active() Init are similar to the LED example. The real difference is that the Pir pin is programmed for input and its initial value is stored in the class member variable me->m_Pir01Value.

However, during the time tick operation the sensor is read and if it has changed it creates an event arg, sets the isTrue boolean and publishes the event. Note that the second argument to PUBLISH, in this case PIR1_SIG is actually a dummy argument in this platform implementation.

Next, the LED blink class was modified to subscribe to the PIR event:

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

and a case for the PIR signal was coded into the active(...) method:

        case PIR1_SIG:
            {
                bool isOn = Q_EVT_CAST(PirEvtArgs)->isTrue;
                if( isOn )
                    me->m_togglePoint = me->m_togglePoint/10;
                else
                    me->m_togglePoint = me->m_togglePoint*10;
            }
            break;

When the PIR_SIG arrives, the boolean isOn is extracted from the *e args using the expected class cast operation, in this case PirEventArgs. To make this example simple, the LED flashes 10 times faster than normal during motion detection. Also note the macro Q_EVT_CAST is defined as:

#define Q_EVT_CAST(class_)   (static_cast<class_ const *>(e))

This means that it expects the the method argument to be "e", as in:

QState BlinkLED::active(BlinkLED *me, QEvent const *e)

So if you changed the signature of the method, you will need to explicitly cast the argument.

Next I added the event pool, class instance storage and start to the ino file:

static QF_MPOOL_EL(PirEvtArgs)    l_medPoolSto[1];
 ...
static QEvent const  *l_Pir01QueueSto[1]; // PIR Store
 ...
QF::poolInit(l_medPoolSto, sizeof(l_medPoolSto),
                  sizeof(l_medPoolSto[0]));

    AO_Pir01->start
    (
        priority++,   
        l_Pir01QueueSto,
        Q_DIM(l_Pir01QueueSto),     
        (void *)0,
        0U
    );

Because the event is published, any class could subscribe to the PIR event and all classes would get the PIR signal, just as all classes get the tick signal that is published in the interrupt. Also note that two event pools are used, and in this case the medium size pool size is set to 1. I believe this will be okay because we do not expect to have more than 1 event in the queue at a time. During the design phase you would need to determine the maximum number of events you could have queued in the store at any one time. If you had 6 input devices that could post a maximum of 1 event each during the time tick phase, then I would expect the store to need enough space for 6 simultaneous events.

No comments:

Post a Comment