Stopwatch example

An example of a more complex FSM, using the state transition table implementation, to control the menu of a stopwatch. The stopwatch menu uses the three buttons on the Sparkfun COlor LCD Shield as software defined buttons to navigate through the menu which is displayed on the LCD. The FSM is implemented in C using a state transition and action table. In this example the actual stopwatch timer and display of the elapsed time is not implemented.

The state transition table for stopwatch application is shown in figure 1.

Figure 1 Stopwatch menu state transition diagram

Each state corresponds to a different menu screen as shown in figure 2

Figure 2 Stopwatch menus

The state transition diagram can then be converted into a state transition table and a transition action table. The entries in the state transition table correspond to the next state for a given event (row) and current state (column). The entries in the transition action table correspond to the action associated with a transition defined by the event (row) and current state (column).

Figure 3 Stopwatch state transition and transition action table

The tables are then directly entered in C as follows


/* States. */
#define MENU 0
#define RESET 1
#define RUN 2
#define LAP 3
#define PAUSE 4

/* Events */
#define SWITCH1 0
#define SWITCH2 1
#define SWITCH3 2

/* Actions */
#define NACT 0
#define INIT 1
#define EXIT 2
#define START 3
#define HOLD 4
#define CONT 5
#define STOP 6
char transtable[3][5] = /* transtab[Input][CurrentState] => NextState */
{
//Menu Reset Run Lap Pause
RESET, MENU, LAP, RUN, MENU, // Switch1
MENU, RESET, RUN, LAP, RESET, // Switch2
MENU, RUN, PAUSE, LAP, RUN, // Switch3
};

char actiontable[3][5] = /* transtab[Input][CurrentState] => NextState */
{
//Menu Reset Run Lap Pause
INIT, EXIT, HOLD, CONT, EXIT, // Switch1
NACT, NACT, NACT, NACT, INIT, // Switch2
NACT, START, STOP, NACT, START, // Switch3
};

The state machine handler, shown below, within the endless while loop first polls the switches using PollLCDShieldSwitches within the if statement, which returns true if a switch has been pressed and released. The switch statement is then used to call the transition action function that is determined by the action table. Finally the current state variable is updated using the state transition table.


while(1)
 {
 if (PollLCDShieldSwitches()) //Poll buttons
 {
 switch (actiontable[button][currentstate])
 { // Call transition action
 case NACT: ;
 break;
 case INIT: InitAction();
 break;
 case EXIT: ExitAction();
 break;
 case START: StartAction();
 break;
 case HOLD: HoldAction();
 break;
 case CONT: ContAction();
 break;
 case STOP: StopAction();
 break;
 }
 currentstate=transtable[button][currentstate]; //Update state
 }
 }

In this example there is no activity associated with the state, however, if the timer functionality was implemented then the update timer display function would be an activity of the Running state. Each the function for transition activity is shown below


void InitAction(void)
{
 LCDPutStr("Stop Watch", HEADERX, HEADERY, LARGE, WHITE, BLUE);
 LCDPutStr("Exit ", SOFTBUTTX, SOFTBUTT1Y, MEDIUM, WHITE, BLUE);
 LCDPutStr(" ", SOFTBUTTX, SOFTBUTT2Y, MEDIUM, WHITE, BLUE);
 LCDPutStr("Start", SOFTBUTTX, SOFTBUTT3Y, MEDIUM, WHITE, BLUE);
}

void ExitAction(void)
{
 LCDPutStr("Main Menu ", HEADERX, HEADERY, LARGE, WHITE, BLUE);
 LCDPutStr("Timer", SOFTBUTTX, SOFTBUTT1Y, MEDIUM, WHITE, BLUE);
 LCDPutStr(" ", SOFTBUTTX, SOFTBUTT2Y, MEDIUM, WHITE, BLUE);
 LCDPutStr(" ", SOFTBUTTX, SOFTBUTT3Y, MEDIUM, WHITE, BLUE);
}

void StartAction(void)
{
 LCDPutStr("Running ", HEADERX, HEADERY, LARGE, WHITE, BLUE);
 LCDPutStr("Lap ", SOFTBUTTX, SOFTBUTT1Y, MEDIUM, WHITE, BLUE);
 LCDPutStr(" ", SOFTBUTTX, SOFTBUTT2Y, MEDIUM, WHITE, BLUE);
 LCDPutStr("Stop ", SOFTBUTTX, SOFTBUTT3Y, MEDIUM, WHITE, BLUE);
}

