Multiple variables

Ive got Roborealm sending a targets position serially to an Arduino which controls a Sabertooth motor controller. Right now I've only got the targets X screen position being sent and it seems to work fine. I also want to add the targets size (0-160) and send that also, but I'm at a loss on how to separate the 2 variables on the Arduino end. This is what I currently have.

#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[i] = Serial.read(); // Assign the input value to the incomingData buffer
      i++; // Increment the counter
    }

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

Thanks,
Dave

The usual technique is to read digits with a non-digit characters in between, for example a "," or space, or something.

Ignoring the length of each number (assuming program sends valid stuff)

int i = 0, j = 0;
int ch;
char incomingData1[5];
char incomingData2[5];
while (Serial.available() && (ch=Serial.read()) >= '0' && (ch <='9')) {
incomingData1* = ch;*

  • i++;*

  • }*

  • while (Serial.available() && (ch=Serial.read()) < '0' && (ch >'9')) {*

  • // nothing to do, just eat the non digits*

  • }*

  • while (Serial.available() && (ch=Serial.read()) >= '0' && (ch <='9')) {*

  • incomingData2[j] = ch;*

  • j++;*

  • }*
    [/quote]
    Now the values are in two character arrays, and atoi can be used to turn them into numbers.
    This insn't very robust, but it's not too far away if the data comes from a program.
    HTH
    GB
    PS - I haven't run this code.

Thanks,

Using your example, I've got the Arduino converting the data and sending it back to Roborealms serial monitor. The two variables are separated by a comma.

/*
Roborealm to Arduino serial test. Roborealm tracks a red object
and sends it's distance from screen center as well as COG box size
to the Arduino. The Arduino converts the ascii data to interger and 
sends back to Roborealms serial monitor.

 */

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

//Servo myServo; // Create a new servo object
int distance = 0; // The distance of the object from the center of the screen
int position = 0;
//int currentAngle = 90; // The current angle of the servo

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(){
 
   int i = 0, j = 0;
  int ch;
  char incomingData1[5];
  char incomingData2[5];
  while (Serial.available() && (ch=Serial.read()) >= '0' && (ch <='9')) {
    incomingData1[i] = ch;
    i++;
  }

  while (Serial.available() && (ch=Serial.read()) < '0' && (ch >'9')) {
    // nothing to do, just eat the non digits
  
  }
  while (Serial.available() && (ch=Serial.read()) >= '0' && (ch <='9')) {
    incomingData2[j] = ch;
    j++;
  }
 {
  position = atoi (incomingData1);
  distance = atoi (incomingData2);
  Serial.println (position); 
  
  Serial.println (distance); 
 }
  i = 0; // Reset the counter
  j = 0; // Reset the counter
  //delay(5); // Delay 20ms to allow the servo to rotate to the position
}

this is a grab of what the monitor shows. The first 6 lines are showing what the arduino is sending back from the previous block of data. The next lines are Roborealm sending out.

14702: -19662
14703: 114
14704: -19662
14705: 114
14706: -19662
14707: 114
14708: 11,70
14709: 11,70
14710: 11,70
14711: 11,70
14712: 11,68

I'm not sure why it's echoing incorrect values.

Dave

I don't see anything terminating the strings before calling "atoi".

The atoi function takes a string as it's argument. You are passing it a character array.

The difference between a character array and a string is that a string is a character array that is null terminated.

You are not null-terminating the arrays after adding characters.

  while (Serial.available() && (ch=Serial.read()) >= '0' && (ch <='9')) {
    incomingData1[i] = ch;
    i++;
    [glow]incomingData1[i] = '\0';  // Add this[/glow]
  }

Thanks Paul,

I had a VB script running on the Roborealm side that changed the position from a range of 0-160 to -80 -80. It looked like the negative values were causing problems so I deleted the script. It's now sending a 0-160 range. I made your change and it's closer but still not right. It's almost like it's getting only a part of the variables.

00318: 42
00319: 3
00320: 42
00321: 173
00322: 4<g1
00323: <g1
00324: 173,142
00325: 173,142
00326: 172,146
00327: 173,142
00328: 172,142
Module Stopped!

Your code is assuming that all the serial data is present before reading begins. That is not a valid assumption.

The first block of code that reads should either wait until Serial.available() returns 4 (xxx,) or keep trying to read until the , has been received.

The second block of code that reads will have a similar problem. There is nothing in the data stream that says that the end of a packet has been reached.

If you can change the sending code, send something like "<153,109>". Then, you'd know if the end of the packet was received. You would also know if part of a packet was lost.

With the code you have now, if a character gets dropped, you send/receive a stream that looks like 124,13130,14156,23. How are you supposed to know whether 13130 is 13 and 130 or 131 and 30 or was supposed to be 130 and 130?

