Many of us who want to program robots or similar applications with the Ardiuno need to run a state machine. For example, my object avoiding robot wants to:
1) Scan the enironment and find the longest possible distance to travel.
2) Turn into the corresponding direction.
3) Drive into that direction while constantly measuring the distance to the next object in front.
4) Stop if that distance is smaller than some minimum value.
5) Go back to step 1.
So how can we write the code in the loop()
function to achieve the above? There are 3 possibilities:
1) We write a very lengthy code block with a switch
or if
/ else
statement. We might introduce an enum
to label the state but if we have more than a couple of states and more than a little bit of logic to run in each state, then that code becomes difficult to read, more difficult to understand and even more difficult to maintain.
2) We use one of the FSM (finite state machine) libraries and write the corresponding adapters to these.
3) We write our own simple object oriented state machine that is taylored towards our project.
What I found on the web was that people mainly went for 1) or 2) in their projects. I will further down explain how to progress using 3).
There is a lot of useful information about (finite) state machines on the web. For example:
https://github.com/jonblack/arduino-fsm
For many simple Arduino like projects some of the available packages could be a little bit of an overkill.
Explanation of the codeIn order to show the elegance and power of an OO approach I have created a little toy project that demonstrates a state machine with two states only. The state machine initialises two states called white
and green
The idea is to have a base class State with two virtual functions enter()
and run()
.
The enter()
code is only called once when a state transition occurs. The run()
function is called repeatedly and returns a State*.
This is used by the caller in order to detect if the state is changing. If this is the case, the enter()
function is called again on the new state.
I have implemented enter()
as empty in the base class, but it could be a pure virtual function, too. I have not added an exit()
function although it could be useful to have and FSM libraries will likely have it. I discovered in my project that with the two functions enter()
, run()
I have enough flexibility to do everything.
Now the loop()
function only checks if the state has changed and calls enter if that is the case. Then it calls run()
as long as this returns the same state. Hence the logic to determine if a state prevails or is about to change is solely located in the run()
function of each state. In a multi state process the function could return different states depending on different input parameters, for example, the return value of sensor readings or the pushing of a button.
As we want to utilise the computational power of the Arduino as good as possible, we don't want to use any blocking function, neither in enter()
nor in run(). Please note, that I have violated that principle in the enter()
method by using a delay()
call inside the auxiliary function flash()
.
In a real-world example you would avoid that, and only utilise calls that return immediately. This has been done accordingly in the run()
functions of the states, where a timer is used in order to determine which part of that function should be called.
We create a unique instance of each of the two states.
The state machine toy projectThe project simply consists of two LEDs in white and green attached to pin 13 and 12. The two classes White
and Green
that implement State produce a flashing light signal upon entering. During the run()
function they will keep the LED switched on for 1s or 2s respectively. At the end of the run()
function we return the other state.
It is probably obvious that the approach 3) presented here will outperform solution 1) above. Assume you had 4 states instead of two and would perform very different tasks in the initial run of each state and return different states from the run()
function, depending on other input parameters. Coding this in a big loop()
function that keeps track of each state and needs to keep track if the state is called the first time or the 2nd and more times will produce lengthy inelegant code.
Also you can add more states to the code without ever requiring to update you loop()
function. You can keep variables like the timer readings locally in your classes without polluting the global namespace (I haven't made use of this in my example code, though).
Solution 2) will likely provide you with a good implementation, too, but you will be tied to the provided framework of the corresponding library. In our example it will be very easy, for example, to detect which was the preceding state. We can simply change the interface of enter()
to enter(State*)
and then run different codes depending on which state we came from.
Comments