void HoldAction(void)
{
 LCDPutStr("Lap ", HEADERX, HEADERY, LARGE, WHITE, BLUE);
 LCDPutStr("Run ", SOFTBUTTX, SOFTBUTT1Y, MEDIUM, WHITE, BLUE);
 LCDPutStr(" ", SOFTBUTTX, SOFTBUTT2Y, MEDIUM, WHITE, BLUE);
 LCDPutStr(" ", SOFTBUTTX, SOFTBUTT3Y, MEDIUM, WHITE, BLUE);
}

void ContAction(void)
{
 LCDPutStr("Running ", HEADERX, HEADERY, LARGE, WHITE, BLUE);
 LCDPutStr("Lap ", SOFTBUTTX, SOFTBUTT1Y, MEDIUM, WHITE, BLUE);
 LCDPutStr(" ", SOFTBUTTX, SOFTBUTT2Y, MEDIUM, WHITE, BLUE);
 LCDPutStr("Stop ", SOFTBUTTX, SOFTBUTT3Y, MEDIUM, WHITE, BLUE);
}

void StopAction(void)
{
 LCDPutStr("Stopped ", HEADERX, HEADERY, LARGE, WHITE, BLUE);
 LCDPutStr("Exit ", SOFTBUTTX, SOFTBUTT1Y, MEDIUM, WHITE, BLUE);
 LCDPutStr("Reset", SOFTBUTTX, SOFTBUTT2Y, MEDIUM, WHITE, BLUE);
 LCDPutStr("Start", SOFTBUTTX, SOFTBUTT3Y, MEDIUM, WHITE, BLUE);
}

The PollLCDShieldSwitches function, shown below, is used to poll the state (Pressed or Released) of the three switches on the Sparkfun Color LCD Shield. It implements a software debounce for each switch, both when pressed and released, by ensuring that the state of each switch remains the same for MAXPOLL=10 samples with a delay of ≈100μs between samples. When the function detects that one of the switches has been pressed and then released it stores the number of the switch in the global variable button and returns 1.


char PollLCDShieldSwitches(void)
{
 static char buttonstate=0;
 static char S1count=0;
 static char S2count=0;
 static char S3count=0;

 pressrelease=0;

 if (!(buttonstate & (1<<SWITCH1)))
 {
 if ((PINS1 & (1 << S1))) S1count=0;
 else S1count++;
 if (S1count > MAXPOLL)
 {
 buttonstate |= (1<<SWITCH1);
 S1count=0;
 }
 //LCDPutStr("S1 Up ", DISPX, DISPY, MEDIUM, WHITE, BLUE); //testing
 }
 else
 {
 if (!(PINS1 & (1 << S1))) S1count=0;
 else S1count++;
 if (S1count > MAXPOLL)
 {
 buttonstate &= ~(1<<SWITCH1);
 S1count=0;
 pressrelease |= (1<<SWITCH1);
 button=SWITCH1;
 }
 //LCDPutStr("S1 Down", DISPX, DISPY, MEDIUM, WHITE, BLUE); //testing
 }

if (!(buttonstate & (1<<SWITCH2)))
 {
 if ((PINS2 & (1 << S2))) S2count=0;
 else S2count++;
 if (S2count > MAXPOLL)
 {
 buttonstate |= (1<<SWITCH2);
 S2count=0;
 }
 //LCDPutStr("S2 Up ", DISPX-10, DISPY, MEDIUM, WHITE, BLUE); //testing
 }
 else
 {
 if (!(PINS2 & (1 << S2))) S2count=0;
 else S2count++;
 if (S2count > MAXPOLL)
 {
 buttonstate &= ~(1<<SWITCH2);
 S2count=0;
 pressrelease |= (1<<SWITCH2);
 button=SWITCH2;
 }
 //LCDPutStr("S2 Down", DISPX-10, DISPY, MEDIUM, WHITE, BLUE); //testing
 }

 if (!(buttonstate & (1<<SWITCH3)))
 {
 if ((PINS3 & (1 << S3))) S3count=0;
 else S3count++;
 if (S3count > MAXPOLL)
 {
 buttonstate |= (1<<SWITCH3);
 S3count=0;
 }
 //LCDPutStr("S3 Up ", DISPX-20, DISPY, MEDIUM, WHITE, BLUE); //testing
 }
 else
 {
 if (!(PINS3 & (1 << S3))) S3count=0;
 else S3count++;
 if (S3count > MAXPOLL)
 {
 buttonstate &= ~(1<<SWITCH3);
 S3count=0;
 pressrelease |= (1<<SWITCH3);
 button=SWITCH3;
 }
 //LCDPutStr("S3 Down", DISPX-20, DISPY, MEDIUM, WHITE, BLUE); //testing
 }
 _delay_us(100);
 if (pressrelease == 0) return 0;
 else return 1;
}

