Change baud rates?

I am working with an Arduino Duemilanove and am having trouble getting serial to work correctly.

I am trying to talk with an Animatics Smart Motor over serial, which defaults to 9600 baud. I have been very successful with using software serial, but I can't get it to be stable at over 9600 baud (at 19.2k it works for a little while, but then stops working.)

I figured that I could use the hardware serial port, but whenever I try and change the serial speed the motor stops responding.

Here is the code that I am using.

void setup()
{
  //Start serial to communicate with motor.
  Serial.begin(9600);
  
  pinMode(13, OUTPUT);
  
  hardStopCalibrate();    //Find zero.
  
  //Set Maximum Acceleration
  sendCom("A=700"); 
  
  //Set Maximum velocity
  sendCom("V=10000000");
  
  //Send the motor to a good central position.
  goTo(inchesToLocation(5));
  
  //Send Go command to start motor servo with new values.
  sendCom("G");
  
  //Tell the motor to go to a new baud rate.
  sendCom("BAUD19200");
  
  //Change the arduino's baud rate.
  Serial.begin(19200);

}

As I said, I can use this with software serial, no problem. (Well, other than that I can only slow down the baud rate.) But it doesn't work when using the hardware port. Any ideas as to why?

Try doing this before changing the baud rate:

Serial.flush ();   // wait for send buffer to empty
delay (2);    // let last character be sent
Serial.end ();      // close serial

Animatics Smart Motor

Do you have a link to a datasheet of the exact motor you used?

How much data are you sending to the motor controller? It seems to me that for a reasonable amount of data, 9600 would be fast enough.

I'll try the serial.flush() technique tomorrow and let you know. I had tried serial.end() before, but it still didn't work.

It is an older version of the SM17205D. I don't have the exact data sheet for this motor, but some information can be found here: Downloads

The problem with the slow baud rate is that I am trying to do an external feedback loop using the Arduino, and so the commands that I send to the motor are very time sensitive. It has been fine at 9600 baud until now, but now I am trying to do two way communication instead of the send only communication that I have been doing until now. I need to maintain at bare minimum 60 Hz, preferably at least twice that, and I just can't do it at this low baud rate with all of the other calculations that I need to do.

At 9600 baud (if my math is correct) if I send a string of 9 characters, which would be fairly normal, I can get a little less than 100 Hz, or about 11ms of communication time. But for bi-directional communication, I need to send a string of four characters, receive a string of 5 or so characters, and then send 9 characters, dropping me to about 50-60 Hz in the best case scenario, or about 20ms of just communications time. At 38400 baud, the maximum supported by the motor, that drops to about 5ms, which leaves me 5-10ms for calculations.

I need to maintain at bare minimum 60 Hz, preferably at least twice that, and I just can't do it at this low baud rate with all of the other calculations that I need to do.

When you make a call to Serial.print() on 1.0 and later, the function returns as soon as the data has been copied into an outgoing buffer. You don't have to wait for the data to be sent to resume work on "all of the other calculations that I need to do.".

It's probably time that posted your code and explained what you are doing. There are likely optimizations to the code that can be done to eliminate the need to talk to the motor faster.

There are basically 5 ways to communicate to a SmartMotor:

  1. By using I/O
  2. By using I2C or I2C devices (AniLink Port)
  3. By messaging over RS-485 (AniLink Port)
  4. By messaging over RS-232
  5. By messaging over CAN - Combitronic, CANopen, DeviceNet, etc.

Perhaps talking via serial isn't even the best way.

PaulS:
When you make a call to Serial.print() on 1.0 and later, the function returns as soon as the data has been copied into an outgoing buffer. You don't have to wait for the data to be sent to resume work on "all of the other calculations that I need to do.".

That is true for a send only control, but it is not true for a feedback loop. In a feedback loop I have to wait until the complete response from the motor arrives to the Arduino before I can do anything, which does indeed force me to wait between 10-20ms before I can continue with my calculations. Though now that you point it out, I wonder if I can do some calculations while waiting for the motor to respond. I'll have to look at my ordering.

