Saturday 29 November 2014

Parsing commands.... Nick Gammons State Machine

I just learn from the Arduino Forum after calling the Serial.end(),the Serial will no longer work. Both Tx and Rx pins in the board will used for General Input and Output.
SO If is there any data present in the Serial Buffer before calling Serial.end(), It will be stay in the buffer but we have no way of knowing where it is in buffer. So we need to empty the buffer before calling the Serial.end().

Quick Note: Serial.flush() is used to Flush the Transmitting Buffer. It has Nothing to do with Receiving Buffer. If we wanted to empty the Serial Receive buffer we can do it by Reading the Data from it using Serial.read()

If we are receiving data with a starting word and ending we can get the data using this code.

int serial_check(){
//this function will check if there is any data in serial buffer format like "R123\r" returns the 123 in int //format
while(Serial.available()){
char data_buffer[50]; // 50 is max index
char in_char = Serial.read();
if(in_char == 'R'){
boolean store_byte = true;
byte in_char_index = 0;//initialize the index to store data
}//end of if


if(store_byte){
//if the store byte is true
if(Serial.read == '\r')
{
//if received character is ending character
data_buffer[in_char_index] = 0; // null terminating the char array to make it String
return atoi(data_buffer);// atoi -- ascii to integer


}//end of if
else{
data_buffer[in_char_index] = Serial.read();// load the data_buffer with incoming data
in_char_index++;// increment the index
}//end of else

}//end of if store byte

}//end of while
}//end of serial_check function


Quick note: If we replace the
data_buffer[in_char_index] = Serial.read() with data_buffer[in_char_index] = in_char we get the return value 0.... and why's that? because we are storing it with starting character.

what if we wanted to distinguish between more than two variables?
we have to write more these if loops


if(in_char == 'R'){
boolean store_byte = true;
byte in_char_index = 0;//initialize the index to store data
}//end of if

for each variable and store the each variable in separate buffer like data_buffer_a and data_buffer_b ..etc

Nick gammon write a excellent code for this
i will try to simplify those things in that code...
I posted this as a initiation ....
now i will try to simplify the things for you.
In Gammon words State machine means looking at each byte in the input stream, and handling it depending on the current state.

what is a state machine?
from wikipedia

It is conceived as an abstract machine that can be in one of a finite number of states. The machine is in only one state at a time; the state it is in at any given time is called the current state. It can change from one state to another when initiated by a triggering event or condition; this is called a transition.


from blog.markshed.com
a state machine will read a series of inputs. When it reads an input it will switch to a different state. Each state specifies which state to switch for a given input.

find more information here


if we are getting three variables from serial like t123T567L89
where t ----; time elapsed
T ---- Temperature
L ---- level
and we wanted to Store them in in different variables and process them

OK lets look at the Nick Gammons Code

// the possible states of the state-machine
typedef enum { NONE, GOT_R, GOT_S, GOT_G } states


from wikipedia:
typedef is a keyword in the C and C++ programming languages. The purpose of typedef is to assign alternative names to existing types, most often those whose standard declaration is cumbersome, potentially confusing, or likely to vary from one implementation to another.

we normally declare the variables as

int current_speed ;
int high_score ;


using typedef we can declare the same as

typedef int km_per_hour ;
typedef int points ;


km_per_hour current_speed ; //"km_per_hour" is synonymous with "int" here,
points high_score ; // "points" is also a name we gave alternate to the int

so what's the use of typedef in the above code?
using the typedef we can declare the two different parameters.
like we can declare more variables as

km_per_hour max_speed;
km_per_hour min_speed;


points average;

we can define the structure as

typedef struct {
int data1;
char data2;
} newtype;

we can assigns the values using newtypes

newtype.data1 = 23;
newtype.data2 = A;


Now the enum
Enumerated Types allow us to create our own symbolic names for a list of related ideas.
we can define the enum for colors to represent set of colours we are going to use in our program

enum color {black,red,green,yellow};//do not forget the semicolon

the type name color is used later to define another variables.

