Jump to content
zeke

State Machine Example Code

Recommended Posts

Hi Guys,

 

I've been thinking about the topic of state machines lately and I thought I'd try to whip up a posting on my approach to state machines.

 

I've written a state machine for my client but I can't post that without a lot of changes. So I'm going to convert it into a traffic light state machine example.

 

Hmmm... I just realized that I should talk about what a state machine is but I haven't got anything prepared yet. So, for now, I'll assume you know what it is.

 

Here's my definitions and declarations:

 

#include   // add header file for MSP430G2231 device.
#include 

#define VERSION(id, rev, tag) const char id##_version[] = "@(#)Id:" #id " Ver:" rev " Released by:" tag " Built:" __DATE__ " " __TIME__


#define INPUT         0
#define OUTPUT        1
#define TRUE          1
#define FALSE         0
#define HIGH          1
#define LOW           0
#define TOGGLE        2


#define nothing1     BIT0  // P1.0, 0x01, 0b0000 0001  
#define onGREENLED    BIT1  // P1.1, 0x02, 0b0000 1000
#define onREDLED      BIT2  // P1.2, 0x04, 0b0001 0000
#define nothing2      BIT3  // P1.3, 0x08, 0b0000 0010
#define nothing3     BIT4  // P1.4, 0x10, 0b0000 0100
#define onYELLOWLED   BIT5  // P1.5, 0x20, 0b0010 0000
#define nothing4  BIT6  // P1.6, 0x40, 0b0100 0000
#define nothing5     BIT7  // P1.7, 0x80, 0b1000 0000

#define TIMEBASE      16384 // This is selected ACLK/2, Production value = 16384
#define STARTTIMEOUT  5     // 5 seconds
#define OVERALLTIMEOUT 43200 // 12hr*60min*60sec = 43200 seconds


/*******************************************************************************
* Global Variables
* 
******************************************************************************/
unsigned char TimerTick;

 

 

 

So, here's my main function and interrupt routine. This will configure the msp430 to interrupt every TIMEBASE counts. In this case, the result is an interrupt every 0.25 seconds.

 

void main(void)
{  
 WDTCTL = WDTPW + WDTHOLD;   // Stop watchdog timer

 BCSCTL1 = CALBC1_1MHZ;
 DCOCTL  = CALDCO_1MHZ;

 BCSCTL3 |= XCAP_3; // Launchpad watch crystals need ~12.5 pF

 // Configure all IO's and their default states on the MSP430   
 InitMSP430();
 InitIOs();

 // Initialize the system tick count
 TimerTick = 0;

 // Initialize all state machines
 InitTimerStateMachine();
 InitTrafficLightStateMachine();


 VERSION(TrafficLightStateMachineExample, "Alpha1", "Zeke");

 _BIS_SR( GIE );   // Sooner or later, you're gonna have to Enter LPM3 w/ interrupt


 while(1)
 {
   // May have to add some servicing routines in here that aren't controlled by the 1 second timer
   // This is a place holder.
   // Decide on whether you're going to use an infinite loop or send the micro into LPM3 mode w/ Interrupts enabled.
 }

}

// Period of this interrupt is set in InitMSP430
#pragma vector=TIMERA0_VECTOR
__interrupt void Timer_A(void)
{
 // Update the system tick count every interrupt
 TimerTick++;

 // Update these state machines every 1 second
 if( TimerTick % 2 )
 {
   UpdateTimerStateMachine();
   UpdateTrafficLightStateMachine();
 }

 // Update the output state of each control line every 0.5 seconds
 UpdateIOs();  
}

 

 

And here's my InitMSP430() function:

/*******************************************************************************
* InitMSP430()
* 
* Initializes the resources of the MSP430 for this program. 
* 
******************************************************************************/
void InitMSP430( void )
{
 // Configure these I/O pins to outputs
 pinModeP1( onGREENLED,   OUTPUT ); 
 pinModeP1( onREDLED,     OUTPUT );
 pinModeP1( onYELLOWLED,  OUTPUT );

 // Set these Outputs to default states
 digitalWriteP1( onGREENLED,   HIGH );
 digitalWriteP1( onREDLED,     HIGH );
 digitalWriteP1( onYELLOWLED , HIGH );

 // Configure the TimerA to run at a 1 second rate    
 CCTL0 = CCIE;             // CCR0 interrupt enabled
 CCR0  = TIMEBASE-1;       // 0.25 second timer operation
 TACTL = TASSEL_1 + MC_1;  // ACLK, upmode
}

 

So, these are the files that will lay the foundation for a well balanced state machine. In this example, I'm going to simulate a traffic light state machine. It seems to me that it is the example that every prof uses in control systems lab class.

 

I haven't posted up the routines that really matter yet. Namely, the UpdateStateMachine() functions. They are the gears in this transmission.

 

So, stay tuned for more. I hope you will like this.

Share this post


Link to post
Share on other sites

I have a state machine code for a pedestrian crossing traffic light simulator that I made. I'll post it here after cleaning it up - stay tuned :)

Share this post