Also, I don't think that I can use I2C to control the motor. According to the manual I have (that came with the motor) the motor can use I2C to control peripherals, but I can't find any information about controlling the motor itself over I2C. (My motor is a series 4, and so has some differences from the newer series 5 SmartMotors.)

I'll post my code after I try to change the baud rate again, but it is rather extensive. . . I would certainly like some help on speeding it up though!

I have been able to change the baud rate now, but once again I can only go slower. If I try to go faster it doesn't work. I could have some other problem with my code, but I don't know what it would be. Either that, or the parser on the motor can't keep up, which could be a problem.

I'll post my code in a bit.

If I try to go faster it doesn't work. I could have some other problem with my code

You can create a dummy sketch that just sends random (but not meaningless) data to the motor, and gets a response. Don;t try to do anything with the response. Then, try changing the baud rate that use to you talk to the motor. It that works, its the complicated stuff you are doing. If not, it's the motor.

Fredjikrang:
I have been able to change the baud rate now, but once again I can only go slower. ...

I'll post my code in a bit.

Yes, better post it. I've known people to ignore my suggestions before, and then complain that it doesn't work.

Well, that isn't the case here, Nick. I basically copy/pasted your code into my project. And as I said, it does let me change the baud rate, which I wasn't able to do before, but it doesn't work if I go to a higher baud rate.

I'll try the dummy program. I actually have one written already, so it should be easy to modify for this test. Thanks for the suggestion!

Here is main:

//#include <SoftwareSerial.h>
//SoftwareSerial serial2(9,10); // Tx = 10, Rx = 9

const int ledPin = 13;

const int scaleFactorInt = 403;  //Use radius of 0.79" to figure this out. This is for 11" long beam.
const double scaleFactorDouble = 403.0;  //Use radius of 0.79" to figure this out. This is for 11" long beam.

//Define the constants from beam measurments.
const int beamLength = 11;  //The length of the beam in inches.
const double beamThickness = 0.022;  //Beam thickness in inches.
const int gaugeLocation = 3;  //Location of the gauges, in inches from the pivot.

void setup()
{
  //Start serial to communicate with motor.
  Serial.begin(9600);
  //Serial.println("Serial started.");
  
  pinMode(13, OUTPUT);
  
  hardStopCalibrate();    //Find zero.
  
  //Set Maximum Acceleration
  sendCom("A=700"); 
  
  //Set Maximum velocity
  sendCom("V=10000000");
  
  //Send the motor to a good central position.
  goTo(inchesToLocation(5));
  
  //Send Go command to start motor servo with new values.
  sendCom("G");
  
  //Tell the motor to go to a new baud rate.
  sendCom("BAUD9600");
  
  //Prepare to change the Arduino baud rate.
  Serial.flush();
  delay(2);
  Serial.end();
  
  //Change the arduino's baud rate.
  Serial.begin(9600);

}


int locationValue = inchesToLocation(5);  //This needs to exist outside of the loop.
const int maxLocation = inchesToLocation(8.5);    //Max location.
const int minLocation = inchesToLocation(3);    //Min location.

void loop()
{

  int prevLocation = locationValue;

  //Determine the deflection.
  double z = calculateZ(calculateStrain(), locationValue);  //This is stored so that it is only calculated once.
  

  //Figure out the new desired location.
  locationValue = inchesToLocation(desiredLocation(z, forceProfile(z)));

  //If the location error is greater than desired, send the updated location to the motor.
  //Done to reduce motor jitter caused by noise on the analog line.
  int e = locationValue - prevLocation;    //Location error value.
  if (abs(e) > 30)
  {
    if (locationValue > maxLocation)  //If the calculated location value is too big, go to endpoint.
    {
      locationValue = maxLocation;
    }
    else if (locationValue < minLocation)  //If the calculated location value is too small, go to endpoint.
    {
      locationValue = minLocation;
    }

    goTo(locationValue);  //Send the motor the desired location.
    
  }
  else{locationValue = prevLocation;}  //Prevents slow drifting by storing the last sent location.
    
    digitalWrite(13, !digitalRead(13)); //Toggle LED after each loop.
  
  
}