Very good points PaulS, I hacked that out too quickly :frowning:

I agree that there needs to be a marker to separate or terminate the packet (or at least differentiate between the first and second value, which in this case amounts to the same thing).

Anything distinct, even a new line, would do as a packet separator or terminator. I don't think there is a big benefit in having a start packet marker. It's more stuff to read, and if it gets lost, the packet gets trashed, which may be unhelpful. If the connection is so bad that there are characters being lost, it may be better to put some kind of error detection in, like a 'checksum' character.

The other problem with dropping characters is it might cause the code to read all the digits of two numbers as one number. In this case the input arrays for the digits of the number are too small. We could make them bigger, or just calculate the integer as the number is read.

Given the code is only reading the numbers, and AFAIK has nothing to do otherwise (the other functions are driven by hardware or interrupts), it could block waiting for the next character:

int getchar() {
while (Serial.available() == 0) {} // assume no error returns
return Serial.read();
}

This might make it easier to understand how to code reading stuff.

Then it could be:
get ready to read packet
read 1st number
if everything went okay: read 1st number separator
if everything went okay: read 2nd number

if everything went okay: read packet terminator
if anything failed: tidy up, read everything upto and including a packet terminator

if everything went okay - do stuff
if anything failed - do different stuff

Nubee, sorry about my ropey code
GB

If you can change the sending code, send something like "<153,109>". Then, you'd know if the end of the packet was received. You would also know if part of a packet was lost.

Would this be as simple as a while statement waiting for the "<" character befor collecting incoming data?

Dave

Would this be as simple as a while statement waiting for the "<" character befor collecting incoming data?

It might be. As PaulS points out, if the sender is slow at sending data, then the receiving (Arduino) program needs to wait for it to arrive.
Remember the Arduino can do things much faster than a character can be sent. It will get ahead if it doesn't wait.

If it's okay to block and wait, then you can use the getchar() function I sketched above.
If that is the case, then you can use the '<' if it makes you feel happier, but the critical separators are the ',' and the '>'.

The important points are:

  1. If the characters haven't arrived, the program needs to stay in the same state until it gets them. I think that is the main problem.
  2. Two different separators, one between the first and second number, and another between the second number and following number, will make it easy to tell where reading the data has got to, and that the receiving program is synchronised with the sender.
  3. If a character gets lost, the two numbers might run together which would write beyond the end of each char array.
  4. If a character is lost, you need to be able to resynch with the sender. The end of second number (or packet terminator), the '>' in the PaulS proposal, should be enough.

I'd tackle this a step at a time. I'd ensure it waits to get characters at the right time, rather trying to fix losing characters, first.

HTH
GB

Thanks for all the help guys...more questions.
This sketch is the closest to what I want to do. I send it something like 'P 123 T 104' and it echoes back those 2 variables nicely separated. The problem is it only works with 3 digit variables. My values range from 0 - 160. Is the ' if( count == 3 ) ' the culprit?

I'm not quite sure how to get it to accept a variable length. I hope it's a relatively easy fix :wink:

This is what I'm using:

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;
    }
  }
}

I feel it's soooo close but my goals outweigh my feeble programming skills.

Thanks,

Dave

Is the ' if( count == 3 ) ' the culprit?

Yes, it is. If you are sending some sort of end-of-packet marker, that marker is not a character in the '0' to '9' range.

When that character is received, regardless of how many characters preceded it, the entire number has been received.

Is the ' if( count == 3 ) ' the culprit?

Yes, that's right.

When there is a character, there are 4 cases:
digit (0-9 character) accumulate the number
space - do nothing
'T' - do the tiltServo.write(pos); pos = 0;
'P' - panServo.write(pos); pos = 0;

count doesn't matter.

It is very close
GB
[edit]Ooops, sorry PaulS, crossed over. I was watching a movie, and just pressed save[/edit]

OK, close is good. If I comment out the count line I only get single digit spurious data. I've tried inserting a character before and after each variable as sort of a terminator, and adding a while to wait for it, but it still won't pass anything less than three digits. Kinda shooting in the dark here.

Dave

It is simpler than the code is trying to do.
I think this is close, but
WARNING - not verified or tested as this is incomplete

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

    if(ch >= '0' && ch <= '9')      {      // is ch a digit?
        pos = pos * 10 + (ch - '0');      // yes, accumulate the value
    }
    else if(ch == 'T') {
        tiltServo.write(pos);
        pos = 0;
    } 
    else if(ch == 'P') {
        panServo.write(pos);
        pos = 0;
    }
  }
}

HTH
GB

This relies on the data being correct. It is not robust enough for a human interface.

Thanks a bunch. That returns the correct values regardless of length. Now to implement the motor control code.

Thanks,

Dave