wrap around typedef and enum
A type definition is just a simple way of declaring a complex data type in terms of a single symbol name

Enumerations offer a way of createing a new data type that can only take a specific set symbolic integer values Enumerations are far better than #define preprocessing statements for this purpose.

quick note :In C++, in contrast to C, the struct, class, and enum key words are optional in variable declarations that are separate from the definitions, as long as there is no ambiguity to another identifier:
so we don't have to write

typedef enum states { NONE, GOT_R, GOT_S, GOT_G } states;


typedef enum { NONE, GOT_R, GOT_S, GOT_G } states;

it will done our job

let me recap the use of the above lines... just defines the variable data type named state with four variables
NONE,GOT_R,GOT_S,GOT_G as far as I can understand.
from forum:these two lines essentially allow you to create a new variable state where the only four possible values for state are in the braces.

using both limits the variable declaration..?
it will not allow us to declare more variables than those four.
using typedef we can declare additional variables as stated. using enum we declaring the four states variables only... those assign to integers.
next..

// current state-machine state
states state = NONE;
// current partial number
unsigned int currentValue;

note that we just initialize the states type variable with name state and initialize it as NONE which is one of the Four declared variables

void setup ()
{
Serial.begin (115200);
state = NONE;
} // end of setup

Serial communication is starting with baudrate 115200;
I dont know is reinitialization really required here, it really doesn't doing any harm. the code will compile without it also..

quick note: if(Serial.available()) will returns true if there is data in Arduino Rx buffer
and if(Serial) returns true if serial communication enables on arduino. we can disable it by Serial.end()

Next we need to look for loop(). Since i wanted to come in a sequence

void loop ()
{
while (Serial.available ())
processIncomingByte (Serial.read ());


// do other stuff in loop as required

} // end of loop


In loop we continuously check for is there any data in serial Rx buffer or not if there is a data in Serial buffer we call function named processIncomingByte(Serial.read())
it means we calling the function and passing Serial.read() as argument.

quick note: Serial.read() returns the byte of data if there is any data present in Serial Buffer

If we wanted to wait until data comes into Serial buffer we can done this by adding a while loop like this

while(!Serial.available()){
//wait until data received
}

please note that if we add this while loop the code will no longer will be non-blocking. and present this code won't require this while loop.
so next to the ProcessIncomingByte() function

void processIncomingByte (const byte c)
{
if (isdigit (c))
{
currentValue *= 10;
currentValue += c - '0';
} // end of digit
else
{


// The end of the number signals a state change
handlePreviousState ();

// set the new state, if we recognize it
switch (c)
{
case 'R':
state = GOT_R;
break;
case 'S':
state = GOT_S;
break;
case 'G':
state = GOT_G;
break;
default:
state = NONE;
break;
} // end of switch on incoming byte
} // end of not digit

} // end of processIncomingByte

in This function Note the first line

void processIncomingByte (const byte c)


void ---- the function will return nothing
processIncomingByte ----- name of the function
const byte c ---- here byte is a variable type and const means we are passing the argument as a const so
variable is only readable and c is argument passed into function which is the return
value of function Serial.read()
if (isdigit (c))
{
currentValue *= 10;
currentValue += c - '0';
} // end of digit


inside if condition there is a function(?) called isdigit()

the isdigit(C) checks whether the passing argument 'C' is digit or not as name implies.
digits --> 0 1 2 3 4 5 6 7 8 9
it return a value different from zero (i.e., true) if indeed c is a decimal digit. Zero (i.e., false) otherwise.

all the magic is done mostly in these two lines...
currentValue *= 10;
currentValue += c - '0';


if the received byte is digit...
we are doing some math with variables named current value and function argument "c"
the currentValue is declared int but we didn't initialize its value.. Quick search in google returns


Declaring Variables

Before they are used, all variables have to be declared. Declaring a variable means defining its type, and optionally, setting an initial value (initializing the variable). Variables do not have to be initialized (assigned a value) when they are declared, but it is often useful.
int inputVariable1;
int inputVariable2 = 0; // both are correct