My "Small Functions":

const String setPoint = "P=";

//Build the position string for the motor.
String goPoint(int location)
{
  return setPoint + location;
}
   
//Send the motor to a specific location.
void goTo(int location)
{
  sendCom(goPoint(location));
  sendCom("G");
}

//Send a command to the motor in a friendly way.
void sendCom(String command)
{
  Serial.print(command);  //Send the command string to the motor.
  Serial.print('\r');      //Send the end line to the motor (delimiter.)
}

//Send a status request to the motor, and return the responce.
String requestStatus(String command)
{ 
  //Send the status request to the motor.
  sendCom(command);
  
  //Wait for a message to arrive, with a maximum delay.
  int i = 0;
  while(!Serial.available() && i < 1000000)
  {i++;}
  
  //If there is a message from the motor, save it as a string.
  boolean done = false;
  String motorStatus;
  while (Serial.available() > 0 && !done)
    {
      delay(10);
      char next;
      next = char(Serial.read());
      //Check for delimiters, so as to not combine messages.
      if (next != '\r' && next != ' ')
      {
        motorStatus += next;
      }
      else{done = true;}
    }
  Serial.flush();  //Clear the buffer of any other data.
  motorStatus.trim();
  return motorStatus;
}

//Just like requestStatus, but only checks for a message. Doesn't send anything to the motor.
String getStatus()
{
  
  boolean done = false;
  String motorStatus;
  
  //Wait for a message to arrive, with a maximum delay.
  int i = 0;
  while(!Serial.available() && i < 1000000)
    {i++;}
    
  //If there is a message from the motor, save it as a string.
  while (Serial.available() > 0 && !done)
    {
      char next;
      next = char(Serial.read());
      //Check for delimiters, so as to not combine messages.
      //(Smart motor uses a carriage return "\r" as a delimiter.)
      if (next != '\r' && next != ' ')
      {
        motorStatus += next;
      }
      else{done = true;}
    }
    
  Serial.flush();  //Clear the buffer of any other data.
  motorStatus.trim();
  return motorStatus;
}


//This is defined as the the distance from the pivot.
//Input in is inches.
int inchesToLocation(double inches)
{
  
  return (int)((inches - 0.875) * scaleFactorInt);
  
}

//Does the opposite of the above function, changing from location to inches.
double locationToInches(int location)
{ 
  return (double)(location/scaleFactorDouble + 0.875);
}


int stringToInt(String stringValue)
{
  int intValue = 0;
  int length = 0;
  
  length = stringValue.length();
  
  int i = 0;
  for(i=0; i<length; i++)
  {
    intValue = (10*intValue) + stringValue.charAt(i)-(int) '0';
  }
  
  return intValue;
}

Calculate Desired Location:

const long modulus = 30000000;
//Width = 3/4", h = 0.02". I=(1/12)bh^3
const double aMomentOfInertia = 0.0000005;

//If this calculation is too slow, look into converting to int math.
//Takes as input a z value (should come from function that calculates it from
//strain guage input) and desired force (should come from user defined function.)
//Returns the new desired location in inches.

const double distanceMultiplier = sqrt(3*modulus*aMomentOfInertia/beamLength);  //Precalculate constant to speed up operation.

double desiredLocation(double z, double force)
{
  double distanceFromEnd = 0.0;  // = sqrt[(3zEI)/(FL)]
  
  distanceFromEnd = distanceMultiplier*sqrt(z/force);
  
  //Serial.print("Calculated distance from end=");
  //Serial.println(distanceFromEnd, 6);
  
  return beamLength - distanceFromEnd;
}

Calculate strain:

const double gaugeFactor = 2;
const int bridgeVoltage = 5;
const int ampFactor = 1603;
const int gaugePin = 2;

//Precalculate the multiplier to speed up operation.
double strainMultiplier = 10/(ampFactor*bridgeVoltage*gaugeFactor)*1/1024;  //Have to calculate 1/1024 seperatly or the denominator gets too big.

