Object color tracking, serial data using strtok() or Serial.parseInt() Updated

Hello,
Hope everyone has a great New Year :slight_smile:
Finally have my webcam hooked up to my bot using RoboRealm tracking 'a red object' setup.
The data 'distanceX' is sent via Serial to my Arduino. DistanceX is basically the center of gravity of the object, X value of a 320 x 240 image on cam.

In my Arduino sketch I have a function 'trackCalc()' to receive the data in a buffer and depending on the value of distanceX, drives my motors, then resets the buffer.
The problem I'm having is that when an object is within the center threshold (forward), every several seconds it stutters just for a second (almost like it wants to turn).
Left and right differential turns are fine though...

Here is the function:

void trackCalc()
{

  if (Serial.available())
  {
    // Get the data coming through the serial port and store it in the buffer
    while (z < 4)
    {
      incomingData[z] = Serial.read(); // Assign the input value to the incomingData buffer
      z++; // Increment the counter
    } 

    distanceX = atoi(incomingData); // Convert ASCII to Int

    if((distanceX > 100) && (distanceX < 220))      // forward threshold of image (X value or 320)
    {
      // go forward
      myservoSteering.write(neutralSteering);  
      myservoThrottle.write(medFowThrottle);
    }

    else
    {

      if(distanceX <= 99)
      {
        myservoThrottle.write(neutralThrottle);
        myservoSteering.write(slowLeftSteering); 
      }

      else if(distanceX >= 221)
      {
        myservoThrottle.write(neutralThrottle);
        myservoSteering.write(slowRightSteering); 
      }

    } 
  }
  z = 0; // Reset the counter
  delay(20);//delay(20);// Delay 20ms to allow the servo to rotate to the position 


}

And the code in the loop:

void loop()
{
  //...
// PING code here... 
//...
  
    if(range [2] >= pingThreshCenter)   // if no obstructions detected on center PING sensor run function 'trackCalc()'
    {
      trackCalc();
    }
    else if(range [2] < pingThreshCenter)  // if there is an obstruction detected, stop motors (neutral).
    {
      myservoThrottle.write(neutralThrottle);
      myservoSteering.write(neutralSteering); 
      //delay(10);
    }
  
}

