Designing serial robot communication software

My robot has servos, pwm controlled speed controllers and sensors. I would like to control the robot using an Arduino board that is connected via usb to a raspberry pi.

I currently have it running using firmata on the Arduino, and node-red on the pi. Works pretty slick, but I would like to do even better. I would like to run code on the Arduino, and firmata does not permit that.

I would like to continue to use node-red, as it permits the construction of a really nice web based dashboard for control of the robot.

Node-Red has various serial communication nodes, such as:

Input

Reads data from a local serial port.

It can either wait for a “split” character (default \n). Also accepts hex notation (0x0a).
wait for a timeout in milliseconds from the first character received
wait to fill a fixed sized buffer

It then outputs msg.payload as either a UTF8 ascii string or a binary Buffer object.
If no split character is specified, or a timeout or buffer size of 0, then a stream of single characters is sent - again either as ascii chars or size 1 binary buffers.

Output

Provides a connection to an outbound serial port.
Only the msg.payload is sent.
Optionally the new line character used to split the input can be appended to every message sent out to the serial port.

So, my question is, what format would be the most efficient for formatting serially encoded commands for movement and the return of data such as distance sensors, position sensors, etc?

How should I encode the serial data for commanding, lets say 20 different servos? Should it be a string of 20 pieces of data, of which say 19 are the same as the last time? Or a single unit that can be sorted upon receipt as to what servo it pertains to, and what position it should move to?

Is there going to be a significant speed difference in the various says of decoding the data serially?

Will having a serial read event impact the speed of the code such that sensor data, such as ping sensors, is not read correctly?

Rather than start writing code in the first way I can think of to make this work, anyone have any suggestions on the most efficient way to do this? Anyone know of any examples?

I have found the following example, but I’m not sure if his method was the fastest and less subject to delays in the code.

PART OF THE CODE:

void loop() {
 String validCommands = "rsSeEwWbBcd";  // The valid commands we will accept for control
 bool cmdComplete = false;
 static int parm = 0;
 static char command;                // Command character

 while ((Serial.available() > 0) & (!cmdComplete)) {
   char ch = Serial.read();
   if (ch != ',') {                 // Not a comma
     if (ch >= '0' && ch <= '9') {  // Accumulate the decimal parameter into parm if character read is numeric
       parm = parm * 10 + ch - '0';
     } else if (validCommands.indexOf(ch) != -1) {
       command = ch;                // If it's not numeric see if it is one of our valid commands
     } else {
       Serial.println("Invalid command");
     }
   } else {
     cmdComplete = true;            // When we get a comma the command is complete
   }
 }
 if (cmdComplete) {
   cmdComplete = false;
   // Do the action depending on the character received.  
   switch (command) {
   case 'r':                   // r just read the sensors and write their values to serial output, don't move
     break;
   case 's':                   // s moves the shoulder forwards
     moveShoulder(FORWARD);
     break;
   case 'S':                   // S moves the shoulder backwards
     moveShoulder(BACKWARD);
     break;
   case 'e':                   // e moves the elbow down
     moveElbow(FORWARD);
     break;
   case 'E':                   // E moves the elbow up
     moveElbow(BACKWARD);
     break;
   case 'w':                   // w moves the wrist down
     moveWrist(FORWARD);
     break;
   case 'W':                   // W moves the wrist up
     moveWrist(BACKWARD);
     break;
   case 'b':                   // b turns the base clockwise (looking down)
     moveBase(FORWARD);
     break;
   case 'B':                   // B turns the base anti-clockwise (looking down)
     moveBase(BACKWARD);
     break;
   case 'c':                   // c toggles the clamp open and closed
     clamp();
     break;
   case 'd':                   // Set the duration of motor run time
     duration = parm;
     motorTime = motor[duration];
     break;
   default:
     break;
   }
   readsensors();     // Read sensors and publish after every command
   command = 'r';     // If user only specifies a number and a comma or just a comma pretend they meant r
   parm = 0;
 }

All credit for above code to “Jim’s Blog”

http://jekw.co.uk/2014/01/06/arduino-code-for-the-robot-arm/

Is switch command a good way to evaluate the received data?

Serial Input Basics - http://forum.arduino.cc/index.php?topic=396450.0

is very helpful and likely answers my questions, aside from how to sort through the received data and make choices based upon it. Any thoughts on using switch command? Or is there a better way to match up a piece of data with a set of instructions?

I am experimenting with some code from a parsed serial example.

The example parses a serial string of <text, integer, floating point variable>

I want to be able to substitute the text part of the string into the command

messageFromPC.write(integerFromPC)

in order to select a particular servo, by having the text be the servo’s name. The substitution of the integer worked fine.

But I get an error message of :

Arduino: 1.8.5 (Windows Store 1.8.10.0) (Windows 10), Board: “Arduino/Genuino Mega or Mega 2560, ATmega2560 (Mega 2560)”

C:\Users\xxxxxxxxxxxx\Documents\Arduino\parsedservoserial\parsedservoserial.ino: In function ‘void servoWrite()’:

parsedservoserial:119: error: request for member ‘write’ in ‘messageFromPC’, which is of non-class type ‘char [32]’

messageFromPC.write(integerFromPC); // tell servo to go to position in variable ‘pos’

^

exit status 1
request for member ‘write’ in ‘messageFromPC’, which is of non-class type 'char [32]

This report would have more information with
“Show verbose output during compilation”
option enabled in File → Preferences.

after editing in the text variable “messageFromPC” into the write command.

Is there a way to convert the variable char messageFromPC
into the correct type of variable that I can use to substitute for the servo name, in the command

messageFromPC.write(integer)

The full code:

// Example 5 - Receive with start- and end-markers combined with parsing

const byte numChars = 32;
char receivedChars[numChars];
char tempChars[numChars];        // temporary array for use when parsing

      // variables to hold the parsed data
char messageFromPC[numChars] = {0};
int integerFromPC = 0;
float floatFromPC = 0.0;

boolean newData = false;
// my addition

#include <Servo.h>

Servo myservo1;  // create servo object to control a servo
// twelve servo objects can be created on most boards

int pos = 0;    // variable to store the servo position

// end my addition
//============

void setup() {
    Serial.begin(9600);
    Serial.println("This demo expects 3 pieces of data - text, an integer and a floating point value");
    Serial.println("Enter data in this style <HelloWorld, 12, 24.7>  ");
    Serial.println();
    // my addition
  myservo1.attach(9);  // attaches the servo on pin 9 to the servo object
    // end my addition
 
}

//============

void loop() {
    recvWithStartEndMarkers();
    if (newData == true) {
        strcpy(tempChars, receivedChars);
            // this temporary copy is necessary to protect the original data
            //   because strtok() used in parseData() replaces the commas with \0
        parseData();
        showParsedData();
        // my addition
        servoWrite ();
        // end my addition
        newData = false;
    }
}

//============

void recvWithStartEndMarkers() {
    static boolean recvInProgress = false;
    static byte ndx = 0;
    char startMarker = '<';
    char endMarker = '>';
    char rc;

    while (Serial.available() > 0 && newData == false) {
        rc = Serial.read();

        if (recvInProgress == true) {
            if (rc != endMarker) {
                receivedChars[ndx] = rc;
                ndx++;
                if (ndx >= numChars) {
                    ndx = numChars - 1;
                }
            }
            else {
                receivedChars[ndx] = '\0'; // terminate the string
                recvInProgress = false;
                ndx = 0;
                newData = true;
            }
        }

        else if (rc == startMarker) {
            recvInProgress = true;
        }
    }
}

//============

void parseData() {      // split the data into its parts

    char * strtokIndx; // this is used by strtok() as an index

    strtokIndx = strtok(tempChars,",");      // get the first part - the string
    strcpy(messageFromPC, strtokIndx); // copy it to messageFromPC
 
    strtokIndx = strtok(NULL, ","); // this continues where the previous call left off
    integerFromPC = atoi(strtokIndx);     // convert this part to an integer

    strtokIndx = strtok(NULL, ",");
    floatFromPC = atof(strtokIndx);     // convert this part to a float

}

//============

void showParsedData() {
    Serial.print("Message ");
    Serial.println(messageFromPC);
    Serial.print("Integer ");
    Serial.println(integerFromPC);
    Serial.print("Float ");
    Serial.println(floatFromPC);
}
// my addition

void servoWrite () {

     messageFromPC.write(integerFromPC);              // tell servo to go to position in variable 'pos'
}

// end my addition

OK, I finally got this working. Now I can feed in a serial string of 5 digits, say 03180, and it moves the servo at pin 3 to 180 degrees. Works on my mega for pins 2-19.

// zoomkat 12-13-11 serial servo (2) test
// for writeMicroseconds, use a value like 1500
// for IDE 1.0
// Powering a servo from the arduino usually DOES NOT WORK.
// two servo setup with two servo commands
// send five character string like 02180 to move servo pin 2 to 180 degrees or 19000 to move servo at pin 19 to 0 degrees
// use serial monitor to test

#include <Servo.h> 
String readString, servoname, servo2;

Servo mys[20]; //Now we have 20 servos but only use 2-19 (ie 18 servos)


void setup() {
  Serial.begin(115200);
  for(int i=2; i<20; i++)
{
   mys[i].attach(i); // was i+2
}
 
  Serial.println("two-servo-test-1.0"); // so I can keep track of what is loaded
}

void loop() {

  while (Serial.available()) {
    delay(3);  //delay to allow buffer to fill 
    if (Serial.available() >0) {
      char c = Serial.read();  //gets one byte from serial buffer
      readString += c; //makes the string readString
    } 
  }

  if (readString.length() >0) {
      Serial.println(readString); //see what was received
      
      // expect a string like 07180 containing the servo position and degrees      
      servoname = readString.substring(0, 2); //get the first two characters name
      servo2 = readString.substring(2, 5); //get the next three characters 
      
      Serial.println(servoname);  //print to serial monitor to see parsed results
      Serial.println(servo2);

      int n1 = servoname.toInt();
      int n2 = servo2.toInt();

      Serial.println("the numbers are :");
      Serial.println(n1);  //print to serial monitor to see number results
      Serial.println(n2);
            
      mys[n1].write(n2); //set servo position 

    readString="";
  } 
}
    delay(3);  //delay to allow buffer to fill

Crap code.

      readString += c; //makes the string readString

That comment is nonsense. The String was already named readString.

      servoname = readString.substring(0, 2); //get the first two characters name
      servo2 = readString.substring(2, 5); //get the next three characters

Those are stupid things to do after reading 1 character.

      int n1 = servoname.toInt();

03 is NOT the name of a servo.

Robin2 has a really nice tutorial on reading serial data. It does NOT use Strings. It would let you send “<1,45>” to move the 2nd Servo instance to 45 degrees, instead of having to pad with 0s. Find it. Read it. Embrace it.

The real advantage of his way is that you don’t have to embarrass yourself posting stupid comments about delay() being a good way of ensuring that serial data has time to arrive.

(And, a point of reference, zoomkat was absolutely welded to the idea that delay() was the only possible way to read data, despite all of our efforts to broaden his horizons. I would NEVER use code he wrote.)

How is this? Seems to work. :slight_smile:

[code]

// Example 5 - Receive with start- and end-markers combined with parsing

const byte numChars = 32;
char receivedChars[numChars];
char tempChars[numChars];        // temporary array for use when parsing

      // variables to hold the parsed data
int firstIntegerFromPC = 0;
int secondIntegerFromPC = 0;
// int floatFromPC = 0.0;

boolean newData = false;

// my servo addition

#include <Servo.h> 
Servo mys[20]; //Now we have 20 servos but only use 2-19 (ie 18 servos)

//

//============

void setup() {
    Serial.begin(9600);
    Serial.println("This demo expects 2 pieces of data - two integers");
    Serial.println("Enter data in this style <1, 180>  ");
    Serial.println();
    // my addition
      for(int i=2; i<20; i++)
{
   mys[i].attach(i); // was i+2
}
 // end my addition
}

//============

void loop() {
    recvWithStartEndMarkers();
    if (newData == true) {
        strcpy(tempChars, receivedChars);
            // this temporary copy is necessary to protect the original data
            //   because strtok() used in parseData() replaces the commas with \0
        parseData();
        showParsedData();
        servoMove();
        newData = false;
    }
}

//============

void recvWithStartEndMarkers() {
    static boolean recvInProgress = false;
    static byte ndx = 0;
    char startMarker = '<';
    char endMarker = '>';
    char rc;

    while (Serial.available() > 0 && newData == false) {
        rc = Serial.read();

        if (recvInProgress == true) {
            if (rc != endMarker) {
                receivedChars[ndx] = rc;
                ndx++;
                if (ndx >= numChars) {
                    ndx = numChars - 1;
                }
            }
            else {
                receivedChars[ndx] = '\0'; // terminate the string
                recvInProgress = false;
                ndx = 0;
                newData = true;
            }
        }

        else if (rc == startMarker) {
            recvInProgress = true;
        }
    }
}

//============

void parseData() {      // split the data into its parts

    char * strtokIndx; // this is used by strtok() as an index

    strtokIndx = strtok(tempChars,",");      // get the first part - the string
    // tim comments out strcpy(firstIntegerFromPC, strtokIndx); // copy it to firstIntegerFromPC
    firstIntegerFromPC = atoi(strtokIndx); // my addition convert first part to an integer
 
    strtokIndx = strtok(NULL, ","); // this continues where the previous call left off
    secondIntegerFromPC = atoi(strtokIndx);     // convert this part to an integer

   // Tim comments out strtokIndx = strtok(NULL, ",");
  //  Tim comments out floatFromPC = atof(strtokIndx);     // convert this part to a float

}

//============

void showParsedData() {
    Serial.print("Servo Number ");
    Serial.println(firstIntegerFromPC);
    Serial.print("Degrees movement ");
    Serial.println(secondIntegerFromPC);
    // Serial.print("Float ");
   // Serial.println(floatFromPC);

}
void servoMove() {
   // start my addition
  mys[firstIntegerFromPC].write(secondIntegerFromPC); //write servo position 
   // end my addition
   }

[/code]

    firstIntegerFromPC = atoi(strtokIndx); // my addition convert first part to an integer

Does that name REALLY mean anything to you?

    secondIntegerFromPC = atoi(strtokIndx);     // convert this part to an integer

That one?

void showParsedData() {
    Serial.print("Servo Number ");
    Serial.println(firstIntegerFromPC);
    Serial.print("Degrees movement ");
    Serial.println(secondIntegerFromPC);

It would make far more sense, to me, to print servoNumber and servoPosition.

Seems to work.

With those meaningless names, it is going to be hard to maintain.

Thanks for the suggestion Paul.

Next step is formatting serial data out (including ping sensor, battery voltage, etc) in JSON, so that node red can easily parse the data.