//Setup for the smoothing filter.
#define filterSamples   5              // filterSamples should  be an odd number, no smaller than 3
int sensSmoothArray [filterSamples];   // array for holding raw sensor values
int rawData1, smoothData1;  // variables for smoothing function.

//This function is used to calculate the strain from the input voltage.
//strain = 2*voltag/(ampFactor*bridegVoltage*gaugeFactor) for two gauges.
//But to convert to the analog input, have to multiply by 5*analogRead/1024.

double calculateStrain()
{
  //Debugging messages.
  //Serial.println("Calculating Strain.");
  //Serial.print("Strain multiplier=");
  //Serial.println(strainMultiplier, 10);
  
  int methodSelector = 2;
  
  
  int readValue = 0;
  
  //Get input from the strain gauges.
  if(methodSelector == 1)  //Simple oversampling.
  {
    int i = 0;
    for(i=0; i < filterSamples; i++)
    {
      readValue += analogRead(gaugePin);
    }
    readValue = readValue/filterSamples;
  }
  else if(methodSelector == 2)  //A simple digital smoothing, uses rolling array.
  {
    readValue = analogRead(gaugePin);
    readValue = digitalSmooth(readValue, sensSmoothArray);
  }
  else if(methodSelector == 3)  //A rolling average of the position. (No sort.) (Doesn't work very well.)
  {
    readValue = analogRead(gaugePin);
    readValue = rollingAverage(readValue, sensSmoothArray);
  }
  else  //Direct read, no filtering.
  {
    readValue = analogRead(gaugePin);    //This is pulled out seperate for debugging purposes.
  }
  
  
  //Serial.print("AR=");
  //Serial.println(readValue);
  
  double strain = readValue*strainMultiplier;  //Calculate strain.
  
  //Serial.print("Strain=");
  //Serial.println(strain, 6);
  
  return strain;
}

Calculate Z:

//Beam length defined in "calculateDesiredLocation"

double zMultiplier = 2*beamLength/(3*beamThickness*gaugeLocation);

//Calculates the z value using a given strain and current location.
double calculateZ(double strain, int locationValue)
{
  double z = 0;
  
  double locationInches = locationToInches(locationValue);
  
  z = strain*locationInches*zMultiplier;
  z = z*(2*beamLength*locationInches-beamLength*beamLength-locationInches*locationInches);
  z = z/(beamLength - locationInches);
  
  return abs(z);    //The program expects this value to be positive, but the equation always returns a negative.
  
}

Force Profile:

//A user defined function for the force that a user should feel at a certain deflection.

double forceProfile(double z)
{
  double force = 0;
  
  int forceSelector = 1;
  
  if(forceSelector == 1)  //The user should feel a constant force.
  {
    force = 0.1;  
  }
  else if(forceSelector == 2)  //The beam is stiff at small deflections, and soft after.
  {
    if(z > 0.35)
    {force = 0.1;}
    else{force = 1.0;}
  }
  else if(forceSelector == 3)  //The beam is soft for small and large deflections, and stiff for a small middle range.
  {
    if(z < 0.5)
    {force = 0.1;}
    else if(z >= 0.5 && z < 0.75)
    {force = 1;}
    else{force = 0.1;}
  }
  else if(forceSelector == 4)  //The beam is soft at small deflections, and stiff thereafter.
  {
    if(z > 0.9)
    {force = 1.0;}
    else{force = 0.1;}
  }
  else if(forceSelector == 5)  //A strange force profile.
  {force = abs(1-1.5*z);}
  else{force = .5;}  //Go to half a pound unless otherwise specified.
  
  return force;
  
}

Digital Smooth:

/* digitalSmooth
 Paul Badger 2007
 A digital smoothing filter for smoothing sensor jitter 
 This filter accepts one new piece of data each time through a loop, which the 
 filter inputs into a rolling array, replacing the oldest data with the latest reading.
 The array is then transferred to another array, and that array is sorted from low to high. 
 Then the highest and lowest %15 of samples are thrown out. The remaining data is averaged
 and the result is returned.

 Every sensor used with the digitalSmooth function needs to have its own array to hold 
 the raw sensor values. This array is then passed to the function, for it's use.
 This is done with the name of the array associated with the particular sensor.
 */




