Jump to content
43oh

A brief state machine implementation tutorial


Recommended Posts

In this thread I would like to go though the pieces of code that implement a state machine to control one or more LEDs (3 in this case). It is written to be a device driver with an interface to a client module. You could argue that it is overkill for controlling LEDs, but this is intended to be an example on translating a two-level hierarchical UML statechart into C code. The complete program includes other modules, but for now I just want to concentrate on the LED state machine.

 

First, though, why use a state machine? There are different choices for the structure of embedded software, and which one works best depends on factors like program size and complexity and ease of maintenance and modification. From most of the the books I've read on the subject (e.g., Simon's "An Embedded Software Primer"), the structures go from the simplest, a round robin with polling, to a round robin with interrupts (including some sleeping), then a task queue and scheduler, and finally a real-time operating system (RTOS). State machines, which seem to be neglected in many books, offer an alternative to task schedulers and real-time operating systems (or can be used in conjunction with them). They are a good approach when the software activity is primarily responses to stimuli from inputs.

 

State machines allow for multiple thread operations combined with a fairly straight-forward way to translate a program specification into code. They can be implemented with large, nested switch statements, but this approach can get out of hand and be difficult to maintain. (I know this from personal experience because I took this approach a couple of years ago with a program to control a homebrew shortwave transceiver. It depended on a mess of flags and multiple switch statements and was difficult to debug.) Instead we will associate with each state a couple of C functions that perform the needed actions, parse signals to the state machine, and make the appropriate transitions.

 