Thomas

  if (Serial.available())
  {
    // Get the data coming through the serial port and store it in the buffer
    while (z < 4)
    {
      incomingData[z] = Serial.read(); // Assign the input value to the incomingData buffer
      z++; // Increment the counter
    }

If there is at least one character available to read, read all 4 of them.

    distanceX = atoi(incomingData); // Convert ASCII to Int

Then, without NULL terminating the array, pass it to a function that expects a NULL terminated array of chars.

Try again.

Thanks for the reply Paul,

Not exactly sure what you mean.
Are you saying that when there is one character being sent and available to read, it's trying to read 4 characters?

Can you explain a bit more about 'NULL terminating the array'?

Instead of 4 elements/buffers in the array, would I need just one?

I may need my hand held just a bit on this one... :confused:

t

Looking around at a few references, would using this be better:

int incomingByte = 0;   // for incoming serial data

void setup() {
        Serial.begin(9600);     // opens serial port, sets data rate to 9600 bps
}

void loop() {

        // send data only when you receive data:
        if (Serial.available() > 0) {
                // read the incoming byte:
                incomingByte = Serial.read();

                // say what you got:
                Serial.print("I received: ");
                Serial.println(incomingByte, DEC);
        }
}

t

I found this code in a previous post here, wondering if I'm getting closer to what Paul suggested?

int serReadInt()
{
  int i, serAva;                           // i is a counter, serAva hold number of serial available
  char inputBytes [7];                 // Array hold input bytes
  char * inputBytesPtr = &inputBytes[0];  // Pointer to the first element of the array
      
  if (Serial.available()>0)            // Check to see if there are any serial input
  {
    delay(5);                              // Delay for terminal to finish transmitted
                                               // 5mS work great for 9600 baud (increase this number for slower baud)
    serAva = Serial.available();  // Read number of input bytes
    for (i=0; i<serAva; i++)       // Load input bytes into array
      inputBytes = Serial.read();
    inputBytes =  '\0';             // Put NULL character at the end
    return atoi(inputBytesPtr);    // Call atoi function and return result
  }
  else
    return -1;                           // Return -1 if there is no input
}

t

inputBytes[i] = Serial.read(); would help keep the compiler happy

  char * inputBytesPtr = &inputBytes[0];  // Pointer to the first element of the array

Why do you think you need another pointer to the start of the array?

    delay(5);                              // Delay for terminal to finish transmitted
                                               // 5mS work great for 9600 baud (increase this number for slower baud)

No, this is a crappy workaround. Whatever is sending the value needs to append an end of packet marker, so that the Arduino can read and store the data as fast as it arrives, until the end of packet marker arrives. Only then should the data be used.

PaulS:

  char * inputBytesPtr = &inputBytes[0];  // Pointer to the first element of the array

Why do you think you need another pointer to the start of the array?

    delay(5);                              // Delay for terminal to finish transmitted

// 5mS work great for 9600 baud (increase this number for slower baud)



No, this is a crappy workaround. Whatever is sending the value needs to append an end of packet marker, so that the Arduino can read and store the data as fast as it arrives, until the end of packet marker arrives. Only then should the data be used.

Thanks for the replies AWOL & Paul,

@ Paul
That wasn't my code, was trying to figure out what you said about 'a function that expects a NULL terminated array of characters' and was just seeing if I was getting somewhat closer to what you were saying with that code I came across...

and was just seeing if I was getting somewhat closer to what you were saying with that code I came across...

Here is code I use to receive properly delimited data on the Arduino:

#define SOP '<'
#define EOP '>'

bool started = false;
bool ended = false;

char inData[80];
byte index;

void setup()
{
   Serial.begin(57600);
   // Other stuff...
}

void loop()
{
  // Read all serial data available, as fast as possible
  while(Serial.available() > 0)
  {
    char inChar = Serial.read();
    if(inChar == SOP)
    {
       index = 0;
       inData[index] = '\0';
       started = true;
       ended = false;
    }
    else if(inChar == EOP)
    {
       ended = true;
       break;
    }
    else
    {
      if(index < 79)
      {
        inData[index] = inChar;
        index++;
        inData[index] = '\0';
      }
    }
  }

  // We are here either because all pending serial
  // data has been read OR because an end of
  // packet marker arrived. Which is it?
  if(started && ended)
  {
    // The end of packet marker arrived. Process the packet

    // Reset for the next packet
    started = false;
    ended = false;
    index = 0;
    inData[index] = '\0';
  }
}

This expects you to send data starting with a '<' and ending with a '>'. Data is ignored until a '<' arrives. It is then stored until a '>' arrives. When the '>' arrives, the "Process the packet" area is where you would call atoi(inData) to get the numeric value, and use it. Then, the flags, array, and index are reset to read the next packet.

PaulS:

and was just seeing if I was getting somewhat closer to what you were saying with that code I came across...

Here is code I use to receive properly delimited data on the Arduino:

#define SOP '<'

#define EOP '>'

bool started = false;
bool ended = false;

char inData[80];
byte index;

void setup()
{
   Serial.begin(57600);
   // Other stuff...
}

void loop()
{
  // Read all serial data available, as fast as possible
  while(Serial.available() > 0)
  {
    char inChar = Serial.read();
    if(inChar == SOP)
    {
       index = 0;
       inData[index] = '\0';
       started = true;
       ended = false;
    }
    else if(inChar == EOP)
    {
       ended = true;
       break;
    }
    else
    {
      if(index < 79)
      {
        inData[index] = inChar;
        index++;
        inData[index] = '\0';
      }
    }
  }

// We are here either because all pending serial
  // data has been read OR because an end of
  // packet marker arrived. Which is it?
  if(started && ended)
  {
    // The end of packet marker arrived. Process the packet

// Reset for the next packet
    started = false;
    ended = false;
    index = 0;
    inData[index] = '\0';
  }
}



This expects you to send data starting with a '<' and ending with a '>'. Data is ignored until a '<' arrives. It is then stored until a '>' arrives. When the '>' arrives, the "Process the packet" area is where you would call atoi(inData) to get the numeric value, and use it. Then, the flags, array, and index are reset to read the next packet.

I appreciate the help and code Paul,
Got it to work and understanding more about packets and 'SOP' start of packet and 'EOP' end of packet.
Right now I have the X-axis or motor forward, left and right working perfect using the packet sent over serial to the Arduino.
I'd like to incorporate the head tilt servo (which is 'distanceY') and having a bit of trouble getting the 2 sets of data to work(together).

In the VBScript in roborealm I have and/or tried under serial and 'Send Sequence':

<distanceX,distanceY>

And in the Arduino code:

distanceX,distanceY = atoi(inData);

If tried a few variations but with no luck.
Should I send the 2 different X and Y data in 2 different packets?
In the Arduino code is there anything else I would need to modify or add?
I read in my book something about adding a comma or 'delimiting character'?

Any help appreciated,

t

In the VBScript in roborealm I have and/or tried under serial and 'Send Sequence':

That will work.

And in the Arduino code:

That won't. The packet looks something like <27,43>, so inData ends up containing 27,43. The atoi() method scans the input and converts as much as possible to an int, so it returns 27, because the comma is not a numeric character. The comma operator on the left side of the equal sign does something, but it is almost certainly not what you are expecting.

You really should read up on what functions you want to use are doing, like atoi().

When the packet contains multiple values, you need to use strtok() to extract each one, as a string, and call atoi() on each token.

PaulS:

In the VBScript in roborealm I have and/or tried under serial and 'Send Sequence':

That will work.

And in the Arduino code:

That won't. The packet looks something like <27,43>, so inData ends up containing 27,43. The atoi() method scans the input and converts as much as possible to an int, so it returns 27, because the comma is not a numeric character. The comma operator on the left side of the equal sign does something, but it is almost certainly not what you are expecting.

You really should read up on what functions you want to use are doing, like atoi().

When the packet contains multiple values, you need to use strtok() to extract each one, as a string, and call atoi() on each token.

Thanks again Paul :slight_smile:
I know that atoi() is used to convert an ASCII to Integer, another is atol(), ASCII to Long. As well as itoa(), convert Integer to ASCII.

Looking up strtok(), function is to split the string into tokens.
An example in C++ would be:

/* strtok example */
#include <stdio.h>
#include <string.h>

int main ()
{
  char str[] ="- This, a sample string.";
  char * pch;
  printf ("Splitting string \"%s\" into tokens:\n",str);
  pch = strtok (str," ,.-");
  while (pch != NULL)
  {
    printf ("%s\n",pch);
    pch = strtok (NULL, " ,.-");
  }
  return 0;
}

I read an example in one of my Arduino books about using 'fields'

//
const int NUMBER_OF_FIELDS = 3;
int fieldIndex = 0;
int values[NUMBER_OF_FIELDS];
//...
//...

Would this be also another approach I could look at?

t

Would this be also another approach I could look at?

Possibly, although it seems to me that you know how many fields there will be (2) and exactly what the fields represent.

PaulS:

Would this be also another approach I could look at?

Possibly, although it seems to me that you know how many fields there will be (2) and exactly what the fields represent.

Hey Paul,
I came up with this here, let me know if I'm getting close (in getting the 2 data tokens I need from Serial) in this code:

void setup()
{  

  char inString[]= "distanceX, distanceY";
  char delimiters[]= "!:,";
  char* valPosition;

  valPosition = strtok(inString,delimiters);
  int distance[] = {0,0};

  Serial.begin(9600);

  for(int i=0; i<2; i++)
  {
    distance[i] = atoi(valPosition);
    Serial.println(distance[i]);
    valPosition = strtok(NULL,delimiters);
  }
}

void loop()
{
  //...
  //...
}

I came up with this here, let me know if I'm getting close (in getting the 2 data tokens I need from Serial) in this code:

You are close. The only issue is that "distanceX" will be rather useless data to pass to atoi(), as will "distanceY".

PaulS:

I came up with this here, let me know if I'm getting close (in getting the 2 data tokens I need from Serial) in this code:

You are close. The only issue is that "distanceX" will be rather useless data to pass to atoi(), as will "distanceY".

Hmm, maybe forgetting this:

void setup()
{  

  char inString[]= "distanceX, distanceY";
  char delimiters[]= "!:,";
  char* valPosition;

  valPosition = strtok(inString,delimiters);
  int distance[] = {0,0};

  Serial.begin(9600);

  for(int i=0; i<2; i++)
  {
    distance[i] = atoi(valPosition);
    Serial.println(distance[i]);
    valPosition = strtok(NULL,delimiters);
  }
}

void loop()
{
 while(Serial.available() > 0)
  {
    distance[i] = Serial.read(); 
// not sure or could be:

    char inString[] = Serial.read();
 }
}

t

What your sender is going to be sending is not the string "distanceX, distanceY" but the VALUES that are in the variables distanceX and distanceY. Something like "42, 56" "42" and "56" are tokens that atoi() knows how to deal with. "distanceX" and "distanceY" are not.

PaulS:
What your sender is going to be sending is not the string "distanceX, distanceY" but the VALUES that are in the variables distanceX and distanceY. Something like "42, 56" "42" and "56" are tokens that atoi() knows how to deal with. "distanceX" and "distanceY" are not.

Ahh, I see...makes sense.

So, something like this:

void setup()
{  

  char inString[2];
  char delimiters[]= "!:,";
  char* valPosition;

  valPosition = strtok(inString,delimiters);
  int distance[] = {0,0};

  Serial.begin(9600);

  for(int i=0; i<2; i++)
  {
    distance[i] = atoi(valPosition);
    Serial.println(distance[i]);
    valPosition = strtok(NULL,delimiters);
  }
}

void loop()
{
 while(Serial.available() > 0)
  {
    char inString[2] = Serial.read();
  }
}

I would still need the code I put inside void loop()?

So, something like this:

No. inString is now an array with no data.

The input to test with should look like this:

  char inString[]= "42, 56"

PaulS:

So, something like this:

No. inString is now an array with no data.

The input to test with should look like this:

  char inString[]= "42, 56"

Thanks again Paul for all the help,
Had a few questions if you have a chance.

  1. When you say 'the input to test', is this just to see if I get good/correct data? Do I keep some random numbers if it does work?

2)Do the number of digits matter at all? In your example, 2 digits per token... even if I might receive Int-ACSII-Int values of 3 digits, etc...?

3)I still would need the code in the loop()?
Wouldn't I still need to use Serial.available() and Serial.read() to see if the Serial port is ready and to read the data?

It looks like distance[i] is the variable/array that collects the 2 tokens from the data received?