Link to post
Share on other sites

I'm thinking that I should change my example code to something maybe a little simpler and possibly more useful. How about a countdown timer instead of a traffic controller?

 

After some time to think, I believe I should spend some time explaining my approach to state machines.

 

I should also point out that the method I propose is not the only way of creating a state machine. You may have a method that makes mine look quite silly. That being said, the method I present is one reliable way of driving an embedded state machine.

 

 

Generally speaking, this state machine example can be compared to a flywheel on a big old tractor. Maybe you have seen one? It stores kinetic energy to keep the crankshaft rotating in between 4-stroke cycles.

 

My example has a timer interrupt that fires regularly. That amount of time can be tuned for the needs of the overall system. If you wanted to check on other things like button presses or other inputs then maybe the interval would be shorter than one second. In this countdown timer example, the interval is one second.

 

Overall, the interrupt is the ticking of the clock. If a tick occurs then do something. In our case, we're going to update the tick counter, do some display updates then go back to waiting for the next tick.

Share this post


Link to post
Share on other sites

The first thing to do is design on paper. It's easier to fix your mistakes. Besides, you have to convince yourself that your approach is going to work.

 

First, characterize the state machine. Identify the:

  • requirements,
    input variables,
    start conditions,
    stop conditions,
    all states and,
    transition conditions

 

Requirements:

  • The device is a countdown timer. The time length is arbitrarily set at 10 seconds. When time is expired, turn on an LED to indicate timeout.

 

Input Variables:

  • The device will have a button. A button press will reset the countdown timer and begin the countdown.

 

Start Conditions:

  • A button press indicates a start signal.

 

Stop Conditions:

  • When the countdown reaches zero, the timer stops.

 

All States

  • OFF: Nothing is happening, no counting, micro isn't running yet
    RESET: No counting, all variables reset to default values
    IDLE: Not counting, variables not updating, waiting for a reset signal, LED OFF
    COUNTING: Counting variable decrementing, display updating
    FINISHED: Not counting, variables not updating, waiting for a reset signal, LED ON

 

Transition Conditions:

  • OFF -> RESET: Apply power. Micro should enter into RESET condition, reset all variables then signal a move to IDLE state
    RESET -> IDLE: Transition to this state once all variables are set to default conditions
    IDLE -> COUNTING: Transition to this state by pressing the Start Button
    COUNTING -> FINISHED: Transition to this state when the count variable has reached zero
    FINISHED -> RESET: Pressing the Start Button causes this transition

 

To the best of our abilities, this is what we know right now.

Share this post


Link to post
Share on other sites

Now, you should attempt to create a state diagram. Here is the state diagram for the above details.

 

This diagram will help sort out and refine the details of the state machine.

 

Use it as guidance on how to structure the C code switch statement.

post-955-135135503657_thumb.png

Share this post


Link to post
Share on other sites

Now is the time to analyze the paper design.

 

Ask yourself "Is everything here that I need?" or "Should I add more features or conditions?"

 

You could add in a pulsing LED to indicate counting activity. Will it be green or will the red LED pulse? It's up to you ultimately.

 

So what's next?

Share this post


Link to post
Share on other sites

The next thing is to figure out all the variables at play in the system.

 

What variables come from hardware?

Input:

  • S2 on P1.3

Output:

  • LED1 on P1.0
    LED2 on P1.6

 

What variables come from software?

Timer:

  • 0,
    60 seconds

Countdown Timer State Machine:

  • OFF,
    STARTING,
    RUNNING,
    COMPLETE

 

This process will reveal missing variables and possibly redundant variables.

Share this post


Link to post
Share on other sites

Now, list out what each variable is doing in each state.

 

OFF:

  • gTimerState = STOPPED
    LED1 = LEDOFF,
    LED2 = LEDOFF,
     
    if( ButtonS2 == FALSE)
    gCountdownTimerState = OFF
    else
    gCountdownTimerState = STARTING
    gTimerState = TICKING

 

STARTING:

  • gTimerState = TICKING
    LED1 = LEDON,
    LED2 = LEDOFF,
     
    if( getButtonState() == PRESSED )
    gCountdownTimerState = STARTING
    else
    if( getButtonState() == RELEASED )
    gCountdownTimerState = RUNNING

 

RUNNING:

  • gTimerState = TICKING
    LED1 = LEDOFF,
    LED2 = LEDON,
     
    if( getTimerCount() < TIMERTIMEOUT )
    gCountdownTimerState = RUNNING
    else
    gCountdownTimerState = COMPLETE

 

COMPLETE:

  • gTimerState = STOPPED
    LED1 = LEDON,
    LED2 = LEDOFF,
     
    if( getButtonPress() == RELEASED )
    gCountdownTimerState = COMPLETE
    else
    gCountdownTimerState = STARTING

 

And, as you can see, the C code naturally flows out of this analysis.

 

Now it's time to write up the code and see if it works.

 

By the way, is anyone reading this thread?

Should I continue?

Share this post


Link to post
Share on other sites