This work is an outgrowth of my study of two books. "MSP430 State Machine Programming with the ES2274" by Tom Baugh shows how to build very low power applications on the MSP430 using flat state machines. It also taught me a lot about C and MSP430 assembly instructions. "Practical UML Statecharts in C/C++" by Miro Samek shows how to use his open-source system to implement hierarchical state machines and its Chapter 2 is a crash course UML statecharts, which are part of the Unified Modeling Language (UML). The approach I will take uses a two-level hierarchical UML statechart for the specification, which I then translate into a flat state machine implemented as per Baugh. (Alternatively, one can use some of Samek's software to implement these state machines.)

 

Here is the UML statechart specifying the state machine for the LED driver module:

post-4636-135135520192_thumb.jpg

Each box with rounded corners in the diagram is a state. There are two higher level states representing two different modes of operation. Each of these "superstates" contains two substates which describe the behavior of the LED state machine for that mode of operation. The superstate ledStateSteady represents the LED being either off or on indefinately. The superstate ledStateBink represents the LED being in a blinking mode.

 

The arrows represent transistions from one state to another caused by a "signal" to the state machine. For example, if the LED is in the steady off state, reception of the signal LED_ON_SIG causes a transition to the steady on state. As another example, if the LED is in either the steady off or steady on states and receives an LED_BLINK_SIG, it will transition to the blinking superstate. Note that some transition arrows start or end on a superstate, while others start or end on a substate.

 

The black dots represent initial starting points. The dot at the very top shows that at start up the state machine goes to the steady superstate. When going into the steady superstate, the dot inside that state shows that initially the transition is into the off steady state. The dot inside the blink superstate shows that upon entering that superstate the state machine transitions to the blink on state. Inside the steady superstate is a dot with an "H" inside. This represents "history" and means that when there is a transition due to the signal LED_STEADY_SIG, the state machine returns to whichever steady substate was last active. (According to strict UML rules, this H dot should replace the solid dot inside the steady superstate, but I think this is confusing.)

 

Finally, each state contains Entry and/or Exit actions which are taken upon entry to or exit from that state. For example, when the blink superstate is entered the state machine software timer (driven by the system tick) is activated to allow blinking. When the blink superstate is left the timer is deactivated. As another example, when the blink on substate is entered the LED is turned on and the timer counter is set for the number of system ticks it will be on. When the timer counts down to zero, a LED_TIMEOUT_SIG signal is sent to the state machine and a transition is made to the blink off state, where upon entry the LED is turned off and the timer counter is loaded with the number of system ticks it is to be off. One rule of UML statecharts is that as you transition out of a substate into another substate in a different superstate, you must execute the applicable entry and exit actions of the superstates (as well as the substates) as you progress up and then down the hierarchy. This simple example does not have any transitions to illustrate this.

 

Well, that's it for this first post. Next time we'll quickly look at the "client interface" (the associated header file) and then begin building the C code to implement the LED state machine.

 

Cheers,

Andy n1ksn

Link to post
Share on other sites

In this part we'll look at a couple of header files that are included in our Led.c module. The first is the file System.h which contains some global defines for our program:

#ifndef SYSTEM_H
#define SYSTEM_H

#define FALSE 0
#define TRUE  1
#define NULL  0

// Each tick approximately 15.6 ms with ACLK at 32768 Hz (1 sec = 64 ticks)
#define ACLK_FREQ      32768
#define TICK_DIVISOR   512   // In this version, set to match WDT interval
#define TICKS_PER_SEC  (ACLK_FREQ / TICK_DIVISOR)

// Port and I/O pin bitmask definitions
#define LEDOUT  P1OUT

#define LED1_BITMASK  BIT0
#define LED2_BITMASK  BIT1
#define LED3_BITMASK  BIT2

#define BTNIN   P1IN
#define BTNIE   P1IE
#define BTNIFG  P1IFG
#define BTNIES  P1IES
#define BTNREN  P1REN
#define BTNOUT  P1OUT

#define BTN1_BITMASK  BIT3
#define BTN2_BITMASK  BIT4
#define BTN3_BITMASK  BIT5

typedef void( * PFN )(void);  // Pointer to function (void) foo(void)

void System_InitializeHW(void);

#endif

After some initial defines for boolean values and NULL we have defines associated with the system tick. Our Led.c state machine module will be using the TICKS_PER_SEC value. Note that the module System.c sets up the watchdog timer to provide a ~15.6 ms system tick by putting the WDT in an interval mode, so these defines must coincide with that interval. (If you use a regular timer for the system tick, these defines help set that up.)

 

The next four defines give aliases for the port(s) to which the LEDs are connected, and the bitmasks for them. This particular demonstration uses a single port, but each LED could be wired to a different port in which case aliases would be need for these. These defines aren't really necessary, as we could use P1OUT, BIT0, etc. in our module, but I like to localize pin assignments in the System.h header and System.c module. Below these are similar defines for the pushbutton module.

 

Near the bottom of the header is a typedef for a pointer to a function which is of the "void foo(void)" variety, with no arguments and no return value. The name of the defined type is PFN (for "pointer to function") and it is the only function pointer type we will use. This state machine implementation uses a function pointer of this type to indicate the current state of the state machine. If you are not comfortable with function pointers in C, you should get to know them. They are our friends.

 

Now let's look at the header Led.h:

#ifndef LED_H
#define LED_H

// Each system tick approximately 15.6 ms with ACLK at 32768 Hz (64 ticks/sec)
#define LED_DEFAULT_TICKS  (TICKS_PER_SEC / 2)   // Default blink half period
#define LED_MAX_TICKS      (32 * TICKS_PER_SEC)  // Max ~ 1 cycle per minute
#define LED_MIN_TICKS      2                     // Should be at least 2

// Called by main at start-up
void Led_InitializeHW(void);
void Led_InitializeApp(void);

// This function is called every system tick from main to drive
// software timers for the LED state machines
void Led_ProcessTimerEvents(void);

//------------------------------------------------------------------------------
// Variable type and values to allow client module to specify LED
typedef enum {
   LED_1 = 0,
   LED_2,
   LED_3
} Led_Index;

#define LED_MAX  3                     // Must coincide with typedef above

// Functions to control LEDs from client module
// Put LED in steady off state
void Led_TurnOff(Led_Index index);

// Put LED in steady on state
void Led_TurnOn(Led_Index index);

// Puts LED in last steady state
void Led_Steady(Led_Index index);

// Puts LED in blink state
void Led_Blink(Led_Index index);

// Sets LED on and off tick lengths (in system ticks),
// restricted to range set by LED_MAX_TICKS and LED_MIN_TICKS.
// Does not change current state of LED.
void Led_SetBlink(Led_Index index, uint16_t on_ticks, uint16_t off_ticks);

// Resets blink period to 2 * LED_DEFAULT_TICKS with 50% duty cycle
// without changing LED state.
void Led_ResetBlink(Led_Index index);

// Retrieves current on_tick value of LED
uint16_t Led_OnTicks(Led_Index index);

// Retrieves current off_tick value of LED
uint16_t Led_OffTicks(Led_Index index);

#endif

Here we first have a few more defines which depend on the TICKS_PER_SEC value. They set the default value for an LED blink period and the minimum and maximum number of system ticks for how long an LED can be off or on when blinking. The minimum value is the most important, as too short an interval could cause trouble with our blink software timer. The other two are arbitrary values.

 

The next three statements are function prototypes which appear in this header so that the main module can call them. (This header is included in the main module.) The first two prototypes are for initialization of module-specific hardware and software on startup, and the third will be called by main on each system tick to operate the LED software timers used when they are blinking.

 

The remainder of the function prototypes are the client interface for this module. Whichever modules that need to operate the LEDs will do so through these function calls. Their names are fairly self-explanatory. In order for a client to identify which LED is to be affected, the functions have the parameter "index" which is of type Led_Index. This is a custom type set up by the typedef enum statement just above these function prototypes. It is a combination of typedef and enum that defines a variable which can only take on the values enumerated in the definition. In this case there are three LED index values, LED_1, etc. Why do this typedef instead of using a regular old integer? It is a way to have the compiler flag some programmer mistakes that would set the index to a value outside the valid range for the LED index. (I don't think it will catch runtime errors, however.) In any case, it is a nice way to remember which index value goes with which LED. A companion define sets LED_MAX to the number of LEDs in our module.

 

That's enough for this part. Next we'll (finally) start to look at the C code in Led.c.

 

If you have any comments or questions about any of these posts, please don't hesitate to write a reply to this thread. I'll eventually include a zip file will all the program files in it.

 

Later,

Andy n1ksn

 

Added in edit: I've added another client function prototype, namely for Led_Steady. This function returns the state machine to whatever steady substate was last active.

Link to post
Share on other sites

Now that we have done some of the preliminaries, it's timer to finally get down to some of the C code to implement our LED state machine.

//------------------------------------------------------------------------------
// Led.c in project StateMachineBlink6
//
// This module contains LED drivers for setting LEDs off, on, or blinking.
// It illustrates the implementation of a simple hierarchical state machine.  
//
// Andrew Palm
// 2011.10.25
//------------------------------------------------------------------------------
#include 
#include 
#include "System.h"
#include "Led.h"

//------------------------------------------------------------------------------
// Variable type and values of signals to LED state machine
typedef enum {
   NULL_SIG = 0,
   LED_OFF_SIG,
   LED_ON_SIG,
   LED_STEADY_SIG,
   LED_BLINK_SIG,
   LED_TIMEOUT_SIG
} Led_Signal;

First we see the includes which are the two header files covered in Part 2 plus the device file and the header stdint.h which defines types such as uint8_t. After these follows another combined typedef enum statement that defines the state machine signals. The signals are those associated with the transition arrows in the UML statechart from Part 1. They are how the world outside the state machine causes it to change state. Specifically (ignoring the NULL_SIG which isn't used), the first four signals are sent by the client interface functions while LED_TIMEOUT_SIG is sent by the software timer. We'll see how they work further on.

 

//------------------------------------------------------------------------------
// LED finite state machine variables
typedef struct LedTag {                // The LED FSM 
   PFN        Current_State;          // Pointer to current state function
   PFN        Last_Steady_State;      // Saved (sub) state for Steady history
   uint16_t   timer_counter;          // Number of delay ticks remaining
   uint16_t   on_ticks;               // Number of delay ticks for on state
   uint16_t   off_ticks;              // Number of delay ticks for off state
   volatile   uint8_t *port;          // Pointer to LED port
   uint8_t    bitmask;                // Bit mask for LED pin
   uint8_t    timer_active;           // Flag that timer is active
} Led;

// Instances of LED state machines
static Led Led_1;                      // Led FSM for LED_1
static Led Led_2;                      // Led FSM for LED_2
static Led Led_3;                      // Led FSM for LED_3

//------------------------------------------------------------------------------
// Array to allow specification of LED with an index variable for clients
// The elements should match up with the typedef enum for Led_Index in Led.h.
static Led *Led_Current[] = {
   &Led_1,
   &Led_2,
   &Led_3
};

//------------------------------------------------------------------------------
// These variables are used to set the currently active LED and signal
// for the state entry and handler functions (rather than passing them 
// as parameters).
static Led         *This_Led;          // Pointer to active state machine
static Led_Signal  This_Signal;        // Active signal for state machine

The next statement is a structure typedef that is the heart of the state machine. It sets up the type "Led" and combines the variables that are part of the LED state machine. Why a structure? Because it is an enormously convenient way to collect together a set of variables that are of different types, just as array allows us to collect together several variables of the same type. Instead of an array index, we use either a dot notation or a special structure pointer notation to reference the individual members of the structure. We'll see that later. The variables are as follows:

 

- Current_State - A function pointer that points to the current state function. Every state machine needs this variable. Recall that we defined its type PFN in System.h.

 

- Last_Steady_State - A function pointer that saves the last (sub)state in the steady superstate. This is used to implement the "History" dot in the UML statechart.

 

- timer_counter - The number of system ticks remaining in the software timer for this state machine (when active).

 

- on_ticks - The number of ticks for the blink on (sub)state. Is used to initialize the timer counter at the beginning of the on part of the blink cycle.

 

- off_ticks - As above, but for the off part of the blink cycle.

 

- port - This is a pointer to the output port for the LED. Note that it of type volatile (uint8_t *). This type is necessary because of how C characterizes the I/O ports in the header files. We will leave it at that here.

 

- bitmask - This is the bit mask for the LED pin on its port.

 

- timer_active - This is a boolean flag (TRUE or FALSE) to turn the software timer on and off.

 

Next we have three "instantiations" of the Led type, one for each LED. These are brilliantly named Led_1, Led_2, and Led_3. Whenever these appear in this module they represent the group of variables in the structure for one of the LEDs. We declare them static so that they maintain their values throughout the life of the program.

 

Next we have an array whose three elements are the addresses of the three state machines just created. The address for Led_1 is denoted &Led_1, and so on. Note that its type is (* Led) which means the elements are pointers to variables of type Led. This array is used to allow the client module to reference a specific LED, via its address, using the index for this array. The array index variable is the parameter in the client interface functions whose prototypes we looked at in Led.h.

 

The last two statements create the variables used to manipulate the state machine variables. The variable This_Led is a pointer to an Led structure and is part of the mechanism to specify the variables of a specific Led structure. For example, if a client function is invoked with the Led_Index variable index = LED2, then the statement

 

This_Led = Led_Current_Led[index];

 

will cause This_Led to have the value &Led_2 and we can reference the structure variables with expressions such as

 

This_Led->timer_counter

 

The variable This_Signal is of type Led_Signal (defined in the first code segment of this part) and it will contain the current signal being sent to a state machine.

 

We will use these last two variables to specify the currently chosen LED state machine and the current signal being sent to the state machine in the various functions used to implement the state machine. These module-scope variables could be eliminated by writing our state machine functions with with two argument parameters. I decided to do it with these variables instead to avoid the overhead that goes with passing parameters to a function. The penalty we pay for this is that we must not forget to set these two variables before we call one of these functions.

 

In the next installment we will see how the state machines are initialized on program startup.

 

Andy n1ksn

Link to post
Share on other sites

Now let's look at how we will initialize our LED state machines on program startup. Here is our next code segment:

//------------------------------------------------------------------------------
// The "constructor" and initializer for LED state machines
void Led_Ctor(Led *This,
             uint16_t on_ticks, uint16_t off_ticks,
             uint8_t  bitmask, volatile uint8_t *port); 

void Led_Initial(void);

// Led state machine state entry and state handler functions
void Led_Enter_Steady(void);
void Led_Enter_Steady_Off(void);
void Led_State_Steady_Off(void);
void Led_Enter_Steady_On(void);
void Led_State_Steady_On(void);

void Led_Enter_Blink(void);
void Led_Enter_Blink_Off(void);
void Led_State_Blink_Off(void);
void Led_Enter_Blink_On(void);
void Led_State_Blink_On(void);

These are the function prototypes for this module Led.c, except for those given in Led.h. The first two are for functions used to initialize an LED state machine, which we will look at in a bit. The remainder are state entry functions and state functions for the state machine. While we see them together here, let's say a few words about them. Looking back at our UML statechart we see that there is an entry function for both superstates (representing the steady and blink modes of operation) and for each of the four substates. There is a state function for each substate, but no state functions for the superstates. Why is this?

 

Generally, the substate entry functions are used to perform the actions labelled "Entry" in the statechart and then set the current state to the associated state function. For example, Led_Enter_Blink_On turns the LED on and loads the timer counter for the number of on ticks. It then sets the Current_State variable in the state machine structure to the address of the function Led_State_Blink_On. Now a state entry function for a superstate also performs any entry actions for that superstate, but there is no corresponding state function because whenever a superstate is entered, one always ends up in one of the substates. So instead of setting the Current_State variable, a superstate entry function just calls the entry function for the substate that will be active. This will become clearer later on.

 

Here is the segment containing the initialization functions:

//------------------------------------------------------------------------------
// LED state machine constructor functon
void Led_Ctor(Led *This,
             uint16_t on_ticks, uint16_t off_ticks,
             uint8_t bitmask, volatile uint8_t *port)
{
   This->Current_State = &Led_Initial;
   This->on_ticks = on_ticks;
   This->off_ticks = off_ticks;
   This->bitmask = bitmask;
   This->port = port;
}

//------------------------------------------------------------------------------
// State machine initial state function
void Led_Initial(void) {
   // Put any special initialization statements here
   Led_Enter_Steady();                // Initially in steady superstate
}

The function Led_Ctor takes a pointer (named This) to the Led structure to be initialized, plus values for some of the variables in the structure. The pointer is used in statements like

 

This->on_ticks = on_ticks;

 

where the left hand side is the structure variable and the right side is the function argument containing the desired initial value for that variable. The first function statement sets the Current_State function pointer to the function Led_Initial, which is the second function shown in this segment. This is a special pseudo-state function that represents the highest level dot on the UML statechart. For this example, this function simply calls the entry function for the steady superstate. Any other special initialization actions could be put in it, but aren't needed here.

 

Now we look at the initialization functions called by main on program startup:

//------------------------------------------------------------------------------
// Initialiation functions called by main on startup

void Led_InitializeHW(void) {
// Led port pins initialized in System.c
}

// Construct LED blink state machines and initialize to defaults
void Led_InitializeApp(void) {

   // Common signal for initial calls to state machines for steady off state
   This_Signal = LED_OFF_SIG;

   Led_Ctor(&Led_1, LED_DEFAULT_TICKS, LED_DEFAULT_TICKS, 
            LED1_BITMASK, &LEDOUT); 
   This_Led = &Led_1;                 // Set active LED
   // Call active LED initial state with led steady off signal
   Led_1.Current_State();

   Led_Ctor(&Led_2, LED_DEFAULT_TICKS, LED_DEFAULT_TICKS,
            LED2_BITMASK, &LEDOUT); 
   This_Led = &Led_2;
   Led_2.Current_State();

   Led_Ctor(&Led_3, LED_DEFAULT_TICKS, LED_DEFAULT_TICKS,
            LED3_BITMASK, &LEDOUT); 
   This_Led = &Led_3;
   Led_3.Current_State();
}

The first function does nothing, as the LED port pins are set up in System.c. I left it here as a placeholder. The second function first sets the variable This_Signal to LED_OFF_SIG. As discussed previously, this variable must be set before any state entry or state function is invoked.

 

Next Led_Ctor is called to initialize the Led_1 state machine. The on and off tick variables are set to a default value (given by a define statement in the header Led.h) that results in a blink with a one second period and a 50% duty cycle (equal on and off times). The output port and bitmask are also set as per the defines in System.h. After this the variable This_Led is set to point to Led_1, again because this variable is used inside entry and state functions.

 

Lastly (for Led_1), there is a call to whatever function has its address stored in the Current_State variable. This was set to the address of Led_Initial, so that function is called. Since that function in turn calls Led_Enter_Steady and This_Signal is set to LED_OFF_SIG, we then go to Led_Enter_Steady_Off and finally (as we will see below) end up with Current_State equal to the address of Led_State_Steady_Off. Then we repeat all this for Led_2 and Led_3.

 

Follow that? It really isn't that bad, but I admit scratching my head a few times at first contact.

 

In the next part we will see just what goes into the state entry and state functions.

 

Andy n1ksn

Link to post
Share on other sites

We are now ready to look at the state entry and state handler functions. Here is the set associated with the steady superstate:

//------------------------------------------------------------------------------
// Led state machine state entry and state handler functions

void Led_Enter_Steady(void) {
   switch(This_Signal) {              // Signal determines substate to enter
       case LED_STEADY_SIG: {
           if(This_Led->Last_Steady_State == &Led_State_Steady_On) {
               Led_Enter_Steady_On();
           } else {
               Led_Enter_Steady_Off();
           }
           break;
       }
       case LED_ON_SIG: {
           Led_Enter_Steady_On();
           break;
       }
       case LED_OFF_SIG: {
           Led_Enter_Steady_Off();
           break;
       }
   }
}

void Led_Enter_Steady_Off(void) {
   *(This_Led->port) &= ~(This_Led->bitmask);   // Turn off LED
   This_Led->Current_State = &Led_State_Steady_Off;
   // Save current (sub)state for reentrance to history
   This_Led->Last_Steady_State = This_Led->Current_State;
}

void Led_State_Steady_Off(void) {
   switch(This_Signal) {              // Signal determines next state
       case LED_ON_SIG: {
           Led_Enter_Steady_On();
           break;
       }
       case LED_BLINK_SIG: {
           Led_Enter_Blink();
           break;
       }
   }
}

void Led_Enter_Steady_On(void) {
   *(This_Led->port) |= (This_Led->bitmask);   // Turn on LED
   This_Led->Current_State = &Led_State_Steady_On;
   // Save current (sub)state for reentrance to history
   This_Led->Last_Steady_State = This_Led->Current_State;
}

void Led_State_Steady_On(void) {
   switch(This_Signal) {              // Signal determines next state
       case LED_OFF_SIG: {
           Led_Enter_Steady_Off();
           break;
       }
       case LED_BLINK_SIG: {
           Led_Enter_Blink();
           break;
       }
   }
}

The first function is Led_Enter_Steady, the entry function for the steady superstate. This is the most complicated of the entry functions because this superstate can be entered (in addition to the initial transformation) on three different signals, LED_OFF_SIG, LED_ON_SIG, and LED_STEADY_SIG. Also, if the signal is the last of these the transformation supposed to go to the last active steady substate. For this reason the switch statement has three case entries, one for each signal, and an if-then-else construct to implement the History dot. So depending on the signal, the appropriate entry function is called. If there were any entry actions for this superstate, they would go in this function before the switch statement.

 

Next we have the Led_Enter_Steady_Off entry function. This is where the steady off state entry actions take place. The first statement uses the bitmask to turn off the LED. Note how the left hand side has the asterisk which makes it denote the contents of the pointer This_Led->port which points to LEDOUT which is defined as P1OUT. Then the Current_State variable is set to the address of Led_State_Steady_Off (see the amperstand?). The next time the current state function is called from outside the state machine, this will be the function that is called. Finally, the current state is saved as part of the implementation of the History dot.

 

The Led_State_Steady_Off state handler function has a single switch statement with two cases, one for each of the signals that result in a transition out of that state. If the state had any exit functions, they would be ahead of this switch statement.

 

The last two functions are the state entry and state handler functions for the steady on state and are very similiar to those for the steady off state.

 

Here are the functions for the blink superstate and its substates:

void Led_Enter_Blink(void) {
   This_Led->timer_active = TRUE;     // Timer needed in this superstate
   Led_Enter_Blink_On();              // Initial substate
}

void Led_Enter_Blink_On(void) {
   *(This_Led->port) |= (This_Led->bitmask);   // Turn on LED
   This_Led->timer_counter = This_Led->on_ticks;  // Reload delay timer
   This_Led->Current_State = &Led_State_Blink_On;
}

void Led_State_Blink_On(void) {
   switch(This_Signal) {              // Signal determines next state
       case LED_TIMEOUT_SIG: {
           Led_Enter_Blink_Off();
           break;
       }
       case LED_OFF_SIG:
       case LED_ON_SIG:
       case LED_STEADY_SIG: {
           // Timer not needed in steady superstate
           This_Led->timer_active = FALSE;
           Led_Enter_Steady();
           break;
       }
   }
}

void Led_Enter_Blink_Off(void) {
   *(This_Led->port) &= ~(This_Led->bitmask);   // Turn off LED
   This_Led->timer_counter = This_Led->off_ticks; // Reload delay timer
   This_Led->Current_State = &Led_State_Blink_Off;
}

void Led_State_Blink_Off(void) {
   switch(This_Signal) {              // Signal determines next state
       case LED_TIMEOUT_SIG: {
           Led_Enter_Blink_On();
           break;
       }
       case LED_OFF_SIG:
       case LED_ON_SIG:
       case LED_STEADY_SIG: {
           // Timer not needed in steady superstate
           This_Led->timer_active = FALSE;
           Led_Enter_Steady();
           break;
       }
   }
}

Compared to the steady superstate, the entry function for the blink superstate is very simple. It executes the entry action of activating the timer and then goes right to the entry function for blink on, as per the dot inside this superstate in the UML statechart.

 

The entry function Led_Enter_Blink_On does the entry actions of turning on the LED and loading the timer counter with the variable on_ticks. It then sets the Current_State variable to the corresponding state handler function address. The state handler function Led_State_Blink_On has a single switch statement with two cases to handle four possible signals. If the timeout signal is received the blink off entry function is called. For the other three signals the transition is back to the steady superstate, so in this case the superstate exit action of deactivating the timer is done followed by a call to the steady superstate entry function. The functions for the blink off state are very similar.

 

These functions illustrate one problem with this simple approach, namely that the exit action for the blink superstate has to appear in two places, in each of the substate state handler functions. This is a potential maintenance problem (you could change the exit action in one place but not the other), but more sophisticated approaches that avoid this are more complicated. I think this is a small price to pay because the writing of these state entry and handler functions flowed quite easily with the UML statechart sitting in front of me.

 

That's it for now. In the next part we will look at the client interface and timer functions which actually send the signals to the state machine and drive its transitions.

 

Andy n1ksn

Link to post
Share on other sites

We are headed down the home stretch. We finish this module with two more sets of functions, those which send signals to the state machine. Here are the functions in the client interface (with prototypes in Led.h):

//------------------------------------------------------------------------------
// Functions to allow client module to control LEDs

// Put LED in steady off state
void Led_TurnOff(Led_Index index) {
   This_Led = Led_Current[index];
   This_Signal = LED_OFF_SIG;
   (This_Led->Current_State)();
}

// Put LED in steady on state
void Led_TurnOn(Led_Index index) {
   This_Led = Led_Current[index];
   This_Signal = LED_ON_SIG;
   (This_Led->Current_State)();
}

// Puts LED in last steady state
void Led_Steady(Led_Index index) {
   This_Led = Led_Current[index];
   This_Signal = LED_STEADY_SIG;
   (This_Led->Current_State)();
}

// Puts LED in blink state
void Led_Blink(Led_Index index) {
   This_Led = Led_Current[index];
   This_Signal = LED_BLINK_SIG;
   (This_Led->Current_State)();
}

// Sets LED on and off tick lengths (in system ticks),
// restricted to range set by LED_MAX_TICKS and LED_MIN_TICKS.
// Does not change current state of LED.
void Led_SetBlink(Led_Index index, uint16_t on_ticks, uint16_t off_ticks) {
   if(on_ticks < LED_MIN_TICKS) { on_ticks = LED_MIN_TICKS; }
   if(on_ticks > LED_MAX_TICKS) { on_ticks = LED_MAX_TICKS; } 
   if(off_ticks < LED_MIN_TICKS) { off_ticks = LED_MIN_TICKS; }
   if(off_ticks > LED_MAX_TICKS) { off_ticks = LED_MAX_TICKS; } 
   This_Led = Led_Current[index];
   This_Led->on_ticks = on_ticks;
   This_Led->off_ticks = off_ticks;
}

// Resets blink period to 2 * LED_DEFAULT_TICKS with 50% duty cycle
// without changing LED state.
void Led_ResetBlink(Led_Index index) {
   This_Led = Led_Current[index];
   This_Led->on_ticks = LED_DEFAULT_TICKS;
   This_Led->off_ticks = LED_DEFAULT_TICKS;
}

// Retrieves current on_tick value of LED
uint16_t Led_OnTicks(Led_Index index) {
   This_Led = Led_Current[index];
   return(This_Led->on_ticks);
}

// Retrieves current off_tick value of LED
uint16_t Led_OffTicks(Led_Index index) {
   This_Led = Led_Current[index];
   return(This_Led->off_ticks);
}

The first four client interface functions all have the same structure. FIrst, This_Led is set to the active LED through the Current_Led array. (I explained the use of this array in an earlier part.) Then This_Signal is set to the appropriate signal to cause the desired transformation. Finally, the current state handler function is called. This last statement is

 

(This_Led->Current_State)();

 

Strickly speaking there should be an asterisk in front of This_Led, as the address of the function to be called is the contents of the pointer This_Led->Current_State. But C syntax allows the asterisk to be dropped (or kept). This is how any state machine transformation is made.

 

The third of these functions was added after I started this tutorial, so there is an edit of the Led.h code shown previously. I realized that I had no client function exercising the return to the History dot in the steady superstate, so I slipped it in.

 

The last four functions in this set are used to modify the lengths of the on and off intervals in the blink superstate. None of them change the state of the LED state machine. They are operations done in response to button pushes in the overall demo program.

 

The next function controls the state machine software timers:

//------------------------------------------------------------------------------
// This function is called every system tick from main.  It drives the state
// machine software timers if they are active.
void Led_ProcessTimerEvents(void) {  

   // Common signal for any timer calls to state machines
   This_Signal = LED_TIMEOUT_SIG;

   if(Led_1.timer_active) {
       Led_1.timer_counter--;
       if(0 == Led_1.timer_counter) {
           This_Led = &Led_1;         // Set active LED
           // Call active LED current state with timeout signal
           Led_1.Current_State();
       }
   }

   if(Led_2.timer_active) {
       Led_2.timer_counter--;
       if(0 == Led_2.timer_counter) {
           This_Led = &Led_2;
           Led_2.Current_State();
       }
   }

   if(Led_3.timer_active) {
       Led_3.timer_counter--;
       if(0 == Led_3.timer_counter) {
           This_Led = &Led_3;
           Led_3.Current_State();
       }
   }
}

Since the only signal a timer sends is LED_TIMEOUT_SIG this is loaded into This_Signal at the start of the function. Then for each state machine if the timer is active the variable timer_counter is decremented and if it has reached zero the pointer This_Led is set to the address of the appropriate Led structure and the function pointed to by Current_State is called. This will be one of the blink state handler functions. If the timer is not active, or if the timer has not counted all the way down to zero, nothing is done to change the state machine.

 

This function could have been implemented using a for(;;) loop with an index going through the three LEDs, and this approach might reduce the program size somewhat. If there were more than three LEDs I would definately consider that approach. However, since this function is called by main on every system tick, we want it to execute as fast as possible so that it doesn't hog too much of the time available in the tick interval. A good exercise would be to try it both ways, look at the disassembly output for each, and count processor cycles for each. Depending on the compiler used, one way could have a distinct time advantage over the other. It is this kind of nitty-gritty inspection of the code that is done in Baugh's book (using the IAR compiler). Fortunately, as a hobbyist none of my projects has required this deep inspection, but I can appreciate why it might be necessary. But I digress.

 

That complete our look at all the code in Led.c. I hope it has been clear enough that you can try using this state machine approach in some of your own projects. In the last installment I'll make some comments on the other modules in the demo program and attach a zip file with all the code in this project.

 

Andy n1ksn

Link to post
Share on other sites

Here is the code for the main module in the complete state machine demo program. I'm including it here because the comments contain general descriptions of the the program modules and the main function is rather short.

//------------------------------------------------------------------------------
// main.c in project StateMachineBlink6
//
// The overall structure of this program follows the approach of Tom Baugh
// found in "MSP430 State Machine Programming."
// 
// 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.
//
//   MyApp:  Demonstration applicaton to control the state of the LEDs
//           using three pushbuttons.
//
//   Led:  Driver that sets up and runs state machines that control three LEDs.
//
//   Button:  Driver that sets up and runs state machines that control 
//            pushbutton functions, including debouncing and optional alternate
//            function or acceleration feature.  The functions actually
//            executed on a push are determined by an event sink registration
//            function called by the client, which in this case is the
//            MyApp module.
//  
// See the individual module headers for further descriptions.
//
// This program is written for the MSP430G2452, but should be easily changed
// to other MSP430 devices with 4K 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.25
//------------------------------------------------------------------------------
#include 
#include 
#include "System.h"
#include "Led.h"
#include "Button.h"
#include "MyApp.h"

void InitializeHW(void);
void InitializeApps(void);

//------------------------------------------------------------------------------
void main(void) {
   WDTCTL = (WDTPW | WDTHOLD);        // Disable WDT

   InitializeHW();
   InitializeApps();

    _enable_interrupts();

   while(1) {                        // Event loop 
       _low_power_mode_3();          // Sleep between ticks
       Led_ProcessTimerEvents();     // Process LED state machine timer events
       Button_ProcessTimerEvents();  // Process button SM timer events 
   }
}

//------------------------------------------------------------------------------
void InitializeHW(void) {
   System_InitializeHW();
   Led_InitializeHW();
   Button_InitializeHW();
MyApp_InitializeHW();
}

//------------------------------------------------------------------------------
void InitializeApps(void) {
   Led_InitializeApp();
   Button_InitializeApp();
MyApp_InitializeApp();
}

After stopping the watchdog timer the initialization functions for all the modules are called and the program enters an endless loop. The processor goes to sleep in LPM3 and is awakened by the watchdog timer ISR (the system tick). The loop contains the calls to the state machine timer functions for the LEDs and the buttons. After these are executed the processor goes back to sleep. There is also a port pin ISR in the module Button.c, but after this performs a few operations the processor goes immediately back to sleep.

 

Basically, all the action is driven by three pushbuttons. Each button is managed by a state machine, similar to the state machines for the LEDs. These are contained in the Button module. There are a couple of differences however. First, the button state machine is "flat"--there are no superstates. Second, since for any given state there is only one signal that causes a transition out of that state, signals are not explicitly used in the code (although they appear on the UML statechart). The button state machines handle debouncing and add functionality to the buttons by implementing either an alternate function or an acceleration feature on an extended push and release in addition to the normal push and release function.

 

Another difference in the button state machines is that the client does not control their transitions through client interface functions as is done for the LEDs. Rather, the client module, named MyApp in this demo, "registers event sinks" with the button state machines. What this comes down to is that the client module passes to the button module the addresses of functions that it wants to be executed when a button is pressed. These registrations can be static, occuring only at program startup, or they can be dynamically changed during program execution in response to different operating modes. The demo shows how this can be done. I'm sure everyone is familiar with applications where a set of panel buttons change their function when the gizmo goes into a different mode of operation.

 

The comments at the beginning of the module MyApp explain the button functions and how they change. One button when pressed and released normally (under one second) rotates through the three LEDs. A long (over one second) push and release of this same button changes the mode of the selected LED. One mode is the steady on/off mode where the other two buttons turn the LED on and off. The other two modes are blinking modes. In one of these the other two buttons change the LED blink period up or down. In the other blinking mode the other two buttons increase and decrease the blink duty cycle. I won't go further into the demo, as the main point of these posts was to show how to code the state machines in Led.c. But you should at least look at Button.h and Button.c to understand event sink registration. (By the way, this user interface has several deficiencies, not the least of which is that the currently selected LED is not indicated. But making this better would take away from putting state machines to work in my "real" projectcs.)

 

Attached below is a zip file with all the project code, plus jpg files containing the UML statecharts for the LEDs and the buttons. I've also included the UMLet files for these. If you decide to use this approach and want to draw statecharts, the UMLet program, which is freeware, is a nice way to go. It can be downloaded as a standalone program or an Eclipse plug-in.

 

Unfortunately, the program compiles under CCS to over 2K, so you will need an MSP430 beyond what comes with the Launchpad. But you should be able to write a simpler demo under 2K that blinks the two Launchpad LEDs and cycles through different LED behaviours with successive pushes of the single Launchpad user pushbutton.

 

I hope those of you that have read these posts will have found something useful.

 

Best regards,

Andy n1ksn

 

StateMachineDemo.zip

Link to post
Share on other sites

I realized after posting this tutorial that the functions associated with the LED state machine can (and should) be declared as static. This will allow an optimizing complier to do a better job compressing the code. I've revised the zip file accordingly, including static declaration for the pushbutton state machine. I have not changed the code snippets.

 

Sorry for any inconvenience.

 

Cheers,

Andy n1ksn

Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...