Serial text command to pan tilt servos

How can I get and process a text command while saving the servos orientation?

If I don't put a delay(1000) in the main loop, the printSerialString() function won't do some math I need correctly. Instead of 1+700, it returns something like 695. Odd. At startup, I also get something like -4895. These functions work fine in code without the servo code.

Some background, I want to control my servos through text commands sent through a com port. I need to wait for a line and then parse it. The first letter indicates (P)an or (T)ilt. The next 3 characters are a 3-digit degrees number I want to orient the pan and tilt servos from. For example P090, (Pan 90 degrees), or T045 (Tilt 45 degrees)

With the delay I assume that I won't be able to keep the servos refreshed with their current positions. I need this because I want to send the absolute position and have them go there.

So, how can I get the Arduino (Duemileuouv) to keep the servos refreshed while waiting for a text string from which to process a new command and orient the servos?

At this point I'm completely lost (and this Arduino stuff is new to me as of a few days ago)

Any other improvements to code greatly appreciated! Hopefully the end result would be useful to others too.

Thanks! Code below hopefullly.

// SERIAL PORT VARS

int  serIn;             // var that will hold the bytes-in read from the serialBuffer
char serInString[100];  // array that will hold the different bytes  100=100characters;
                        // -> you must state how long the array will be else it won't work.
int  serInIndx  = 0;    // index of serInString[] in which to insert the next incoming byte
int  serOutIndx = 0;    // index of the outgoing serInString[] array;

// SERVO VARS

char panval[2];
char tiltval[2];
int icount = 0;
long pval = 0;
long tval = 0;

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

int pulsePanWidth = 0;    // Amount to pulse the servo
int pulseTiltWidth = 0;    // Amount to pulse the servo

long lastPanPulse = 0;    // the time in millisecs of the last pulse
long lastTiltPulse = 0;    // the time in millisecs of the last pulse
int refreshTime = 20;  // the time in millisecs needed in between pulses

int minPulse = 700;   // minimum pulse width

void setup() {
  pinMode(servoPanPin, OUTPUT);  // Set servo pin as an output pin
  pinMode(servoTiltPin, OUTPUT);  // Set servo pin as an output pin
 // pulsePanWidth = minPulse;      // Set the motor position to the minimum
 // pulseTiltWidth = minPulse;      // Set the motor position to the minimum
  Serial.begin(9600);         // connect to the serial port
  Serial.println("servos_ready");
}

void loop() {

  //read the serial port and create a string out of what you read
  //readSerialString(serInString, serInIndx);
  readSerialString();
  
  //try to print out collected information. it will do it only if there actually is some info.
  printSerialString();
 
//  updatePanServo();   // update pan servo position
//  updateTiltServo();   // update tilt servo position

  //delay(1000);

}

// called every loop(). 
// uses global variables servoPi, pulsewidth, lastPulse, & refreshTime
void updatePanServo() {
  // pulse the servo again if rhe refresh time (20 ms) have passed:
  if (millis() - lastPanPulse >= refreshTime) {
    digitalWrite(servoPanPin, HIGH);   // Turn the motor on
    delayMicroseconds(pulsePanWidth);  // Length of the pulse sets the motor position
    digitalWrite(servoPanPin, LOW);    // Turn the motor off
    lastPanPulse = millis();           // save the time of the last pulse
  }
}

// called every loop(). 
// uses global variables servoPi, pulsewidth, lastPulse, & refreshTime
void updateTiltServo() {
  // pulse the servo again if rhe refresh time (20 ms) have passed:
  if (millis() - lastTiltPulse >= refreshTime) {
    digitalWrite(servoPanPin, HIGH);   // Turn the motor on
    delayMicroseconds(pulseTiltWidth);  // Length of the pulse sets the motor position
    digitalWrite(servoTiltPin, LOW);    // Turn the motor off
    lastTiltPulse = millis();           // save the time of the last pulse
  }
}

