FiniteStateMachine

.:Intro:.

If you are using mbed for prototyping you might not feel that a full blown state machine is for you. However if you want to get a taste for using a state machine for handling interrupts and timers, you might want to try this out. If you are new to state machines check out http://en.wikipedia.org/wiki/Finite-state_machine .

This implementation of a state machine supports actions on state transition, and actions on state entry and state exit (state entry/exit actions are used if you want to guarantee certain things happen no matter what event occured to enter or exit a state).

.:How To Use:.

Connections:

The demo code class MyInterruptHandler uses mbed pins 5 and 6 to generate the test interrupts, and a timer. By default, the interrupt pins are setup with internal pull-down resistors, so to trigger an interrupt (on the rising edge), the pin can be connected to 5 volts (e.g. the mbed VU pin which provides 5 volts out), by using a switch, or just by momentarily connecting a flying lead from your breadboard.

Demo:

The demo starts in an initial state ("Start") waiting from an interrupt from pin 5 going high. Setting pin 5 momentarily high takes it to the "LedSeq1" state. Entry to the state calls a function to power a LED on the next postion to the right (or wrap round when the end is reached). In this state any Ticker interrupts will keep it in the same state and keeping advancing the LED to the right. The Ticker activates every 0.5 seconds.

In the "LedSeq1" state, setting pin 6 momentarily high takes it to the "LedSeq2" state. Entry to the states calls a function to power a LED on the next position to the left (or wrap round when the end is reached). In this state any Ticker interrupts will keep it in the same state and keeping advancing the LED to the left.

In the "LedSeq2" state, setting pin 5 momentarily high takes it back to the "LedSeq1" state.

Usage:

As you can see below main( ) (main.cpp) just creates a MyInterruptHandler object, and loops around doing nothing except waiting for 5 seconds - just to show it could be doing something else.

#include 
#include "MyInterruptHandler.h"

int main() 
{
    MyInterruptHandler client;

    while (true)
    {
        wait(5);
    }

}

MyInterruptHandler provides all the example code for using the Finite State Machine (FSM). If you want to create your own class to use the FSM you need to provide the following:

  1. an enumeration of the events (clientEvent in this example)
  2. a  processEvent( ) function which takes an event enumeration (e.g. clientEvent)
  3. interrupt callback functions to attach to your interrupts  - intrhandler1( ), intrhandler2( ), and tickerhandler1( ) in this example
  4. state or transition action functions (ledSequence1( ) and ledSequence2( ) in this example)
  5. private data members for the mbed interrupts and timers (interrupt1, interrupt2, and ticker1 below)
// event names
enum clientEvent {intr1, intr2, tickr1};

class MyInterruptHandler
{
public:
    MyInterruptHandler();

    void processEvent(clientEvent eventId);
    
    // interrupt & timer handlers
    void intrhandler1();
    void intrhandler2();
    void tickerhandler1();

    // state or transition actions
    void ledSequence1();
    void ledSequence2();
    
private:
    InterruptIn interrupt1;
    InterruptIn interrupt2;
    Ticker ticker1;
};

Next in MyInterruptHandler.cpp we complete setting up the state machine for use:

  • declare a function pointer for the state/transition actions - typedef void (MyInterruptHandler::* clientActionPtr)()
  • declare string constants for the state names (optional)
  • declare the state definition array - StateDefinition states[] in this example. This is an array of state names, the entry action function pointer, and the exit action function pointer. If there are no action use NULL. Don't forget the total_states value as shown in the example.
  • declare the transitions array - TransitionDefinition transitions[] in this example. This is an array of start state name, event value, action type (this allows you to skip state actions if set to 'noactions', use 'actions' otherwise), function pointer to transition action (use NULL if there is none), and the name of the state you arrive in after the transition. Don't forget to add the total_transitions value as shown in the example.
  • Finally declare the FSM - static FiniteStateMachine itsStateMachine.
#include 
#include "MyInterruptHandler.h"
#include "FSMDefs.h"
#include "FSM.h"
#include "DebugTrace.h"

DebugTrace pc(ON, TO_SERIAL);

typedef void (MyInterruptHandler::* clientActionPtr)( );

// states
const char* S_START     = "Start";
const char* S_LED_SEQ1  = "LedSeq1";
const char* S_LED_SEQ2  = "LedSeq2";

const StateDefinition states[] = 
   {// state,        entry action,                         exit action
       S_START,       NULL,                                NULL,
       S_LED_SEQ1,    &MyInterruptHandler::ledSequence1,   NULL,
       S_LED_SEQ2,    &MyInterruptHandler::ledSequence2,   NULL
   };
const int total_states = sizeof(states)/sizeof(StateDefinition);

// transitions
const TransitionDefinition transitions[] = 
   {// start state,     event,          type        transition action,    end state
       S_START,         (int)intr1,     actions,    NULL,                 S_LED_SEQ1,
       S_LED_SEQ1,      (int)intr2,     actions,    NULL,                 S_LED_SEQ2,
       S_LED_SEQ2,      (int)intr1,     actions,    NULL,                 S_LED_SEQ1,
       S_LED_SEQ1,      (int)tickr1,    actions,    NULL,                 S_LED_SEQ1,
       S_LED_SEQ2,      (int)tickr1,    actions,    NULL,                 S_LED_SEQ2,
   };
const int total_transitions = sizeof(transitions)/sizeof(TransitionDefinition);

// declare a state machine
static FiniteStateMachine itsStateMachine;

// put LEDs on a bus for convenience
BusOut leds(LED1, LED2, LED3, LED4);


MyInterruptHandler::MyInterruptHandler() : interrupt1(p5), interrupt2(p6)
{
    // init state machine
    itsStateMachine.initialize(this, states, total_states, transitions, total_transitions, S_START);
    
    // attach callbacks for interrupts and timer
    interrupt1.rise(this, &MyInterruptHandler::intrhandler1);
    interrupt2.rise(this, &MyInterruptHandler::intrhandler2);
    ticker1.attach(this, &MyInterruptHandler::tickerhandler1, 0.5);
}

void MyInterruptHandler::processEvent(clientEvent event_id)
{
    itsStateMachine.traverse((int)event_id);
}

void MyInterruptHandler::intrhandler1()
{
    processEvent(intr1);
}

void MyInterruptHandler::intrhandler2()
{
    processEvent(intr2);
}

void MyInterruptHandler::tickerhandler1()
{
    processEvent(tickr1);
}

void MyInterruptHandler::ledSequence1()
{
    pc.traceOut("ledSequence1\r\n");
    (0x00 == leds || 0x08 == leds) ? leds = 1 : leds = leds << 1;
}

void MyInterruptHandler::ledSequence2()
{
    pc.traceOut("ledSequence2\r\n");
    (0x00 == leds || 0x01 == leds) ? leds = 8 : leds = leds >> 1;
}

To kick the whole thing off in your code you call the FSM's initialize function as shown, and attach the callback functions to your interrupts and timers. Each interrupt callback function calls processEvent( ) with an event ID. This will trigger the FSM to do an action (if defined).

The FSM libarary code is contrained in FSMDefs.h, FSM.h, State.h, and StateTransition.h. This relies heavily on C++ templates. Basically the FSM has an array of States, and each State has an array of StateTransitions leaving that State.

.:Library Files:.

FSM.h - FiniteStateMachine class declaration + source

FSMDefs.h - template definitions

State.h - State class declaration + source

StateTransition.h - StateTransition class declaration + source


1 comment

16 Jul 2011

This is great, thanks for writing this up. i'm going to try your code out.

You need to log in to post a comment