Arduino + Roborealm

Thanks in advance for anyone who takes the time to help me.

Ok so I have a webcam on top of a servo that pans. The webcam is running a program from Roborealm that tracks motion and sends the data to the arduino as ascii. The arduino is supposed to convert these values and then adjust the servo's position accordingly. Here is what I'm getting: roborealm is sending the proper coordinates. The servo is responding to the data, but it sweeps with random jolts. I know it is acting somehow from the information from roborealm because when I stop the serial communication in roborealm the servo stops moving. Both of my programs should work fine because I got them from a tutorial. Here is my code on the arduino side...

#include <Servo.h> // Include the servo library

Servo myServo; // Create a new servo object
char incomingData[4] = {
0, 0, 0, 0}; // A buffer to store the ASCII value read in from the serial port
int distance = 0; // The distance of the object from the center of the screen
int currentAngle = 90; // The current angle of the servo
int i = 0; // counter

void setup(){
Serial.begin(9600); // Open the serial port with a 9600 baud rate
Serial.println("Serial port ready"); // Print on screen
myServo.attach(9); // Attach the servo signal line to pin 9
myServo.write(currentAngle); // Set the starting angle at 90 degrees
}

void loop(){
// Wait for data to become available at the serial port
if (Serial.available()){
// Get the data coming through the serial port and store it in the buffer
while (i < 4){
incomingData = Serial.read(); // Assign the input value to the incomingData buffer

  • i++; // Increment the counter*
  • }*
  • distance = atoi(incomingData); // Convert ASCII to Int*
  • // If the distance is negative then add 5 to the currentAngle and update the servo, and vise versa*
  • if (distance < -15){*
  • currentAngle++;*
  • currentAngle = constrain(currentAngle, 0, 180); // Constrain the value of currentAngle to within 0-180 degrees*
  • myServo.write(currentAngle);*
  • }*
  • else if (distance > 15){*
  • currentAngle--;*
  • currentAngle = constrain(currentAngle, 0, 180); // Constrain the value of currentAngle to within 0-180 degrees*
  • myServo.write(currentAngle);*
  • }*
  • }*
  • i = 0; // Reset the counter*
  • delay(50); // Delay 20ms to allow the servo to rotate to the position*
    }
    [/quote]
    Thanks to anyone more knowledgeable than me (it doesn't take much ;)) who helps me

Try Serial.println(currentAngle); to see what it is when the servo moves. Also, to add five to currentAngle use +=5 instead of ++.
The jolts could maybe occur if the distance data is jumpy and it's going between 15 and -15.

while (i < 4){
     incomingData[i] = Serial.read(); // Assign the input value to the incomingData buffer
     i++; // Increment the counter
   }

incomingData is now an array of characters. It is NOT a string. The atoi function expects a string.

The difference between a string and an array of characters is that a string is an array of characters that is NULL terminated.

You are not NULL-terminating the array, nor do you have room in the array to add the NULL terminator.

You need to change the size of incomingData from 4 to 5, and the code snippet to:

while (i < 4){
     incomingData[i] = Serial.read(); // Assign the input value to the incomingData buffer
     i++; // Increment the counter
     [glow]incomingData[i] = '\0'; // Add a NULL terminator.[/glow]
   }

The atoi function is looking at all the characters from incomingData[0] through incomingData[n] for a NULL. If the NULL that it finds is at n=27, for instance, that is well beyond the end of the space you have reserved for the value, but atoi does not know how much space you have reserved. So, it will convert the n-1 digits to an integer, if it can.

That, of course, will result in distance not being valid.

One final thing to note, with the way that you read the serial data. You check that there is at least one byte available to read. That is good. Then, you proceed to read all 4 of them. That is bad.

If there are to be 4 digits in every packet, wait for there to be at least 4 characters to read. If there can be less then 4, you need some end-of-packet marker to indicate when the end of the packet has been reached.

You also need to think about what happens if a character in a packet gets lost.

Suppose the stream of characters looks like this:
001200140015001000090008
Now, if you read them 4 at a time, you get 0012, 0014, 0015, 0010, 0009, 0008. That's good.

Now, suppose a character gets lost from the stream. It now looks like this:
00120014001001000090008
Now, if you read them 4 at a time, you get 0012, 0014, 0010, 0100, 0090, 008.

Not exactly the same set of values, are they?

Now suppose the stream looked like this instead:
<0012><0014><0015><0010><0009><0008>
You could tell right away if a < was dropped. You'd get too many characters in a packet. You could tell right away is a > was dropped. You'd get too many characters in a packet. You could tell if a digit was dropped. You get too few characters in a packet.

In any of the three cases, you would know to ignore the packet that was too large or too small.

First of all thanks to PaulS! After a little bit of finagling my webcam tracked colors like it was supposed to. Your advice was stellar!

I have one more thought...what would I have to do to add a tilt servo in addition to the pan servo? My webcam is actually on a pan/tilt servo system already. I tried basically copying the section of code that applied to the pan servo and changing it to the tilt, but it didn't work at all. I am assuming because my arduino actually read a mix of the x and y coordinates through the serial com. Here is my code starting at void loop...

void loop(){
// Wait for data to become available at the serial port
if (Serial.available())
// Get the data coming through the serial port and store it in the buffer
while (i < 4){
incomingData_ = Serial.read(); // Assign the input value to the incomingData buffer_

  • i++; // Increment the counter*
    _ incomingData* = '/0';_
    _
    }_
    _
    if(i=5){_
    _
    distancePan = (atoi(incomingData))/4;_
    _
    } // Convert ASCII to Int*_
    * // If the distance is negative then add 5 to the currentAngle and update the servo, and vise versa*
    * if (distancePan < -5){*
    * currentAnglePan--;*
    * currentAnglePan = constrain(currentAnglePan, 0, 180); // Constrain the value of currentAngle to within 0-180 degrees*
    * pan.write(currentAnglePan);*
    * }*
    * else if (distancePan > 5){*
    * currentAnglePan++;*
    * currentAnglePan = constrain(currentAnglePan, 0, 180); // Constrain the value of currentAngle to within 0-180 degrees*
    * pan.write(currentAnglePan);*
    * }*
    * i = 0; // Reset the counter*
    * //delay(20); if necessary to toggle from x to y*
    _ if (Serial.available())
    * // Get the data coming through the serial port and store it in the buffer*
    * while (i < 4){*
    incomingData = Serial.read(); // Assign the input value to the incomingData buffer_

    * i++; // Increment the counter*
    _ incomingData* = '/0';
    }
    if (i=5){
    distanceTilt = (atoi(incomingData))/4;
    } // Convert ASCII to Int*

    * // If the distance is negative then add 5 to the currentAngle and update the servo, and vise versa*
    * if (distanceTilt < -5){
    currentAngleTilt--;
    currentAngleTilt = constrain(currentAngleTilt, 0, 180); // Constrain the value of currentAngle to within 0-180 degrees*

    * tilt.write(currentAngleTilt);
    }
    else if (distanceTilt > 5){
    currentAngleTilt++;
    currentAngleTilt = constrain(currentAngleTilt, 0, 180); // Constrain the value of currentAngle to within 0-180 degrees*

    * tilt.write(currentAngleTilt);
    }
    //delay(20); again if necessary*

    }
    [/quote]
    Much thanks to anyone willing to help :slight_smile:_

You may want to look at using the WString.h library to make the string handling a little easier.

What is the format of the serial data that roborealm is sending to the Arduino?

Ideally, it would be something like <123.456, 789.012> so you could see where a packet started (<), where it ended (>), and where the data in the packet was separated (,). Parsing it would then be easy.

But how to parse it all depends on how the data is being sent.

Roborealm sends the x-coordinate then the y-coordinate only separated by a space. Such as 67 -7. I had no ideas how to parse it or direct one servo to only read the first number and the other to read the second.

If you use the string library, you can collect all the serial data in a string. Then, you can use indexOf on the string to find the space, and substring to get the strings before and after the space.

Then, use atof to convert to numbers - once for each string.

Below is a simple servo control code I made for testing. You probably could modify it such that when a "," is encountered in the character capture loop, the loop is exited and then the captured string is parced. The parcing would be easy. Find the length of the captured string, find the location of the space in the string, and then capture the string on each side of the space as a variable. Then do what ever is needed with the variable. If you can provide an actual string sent by roborealm, that would be helpful in making a parcing routine.

//zoomkat 7-30-10 serial servo test
//type servo position 0 to 180 in serial monitor
//for writeMicroseconds, use a value like 1500

#include <WString.h> //provides easy string handling
String readString = String(100);
#include <Servo.h> 
Servo myservo;  // create servo object to control a servo 

void setup() {
      Serial.begin(9600);
        myservo.attach(9);
        }

void loop() {

        while (Serial.available()) {
        delay(10);  
          if (Serial.available() >0) {
        char c = Serial.read();  //gets one byte from serial buffer
        readString.append(c); } //makes the string readString
        }
        
      if (readString.length() >0) {
      Serial.println(readString);
      int n;
      n = atoi(readString); //convert string to number
      myservo.writeMicroseconds(n);
      //myservo.write(n);
      readString="";
      } 
   }

Ok I'm working on implementing your code. In regards to what string Roborealm sends, it gives me a box where I can choose what sequence to send. So I could instruct it to send the two numbers in whatever string is convenient to work with.

I had the same question awhile back. Someone was nice enough to post this sketch:

#include <Servo.h>

int servoPanPin = 9;     // Control pin for servo motor
int servoTiltPin = 10;     // Control pin for servo motor

int pos = 0;                 // position
int count = 0;
char command;

Servo panServo;
Servo tiltServo;

void setup()
{
  Serial.begin(9600);
  panServo.attach(servoPanPin);
  tiltServo.attach(servoTiltPin);
}

void loop()
{
  if ( Serial.available())
  {
    char ch = Serial.read();

    if(ch >= '0' && ch <= '9')      {        // is ch a number?
       pos = pos * 10 + ch - '0';           // yes, accumulate the value
         count++ ;
    }
    else {
       command = ch;
       count = 0;
       pos = 0;
    }
    if( count == 3){
      Serial.print(command); Serial.print(" "); Serial.println( pos);
      if(command == 'T')
        tiltServo.write(pos);
      else if(command == 'P')
         panServo.write(pos);
       pos = 0;
       count = 0;
    }
  }
}

Although there is probably a more elegant way, I just prefixed the Center X and Center Y in Roborealm with a T or P. I mapped the values to be between 0-180 for the servos. It's been a while, but from what I remember, it worked fine.

Dave

Below is a little more on the code that takes a string like "wer,qwe rty,123 456,hyre kjhg," (two parameters seperated by a space and terminated with a ","), and parces out the parameters. This can be tested using the serial monitor.

//zoomkat 8-12-10 roborealm serial servo parce test
//type servo position 0 to 180 in serial monitor
//for writeMicroseconds, use a value like 1500

#include <WString.h> //provides easy string handling
String readString = String(100);
String parce1 = String(10);
String parce2 = String(10);
int pos = 0;
int ind1 = 0;
int ind2 = 0;

 
#include <Servo.h> 
Servo myservo;  // create servo object to control a servo 

void setup() {
      Serial.begin(9600);
        myservo.attach(9);
        }

void loop() {

        //expect a string like wer,qwe rty,123 456,hyre kjhg,
        while (Serial.available()) {
        delay(10);  
          if (Serial.available() >0) {
        char c = Serial.read();  //gets one byte from serial buffer
        if (c == ',') { goto parce;}
        readString.append(c); } //makes the string readString
        }
      
      
      parce:  
      if (readString.length() >0) {
      Serial.println(readString);
      pos = readString.length(); //capture string length
      ind1 = readString.indexOf(' '); //position of the space
      parce1 = readString.substring(0, ind1); //first part of string
      parce2 = readString.substring(ind1+1, pos); //second part of string
      Serial.println(parce1);
      Serial.println(parce2);
      Serial.println();
      
      //int n;
      //n = atoi(readString); //convert string to number
      //myservo.writeMicroseconds(n);
      //myservo.write(n);
      readString="";
      } 
   }

Wow thanks to everyone for their help. I'll put some time in this weekend and let you know how it turns out.