//read a string from the serial and store it in an array
//this func uses globally set variable so it's not so reusable
//I need to find the right syntax to be able to pass to the function 2 parameters:
// the stringArray and (eventually) the index count
void readSerialString () {
    int sb;   
    if(Serial.available()) { 
       //Serial.print("reading Serial String: ");     //optional confirmation
       while (Serial.available()){ 
          sb = Serial.read();             
          serInString[serInIndx] = sb;
          serInIndx++;
          //serialWrite(sb);                        //optional confirmation
       }
       //Serial.println();
    }  
}
 
//print the string all in one time
//this func as well uses global variables
void printSerialString() {
   if( serInIndx > 0) {
      Serial.print("Command: ");     
      //loop through all bytes in the array and print them out
      for(serOutIndx=0; serOutIndx < serInIndx; serOutIndx++) {
          Serial.print( serInString[serOutIndx] );    //print out the byte at the specified index
          //serInString[serOutIndx] = "";            //optional: flush out the content
      }
      
      Serial.println();

      if(serInString[0] == 'P')
      {
      Serial.print("Pan!");
   
      for(icount=0; icount<3; icount++)
      {
        panval[icount] = serInString[icount+1];
      }  
      
      pval = stringToNumber(panval, 3); 
      pval = pval + 700;
      pulsePanWidth = pval;
      Serial.println();
      Serial.print("Value to pan servo: ");
      Serial.print(pval, DEC);
      pval = 0;
      icount = 0;
      
      //updatePanServo();

      }
  
      if(serInString[0] == 'T')
      {
      Serial.print("Tilt!");
     
      for(icount=0; icount<3; icount++)
      {
        tiltval[icount] = serInString[icount+1];
      }  

      tval = stringToNumber(tiltval, 3); 
      tval = tval + 700;
      pulseTiltWidth = tval;
      Serial.println();
      Serial.print("Value to tilt servo: ");
      Serial.print(tval, DEC);
      tval = 0;
      icount = 0;     
     
      //updateTiltServo();
      
      }      
      
      //reset all the functions to be able to fill the string back with content
      serOutIndx = 0;
      serInIndx  = 0;
      Serial.println();
      
   }
 
}

/*
  THANKS tigoe.net for string to number functions!

  This method converts a string if ASCII numbers to a decimal number.
 There's no error checking in it, so it can return mistakes
 if you give it non-numeric ASCII.
 When I wrote this program, the standard C lib was not part of
 Arduino, so I couldn't use atoi().
 */
long stringToNumber(char thisString[], int length) 
{
  int thisChar = 0;
  long value = 0;

  for (thisChar = length-1; thisChar >=0; thisChar--) 
  {
    char thisByte = thisString[thisChar] - 48;
    value = value + powerOfTen(thisByte, (length-1)-thisChar);
  }
  return value;
}

/*
  This method takes a number between 0 and 9,
 and multiplies it by ten raised to a second number.
 */

long powerOfTen(char digit, int power) {
  long val = 1;
  if (power == 0) {
    return digit;
  }
  else {
    for (int i = power; i >=1 ; i--) {
      val = 10 * val;
    }
    return digit * val;
  }
}

There is a Servo library distributed with Arduino that will do the refreshing for you (its handled by timer hardware on the Arduino controller chip)

See: Servo - Arduino Reference

Here is an example, it expects an angle using one to three digits followed by a letter indicating the servo to command.

25t writes 25 to the tilt servo, 160p writes 160 to the pan servo

#include <Servo.h> 

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

int pos = 0;                 // position

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
    else if(ch == 'p' || ch == 'P')  // pan
    {
      panServo.write(pos);          
      Serial.print("Pan Servo angle: "); 
      Serial.println(pos); 
      pos = 0;
    }
    else if(ch == 't' || ch == 'T')  // tilt
    {
      tiltServo.write(pos);        
      Serial.print("Tilt Servo angle: "); 
      Serial.println(pos); 
      pos = 0;
    }   
  }
}

Mem, THANKS for your help. I've put in the servo library functions as you suggested.

But I'm still hung up on why I need delays while waiting for a string to process. . Anyway, without a delay(500) on the Serial.read it seems the Arduino processes further instructions without waiting for the vars to be fully set. I don't know how else to explain it.