so by default it is initialized as 0;
if we are receiving data as is R4500S80G3... if we assume that first we get character then the If loop is not executed..

else
{

// The end of the number signals a state change
handlePreviousState ();

// set the new state, if we recognize it
switch (c)
{
case 'R':
state = GOT_R;
break;
case 'S':
state = GOT_S;
break;
case 'G':
state = GOT_G;
break;
default:
state = NONE;
break;
} // end of switch on incoming byte
} // end of not digit


in the else the function handlePreviousState() is executed.
if we look at the fuction

void handlePreviousState ()
{
switch (state)
{
case GOT_R:
processRPM (currentValue);
break;
case GOT_S:
processSpeed (currentValue);
break;
case GOT_G:
processGear (currentValue);
break;
} // end of switch

currentValue = 0;
} // end of handlePreviousState

look into the state... we declare only 4 possible values for it.
we initialized it as NONE so it Out from switch statement and put a value 0 to the variable currentValue.

next it goes to the switch statement in processIncomingByte function
we are received a character 'R' so it assign GOT_R in to the state and come out of the loop (because of break)

loop in arduino program is executed over and over so we next receive 4 in 4500
it is a digit so if condition is executed.
line 1 currentValue = currentValue * 10 returns 0 since the initial currentValue is 0
line 2 curreentValue = currentValue + c - 0; we add the incoming digit to the currentValue so it becomes 0 + '4' - '0' = 0 + 52 - 48 = 4
if we look at the ASCII chart here the char '4' is represented by decimal value 52 and '0' is represented by decimal value 48.
if we did the calculation then we can get 4 which is the incoming int.

next we receive the char '5' ascii decimal equalent is 53.
currentValue is '4'
so currentValue = currentValue*10 = 40
the currentValue = currentValue + c- 0 = 40 + 53 - 48 = 45

for next two bytes the currentValue become = 4500

next we receive a charactern 'S' so the else is executed...
in the else the first line if code is handlePreviousState () fu
nction
when we first received the R we assigned state variable state = GOT_R;
in hadlePreviousStates() function

case GOT_R:
processRPM (currentValue);
break;

is executed. and currentValue is passes to the functiob processRPM function

In processRPM we are having the value.. so we can further manipulate it..

the full code is


// Example state machine reading serial input
// Author: Nick Gammon
// Date: 17 December 2011

// the possible states of the state-machine
typedef enum { NONE, GOT_R, GOT_S, GOT_G } states;

// current state-machine state
states state = NONE;
// current partial number
unsigned int currentValue;

void setup ()
{
Serial.begin (115200);
state = NONE;
} // end of setup

void processRPM (const unsigned int value)
{
// do something with RPM
Serial.print ("RPM = ");
Serial.println (value);
} // end of processRPM

void processSpeed (const unsigned int value)
{
// do something with speed
Serial.print ("Speed = ");
Serial.println (value);
} // end of processSpeed

void processGear (const unsigned int value)
{
// do something with gear
Serial.print ("Gear = ");
Serial.println (value);
} // end of processGear

void handlePreviousState ()
{
switch (state)
{
case GOT_R:
processRPM (currentValue);
break;
case GOT_S:
processSpeed (currentValue);
break;
case GOT_G:
processGear (currentValue);
break;
} // end of switch

currentValue = 0;
} // end of handlePreviousState

void processIncomingByte (const byte c)
{
if (isdigit (c))
{
currentValue *= 10;
currentValue += c - '0';
} // end of digit
else
{

// The end of the number signals a state change
handlePreviousState ();

// set the new state, if we recognize it
switch (c)
{
case 'R':
state = GOT_R;
break;
case 'S':
state = GOT_S;
break;
case 'G':
state = GOT_G;
break;
default:
state = NONE;
break;
} // end of switch on incoming byte
} // end of not digit

} // end of processIncomingByte

void loop ()
{
while (Serial.available ())
processIncomingByte (Serial.read ());

// do other stuff in loop as required

} // end of loop

and this code won't work with float values.

No comments:

Post a Comment