The complete code is given below.


/*
 * StopWatch.c
 *
 * Demonstration of a FSM driven Stopwatch menu that uses the three buttons on the Sparkfun COlor LCD Shield
 * as software defined buttons to navigate through the menu displayed on the LCD.
 * The FSM is implemented using a state transition and action table.
 * The actual stopwatch timer is not implemented.
 *
 * Created: 21/12/2011 10:31:35
 * Author: Benn Thomsen
 */

#define F_CPU 16000000UL /* 16 MHz Crystal Oscillator */
#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>
#include "..\..\Utilities\Color_LCD_driver.h"

/* States. */
#define MENU 0
#define RESET 1
#define RUN 2
#define LAP 3
#define PAUSE 4

/* Events */
#define SWITCH1 0
#define SWITCH2 1
#define SWITCH3 2

/* Actions */
#define NACT 0
#define INIT 1
#define EXIT 2
#define START 3
#define HOLD 4
#define CONT 5
#define STOP 6
char transtable[3][5] = /* transtab[Input][CurrentState] => NextState */
{
//Menu Reset Run Lap Pause
RESET, MENU, LAP, RUN, MENU, // Switch1
MENU, RESET, RUN, LAP, RESET, // Switch2
MENU, RUN, PAUSE, LAP, RUN, // Switch3
};

char actiontable[3][5] = /* transtab[Input][CurrentState] => NextState */
{
//Menu Reset Run Lap Pause
INIT, EXIT, HOLD, CONT, EXIT, // Switch1
NACT, NACT, NACT, NACT, INIT, // Switch2
NACT, START, STOP, NACT, START, // Switch3
};

#define SOFTBUTTX 5
#define SOFTBUTT1Y 5
#define SOFTBUTT2Y 45
#define SOFTBUTT3Y 90
#define HEADERX 110
#define HEADERY 10
#define DISPX 60
#define DISPY 20

#define SWITCH1PR 0x01
#define SWITCH2PR 0x02
#define SWITCH3PR 0x04
#define MAXPOLL 10

// Function Prototypes
void InitAction(void);
void ExitAction(void);
void StartAction(void);
void HoldAction(void);
void ContAction(void);
void StopAction(void);

char PollLCDShieldSwitches(void);

// Global variable

volatile char pressrelease=0;
volatile char button=0;
int main(void)
{
 LCDInit(); //Initialize the LCD
 LCDClear(BLUE);
 ExitAction();

 char currentstate = MENU;
 while(1)
 {
 if (PollLCDShieldSwitches()) //Poll buttons
 {
 switch (actiontable[button][currentstate])
 { // Call transition action
 case NACT: ;
 break;
 case INIT: InitAction();
 break;
 case EXIT: ExitAction();
 break;
 case START: StartAction();
 break;
 case HOLD: HoldAction();
 break;
 case CONT: ContAction();
 break;
 case STOP: StopAction();
 break;
 }
 currentstate=transtable[button][currentstate]; //Update state
 }
 }
}

void InitAction(void)
{
 LCDPutStr("Stop Watch", HEADERX, HEADERY, LARGE, WHITE, BLUE);
 LCDPutStr("Exit ", SOFTBUTTX, SOFTBUTT1Y, MEDIUM, WHITE, BLUE);
 LCDPutStr(" ", SOFTBUTTX, SOFTBUTT2Y, MEDIUM, WHITE, BLUE);
 LCDPutStr("Start", SOFTBUTTX, SOFTBUTT3Y, MEDIUM, WHITE, BLUE);
}

void ExitAction(void)
{
 LCDPutStr("Main Menu ", HEADERX, HEADERY, LARGE, WHITE, BLUE);
 LCDPutStr("Timer", SOFTBUTTX, SOFTBUTT1Y, MEDIUM, WHITE, BLUE);
 LCDPutStr(" ", SOFTBUTTX, SOFTBUTT2Y, MEDIUM, WHITE, BLUE);
 LCDPutStr(" ", SOFTBUTTX, SOFTBUTT3Y, MEDIUM, WHITE, BLUE);
}

void StartAction(void)
{
 LCDPutStr("Running ", HEADERX, HEADERY, LARGE, WHITE, BLUE);
 LCDPutStr("Lap ", SOFTBUTTX, SOFTBUTT1Y, MEDIUM, WHITE, BLUE);
 LCDPutStr(" ", SOFTBUTTX, SOFTBUTT2Y, MEDIUM, WHITE, BLUE);
 LCDPutStr("Stop ", SOFTBUTTX, SOFTBUTT3Y, MEDIUM, WHITE, BLUE);
}