Is there a way to get rid of the delay?

I got the following code to work thanks to Nigel's post on the string to number. But I still wonder if there's a more elegant way of taking any text string and converting it to a number. Here I know I'll have 3 digits so hard coding works.

#include <Servo.h>
Servo panServo;
Servo tiltServo;

// SERIAL PORT VARS

int  serIn;             // var that will hold the bytes-in read from the serialBuffer
char serInString[80];  // array that will hold command string (though we'll only need length 3)
int serInIndx = 0;

// SERVO VARS

int icount = 0;
long pval = 0;
long tval = 0;

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

void setup() {
 
  Serial.begin(9600);         // connect to the serial port

  panServo.attach(servoPanPin);  
  tiltServo.attach(servoTiltPin);  
  
  Serial.println("servos_ready");
}

void loop() {

  //read the serial port and create a string out of what you read
  //readSerialString(serInString, serInIndx);
  readSerialString();
  
  //try to print out collected information. it will do it only if there actually is some info.
  printSerialString();

}

void readSerialString () {
    int sb = 0;   
    if(Serial.available()) { 
       // Do while we don't have a line feed or carriage return
       while (Serial.available()){ 
          sb = Serial.read();    
         delay(500);  // why must I have this or pval and tval don't update properly?       
          
          if(sb == 10) // line feed
          {
            break;
          }
          
          serInString[serInIndx] = sb;
          serInIndx++;
       }
    }  
}
 
//print the string all in one time
//this func as well uses global variables
void printSerialString() {
   if( serInIndx > 0) {
     int serOutIndx;
      Serial.print("Command: ");     
      //loop through all bytes in the array and print them out
      for(serOutIndx=0; serOutIndx < serInIndx; serOutIndx++) {
          Serial.print( serInString[serOutIndx] );    //print out the byte at the specified index
      }

      if(serInString[0] == 'P') // first char should be P or T
      {
      Serial.print("Pan!");
   
        // Nigel Method
        pval += (serInString[1]-48)*100;
        pval += (serInString[2]-48)*10;
        pval += (serInString[3]-48)*1;
  
      Serial.println();
      Serial.print("Value to pan servo: ");
      Serial.print(pval, DEC);
      
      panServo.write(pval);      

      }
  
      if(serInString[0] == 'T')
      {
      Serial.print("Tilt!");
   
      tval += (serInString[1]-48)*100;
      tval += (serInString[2]-48)*10;
      tval += (serInString[3]-48)*1;
  
      Serial.println();
      Serial.print("Value to tilt servo: ");
      Serial.print(tval, DEC);
      
      tiltServo.write(tval);       
      
      }      
      
      Serial.println();
      // Re-init
      serInIndx = 0; 
      icount = 0;
      pval = 0;
      tval = 0 ;
      
      
   }
 
}
  1. Wait for string to be terminated by a line feed or character return
char command[30];
int index = 0;
while(Serial.available() > 0)
{
    byte inByte = Serial.read();
    if(inByte == '\n' || inByte == '\r')
        break;

    // Do something with the character just read...
    command[index] = inByte;
    index++;
    command[index] = '\0';
}
  1. Parse out the first letter P or T
if(index > 0)
{
    if(command[0] == 't' || command[0] == 'T')
    {
        // Deal with a T
    }
    if(command[0] == 'p' || command[0] == 'P')
    {
        // Deal with a P
    }
}
  1. Create a number from the next 3 characters (050 would be 50)

Replace the first letter in command with a space.

command[0] = ' ';

Then, pass the string to atoi, which returns an integer.

  1. Direct the servos to that position.

You know how to do this.

THANKS! Got the atoi function working. So now the only thing bugging me is the need for the delay in the serial reading. I changed to your coding but still needed to insert a delay(500) after each read or it sends strange values to the servos.

The code I posted does not need a delay :wink:

It may help if you echo the incoming characters so you can see what is coming in.

If that doesn't help, post the latest version of your sketch.

I'm now wondering if the way I'm integrating the servo library is creating the problem.

