A Function Pointer Based State Machine

Overview

This type of state machine involves:

  • An enumeration of all possible states
  • An enumeration of all possible events
  • A state transition matrix (array) where each row contains a state, and event, and the next state to tramsition to
  • Another array which maps each state with a state function pointer that gets called.
  • A simple main loop that does not need to be edited (all the control is changed by modifying the state transition matrix)

Advantages And Disadvantages

The advantages:

  • The ‘flow’ of the program is really easy to understand
  • The ‘flow’ is really easy to change
  • Function calling is fast, as there is no switch/if statement to determine function to call (instead uses function pointers)

The disadvantages:

  • Single event to function-to-call correlation, i.e. you cannot call a function only when two or more events have occurred.
  • It is not necessarily clear what the function-to-call does (i.e. does it change the state or keep in the same?)
  • Event determination can be slow when ‘if’ statements are used.
  • Can be potentially difficult to scale in larger programs, where you may want multiple state machines and threading. 

An Example

This type of state machine is explained and demo’d below with a simple light flashing circuit that is turned on and off with a button press. When the button is pressed, the light starts flashing on/off every 2 seconds. Further button pushes toggle the light between the flashing mode and the off state.

The full, working example can be found at https://github.com/mbedded-ninja/FunctionPointerStateMachineExample.

The State And Event Enumerations

These enumerations hold values for every possible state and every possible event that your program will require. I have prefixed all states with ST_ and events with EV_ for readability. Sometimes, you could just use #define commands for every state and manually associate them with a number. But since the number doesn’t actually matter, why not use an enumeration? Enumerations also allow you to insert new states/events anywhere on the list, allowing you to keep a visual clue of the flow between states/events (one after the other), which would get messy if you used the #define command.

Our events are defined in another enum:

The State Transition Matrix

This following code defines a row in the state transition matrix (the state transition matrix is just an array of this structure). This structure contains the current state, an event, and the state to transition to.

The state transition matrix is the heart of this state machine methodology. It specifies what the next state should be, given the current state and the event that just occurred. It is built as an array of the current-state/event/next-state structure defined above.

The State Function Array

The state function array holds a function pointer to the function which gets called for each state. In this example, we also store a printable state name for debugging purposes. Each row in the state function array is defined by a struct:

And then the actual array is initialised as follows:

Note that this array has to stay in sync with the state_t enumeration. That is, there must be the same number of rows in stateFunctionA as there are states in state_t, and they must be in the same order.

This could be mitigated by saving the state enumeration in the array also, and iterating over each element until you have found the state you are looking for. However, this will incur a performance penalty (which may be insignificant).

The State Functions

The state functions are the functions which are called when the current state and event matches a pair in the state transition matrix. While the rest of the state machine controls the high-level flow, the state functions are the guts of the state machine and are the functions which actually DO THINGS.

These state functions below are only really stubs, and don’t do much. Real life state functions might toggle GPIO pins, send bytes of data over UART, e.t.c.

The State Machine Internal Variables

This simple state machine needs to remember only one thing, the current state. As I am writing this code in a “C with classes” style, all the state machine’s variables are declared in the stateMachine_t struct, as shown below:

A pointer to this struct gets passed in as the first variable to all the state machine functions, just like the this object in an object-orientated world.

The Get Event Function

The function provides the state machine with an event to act upon (incl. EV_NONE, if no real event occurred). In an embedded environment, this would do things like read GPIO pins to see if a button had been pushed, check a UART flag to see if a byte has been received, e.t.c.

In our example world, we will just check the flag buttonPushed, which simulates a button press.

The State Machine RunIteration() Function

The RunIteration() function for this state machine is pretty generic and simple, and you don’t usually have to modify it. All of the logic is controlled by the state transition matrix above. Essentially, the function gets an event (lets call it the current event), and then runs through the state transition matrix row by row to find a pre-defined state and event pair that match the current state and event. If so, it transitions to the specified next state, and then calls the state function pointed to by the function pointer.

As mentioned above, the full working example can be found at https://github.com/mbedded-ninja/FunctionPointerStateMachineExample.

Posted: September 22nd, 2011 at 5:57 pm
Last Updated on: April 17th, 2017 at 4:14 am

  • Geoffrey Hunter

    Will fix the formatting issues!

  • Rob

    Great howto. What kind of hardware have you implemented on?

    • I have used this on both Atmel AVR’s and PSoC 3/5’s. However, I do prefer the RTOS approach (using threads to reduce the number of state machines, and then using switch statements to implement small ones inside those threads) over this for most projects.