void HoldAction(void)
{
 LCDPutStr("Lap ", HEADERX, HEADERY, LARGE, WHITE, BLUE);
 LCDPutStr("Run ", SOFTBUTTX, SOFTBUTT1Y, MEDIUM, WHITE, BLUE);
 LCDPutStr(" ", SOFTBUTTX, SOFTBUTT2Y, MEDIUM, WHITE, BLUE);
 LCDPutStr(" ", SOFTBUTTX, SOFTBUTT3Y, MEDIUM, WHITE, BLUE);
}

void ContAction(void)
{
 LCDPutStr("Running ", HEADERX, HEADERY, LARGE, WHITE, BLUE);
 LCDPutStr("Lap ", SOFTBUTTX, SOFTBUTT1Y, MEDIUM, WHITE, BLUE);
 LCDPutStr(" ", SOFTBUTTX, SOFTBUTT2Y, MEDIUM, WHITE, BLUE);
 LCDPutStr("Stop ", SOFTBUTTX, SOFTBUTT3Y, MEDIUM, WHITE, BLUE);
}

void StopAction(void)
{
 LCDPutStr("Stopped ", HEADERX, HEADERY, LARGE, WHITE, BLUE);
 LCDPutStr("Exit ", SOFTBUTTX, SOFTBUTT1Y, MEDIUM, WHITE, BLUE);
 LCDPutStr("Reset", SOFTBUTTX, SOFTBUTT2Y, MEDIUM, WHITE, BLUE);
 LCDPutStr("Start", SOFTBUTTX, SOFTBUTT3Y, MEDIUM, WHITE, BLUE);
}

char PollLCDShieldSwitches(void)
{
 static char buttonstate=0;
 static char S1count=0;
 static char S2count=0;
 static char S3count=0;

 pressrelease=0;

 if (!(buttonstate & (1<<SWITCH1)))
 {
 if ((PINS1 & (1 << S1))) S1count=0;
 else S1count++;
 if (S1count > MAXPOLL)
 {
 buttonstate |= (1<<SWITCH1);
 S1count=0;
 }
 //LCDPutStr("S1 Up ", DISPX, DISPY, MEDIUM, WHITE, BLUE); //testing
 }
 else
 {
 if (!(PINS1 & (1 << S1))) S1count=0;
 else S1count++;
 if (S1count > MAXPOLL)
 {
 buttonstate &= ~(1<<SWITCH1);
 S1count=0;
 pressrelease |= (1<<SWITCH1);
 button=SWITCH1;
 }
 //LCDPutStr("S1 Down", DISPX, DISPY, MEDIUM, WHITE, BLUE); //testing
 }

if (!(buttonstate & (1<<SWITCH2)))
 {
 if ((PINS2 & (1 << S2))) S2count=0;
 else S2count++;
 if (S2count > MAXPOLL)
 {
 buttonstate |= (1<<SWITCH2);
 S2count=0;
 }
 //LCDPutStr("S2 Up ", DISPX-10, DISPY, MEDIUM, WHITE, BLUE); //testing
 }
 else
 {
 if (!(PINS2 & (1 << S2))) S2count=0;
 else S2count++;
 if (S2count > MAXPOLL)
 {
 buttonstate &= ~(1<<SWITCH2);
 S2count=0;
 pressrelease |= (1<<SWITCH2);
 button=SWITCH2;
 }
 //LCDPutStr("S2 Down", DISPX-10, DISPY, MEDIUM, WHITE, BLUE); //testing
 }

 if (!(buttonstate & (1<<SWITCH3)))
 {
 if ((PINS3 & (1 << S3))) S3count=0;
 else S3count++;
 if (S3count > MAXPOLL)
 {
 buttonstate |= (1<<SWITCH3);
 S3count=0;
 }
 //LCDPutStr("S3 Up ", DISPX-20, DISPY, MEDIUM, WHITE, BLUE); //testing
 }
 else
 {
 if (!(PINS3 & (1 << S3))) S3count=0;
 else S3count++;
 if (S3count > MAXPOLL)
 {
 buttonstate &= ~(1<<SWITCH3);
 S3count=0;
 pressrelease |= (1<<SWITCH3);
 button=SWITCH3;
 }
 //LCDPutStr("S3 Down", DISPX-20, DISPY, MEDIUM, WHITE, BLUE); //testing
 }
 _delay_us(100);
 if (pressrelease == 0) return 0;
 else return 1;
}

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s