Zeke, a while back we had some exchanges in your MSP430 info thread. There you showed me a link to this thread on state machines and I think you invited me to continue discussion here. So here goes...

 

I have used the state machine approach to programming in several previous projects using giant switch statements in C or the equivalent in assembly language. (These projects were on AVR chips.)

 

I few weeks ago I finished going through Baugh's "MSP430 State Machine Programming." This was, in my opinion, a great book to work through. I learned a lot about C and many things like bit-banging a UART, etc. The exercises were done using an MSP430F2274 on an Olimex header board stuck in a breadboard. The one down-side to Baugh's approach for me (and maybe only me) is that the state machine structures were not easy to follow. It was easy to get lost in a maze of interconnected functions.

 

So I bought "Practical UML Statecharts in C/C++", 2nd. ed., by Miro Samek to learn about a more formal approach. This book is somewhat overwhelming, but written in a way to get you through many, many details. And the accompanying code includes a project for a small MSP430--at the very end of the book. In Chapter 3 Samek gives examples of generic approaches, the switch statement and the state transition table approaches. Before going further in the book I decided to try a programming exercise using the state transition table approach. This is it:

 

StateTableBlink2.zip

 

The program is written for an MSP430G2452 on my homebrew Launchpad/expansion board combo. It simply blinks three LEDs at different rates and duty cycles. (It could easily be modified for the G2211 on the Launchpad provided the Launchpad has the clock crystal installed and the third LED is eliminated from the code. One would also have to modify the #include statements for the processor header and the name of the TimerA Channel 0 ISR vector. I wrote it in C using the CCS complier, but it should be OK for the IAR compiler.)

 

Here is the key module:

//------------------------------------------------------------------------------
// Blink.c in project StateTableBlink2
//
// This program blinks three LEDs at different rates using a common state
// table framework.  The state transition table contains pointers to transition
// functions.  The table rows are states, the table columns are events.  
//
// The basic state table structure and functions are code copyrighted by Miro
// Samek in StateTable.c and StateTable.h.  The overall structure of the
// program follows the approach of Tom Baugh.
//
// When an event occurs, the transition function in the table for that event
// and the current state is executed via the StateTable_Dispatch function.
//
// The transition function performs the following:
//   - Check any transition guard conditions
//   - Execute any exit functions of the state being left
//   - Change the current state to the new state using the macro TRAN
//   - Execute any entry functions of the new state
//
// There are two states in this LED blinking state machine, LED_IsOn and
// LED_IsOff.  
//
// There is only one type of event in this state machine, a tick from a timer
// interrupt ISR.  Blink_ProcessEvents is called by main upon wake-up from
// this ISR.
//
// The transition functions in this state machine also implement a countdown
// timer for each state which determines how long that state is held. On
// initial entry the timer counter is set for the delay interval.  The counter
// is decremented if the state is active and a tick event occurs.  If the
// delay interval is not over no state transition occurs.  When the counter
// times out the guard condition is met and there is a transition to the new
// state.
//
// Alternately, the state delay timers could be decremented and the transition 
// guard conditions checked in Blink_ProcessEvents.  This simplifies the
// transition functions and gives somewhat faster execution since the
// StateTable_Dispatch function is only called on delay timeouts.  This
// approach requires Blink_ProcessEvents to directly access state machine
// variables, so could be considered less "clean."  
//
// Andrew Palm
// 2011.09.28
//------------------------------------------------------------------------------
#include 
#include 
#include "System.h"
#include "StateTable.h"
#include "Blink.h"

// Each tick approximately 3.9 ms with ACLK at 32768 Hz (256 ticks/sec)
#define LED1_IS_ON_TICKS   (TICKS_PER_SEC / 32)   // 1/3 Hz, short duty cycle
#define LED1_IS_OFF_TICKS  (95*(TICKS_PER_SEC / 32))

#define LED2_IS_ON_TICKS   (TICKS_PER_SEC / 2)   // 1 Hz, 50% duty cycle
#define LED2_IS_OFF_TICKS  (TICKS_PER_SEC / 2)

#define LED3_IS_ON_TICKS   (TICKS_PER_SEC / 32)  // 16 Hz, 25% duty cycle
#define LED3_IS_OFF_TICKS  (3*(TICKS_PER_SEC / 32))

//------------------------------------------------------------------------------
// Blink LED finite state machine
enum BlinkSignals
{                                      // Signals for the Blink FSM 
   TICK_SIG,

   MAX_SIG                            // Number of signals 
};

enum BlinkStates
{                                      // States for the Blink FSM 
   LED_IS_OFF_STATE,
   LED_IS_ON_STATE,

   MAX_STATE                          // Number of states 
};

typedef struct TickEvtTag
{
   Event super;                       // Derived from Event structure 
} TickEvt;

typedef struct BlinkTag
{                                      // The Blink FSM 
   StateTable super;                  // Derived from StateTable structure 
   uint16_t delay_ticks;              // Number of delay ticks remaining
   uint16_t led_is_on_ticks;          // Number of delay ticks for on state
   uint16_t led_is_off_ticks;         // Number of delay ticks for off state
   volatile uint8_t *led_port;        // Pointer to LED port
   uint8_t  led_bitmask;              // Bit mask for LED pin
   uint8_t  filler;
} Blink;

static Blink Blink_1;                  // Blink FSM for LED1
static Blink Blink_2;                  // Blink FSM for LED2
static Blink Blink_3;                  // Blink FSM for LED3

//------------------------------------------------------------------------------
// The "constructor"
void Blink_Ctor(Blink *me, 
               uint16_t led_is_on_ticks, uint16_t led_is_off_ticks,
               uint8_t  led_bitmask, volatile uint8_t *led_port); 

void Blink_Initial(Blink *me, Event const *e);         // Initial transition 
void Blink_LED_IsOff_TICK(Blink *me, Event const *e);  // Transition function 
void Blink_LED_IsOn_TICK(Blink *me, Event const *e);   // Transition function 

// State table for all Blink state machines (moved outside constructor)
static const Tran Blink_StateTable[MAX_STATE][MAX_SIG] = 
{
   { (Tran)&Blink_LED_IsOff_TICK },
   { (Tran)&Blink_LED_IsOn_TICK  }
};

//------------------------------------------------------------------------------
void Blink_Ctor(Blink *me, 
               uint16_t led_is_on_ticks, uint16_t led_is_off_ticks,
               uint8_t led_bitmask, volatile uint8_t *led_port)
{
   StateTable_Ctor(&me->super,
                   &Blink_StateTable[0][0], MAX_STATE, MAX_SIG,
                   (Tran)&Blink_Initial);   // Construct the superclass

   me->led_is_on_ticks = led_is_on_ticks;
   me->led_is_off_ticks = led_is_off_ticks;
   me->led_bitmask = led_bitmask;
   me->led_port = led_port;
}

//------------------------------------------------------------------------------
void Blink_Initial(Blink *me, Event const *e)
{
   (void)e;                               // Avoid unused parameter warning

   // Transistion to initial state
   TRAN(LED_IS_ON_STATE);
   // New state entry actions
   *(me->led_port) |= me->led_bitmask;    // Turn on LED
   me->delay_ticks = me->led_is_on_ticks; // Load delay timer
}

//------------------------------------------------------------------------------
void Blink_LED_IsOff_TICK(Blink *me, Event const *e)
{
   (void)e;                                   // Avoid unused parameter warning

// This state re-entry actions
   me->delay_ticks--;                         // Decrement timer counter

   if(0 == me->delay_ticks)                   // Transition guard condition
   {
       // Transistion to new state
       TRAN(LED_IS_ON_STATE);
       // New state entry actions
       *(me->led_port) |= me->led_bitmask;    // Turn on LED
       me->delay_ticks = me->led_is_on_ticks; // Reload delay timer
   }
}

//------------------------------------------------------------------------------
void Blink_LED_IsOn_TICK(Blink *me, Event const *e)
{
   (void)e;                                   // Avoid unused parameter warning

// This state re-entry actions
   me->delay_ticks--;                         // Decrement timer counter

   if(0 == me->delay_ticks)                   // Transition guard condition
   {
       // Transistion to new state
       TRAN(LED_IS_OFF_STATE);
       // New state entry actions
       *(me->led_port) &= ~(me->led_bitmask);   // Turn off LED
       me->delay_ticks = me->led_is_off_ticks;  // Reload delay timer
   }
}

//------------------------------------------------------------------------------
void Blink_InitializeHW(void)
{
   TACCR0 = TICK_DIVISOR;               // Prepare first interrupt
}

void Blink_InitializeApp(void)
{
   Blink_Ctor(&Blink_1, LED1_IS_ON_TICKS, LED1_IS_OFF_TICKS, LED1, &LEDOUTA); 
   StateTable_Init((StateTable *)&Blink_1);  // Make initial transition 

   Blink_Ctor(&Blink_2, LED2_IS_ON_TICKS, LED2_IS_OFF_TICKS, LED2, &LEDOUTA); 
   StateTable_Init((StateTable *)&Blink_2);  // Make initial transition 

   Blink_Ctor(&Blink_3, LED3_IS_ON_TICKS, LED3_IS_OFF_TICKS, LED3, &LEDOUTB); 
   StateTable_Init((StateTable *)&Blink_3);  // Make initial transition 
}

//------------------------------------------------------------------------------
void Blink_ProcessEvents(void)
// Called from main after wakeup
{
   static TickEvt tick_evt = { TICK_SIG };  // Single event type, a tick

   // Send tick event to each state machine
   StateTable_Dispatch((StateTable *)&Blink_1, (Event *)&tick_evt);        
   StateTable_Dispatch((StateTable *)&Blink_2, (Event *)&tick_evt);        
   StateTable_Dispatch((StateTable *)&Blink_3, (Event *)&tick_evt);        
}

//------------------------------------------------------------------------------
#pragma vector=TIMER0_A0_VECTOR
__interrupt void TimerA_CCR0_ISR(void)
// ISR to generate regular ticks
{
   TACCR0 += TICK_DIVISOR;            // Update for next interrupt
   __low_power_mode_off_on_exit();    // Wake the processor
}

 

The comments at the head of the Blink.c module briefly explain how the thing runs. There are only two states (on and off) and one event (tick from the ISR), but I jazzed things up a tad by making the state table structure generic for blinking an LED and setting the port and pin bit mask in the constructor. I also had the state transition functions implement the delay timers for the states. The generic state table structures and processing are in StateTable.c and StateTable.h (in the zip file with the rest of the modules). These are from Samek's book and are copyrighted.

 

Why this "simple" exercise? Well, in fact it took some head scratching to get a clear conceptual idea of what is going on here. In this approach the states themselves are represented as rows in Blink_StateTable. The functions pointed to in the table cells are the TRANSITION ACTIONS from one state to the other, not the states. This was a key insight for me. So any particular one of these functions contains code for transition guard conditions, current state exit functions, changing the current state to the new state, and the new state entry functions. This makes the primary model here a Mealy state machine (as opposed to a Moore).

 

In this particular case I have added code to implement a delay timer for each state within the transition functions, so when a tick event occurs but the delay is not completed, we can conceptually think of this as a loop-back transition to the current state. This requires the state table event dispatch function to be called for every ISR tick, but it is cleaner than having the event processor loop keep tabs of the timer counters internal to each LED state machine.

 

My next step is to develop state machines for push buttons, formalizing what Baugh did in his book. These will include normal presses and long presses for acceleration. I'll see how much the program size grows with these additions. By the way, I chose the G2452 because (a) I had some on hand, (B) they have a complete PORT2, and © they have plenty of code space for this messing around.

 

Cheers,

Andy

Share this post


Link to post
Share on other sites

*** Note: There is an upgraded version of this code in a following post. ***

 

This is a second state machine implementation based on material in Chapter 3 in Samek. Once again, it blinks three LEDs.

 

In this version there is no table of state transition functions. Instead there is one "state handler function" for each state. The state handler function contains a switch statement based on the Event/Signal received. There are special switch cases for state entry and exit functions, and these are automatically executed on a state transition by the state machine dispatch function. The state handler functions return status codes, but in this version one of these is actually used (the one for transitions). The others were intended, I believe, to be used in a tracing facility.

 

As for the previous implementation, the user adds state machine parameters or event parameters by derivation from base structures.

 

I have made a small change in the applicaton by replacing led_is_off_ticks with led_period_ticks. This was done in anticipation of a future implementation that uses pushbuttons to modify the led blink rates. I also increased the system tick period to about 15.9 ms, since this is small enough for led flashing.

 

Although this implementation uses a couple of funky macros and is somewhat more difficult to follow (especially the initialization steps), it is nice in that there is one state handler function for each state, and the entry and exit functions are clearly indicated. This makes for a better correspondence between code and the state diagram. Also, there is no storage needed for a state transition table which could get large and sparse in large systems.

 

On the down side, these handler functions might get long if there are many signal types to be checked in the switch statement, and this might be a problem when things must be accomplished within a single system tick. Fortunately, the switch statement in each handler only needs to include events/signals relevant to that state.

 

Cheers,

Andy N1KSN

 

StateMachineBlink.zip

 

The C code for the primary module:

 

//------------------------------------------------------------------------------
// Blink.c in project StateMachineBlink
//
// This program blinks three LEDs at different rates using state machine
// functions based on the QEP FSM processor found in Chapter 3 of "Practical
// UML Statecharts in C/C++" by Miro Samek.  (Copyright (C) 2002-2007 Miro
// Samek. All rights reserved.)
//   
// In this implementation there is no table of state transition functions.
// Instead there is one "state handler function" (SHF) for each state. Each SHF
// contains a switch statement with cases based on the event signals received.
// There are special cases for state entry and state exit functions, which are
// automatically invoked by the state machine dispatch function when a state
// transition occurs.  
//
// There are two states in this LED blinking state machine, LED_IsOn and
// LED_IsOff.  
//
// There is only one type of user event in this state machine, a tick from a
// timer interrupt ISR.  Blink_ProcessEvents is called by main upon wake-up
// from this ISR.
//
// The state handler functions in this state machine also implement a countdown
// timer which determines how long that state is held. On entry the timer 
// counter is set for the delay interval.  The counter is decremented if the
// state is active and a tick event occurs.  If the delay interval is not over
// no state transition occurs.  When the counter times out the guard condition
// is met and there is a transition to the new state.
//
// Andrew Palm
// 2011.09.30
//------------------------------------------------------------------------------
#include 
#include 
#include "System.h"
#include "StateMachine.h"
#include "Blink.h"

// Each tick approximately 15.6 ms with ACLK at 32768 Hz (256 ticks/sec)
#define LED1_PERIOD_TICKS  3*(TICKS_PER_SEC)
#define LED1_IS_ON_TICKS   (LED1_PERIOD_TICKS / 96) // 1/3 Hz, short duty cycle

#define LED2_PERIOD_TICKS  TICKS_PER_SEC
#define LED2_IS_ON_TICKS   (LED2_PERIOD_TICKS / 2)   // 1 Hz, 50% duty cycle

#define LED3_PERIOD_TICKS  (TICKS_PER_SEC / 8)
#define LED3_IS_ON_TICKS   (LED3_PERIOD_TICKS / 4)   // 8 Hz, 25% duty cycle

//------------------------------------------------------------------------------
// Blink LED finite state machine
enum BlinkSignals
{                                      // Signals for the Blink FSM 
   TICK_SIG = SM_USER_SIG
};

typedef struct TickEvtTag
{
   Event super;       // Derived from Event structure 
} TickEvt;             // For this application, no additional user parameters

typedef struct BlinkTag
{                                      // The Blink FSM 
   StateMachine super;                // Derived from StateTable structure 
   uint16_t delay_ticks;              // Number of delay ticks remaining
   uint16_t led_is_on_ticks;          // Number of delay ticks for on state
   uint16_t led_period_ticks;         // Number of ticks for flash period
   volatile uint8_t *led_port;        // Pointer to LED port
   uint8_t  led_bitmask;              // Bit mask for LED pin
   uint8_t  filler;
} Blink;

static Blink Blink_1;                  // Blink FSM for LED1
static Blink Blink_2;                  // Blink FSM for LED2
static Blink Blink_3;                  // Blink FSM for LED3

//------------------------------------------------------------------------------
// The "constructor" for Blink state machines
void Blink_Ctor(Blink *me, 
               uint16_t led_is_on_ticks, uint16_t led_period_ticks,
               uint8_t  led_bitmask, volatile uint8_t *led_port); 

static Status Blink_Initial(Blink *me, Event const *e);    // Initial trans 
static Status Blink_LED_IsOff(Blink *me, Event const *e);  // State handler fn 
static Status Blink_LED_IsOn(Blink *me, Event const *e);   // State handler fn 

//------------------------------------------------------------------------------
void Blink_Ctor(Blink *me, 
               uint16_t led_is_on_ticks, uint16_t led_period_ticks,
               uint8_t led_bitmask, volatile uint8_t *led_port)
{
   StateMachine_Ctor(&me->super, (SHF)&Blink_Initial); // Construct superclass
   // Set user parameters
   me->led_is_on_ticks = led_is_on_ticks;
   me->led_period_ticks = led_period_ticks;
   me->led_bitmask = led_bitmask;
   me->led_port = led_port;
}

//------------------------------------------------------------------------------
Status Blink_Initial(Blink *me, Event const *e)
{
   (void)e;                               // Avoid unused parameter warning

   // Insert any additional initialization actions here that are not state
   // entry actions or initializations done in the constructor.

return SM_TRAN(&Blink_LED_IsOn);       // Transition to initial state
}

//------------------------------------------------------------------------------
Status Blink_LED_IsOff(Blink *me, Event const *e)
{
   switch (e->sig)
{
       case TICK_SIG:
	{
           me->delay_ticks--;         // Decrement timer counter
           if(0 == me->delay_ticks)   // Transition guard condition
           {
               return SM_TRAN(Blink_LED_IsOn);  // Transistion to new state	
		}
		return SM_HANDLED();
	}
       // Insert any other user signal type cases here
	case SM_ENTRY_SIG:
	{
           *(me->led_port) &= ~(me->led_bitmask);   // Turn off LED
           // Reload delay timer
           me->delay_ticks = me->led_period_ticks - me->led_is_on_ticks;
		return SM_HANDLED(); 
	}
	case SM_EXIT_SIG:
	{
	    return SM_HANDLED();
	}
   }
   return SM_IGNORED();
}

//------------------------------------------------------------------------------
Status Blink_LED_IsOn(Blink *me, Event const *e)
{
   switch (e->sig)
{
       case TICK_SIG:
	{
           me->delay_ticks--;         // Decrement timer counter
           if(0 == me->delay_ticks)   // Transition guard condition
           {
               return SM_TRAN(Blink_LED_IsOff); // Transistion to new state	
		}
		return SM_HANDLED();
	}
       // Insert any other user signal type cases here
	case SM_ENTRY_SIG:
	{
           *(me->led_port) |= me->led_bitmask;    // Turn on LED
           me->delay_ticks = me->led_is_on_ticks; // Reload delay timer
		return SM_HANDLED();
	}
	case SM_EXIT_SIG:
	{
	    return SM_HANDLED();
	}
   }
   return SM_IGNORED();
}

//------------------------------------------------------------------------------
void Blink_InitializeHW(void)
{
   TACCR0 = TICK_DIVISOR;               // Prepare first interrupt
}

void Blink_InitializeApp(void)
{
   Blink_Ctor(&Blink_1, LED1_IS_ON_TICKS, LED1_PERIOD_TICKS, LED1, &LEDOUTA); 
   // Make initial transition
   StateMachine_Init((StateMachine *)&Blink_1, (Event *)0); 

   Blink_Ctor(&Blink_2, LED2_IS_ON_TICKS, LED2_PERIOD_TICKS, LED2, &LEDOUTA); 
   // Make initial transition
   StateMachine_Init((StateMachine *)&Blink_2, (Event *)0);   

   Blink_Ctor(&Blink_3, LED3_IS_ON_TICKS, LED3_PERIOD_TICKS, LED3, &LEDOUTB); 
   // Make initial transition
   StateMachine_Init((StateMachine *)&Blink_3, (Event *)0); 
}

//------------------------------------------------------------------------------
void Blink_ProcessEvents(void)
// Called from main after wakeup
{
   static TickEvt tick_evt = { TICK_SIG };  // Single event type, a tick

   // Send tick event to each state machine
   StateMachine_Dispatch((StateMachine *)&Blink_1, (Event *)&tick_evt);        
   StateMachine_Dispatch((StateMachine *)&Blink_2, (Event *)&tick_evt);        
   StateMachine_Dispatch((StateMachine *)&Blink_3, (Event *)&tick_evt);        
}

//------------------------------------------------------------------------------
#pragma vector=TIMER0_A0_VECTOR
__interrupt void TimerA_CCR0_ISR(void)
// ISR to generate regular ticks
{
   TACCR0 += TICK_DIVISOR;            // Update for next interrupt
   __low_power_mode_off_on_exit();    // Wake the processor
}

Share this post


Link to post
Share on other sites

*** Note: There is an upgraded version of this code in a post below in this thread. ***

 

This is the final installment in this series of demo programs for using a state machine approach. This version continues to blink three LEDs, but I've added three pushbuttons to the setup. Each LED and each pushbutton is managed by a separate state machine. Both types of state machine use the same base functions and structures found in StateMachine.h and StateMachine.c that were used in the last version.

 

I've set things up so that each LED blinks with a period of one second, but the duty cycle can be modified either up or down using the pushbuttons as follows:

 

PB1 increases the LED duty cycle by a factor of 2 for each push, with a maximum of 50%.

PB2 decreases the LED duty cycle by a factor of 1/2 for each push, with a minimum of 3.125%.

PB3 selects which LED is active for modification by PB1 and PB2 by rotating through them, 1, 2, 3, 1, 2, 3, ....

 

PB1 and PB2 have an acceleration function. If pushed for 1 second they slew though their associated function 4 times a second. To keep things relatively simple in this demo, there is no user indication of which LED is active.

 

The pushbutton module is not hard-wired to the LED blink module. The LED blink module must "register" an "event sink" function with each pushbutton it wishes to use. It also specifies if the acceleration feature is wanted. This allows the pushbutton module to be used by more than one client module. Pushbutton functionality can be changed on the fly.

 

This approach to pushbutton management is found in Tom Baugh's book on state machine programming. The overall organization of the program also comes from his book, although I have made no efforts to minimize current use other than put the program into LPM3 between ticks. (Baugh is a master of low current design and I highly recommend his book.)

 

However, the state machine implementation is more formalized than Baugh's approach. The code in StateMachine.c and StateMachine.h is modified from Miro Samek's book "Practical UML Statecharts in C/C++" so I've included GNU Public License Version 2 notices in those files. Any software derived from this code must retain these notices, or at least that is my understanding of the legal mumbo-jumbo.

 

In this version the system ticks are now provided by the watchdog timer instead of Channel 0 on TimerA, and the system tick interval is set to about 16 ms (corresponding to a tick divisor of 512 when using a 32768 Hz clock crystal). The state machine timers have been moved out of the state handler functions and now reside in the timer event processor functions. Thus the state handler functions are no longer called at each system tick, but only upon timeout events. The timers for the LEDs run continuously, but the timers for the pushbuttons do not, so a timer_active flag was added for them. Also, the pushbutton state machine uses the timer for both debouncing and timing event sink execution in acceration mode.

 

Another difference between the LED and PB state machines is that a port pin interrupt for a pushbutton generates an event from the port pin ISR. Thus, events for the state machine are generated in more than one place.

 

The full code plus selected comments are shown below. With the changes made to this version, one should be able to run it on the MSP430G2211 or similar chips by changing only the device header include statements. I have not tested compilation under IAR Kickstart, but I see no reason for this to be a problem, as only standard intrinsic functions are used.

 

Wrapup: Using state machines is an alternative to using a real time operating system to manage separate execution threads in a program. These demo programs illustrate the basic use of state machines to independently blink three LEDs and manage pushbutton functions. (If code size or current draw are big issues the "naked" state machine approach of Baugh is preferable, perhaps at the cost of some code readability.) Learning some of this material has also increased my knowledge of the C language, especially when it comes to structures and function pointers. Note that these state machines are non-hierarchical. Greater flexibility would be obtained using hierarchical state machines, but these remain a future study topic for me.

 

I hope some of you will find these programs as interesting as I did.

 

Andy

 

StateMachineBlink2.zip

 

//------------------------------------------------------------------------------
// main.c in project StateMachineBlink2
//
// The overall structure of this program follows the approach of Tom Baugh
// found in "MSP430 State Machine Programming."
// 
// This program was written to explore the use of non-hierarchical state
// machines as a way of organizing a program.  The state machine structures
// and functions used here are based on the QEP FSM processor found in Chapter
// 3 of "Practical UML Statecharts in C/C++" by Miro Samek.  
// 
// The modules in this program:
//
//   Main:  General shell program for initialization and execution of the
//          system tick loop.  Program sleeps in LPM3 when not executing a
//          tick.  This version uses the watchdog timer to generate system
//          ticks, leaving TimerA free for other modules.
//
//   System:  Does general low-level hardware initialization and contains
//            the WDT ISR.
//
//   StateMachine:  Contains base state machine and event structures and
//                  performs base state machine functions.  This code is
//                  modified from code copyrighted by Miro Samek under the
//                  GNU Public License Version 2.  This code is free for any
//                  use or distribution provided that the copywrite notice
//                  at the beginning of the module files is retained.
//
//   Blink:  Sets up and runs state machines that blink three LEDs.
//
//   PushButton:  Sets up and runs state machines that control pushbutton
//                functions, including debouncing and an optional acceleration
//                feature.  The functions actually executed on a push are
//                determined by "event sink registration functions" called by
//                the client, which in this case is the Blink module.
//  
// See the individual module headers for further descriptions.
//
// This program is written for the MSP430G2452, but should be easily changed
// to the MSP430G2211 or similar with 2K or more of flash memory by modifying
// the device header file include statements.  Note that a clock crystal of
// 32768 Hz is used to allow LPM3 sleep between ticks.  Also, this program was
// compiled and tested using CCS Workbench, with the compiler set to level 4
// optimiazation and minimum size.  It should compile under IAR Kickstart with
// no modifications.
//
// Andrew Palm
// 2011.10.04
//------------------------------------------------------------------------------

 

//------------------------------------------------------------------------------
// Blink.c in project StateMachineBlink2
//
// This module blinks three LEDs at different duty cycles using state machines.
//
// This version adds three pushbuttons with the following functions:
//    PB1:  Increases duty cycle of the active LED
//    PB2:  Decreases duty cycle of the active LED
//    PB3:  Selects the active LED.  Rotates through the three LEDs in the
//          order 1, 2, 3, 1, 2, 3,.... 
//
// To simplify this demo, all three LEDs have the same period of one
// second--only the duty cycles can be changed by the pushbuttons.  And there
// is no indication of which LED is currently active based on PB3 pushes.
//
// PB1 and PB2 are set up to have an acceleration feature.  Holding the button
// down for one second causes the assocated action to occur four times a
// second.  This feature is selectable using the event sink registration
// function for each pushbutton.
//
// There is one "state handler function" (SHF) for each state. Each SHF
// contains a switch statement with cases based on the event signals received.
// There are special cases for state entry and state exit functions, which are
// automatically invoked by the state machine dispatch function when a state
// transition occurs.  
//
// There are two states in this LED blinking state machine, LED_IsOn and
// LED_IsOff.  
//
// There is only one type of user event in this state machine, a delay timer
// timeout event.  Blink_ProcessEvents is called by main upon wake-up
// from a timer interrupt, decrements the timer counters, and issues timeout
// events at the end of the delays.  
//
// The state handler functions in this state machine set up these timers
// which determine how long a state is held. On entry the time counter is
// set for the delay interval.  When the counter times out there is a 
// transition to the new state.
//
// Andrew Palm
// 2011.10.04
//------------------------------------------------------------------------------

 

//------------------------------------------------------------------------------
// PushButton.c in project StateMachineBlink2
//
// This module uses state machines to debounce three pushbuttons.  The state
// machines use the same machinery as the module Blink.c.  Events are sent to
// the state machine dispatch function by either the timer tick generator or
// the port ISR.  The port ISR is used to detect the initial push.  The timer
// is used for both debouncing and the accelerated mode interval.
//
// The function of each pushbutton is controlled by the client application
// Blink.c, which upon initialization "registers" with each pushbutton the
// address of a function to execute when a push is confirmed.  This function
// is called an "event sink."  This method is based on the approach found in
// "MSP430 State Machine Programming" by Tom Baugh.
//
// The pushbutton state machine has four states:
//   - IsIdle:  Wait for a push detected by port pin ISR
//   - PushDetected:  Wait for the debounce delay, check pin status, execute
//                    registered event sink if button still pushed
//   - PushConfirmed:  Wait for button release or extended push.  If released,
//                     return to idle.  If extended push, enter accelerated
//                     mode.
//   - Accelerated:  Execute event sink at specified interval until release.
//
// The client module sets whether acceleration is used by setting the
// accel_mode variable to FALSE in the registration call for that button.
// In this demonstration program, the pushbuttons are set up with PB1 and PB2
// accelerated and PB3 not accelerated.
//
// See Blink.c for the PB functions in this demo.
//
// Andrew Palm
// 2011.10.04
//------------------------------------------------------------------------------

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×