int digitalSmooth(int rawIn, int *sensSmoothArray)   // "int *sensSmoothArray" passes an array to the function - the asterisk indicates the array name is a pointer
{    
  int j, k, temp, top, bottom;
  long total;
  static int i;
 // static int raw[filterSamples];
  static int sorted[filterSamples];
  boolean done;

  i = (i + 1) % filterSamples;    // increment counter and roll over if necc. -  % (modulo operator) rolls over variable
  sensSmoothArray[i] = rawIn;                 // input new data into the oldest slot

  // Serial.print("raw = ");

  for (j=0; j<filterSamples; j++)  // transfer data array into anther array for sorting and averaging
  {     
    sorted[j] = sensSmoothArray[j];
  }

  done = 0;                // flag to know when we're done sorting              
  while(done != 1)  // simple swap sort, sorts numbers from lowest to highest
  {        
    done = 1;
    for (j = 0; j < (filterSamples - 1); j++)
    {
      if (sorted[j] > sorted[j + 1])  // numbers are out of order - swap
      {   
        temp = sorted[j + 1];
        sorted [j+1] =  sorted[j] ;
        sorted [j] = temp;
        done = 0;
      }
    }
  }


  // throw out top and bottom 15% of samples - limit to throw out at least one from top and bottom
  bottom = max(((filterSamples * 15)  / 100), 1); 
  top = min((((filterSamples * 85) / 100) + 1  ), (filterSamples - 1));   // the + 1 is to make up for asymmetry caused by integer rounding
  k = 0;
  total = 0;
  for ( j = bottom; j< top; j++)
  {
    total += sorted[j];  // total remaining indices
    k++; 
    // Serial.print(sorted[j]); 
    // Serial.print("   "); 
  }

//  Serial.println();
//  Serial.print("average = ");
//  Serial.println(total/k);
  return total / k;    // divide by number of samples
}

If you have a lot of files you can zip them up and upload that. Click on "Additional Options" to get an upload button.

void setup()
{
  //Start serial to communicate with motor.
  Serial.begin(9600);
  //Serial.println("Serial started.");
  
...
  //Tell the motor to go to a new baud rate.
  sendCom("BAUD9600");
  
  //Prepare to change the Arduino baud rate.
  Serial.flush();
  delay(2);
  Serial.end();
  
  //Change the arduino's baud rate.
  Serial.begin(9600);
}

That's hardly a new baud rate.

True, in this example it isn't a new baud rate. But I did run it at 4800 baud (Which worked, but was substantially slower, even in send only mode.), as well as 19200 and 38400, which did not work. But it also works simply reseting the baud rate to what it already was, which means that at least the motor isn't complaining at the command, and neither is the Arduino.

I'll remember that thing about zip files in the future. Thank you.

I think that the first thing that I am going to try is to limit the update rate to 100-150 Hz and see if maybe it is an issue with the motor not being able to handle the increased data throughput.

I can't reproduce your problem. Using this test code:

void setup()
{
  pinMode (3, OUTPUT);
  digitalWrite (3, LOW);
  
  Serial.begin(9600);
  Serial.println("Serial started.");
  
  //Tell the motor to go to a new baud rate.
  Serial.println ("BAUD9600");
  
  //Prepare to change the Arduino baud rate.
  Serial.flush();
  delay(2);
  Serial.end();
  
  //Change the arduino's baud rate.
  Serial.begin(115200);
  digitalWrite (3, HIGH);
  Serial.println ("This is at 115200 baud.");
}

void loop () {}

The above sends some text at 9600, switches to 115200 using the method I described, and raises pin D3 at that exact moment.

First, the logic analyzer output, set to 9600:

You can see the text on the left at 9600 baud, and then D3 going high with a clump of white on the right (the faster output).

Now changing the logic analyzer to 115200:

The new text is clearly visible following D3 going high.

