controlling 2 servos via usbserial

Hi,

I hacked away at some example code to control 2 servos realtime via my pc with an old Uno over USB.
First off, is an Uno apropos for this ? Im getting stuttered jumpy movements. My host app sends serial 30x per sec. tried different baud rates, buffer size. I'm wondering if I need to switch to an Arduino with a hardware serial port, or if my code is just awful, or if it's something else:

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

#include <Wire.h>
#include <Adafruit_PWMServoDriver.h>

Adafruit_PWMServoDriver pwm = Adafruit_PWMServoDriver();
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;
int Int2FromPC = 0;

boolean newData = false;

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

void setup() {

    Serial.begin(57600);
//    Serial.println("This demo expects 3 pieces of data - text, an integer and an integer");
//    Serial.println("Enter data in this style <HelloWorld, 12000, 24000>  ");
//    Serial.println();
  pwm.begin();
  
  pwm.setPWMFreq(60);  // Analog servos run at ~60 Hz updates

  delay(10);
}

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

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();
        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
//    dxlRegWrite2(1, AX_GOAL_POSITION_L, integerFromPC);
    pwm.setPWM(0, 0, integerFromPC);

    strtokIndx = strtok(NULL, ",");
    Int2FromPC = atoi(strtokIndx);     // convert this part to an int
    pwm.setPWM(1, 0, Int2FromPC);

}

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

void showParsedData() {
    Serial.print("Message ");
    Serial.println(messageFromPC);
    Serial.print("Integer ");
    Serial.println(integerFromPC);
    Serial.print("INt2 ");
    Serial.println(Int2FromPC);
}

Why are you using Adafruit_PWMServoDriver.h for driving the servos rather than the simpler Servo.h? Are you actually using an Adafruit I2C servo driver board? Which one?

Second question - what servos and how are they powered? A circuit diagram would be good.

Steve

yes, thanks. Im using adafruit 16x12bit servo shield and 2 giant Torxis 12 volt servos with their own separate discrete power supply lines. As to why Im using it instead of servo lib ..... hmmm. though it might be better for realtime remote serial control since the shield would be doing the PWM I guess.

I need to switch to an Arduino with a hardware serial port

Your Uno HAS a hardware serial port.

        strcpy(tempChars, receivedChars);
            // this temporary copy is necessary to protect the original data
            //   because strtok() used in parseData() replaces the commas with \0

But, you never use the original data, so it is NOT necessary to copy it. Save time; stop copying.

What, EXACTLY, are you sending to the Arduino?

thks Paul,
I commented out the offending string copy.
I upped baud, reduced size of buffer to 12 chars.
No noticeable difference in stutter yet. It happens mostly when I send a rush of numbers from mouse or kinect gesture, as if the whole communication chain just isn't keeping up. I may be just overwhelming the response time that the servos are capable of ? I haven't opened them up yet, but i think there's a little board in there with a few adjustments. I wonder now if I'm just sending the servo too much too fast before it can react and settle in it's destination.

I am sending ascii to arduino as

<P,300,100>

via python on PC:

stringy = "<P,{},{}>".format(x,y)
op('serial2').send(stringy)

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

#include <Wire.h>
#include <Adafruit_PWMServoDriver.h>

Adafruit_PWMServoDriver pwm = Adafruit_PWMServoDriver();
const byte numChars = 12;
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;
int Int2FromPC = 0;

boolean newData = false;

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

void setup() {
    Serial.begin(115200);
//    Serial.println("This demo expects 3 pieces of data - text, an integer and an integer");
//    Serial.println("Enter data in this style <HelloWorld, 12000, 24000>  ");
//    Serial.println();
  pwm.begin();
  
  pwm.setPWMFreq(60);  // Analog servos run at ~60 Hz updates

  delay(10);
}

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

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();
        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(receivedChars,",");      // 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

    pwm.setPWM(0, 0, integerFromPC);

    strtokIndx = strtok(NULL, ",");
    Int2FromPC = atoi(strtokIndx);     // convert this part to an int
    pwm.setPWM(1, 0, Int2FromPC);

}

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

void showParsedData() {
    Serial.print("Message ");
    Serial.println(messageFromPC);
    Serial.print("Integer ");
    Serial.println(integerFromPC);
    Serial.print("INt2 ");
    Serial.println(Int2FromPC);
}

Since you never use the 'P', it doesn't make sense to send it.

Are you sending data only when there is a change? Or, are you sending data continually, telling the servos over and over to do where they are?

Have you tried driving the servos using a for loop to generate the position values, instead of getting them from the serial port?

wanted to say thanks for the tips, Paul. I was able to narrow down some things about my motors stuttering , inspired by you 'asking the right questions' :slight_smile:

basically I tuned the PID controls on the onboard microcontrollers and got acceptable performance/smoothness. The issue didnt seem to be the serial throughput after all. Your suggestion inspired me in this direction. Cheers.

As for the 'P' in my data stream that was a placeholder for a future feature. I wanted to 'stress test ' the amount of serial I could send. My current thought is that serial is probably not a bottleneck as much as I thought.
I will be going back to my dynamixels soon enough anyhow, as they have the ability to send high res position info. Will definitely be more serial 'fun' ahead.