HowToCodeFSMs
HowToCodeFSMs
net/publication/273636602
CITATION READS
1 14,781
1 author:
Lluís Ribas-Xirgo
Autonomous University of Barcelona
76 PUBLICATIONS 144 CITATIONS
SEE PROFILE
All content following this page was uploaded by Lluís Ribas-Xirgo on 17 March 2015.
State machines are used in a number of applications, from games to embedded systems. They
are used to control elements like characters in games or devices with embedded systems.
A state machine is a model of a machine that reacts to any change at inputs from outside and
generates outputs accordingly. The way it reacts to input changes depends on its state.
Take, for instance, a “useless machine” (you bet they exist!) that switches itself off every time
someone switches it on. The control of such a machine can be modeled (you guessed it!) by a
state machine. If the switch is off, it stays idle. If the switch is on, it moves a finger to push it
back to the off position. Then, it returns the finger to the original position and sets itself idle
again. So it has two main states: being idle and switching itself off, and one input: switch
position, and one output to make the finger move.
off
on SWITCH
IDLE
-ING
/0
/1
off on
There are many ways to code the control of this machine. Typically, you may translate the
behavior into a control flow like this:
1: int main() {
2: int get_switch_position(); /* Input function defined elsewhere */
3: void move_finger(); /* Output function defined elsewhere */
4:
5: while( 1 ) { /* Infinite loop, as the controller is permanently on */
6: while( get_switch_position() == 0 ) {
7: move_finger( 0 );
8: } /* while */
9: while( get_switch_position() == 1 ) {
10: move_finger( 1 );
11: } /* while */
12: } /* while */
13: } /* main */
However, it’s much better you code into a state‐based control‐flow, with states of the machine
explicitly stored in a variable. This way, you can easily link the state machine with the program
code.
1: int main() {
2: int get_switch_position(); /* Input function defined elsewhere */
3: void move_finger(); /* Output function defined elsewhere */
4: enum { IDLE, SWITCHING } state;
5:
6: state = IDLE; /* Initially, the device’s switch is off */
7: while( 1 ) { /* Infinite loop, as the controller is permanently on */
8: switch( state ) {
9: case IDLE:
10: move_finger( 0 );
11: if( get_switch_position() == 1 ) { state = SWITCHING; }
12: break;
13: case SWITCHING:
14: move_finger( 1 );
15: if( get_switch_position() == 0 ) { state = IDLE; }
16: break;
17: } /* switch */
18: } /* while */
19: } /* main */
What’s more, by using this coding pattern, it’s possible to change from one state to another
upon any condition, and, last but not least, in embedded systems, it allows verification
methods other than simulation and makes it easy to compute execution costs.
Take, for instance, that you can compute the worst‐case execution time (WCET) of a state‐
machine transition by carefully profiling each possible case. You can, then, use this information
to determine the WCET of the controller, thus the longest reaction time to any input change or
event. That’s critical in real‐time systems.
There are many choices you can take about the processor: Arduino, Raspberry Pi, BeagleBone,
and so on. Indeed, you can use any computer (including portables, tablets, smartphones, …),
with I/O functions interacting with users through some graphical user interface. For the first
kind of processors you will need to build the actual device (a prototype), and the latter can be
used to interface with it or with a device simulator, i.e. with a program that simulates the
physical elements of the useless machine, either autonomously or by interacting with some
user. Picture, for example, a user clicking on an image of a switch on the screen to turn it on
and a program that automatically turns it back to the off position.
In the following pages, you will see how more complex state machines can be systematically
coded into any imperative language, such as C. And C is one of the most used for embedded
systems.
The following state machine corresponds to a four‐state machine (S0, S1 and S2) which changes
states upon function C() returning true. The initial state is S0 and there is an outgoing arc from
S1 that stops the machine when C(1, END). Each state is linked to some output update, which is
represented by a call to a function Out() that takes the state index as argument.
It is assumed that the machine doesn’t change state if no conditions C() are satisfied, i.e. all
states have a loop over themselves.
C(1,0)
Init()
C(2,0) S2 C(1,2)
/ Out(2)
Figure 2. Sample FSM with 3 states and a sink one, representing a final stop (big black dot)
To translate a simple state machine into the corresponding C program you can use the model
given below.
1: int main() {
2: enum { S0, S1, S2, Sstop } state;
3:
4: state = S0;
5: while( state != Sstop ) {
6: switch( state ) {
7: case S0:
8: Out( 0 );
9: if( C(0, 1) ) { state = S1; }
10: break;
11: case S1:
12: Out( 1 );
13: if( C(1, 0) ) { state = S0; }
14: if( C(1, 2) ) { state = S2; }
15: if( C(1, END) ) { state = Sstop; }
16: break;
17: case S2:
18: Out( 2 );
19: if( C(2, 0) ) { state = S0; }
20: break;
21: } /* switch */
22: } /* while */
23: return 0;
24: } /* main */
Note that there is an initializing section where the state variable is set to the initial one and
that the whole state machine code is run inside a loop.
1.1. Rules
(1) perform all actions within the state, i.e. computing outputs, and
Out( 0 );
(2) include an if to test each outgoing arc condition.
if( C(1, 0) ) { state = S0; }
if( C(1, END) ) { state = Sstop; }
if( C(1, 2) ) { state = S2; }
It is important to recall that, in the model above, functions C() and Out() have to be
replaced by corresponding condition evaluation and output updating procedures. Actually,
they can be function calls, but to begin with, they will be expressions and instructions.
1.2. Example
One of the uses of state machines is that of recognizing sequences, for instance, to identify an
incoming message and validate its data. Let’s do a machine that says “HI” every time it gets a
“HI” messages. This machine gets a stream of characters (one per cycle) and generates a
stream of the same kind with dots or ‘H’s followed by ‘I’s. For the sake of simplicity, we assume
that the input stream does never contain concatenated “HI”s.
‘H’ 1
NOT(‘H’)
AND
NOT(‘I’)
The machine responds with an ‘H’ followed by an ‘I’ each time it receives “HI” from the input,
thus it takes two states (OK and NEXT) to generate the output sequence in response to a “HI”.
Note that the arc from OK to NEXT is labeled with 1 (always true) because OK is used to output
‘H’ and nothing else. As input string is said not to contain two consecutives “HI”, losing the
input character at OK state doesn’t affect the machine’s function.
1: int main() {
2: char read_input(); /* character input function defined elsewhere */
3: void write_output( char c ); /* character output function */
4: enum { BEGIN, WAIT, OK, NEXT } state;
5: char input, output;
6:
7: state = BEGIN;
8: while( 1 ) { /* It is running continuously */
9: input = read_input();
10: switch( state ) {
11: case BEGIN:
12: output = '.';
13: if( input == 'H' ) { state = WAIT; }
14: break;
15: case WAIT:
16: output = '.';
17: if( input == 'I' ) { state = OK; }
18: if( input != 'H' && input != 'I' ) { state = BEGIN; }
19: break;
20: case OK:
21: output = 'H';
22: state = NEXT;
23: break;
24: case NEXT:
25: output = 'I';
26: if( input == 'H' ) { state = WAIT; }
27: if( input != 'H' ) { state = BEGIN; }
28: break;
29: } /* switch */
30: write_output( output );
31: } /* while */
32: return 0; /* Unreachable! */
33: } /* main */
In this case, two variables are added to hold the value of the current input character and for
the output character at the present state. By doing so, the previous model is extended by
decomposing the FSM cycle into three phases:
This organization prevents from multiple readings and writings in a single iteration. In this
example, it prevents from taking two characters from input stream in cases with multiple
accesses to the current input character (lines 17‐18 and 26‐27). Note that, this would happen if
instead of taking the value from variable input a call to read_input() is done: two calls,
two characters read in a single step.
Finally, note that the return from main() will never be executed because of the infinite
loop. It is left in the code for consistency with the model given before. We will see that, in the
end, this instruction can be reached when executing improved versions of FSM programs.
1.3. Tips
When programming it is important to keep a naming convention so the result source code is
easy to understand and to manipulate. Therefore, we enumerate states with proper symbol
names, e.g. by using state names beginning with “S_”.
Include a default case to avoid unwanted state variable values let the machine stuck because
of being at an undefined state. As this is an unrecoverable error, it’s better to stop the
execution after it. To do so, the main loop has to be changed as shown:
Indeterminism happens when there is more than one possible next state, i.e. it cannot be
determined which transition should be fired. Of course, with the given programming model,
the state machine will set the next state to be the one included in the last listed if instruction,
but that’s not what it should be. It’s much better to set a specific error code and stop the state
machine.
Checking for indeterminism can be done by using a variable, e.g. firings) that is set to zero
at the beginning of the transition (i.e. of the iteration) and increased by 1 every time an if
condition is satisfied. At the end, if firings is zero (stay at the same state) or one, it’s OK.
But if it’s more than one, there’s an indeterminism.
1: int main() {
2: /* … */
3: enum { S_BEGIN, S_WAIT, S_OK, S_NEXT } state;
4: char input, output;
5: int errcod, firings;
6:
7: state = S_BEGIN;
8: errcod = 0;
9: while( errcod == 0 ) {
10: input = read_input();
11: firings = 0;
12: switch( state ) {
13: case S_BEGIN:
14: output = '.';
15: if( input == 'H' ) { state = S_WAIT; firings = firings +1; }
16: break;
17: case S_WAIT:
18: output = '.';
19: if( input == 'I' ) { state = S_OK; firings = firings +1; }
20: if( input!='H'&&input!='I' ) { state = S_BEGIN; firings = firings +1; }
21: break;
22: case S_OK:
23: output = 'H';
24: state = S_NEXT;
25: break;
26: case S_NEXT:
27: output = 'I';
28: if( input == 'H' ) { state = S_WAIT; firings = firings +1; }
29: if( input != 'H' ) { state = S_BEGIN; firings = firings +1; }
30: break;
31: default:
32: errcod = -1;
33: } /* switch */
34: if( firings > 1 ) { errcod = firings; }
35: write_output( output );
36: } /* while */
37: return errcod;
38: } /* main */
All these tips do not affect the normal behavior of the program but make it robust.
During execution, a program that includes all those former elements would rarely fail due to a
flaw in the specifications or because of memory bit upsets (imagine the code is embedded on
hardware that is exposed to radiation that may spuriously change a bit in the variables).
Instead, it will detect (most of) errors and, eventually, perform appropriate error‐recovering
procedures.
1.4. Summary
The template for programming a FSM in accordance with what’s been said is given below.
1: int main() {
2: /* … */
3: enum { S_0 = 0, S_1 = 1, …, S_stop = N } state;
4: /* … */
5: int errcod, firings;
6:
7: state = S_0;
8: errcod = 0;
9: while( errcod == 0 && state != S_stop ) {
10: /* READ INPUTS */
11: firings = 0;
12: switch( state ) {
13: case S_0:
14: /* … */
15: if( C(0,0) ) { state = S_0; firings = firings +1; }
16: if( C(0,1) ) { state = S_1; firings = firings +1; }
17: if( C(0,2) ) { state = S_2; firings = firings +1; }
18: /* … */
19: if( C(0,N) ) { state = S_stop; firings = firings +1; }
20: break;
21: case S_1:
22: /* … */
23: if( C(1,N) ) { state = S_stop; firings = firings +1; }
24: break;
25: /* … */
26: default:
27: error_code = -1;
28: } /* switch */
29: if( firings > 1 ) { errcod = firings; }
30: /* UPDATE OUTPUTS */
31: } /* while */
32: return errcod;
33: } /* main */
It may look like a long code that can be minimized, and it’s true! However, we should not mix
the mechanical translation from transitions graphs (stage 1 of the FSM programming process)
with possible further code optimizations (stage 2).
It is also recommendable to include self‐loop conditions (line 15) so to verify that the machine
stays at the same state only under the expected conditions and to make the determinism test
complete. Note that you can also detect unexpected self‐loops when firings==0.
A reset button is something we expect from any electronic product to bring it into a known
state. Usually from one state in which the product is useless to one where users gain control
over it.
For plugged devices, we can force a reset by unplugging them. For battery‐operated ones,
things are more difficult, though. Anyhow, the ideal case is to have a neat reset mechanism.
A possible form of that is including a specific input reset signal that cause state machines
return to the initial state. The model will look like the left transition graph below.
C(1,0) OR R
Init()
Init()
C(1,0)
C(0,1) C(1, END)
AND AND
S0 NOT(R) S1 NOT(R) S0 C(0,1) S1 C(1, END)
/ Out(0) / Out(1) / Out(0) / Out(1)
C(1,2) C(1,2)
Transition
AND conditions
C(2,0) OR R S2 NOT(R) S2 logically
/ Out(2) C(2,0) / Out(2) ANDed with
NOT(R)
Figure 4. FSMs with reset input R, flat (left) and hierarchical (right)
On one hand, the reset signal has to be taken into account in all states, i.e. when R is present,
next state has to be the initial one. On the other hand, the rest of the transitions can only be
done if R is absent, i.e. the transition conditions are logically ANDed with NOT(R).
Given the highest priority of R and that it affects all states and transitions, this signal has to be
processed before the output and next state computation stage and, in case there’s reset,
prevent it from executing.
Following the example of the detection of “HI” sequences in the input stream, it is possible to
use character ‘R’ as a resetting signal. Therefore, instead of adding a new input signal, the
machine uses the character stream input to get the reset indicator.
1: int main() {
2: /* … */
3: enum { S_BEGIN, S_WAIT, S_OK, S_NEXT } state;
4: char input, output;
5: int errcod, firings, reset;
6:
7: state = S_BEGIN;
8: errcod = 0;
9: while( errcod == 0 ) {
10: input = read_input();
11: if( input == 'R' ) { reset = 1; } else { reset = 0; }
12: /* if( reset == 1 ) { state = S_BEGIN; } /* IMMEDIATE RESET */ */
13: firings = 0;
14: switch( state ) {
15: case S_BEGIN:
16: output = '.';
17: if( input == 'H' ) { state = S_WAIT; firings = firings +1; }
18: break;
19: case S_WAIT:
20: output = '.';
21: if( input == 'I' ) { state = S_OK; firings = firings +1; }
22: if( input!='H'&&input!='I' ) { state = S_BEGIN; firings = firings +1; }
23: break;
24: case S_OK:
25: output = 'H';
26: state = S_NEXT;
27: break;
28: case S_NEXT:
29: output = 'I';
30: if( input == 'H' ) { state = S_WAIT; firings = firings +1; }
31: if( input != 'H' ) { state = S_BEGIN; firings = firings +1; }
32: break;
33: default:
34: errcod = -1;
35: } /* switch */
36: if( firings > 1 ) { errcod = firings; }
37: if( reset == 1 ) { state = S_BEGIN; } /* DELAYED RESET */
38: write_output( output );
39: } /* while */
40: return errcod;
41: } /* main */
Note that, in this case, upon reset, the FSM returns to S_BEGIN in the next iteration. This is the
behavior corresponding to the FSM models shown in Fig. 4. However, sometimes is interesting
to immediately return to the initial state, without execution of the code for the current state.
For that, you have to comment out line 37 and remove comment marks from line 12 in the
listing above.
To distinguish between the two options, we can call the first one “delayed reset”, and the
second, “immediate reset”. The choice entirely depends on the machine’s desired behavior.
With reset, we can bring a FSM to the initial state, for simulation, we need to bring it to some
stop state, to be able to control its execution fully, i.e. to start, reset and stop it.
As with reset insertion, adding a stop input can be done either by adding a new specific input
signal or by checking for a stop indicator in an existent input. Following the example given in
this text, we shall use character ‘Q’ (from “quit simulation”) as a stop indicator in the input
character stream.
1: int main() {
2: /* … */
3: enum { S_BEGIN, S_WAIT, S_OK, S_NEXT } state;
4: char input, output;
5: int errcod, firings, reset, quit;
6:
7: quit = 0;
8: state = S_BEGIN;
9: errcod = 0;
10: while( errcod == 0 && quit == 0 ) {
11: input = read_input();
12: if( input == 'Q' ) { quit = 1; }
13: if( quit == 0 ) {
14: if( input == 'R' ) { reset = 1; } else { reset = 0; }
15: firings = 0;
16: switch( state ) {
17: case S_BEGIN:
18: output = '.';
19: if( input == 'H' ) { state = S_WAIT; firings = firings +1; }
20: break;
21: case S_WAIT:
22: output = '.';
23: if( input == 'I' ) { state = S_OK; firings = firings +1; }
24: if( input!='H'&&input!='I' ) { state=S_BEGIN; firings=firings+1; }
25: break;
26: case S_OK:
27: output = 'H';
28: state = S_NEXT;
29: break;
30: case S_NEXT:
31: output = 'I';
32: if( input == 'H' ) { state = S_WAIT; firings = firings +1; }
33: if( input != 'H' ) { state = S_BEGIN; firings = firings +1; }
34: break;
35: default:
36: errcod = -1;
37: } /* switch */
38: if( firings > 1 ) { errcod = firings; }
39: if( reset == 1 ) { state = S_BEGIN; }
40: write_output( output );
41: } /* if */
42: } /* while */
43: return errcod;
44: } /* main */
The behavior of a FSM must be encapsulated in a set of data structure and functions, and the
main one in charge of the simulation. As in object‐oriented programming, a FSM can be
modeled by a set of data that includes state, inputs and outputs and a couple of related
methods or functions, namely the one to initialize the data set and the one to perform a single
step or state transition:
1: struct {
2: enum { fsm_BEGIN, fsm_WAIT, fsm_OK, fsm_NEXT } state;
3: char input, output;
4: } fsm_data;
5:
6: int fsm_init() {
7: fsm_data.state = S_BEGIN;
8: return 0;
9: } /* fsm_init */
10:
11: int fsm_step() {
12: int e, f, r; /* e: errcod, f: firings, r: reset */
13:
14: e = 0;
15: f = 0;
16: if( fsm_data.input == 'R' ) { r = 1; } else { r = 0; }
17: switch( fsm_data.state ) {
18: case fsm_BEGIN:
19: fsm_data.output = '.';
20: if( fsm_data.input == 'H' ) { fsm_data.state = fsm_WAIT; f = f +1;
}
21: break;
22: case fsm_WAIT:
23: fsm_data.output = '.';
24: if( fsm_data.input == 'I' ) { fsm_data.state = fsm_OK; f = f +1; }
25: if( fsm_data.input != 'H' && fsm_data.input != 'I' ) {
26: fsm_data.state = fsm_BEGIN; f = f +1;
27: } /* if */
28: break;
29: case fsm_OK:
30: fsm_data.output = 'H';
31: fsm_data.state = fsm_NEXT;
32: break;
33: case fsm_NEXT:
34: fsm_data.output = 'I';
35: if( fsm_data.input == 'H' ) { fsm_data.state = fsm_WAIT; f = f +1; }
36: if( fsm_data.input != 'H' ) { fsm_data.state = fsm_BEGIN; f = f +1; }
37: break;
38: default:
39: e = -1;
40: } /* switch */
41: if( f > 1 ) { e = f; }
42: if( r == 1 ) { fsm_data.state = fsm_BEGIN; }
43: return e;
44: } /* fsm_step */
45:
Note that, in the former code, all state names have been changed their prefixes from “S_” to
“fsm_” so to be in accordance with the rest of the naming convention.
The main function is now only devoted to initialize the set of data and to repeat the
fsm_step() execution until the machine reaches a stop state, runs into an error or
simulation is stopped.
This code organization enables stop the simulation whenever needed. For instance, after some
error has been detected or, if we are lucky, when all tests have been successfully passed.
Note that it is very interesting to prepare such kind of input sequences and expected output
pairs even before inception of the state machine. This is a sort of specification that helps in the
FSM design and, for sure, can be used to verify it.
In order to have a structured simulator of a FSM you should follow the model presented here.
Hence, to have a C program of a FSM simulation you have to:
(1) Use a specific prefix for all FSM related objects, such as “fsm_”
This is particularly useful when other data structures and functions are also used in the
program.
1: struct {
2: enum { fsm_BEGIN, fsm_WAIT, fsm_OK, fsm_NEXT } state;
3: char input, output;
4: } fsm_data;
5:
6: int fsm_init() {
7: fsm_data.state = fsm_BEGIN;
8: fsm_data.input = '\0'; /* Unknown (void) input: NULL character */
9: fsm_data.output = '*'; /* Unset output: asterisk */
10: return 0;
11: } /* fsm_init */
12:
Note that initialization of FSM variables in lines 8 and 9 were not included in the
previous version of fsm_init().
Note that the simulator motor can have an input on its own instead of sharing the
input of the FSM, like in the example given.
In a nutshell, the FSM simulator is decomposed into a data structure, fsm_data, two
functions, fsm_init() and fsm_step(), and a simulator motor that implements the
fsm_step() calling loop.
Here, “fsm” is used for the FSM, but any other name could be used. In the example given, you
could use a name like “sayHi” ;‐)
To monitor what’s going on during simulation you need to see inputs and outputs, but also
iteration count as well as the state and other internal data of the simulated FSM. Therefore,
the simulation motor must include an iteration counter, tick, and a call to a function
fsm_monitor(), that outputs the internal data:
Typically, the least thing fsm_monitor() does is to print the state of the FSM. Here is an
example for the study case:
1: int fsm_monitor() {
2: printf( "mon: { state:" );
3: switch( fsm_data.state ) {
4: case fsm_BEGIN: printf( "BEGIN" ); break;
5: case fsm_WAIT: printf( "WAIT" ); break;
6: case fsm_OK: printf( "OK" ); break;
7: case fsm_NEXT: printf( "NEXT" ); break;
8: default: printf( "UNKNOWN!");
9: } /* switch */
10: printf( " }\n" );
11: return 0;
12: } /* fsm_monitor */
1: int fsm_monitor() {
2: printf( "mon: fsm_data = { state: " );
3: switch( fsm_data.state ) {
4: case fsm_BEGIN: printf( "BEGIN" ); break;
5: case fsm_WAIT: printf( "WAIT" ); break;
6: case fsm_OK: printf( "OK" ); break;
7: case fsm_NEXT: printf( "NEXT" ); break;
8: default: printf( "UNKNOWN!");
9: } /* switch */
10: printf( ", input: %c", fsm_data.input );
11: printf( ", output: %c", fsm_data.output );
12: printf( " }\n" );
13: return 0;
14: } /* fsm_monitor */
In the example, monitoring input and output is not really necessary, but the former
fsm_monitor() function is given only for illustrative purposes.
Ideally, I/O of simulation monitoring and control should be independent from FSM I/O. For
instance, they can be done through different windows on the screen. However, for the sake of
simplicity, we shall use the standard console for both. This is why it is so important to make it
clear to the user that input stream is shared and which shown messages correspond to
simulation control and which others to simulated machine.
For instance an input “HImmQ” would generate the following dialogue (bold typeface
corresponds to user input):
However, what if an erroneous behavior is found after a long sequence of inputs? Stop the
simulation, make changes and repeat the procedure until the error discovery cycle or try and
go directly to the state and inputs of the cycle previous to the one where the error was
discovered? Next section will discuss about that.
The previous code organization enables stopping the simulation whenever needed. However,
sometimes it is interesting to hold the simulation and resume it after eventual changes on the
state machine’s data structure.
To include the extra feature, a new input must be defined. In our example, we will take profit
of the same input character stream, as for ‘Q’. In this case, we use ‘D’. When ‘D’ is given, an
event to enter a debugger is set.
In this context, a debugger is a function that allows modifying any variable of a given state
machine and, particularly, that of its state. Following the example, also the input character can
be updated:
1: int fsm_debug() {
2: int state_no;
3: printf( "dbg: fsm_data.state [0:BEGIN, 1:WAIT, 2:OK, 3:NEXT] = " );
4: scanf( "%u", &state_no );
5: switch( state_no ) {
6: case 0: fsm_data.state = fsm_BEGIN; break;
7: case 1: fsm_data.state = fsm_WAIT; break;
8: case 2: fsm_data.state = fsm_OK; break;
9: case 3: fsm_data.state = fsm_NEXT; break;
10: default: printf( "dbg: fsm_data.state not changed!\n");
11: } /* switch */
12: printf( "dbg: fsm_data.input = " );
13: scanf( " %c\n", &fsm_data.input );
14: fsm_data.input = toupper( fsm_data.input ); /* #include <ctype.h> */
15: return 0;
16: } /* fsm_debug */
The simulation motor is thus complete, with capability to stop (quit), monitor and modify
(debug) simulated FSM. For this, the FSM requires: a data structure to hold all of its internal
variables, fsm_data and functions to perform its initialization, fsm_init(); a single
transition, fsm_step(); its monitoring, fsm_monitor(), and eventual internal variable
updates, fsm_debug().
Finite state machines can have more complex states, with additional variables that hold
internal values along machine execution cycles, exactly the same as with the state variable.
A simple, recurrent example is the one of a counter. The EFSM of a counter from 0 to M–1 has
a start and a counting state, but requires an additional variable to store the actual counter:
START COUNT
/ C+ = 0 / C+ = C+1
C=M─2
This counter is said to be autonomous, as it does not take any input from the outside, i.e. it
performs its function as time goes by or, in other words, along a(n infinite) sequence of control
loop iterations. The code for such EFSM could be:
1: struct {
2: enum { c_START, c_COUNT } state;
3: int C, M;
4: } c_data;
5:
6: int c_init() {
7: c_data.state = c_START;
8: c_data.C = -1; /* Unset value: -1 */
9: c_data.M = 8; /* Maximum cycle count */
10: return 0;
11: } /* c_init */
12:
13: int c_step() {
14: int e; /* e: errcod */
15:
16: e = 0;
17: switch( c_data.state ) {
18: case c_START: c_data.C = 0;
19: c_data.state = c_COUNT;
20: break;
21: case c_WAIT: if( c_data.C == c_data.M – 2 ) { c_data.state = c_START; }
22: c_data.C = c_data.C + 1;
23: break;
24: default: e = -1;
25: } /* switch */
26: return e;
27: } /* c_step */
28:
(To keep the code short, the “firings control” has been taken out, as well as the reset
mechanism.)
In this case, though, the order of execution matters. To make all these computations truly
order‐independent two sets of data can be used for the state machine: the one for current
state and the one for the next state. They can be put in a 2‐position vector of state machine
data sets, with two symbols to identify the current (CURR) and the next (NEXT) set. Following
the example of the counter, the updated program is:
Note that c_init() sets the values for the current data set and that it is in c_step()
where the values for the next period are computed. As they are stored into the NEXT position
of the c_data vector and all computations are done in terms of values in the CURR positions,
the order of execution of instructions at state cases is now irrelevant.