o. When the servos are called the pval and tvals remain 0.
o. When I comment out the servo calls pval and tval get the right values.

If I put a delay in the serial.read pval and tval get the right values and the servos position properly. I tried putting delays before after the servo calls but that didn't work either.

#include <Servo.h>
Servo panServo;
Servo tiltServo;

// SERIAL PORT VARS

int  serIn;             // var that will hold the bytes-in read from the serialBuffer
char serInString[80];  // array that will hold command string (though we'll only need length 3)
int serInIndx = 0;

// SERVO VARS

int icount = 0;
long pval = 0;
long tval = 0;

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

void setup() {
 
  Serial.begin(9600);         // connect to the serial port

  panServo.attach(servoPanPin);  
  tiltServo.attach(servoTiltPin);  
  
  Serial.println("servos_ready");
}

void loop() {

  //read the serial port and create a string out of what you read
  //readSerialString(serInString, serInIndx);
  readSerialString();
  
  //try to print out collected information. it will do it only if there actually is some info.
  printSerialString();

}

void readSerialString () {

  while(Serial.available() > 0)
  {
    byte inByte = Serial.read();
   delay(500);
    if(inByte == '\n' || inByte == '\r')
          break;
  
    // Do something with the character just read...
    serInString[serInIndx]  = inByte;
    serInIndx++;
    serInString[serInIndx]= '\0';
  }
}


 
//print the string all in one time
//this func as well uses global variables
void printSerialString() {
   if( serInIndx > 0) {
     int serOutIndx;
      Serial.print("Command: ");     
      //loop through all bytes in the array and print them out
      for(serOutIndx=0; serOutIndx < serInIndx; serOutIndx++) {
          Serial.print( serInString[serOutIndx] );    //print out the byte at the specified index
      }
      
     
      if(serInString[0] == 'P') // first char should be P or T
      {
      Serial.print("Pan!");
   
        serInString[0] = ' ';
        pval = atoi(serInString);
  
      Serial.println();
      Serial.print("Value to pan servo: ");
      Serial.print(pval, DEC);
      

      panServo.write(pval);      

      }
  
      if(serInString[0] == 'T')
      {
      Serial.print("Tilt!");
   
      serInString[0] = ' ';
      tval = atoi(serInString);
  
  
      Serial.println();
      Serial.print("Value to tilt servo: ");
      Serial.print(tval, DEC);
      
      tiltServo.write(tval);       
      
      }      
      
      Serial.println();
      // Re-init
      serInIndx = 0; 
      icount = 0;
      pval = 0;
      tval = 0 ;
      
      
   }
 
}

I modified your read function and loop as follows and it seems to work for me without any delay

void loop(){
  if(readSerialString())  
     printSerialString();
}

boolean readSerialString () {

  while(Serial.available() > 0)
  {
    byte inByte = Serial.read();
   //delay(500);
    if(inByte == '\n' || inByte == '\r')
          return true;
  
    // Do something with the character just read...
    serInString[serInIndx]  = inByte;
    serInIndx++;
    serInString[serInIndx]= '\0';
  }
  return false;
}

Or even:

void loop(){
  readSerialString();  
  printSerialString();
}

void readSerialString () {
  while(1) {
     if (Serial.avaliable ()) {    
       byte inByte = Serial.read();
       if(inByte == '\n' || inByte == '\r')
          return;
  
       // Do something with the character just read...
       serInString[serInIndx]  = inByte;
       serInIndx++;
       serInString[serInIndx]= '\0';
    }
  }
}

Unfortunately, both present another problem. printSerialString() never seems to run. I type into the serial port and nothing is returned. In the first example, when I had it output characters they were all boxes.

#include <Servo.h>
Servo panServo;
Servo tiltServo;

// SERIAL PORT VARS

int  serIn;             // var that will hold the bytes-in read from the serialBuffer
char serInString[80];  // array that will hold command string (though we'll only need length 3)
int serInIndx = 0;

// SERVO VARS

int icount = 0;
long pval = 0;
long tval = 0;

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

