I was looking at Nick Gammon's state machine example and had a hard time trying to figure out how he defined his states. I guess state machines is a state of mind.
In any case, I used his example to write my version of a state machine, defining the states as how the program is processing the inputs.
Any comments or critiques would be welcomed.
// the possible states of the state-machine
typedef enum { STATE_BEGIN_INPUT,
STATE_GETTING_ROTATION,
STATE_GETTING_SPEED,
STATE_OUTPUT_RS} states;
// current state-machine state
states state = STATE_BEGIN_INPUT;
// variables
volatile long speed_number = 0;
volatile long rotation_number = 0;
// flags
volatile bool speed_negative = false; // if true, then negative
volatile bool rotation_negative = false;
void setup ()
{
Serial.begin (115200);
Serial.println (F("Starting ..."));
Serial.println(F("Valid inputs are:"));
Serial.println(F("\t""S(-)12345R(-)1234"));
Serial.println(F("\t""S(-)12345 R(-)1234"));
Serial.println(F("\t""R(-)12345S(-)1234"));
Serial.println(F("\t""R(-)12345 S(-)1234"));
} // end of setup
// State functions
void begin_input()
{
byte c = Serial.read();
switch (toupper (c))
{
case 'R':
state = STATE_GETTING_ROTATION;
break;
case 'S':
state = STATE_GETTING_SPEED;
break;
case '\n': // line feed
case '\r': // carrage return
break; // ignore
default: // all other charactors, including numbers
// no change of state
unexpectedInput (c);
break;
}
}
void getting_rotation()
{
byte c = Serial.read(); // Check for sign, then expecting numbers. Will end with space, S, or return.
// carriage return ends input
switch (toupper(c))
{
case '-': // check for - sign
rotation_negative = true;
break;
case '0' ... '9':
rotation_number *= 10;
rotation_number += c - '0';
break;
case ' ': // space. Done with rotation number. See what is next
break;
case 'S': // done with rotation number. now start Speed number
state = STATE_GETTING_SPEED;
break;
case '\r':
state = STATE_OUTPUT_RS;
break;
default:
state = STATE_BEGIN_INPUT; //invalid data input, start over
unexpectedInput (c);
break;
}
}
void getting_speed()
{
byte c = Serial.read(); // Check for sign, then expecting numbers. Will end with space, R, or return.
// carriage return ends input
switch (toupper(c))
{
case '-': // check for - sign
speed_negative = true;
break;
case '0' ... '9':
speed_number *= 10;
speed_number += c - '0';
break;
case ' ': // space. Done with speed number. See what is next
break;
case 'R': // speed number is done. Rotation number is next
state = STATE_GETTING_ROTATION;
break;
case '\r':
state = STATE_OUTPUT_RS;
break;
default:
state = STATE_BEGIN_INPUT; //invalid data input, start over
unexpectedInput (c);
break;
}
}
void output_rs()
{
if(rotation_negative)
{
rotation_number = -rotation_number;
rotation_negative = false;
}
if(speed_negative)
{
speed_number = -speed_number;
speed_negative = false;
}
Serial.print (F("Rotating to "));
Serial.println (rotation_number);
Serial.print (F("Setting Speed to "));
Serial.println (speed_number);
Serial.println();
state = STATE_BEGIN_INPUT;
speed_number = 0;
rotation_number = 0;
}
// output functions
void unexpectedInput(char c)
{
Serial.print ("Unexpected input: '");
Serial.print (char (c));
Serial.println ("'");
Serial.println("");
speed_number = 0;
rotation_number = 0;
} // end of unexpectedInput
void loop ()
{
if (Serial.available())
{
switch (state)
{
case STATE_BEGIN_INPUT:
begin_input();
break;
case STATE_GETTING_ROTATION:
getting_rotation();
break;
case STATE_GETTING_SPEED:
getting_speed();
break;
case STATE_OUTPUT_RS:
output_rs();
break;
}
}
} // end of loop
had a hard time trying to figure out how he defined his states.
The code uses an enum (something for you to Google) which is a convenient way to assign a numerical value to a variable without even having to know the value assigned. The values can be used throughout the program by using the descriptive names assigned to them. Out of interest try printing the states to see the values assigned.
// the possible states of the state-machine
typedef enum {
STATE_BEGIN_INPUT,
STATE_GETTING_ROTATION,
STATE_GETTING_SPEED,
STATE_OUTPUT_RS} states;
typedef is no longer needed.
by default enum entries are stored using an int, which is two bytes. As you have very few and you don't care what value they hold, I would suggest to define them as byte
semantically your states variable only represent one state. so better practice to call that state or currentState for example
I would not have if Serial.available() in loop() because that means that none of the states is called unless there is something in the Serial input buffer. And if you take characters from it with Serial,read() then there may be none left to allow the rest of the program to work.
Also, I would not have a "state" for reading the serial input. I would check that all the time and independently of all states and save the data because you never know when the data might arrive, and serial data is very slow. See Serial Input Basics
Using ENUMs can make code easy to understand but you can manage states with any values in any variable - you just need something that lets the program know where it is in a sequence of actions. If you look at the code in Serial Input Basics you will see that it uses the boolean variable newData to reflect the state of whether a new message has arrived.
I would not have if Serial.available() in loop() because that means that none of the states is called unless there is something in the Serial input buffer. And if you take characters from it with Serial,read() then there may be none left to allow the rest of the program to work.
Robin, I'm not following your logic here. There is no need to go to a state if there is nothing in the serial buffer to process. If Serial.available() is true, something is in the stack. Depending on the current state, it is read and dealt with. If nothing is left, it sits in the main loop waiting for the next string.
MikeLittle:
Robin, I'm not following your logic here. There is no need to go to a state if there is nothing in the serial buffer to process. If Serial.available() is true, something is in the stack. Depending on the current state, it is read and dealt with. If nothing is left, it sits in the main loop waiting for the next string.
you are on the right track.
What is your program doing/not doing that you want to change?
there doesn't seem to be a question in your original post...
MikeLittle:
There is no need to go to a state if there is nothing in the serial buffer to process.
If that is true then your approach will work. I had not considered designing a program like that. I prefer to receive the data independently of other activities as that allows me to view the received data as part of the debugging process.
MikeLittle:
Robin, I'm not following your logic here. There is no need to go to a state if there is nothing in the serial buffer to process. If Serial.available() is true, something is in the stack. Depending on the current state, it is read and dealt with. If nothing is left, it sits in the main loop waiting for the next string.
That's fine until you need to implement a state that doesn't process serial input.
aarg:
That's fine until you need to implement a state that doesn't process serial input.
A car is perfectly fine transportation unless you want to cross a sea.
This is a mechanism that processes serial input... analogous to a keypad entry or a remote control or countless other programs driven from user inputs.
Take a look at Nick's example and see if you can follow it easily. What I had a hard time with was how he structured his state machine. I looks like he chose his states based on the inputs coming in. I chose mine based on what was to be done with the inputs.
I'm not a programmer, but a EE. I like logic flow. I have been looking at state machines on and off for the past 20 years, and now with the arduino, I've found something that I can play with easily.
I cut my teeth on state machines using a program call Libero. The guy who wrote it wanted users to understand why they were using a state machine. They are overkill for some things, but can simplify others. He also wanted the code to be easy to maintain, and follow. To me, Nick's program isn't easy to follow.
That is why is said the state machines are a state of mind. When you choose the right states, things are simple.
BulldogLowell:
A car is perfectly fine transportation unless you want to cross a sea.
This is a mechanism that processes serial input... analogous to a keypad entry or a remote control or countless other programs driven from user inputs.
why do people get off on these tangents?
Because it's real. I am currently working on a program that has some states that receive and some that don't... the ones that don't are for the purpose of sending serial. I'm not sure if it's the only way to do it, but it seemed natural.
aarg:
Because it's real. I am currently working on a program that has some states that receive and some that don't... the ones that don't are for the purpose of sending serial. I'm not sure if it's the only way to do it, but it seemed natural.
Start another post with your code and see where it goes from there. That way your stuff won't get mixed up with this.