Maybe you need a slight delay after switching baud rates to let the motor reconfigure itself.

  while(!Serial.available() && i < 1000000)
  {i++;}

How long do you think it will take the Arduino to count to 1,000,000? This is NOT how to wait a reasonable time for a response.

  while (Serial.available() > 0 && !done)
    {
      delay(10);
      char next;
      next = char(Serial.read());
      //Check for delimiters, so as to not combine messages.
      if (next != '\r' && next != ' ')
      {

The response from the motor has an end of packet marker, and yet you still have your fingers crossed hoping that if you simply wait long enough between characters you'll get a complete response in time.

And, if the motor responds quickly, you still twiddle your thumbs between reading each character.

AND, you want to talk to the motor faster. Are you going to be listening faster? Will you be twiddling the unnecessary delays, too?

Fredjikrang:
I am trying to talk with an Animatics Smart Motor over serial, ...

Perhaps a link to this motor?

PaulS:

  while(!Serial.available() && i < 1000000)

{i++;}



How long do you think it will take the Arduino to count to 1,000,000? This is NOT how to wait a reasonable time for a response.



while (Serial.available() > 0 && !done)
    {
      delay(10);
      char next;
      next = char(Serial.read());
      //Check for delimiters, so as to not combine messages.
      if (next != '\r' && next != ' ')
      {



The response from the motor has an end of packet marker, and yet you still have your fingers crossed hoping that if you simply wait long enough between characters you'll get a complete response in time.

And, if the motor responds quickly, you still twiddle your thumbs between reading each character.

AND, you want to talk to the motor faster. Are you going to be listening faster? Will you be twiddling the unnecessary delays, too?

Paul, I appreciate help, but to be honest, your tone is a bit abrasive here. I'm open to criticism and am more than willing to admit that I am not an experienced coder. I'm glad that you can recognize some of the problems with my code, but if you could explain it to me, perhaps with a suggestion of another way to do it, I would appreciate it. As is, I'm not really sure what I am doing wrong.

To answer your first question, I expect it to take between 1/16 and 1/4 second to count to 1,000,000, based on the clock speed of the ATMega 328 and its ability to handle simple operations in very few cycles. I originally had a delay(10) there, but I figured that a more elegant solution would be to poll the serial port, so that I don't end up waiting needlessly. The counting is just to kick it out if it doesn't get a response in order to avoid an infinite loop. Unless there is some error elsewhere in the code or with the communications with the motor I don't expect to wait anywhere near that long.

For the next part I'm not really sure what you are suggesting. I'm guessing that you are referencing the delay(10). I added that after some significant testing since the function wasn't stable without it, probably because I wasn't previously checking to see if Serial.available() returned true. Would it be better if I did the following?

if(Serial.available())
{
  while(!done)
    {
       char next;
      next = char(Serial.read());
      //Check for delimiters, so as to not combine messages.
      if (next != '\r' && next != ' ')
      {
        motorStatus += next;
      }
      else{done = true;}
    }
}

Well, I guess that I don't actually have to check that Serial.available() again, since I just did a couple of lines above. But is that the basic idea of what you are saying?

I just noticed an error with the example code I posted above. I believe that I need to check if Serial.read()==-1, which is the error code. If it does then I can skip the rest of the code and read again. So maybe

char temp;
do{temp == Serial.read();}
while(temp == -1);

Would that do it?

Would that do it?

No, for two reasons. The first is the == in the middle line. That is the comparison operator, not the assignment operator.

Second is that function simply blocks.

Back you your previous reply. How would YOU detect that a certain amount of time had passed? You wouldn't just count to 1 million, would you? Certainly not using an int which can't hold 1,000,000. You'd look at your watch (millis()) and note what time you started waiting. Then, you'd periodically check to see if you had waited long enough.

In the second section, you are checking for an end of packet marker, yet you have an interminable wait before reading each character and checking if it was the end of packet marker.

You need to use something more like this:

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

to read all the data as fast as possible. Only when you KNOW that the end of packet marker has arrived should you do anything. No blocking, no waiting, no need for a timeout, but, no action until the end of the packet arrives.