void setup() {
 
  Serial.begin(9600);         // connect to the serial port

  panServo.attach(servoPanPin);  
  tiltServo.attach(servoTiltPin);  
  
  Serial.println("servos_ready");
}

void loop() {
  readSerialString();  
  printSerialString();
}

void readSerialString () {
  while(1) {
     if(Serial.available())
     {    
       byte inByte = Serial.read();
       if(inByte == '\n' || inByte == '\r')
          return;
  
       // Do something with the character just read...
       serInString[serInIndx]  = inByte;
       serInIndx++;
       serInString[serInIndx]= '\0';
    }
  }
}


 
//print the string all in one time
//this func as well uses global variables
void printSerialString() {
   if( serInIndx > 0) {
     int serOutIndx;
      Serial.print("Command: ");     
      //loop through all bytes in the array and print them out
      for(serOutIndx=0; serOutIndx < serInIndx; serOutIndx++) {
          Serial.print( serInString[serOutIndx] );    //print out the byte at the specified index
      }
      
     
      if(serInString[0] == 'P') // first char should be P or T
      {
      Serial.print("Pan!");
   
        serInString[0] = ' ';
        pval = atoi(serInString);
  
      Serial.println();
      Serial.print("Value to pan servo: ");
      Serial.print(pval, DEC);
      

      panServo.write(pval);      

      }
  
      if(serInString[0] == 'T')
      {
      Serial.print("Tilt!");
   
      serInString[0] = ' ';
      tval = atoi(serInString);
  
  
      Serial.println();
      Serial.print("Value to tilt servo: ");
      Serial.print(tval, DEC);
      
      tiltServo.write(tval);       
      
      }      
      
      Serial.println();
      // Re-init
      serInIndx = 0; 
      icount = 0;
      pval = 0;
      tval = 0 ;
      
      
   }
 
}

What output do you get if you try the sketch posted in reply#1?

void readSerialString ()
{
  [glow]while(1) // Forever...[/glow]
  {
     if(Serial.available())
     {    
       byte inByte = Serial.read();
       if(inByte == '\n' || inByte == '\r')
          return;
  
       // Do something with the character just read...
       serInString[serInIndx]  = inByte;
       serInIndx++;
       serInString[serInIndx]= '\0';
    }
  }
}

This explains why the function never ends...

The function should return with a \n or \r (but my guess is that neither are being sent)

Duh. Got to stop multi-tasking while posting...

I put the sketch back to Mem's idea (while we wait on other "multitasking" :wink: )

I get 4 boxes from this in the serial read function
Serial.print(serInString[serInIndx], BYTE);

But the printSerialString seems to be never called.

are you sure the baud rate in serial begin matches the serial monitor?

I think you could be right about not transmitting CR/LF.
The original would have had a delay of 1/2 second, more than enough time to read a Txxx or Pxxx and return (via the "while" not the "break") without even seeing/needing a CR/LF.

void readSerialString () {

  while(Serial.available() > 0)  // We've got the first char
  {
    byte inByte = Serial.read(); // read it.
   delay(500);  // the other chars arrive during this time
    if(inByte == '\n' || inByte == '\r')  // eventually(!), we'll empty the buffer and the "while" will exit.
          break;  // but we may never get here!
  
    // Do something with the character just read...
    serInString[serInIndx]  = inByte;
    serInIndx++;
    serInString[serInIndx]= '\0';
  }
}

I checked the serial matching in both and they are the same. I made it higher but it just got the boxes back higher. If lower, they got garbled.

I put a delay in but just got the boxes one at a time slowly.

Went to other code, and yes, with delay works, without doesn't.

Can you put a print on the "break" condition, to prove you're seeing a CR/LF?

but just got the boxes one at a time slowly.

I don't understand - what "boxes"?

Hi Groove, I think the boxes are from scrambled/incomplete values going over the com port. I get similar type characters when I slow the baud down on the serial monitor. I can't paste them here. but they look like [] only completely enclosed.

Just saw the break thing. I've tried to put the delay there but that didn't work. I'll try what you say now