Servo and Serial Command Issue

Obligatory Novice Statement

I am trying to control a Servo linked to arduino Uno using the serial interface. When I give an input ‘m’ I want the servo to turn one direction. When I give an input ‘n’ I want it to go the other direction.

My issue is that once the servo begins in one direction, it will continue on until it gets to the maximum condition set out in the for loop. I need that it stops when the condition is no longer met. (‘n’ will override ‘m’ and begin in the opposite direction.

I have tried using various do/while loops, for loops, and if statements. I have also tried breaking out of the while loops. None of these seem

Here is the code:

//move a servo to a position using serial inputs

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

//Define servo as a control object
Servo xServo;      

//define servo pin
int xServoPin = 9; 

//declare variables

char motionCommand;   //this variable stores serial input data
int pos = 90;       //this variable is used in for loops to move servo


void setup(){

Serial.begin(9600);          //begin Serial communication
xServo.attach(xServoPin);    //attach servo
xServo.write(pos);            //servo in 'middle' position
}

void loop(){
 
  
  while (Serial.available() > 0){
   
  motionCommand = Serial.read(); 
 
  
if (motionCommand == 'm'){
  for (pos; pos<180; pos ++){
  xServo.write(pos);
  delay(50);
  }
}
  
  
if  (motionCommand == 'n'){
 for (pos; pos>0; pos--) {
  xServo.write(pos);
  delay(50); 
 }
   }

  if (motionCommand == 'o'){
  xServo.write(90);}
  
  
  }
}

I apologize if this is unclear. I have searched the forums and the web for advice and have taken many different approaches but can’t seem to figure this one out.

if (motionCommand == 'm')
{
  for (pos; pos<180; pos ++)
  {
  xServo.write(pos);
  delay(50);
  }
}

once the servo begins in one direction, it will continue on until it gets to the maximum condition set out in the for loop.

No surprise really because of the for loop that will always run to completion.

I need that it stops when the condition is no longer met.

You need to restructure the program so that on receipt of a relevant character the servo moves a little then checks whether the input has changed. If so then obey that new command else carry on doing what was previously commanded.

start of loop()
  if serial available
    read serial
  end if
  
  if serial character equals m
    move one step in the m direction
    wait a while
  end if
  else
  if serial character equals n
    move one step in the n direction
    wait a while
  end if
  else 
    stop
  end else
end of loop()

Note that using delay will make the input unresponsive so you would be better off using millis() for timing. You will also need to add checks so that the servo does not move past its limits at either end.

The use of a while inside the loop is quite useless because loop will be looped anyway. A part of any C program is the main function; while writing code for an Arduino, it is hidden. It looks something like

void main()
{
  setup();
  while(true)
  {
    loop();
  }
}

The main code first calls the setup function and next calls the the loop function in an endless while loop. So every time the loop function ends, the code returns to the main function which calls the loop function again.

You can replace your while loop by a simple if statement

void loop()
{
  if (Serial.available() > 0) {
    motionCommand = Serial.read();
  }

  ...
  ...
}

As your 'motionCommand' has global scope, it will be remembered.

Next you must get rid of your for loops. Your 'pos' also has global scope so it will be remembered as well when the loop() finishes.

You can use something like below

    if (motionCommand == 'm' && pos < 180)
    {
        xServo.write(pos++);
        delay(50);
    }

The full loop code will be

void loop() {

  if (Serial.available() > 0)
  {
    motionCommand = Serial.read();
  }

  if (motionCommand == 'm')
  {
    if ( pos < 180)
    {
      // update pos and write updated value to servo
      xServo.write(++pos);
      delay(50);
    }
  }

  if (motionCommand == 'n')
  {
    if ( pos > 0)
    {
      // update pos and write updated value to servo
      xServo.write(--pos);
      delay(50);
    }
  }

  if (motionCommand == 'o')
  {
    xServo.write(90);
  }
}

The code now checks if data is available and if so, stores it.
It continues with the checks for 'm', 'n' and 'o' and executes e.g. the code related to the 'm' command. This checks if the max position is reached and if not writes a new position to the servo and increments the the servo position.
Now the loop is finished, returns to main and main will call loop again.

This way your loop function will stay responsive. Next step can be to replace delay() by a function that simulates the delay; do a search for blink without delay

Notes:
1)
The code can write 0 and 180 to the servo; not sure if that is the intention. If not, replace --pos and ++pos by pos-- and pos++ respectively.
2)
Code not tested

//Edit
Reading UKHeliBob's reply:
I might have missed the fact that you don't want the servo to go to its end position but actually step each time it receives a command. In that case, move the if(motionCommmand == ...) blocks inside the if(Serial.available > 0) block.

void loop() {

  if (Serial.available() > 0)
  {
    motionCommand = Serial.read();

    if (motionCommand == 'm')
    {
      if ( pos < 180)
      {
        xServo.write(++pos);
        delay(50);
      }
    }

    if (motionCommand == 'n')
    {
      if ( pos > 0)
      {
        xServo.write(--pos);
        delay(50);
      }
    }

    if (motionCommand == 'o')
    {
      xServo.write(90);
    }
  }
}

Some test code from the past (model fire truck ladder operation) that may be similar to your project. You might modify to use a received character instead of a button press.

//zoomkat servo button sweep test 12-23-2013
// Powering a servo from the arduino usually *DOES NOT WORK*.

#include <Servo.h>
int button1 = 6; //button pin, connect to ground to move servo
int press1 = 0;
int button2 = 5; //button pin, connect to ground to move servo
int press2 = 0;
Servo servo1;
Servo servo2;
int extend = 5; // variable to store and set the servo position 
int lift = 5; // variable to store and set the servo position 

void setup()
{
  Serial.begin(9600);
  pinMode(button1, INPUT); //out and down
  pinMode(button2, INPUT); //up and in
  servo1.attach(7); //extend servo
  servo2.attach(8); //lift  servo
  servo1.write(extend); //starting position
  servo2.write(lift); //starting position
  digitalWrite(6, HIGH); //enable pullups to make pin high
  digitalWrite(5, HIGH); //enable pullups to make pin high
  Serial.println("servo button sweep test 12-23-2013");
}
void loop()
{
  press1 = digitalRead(button1);
  if (press1 == LOW)
  {  
    if (extend<175) 
      {  
          extend=(extend+1);
          if(extend>180) extend=180; //limit upper value
          Serial.println(extend); //for serial monitor debug
          servo1.write(extend); // tell servo to go to position in variable 'extend' 
          delay(100); // waits 100ms to slow servo movement   
      }        
    else
      {
      
          lift=(lift+1);
          if(lift>180) lift=180; //limit upper value
          Serial.println(lift); //for serial monitor debug
          servo2.write(lift); // tell servo to go to position in variable 'lift' 
          delay(100); // waits 100ms to slow servo movement   
      }
  }          
press2 = digitalRead(button2);
  if (press2 == LOW)
  {   
    if (lift>5) 
      {  
          lift=(lift-1);
          if(lift<0) lift=0; //limit upper value
          Serial.println(lift); //for serial monitor debug
          servo2.write(lift); // tell servo to go to position in variable 'lift' 
          delay(100); // waits 100ms to slow servo movement 
        
      }  
    else
      {
          extend=(extend-1);
          if(extend<0) extend=0; //limit upper value
          Serial.println(extend); //for serial monitor debug
          servo1.write(extend); // tell servo to go to position in variable 'extend' 
          delay(100); // waits 100ms to slow servo movement 
      }
   }     
}

Wow you guys are great!

I tried UKHeliBob's approach initially and I think I have the servo working as it should. In arduino's serial monitor, if i hit 'm' and enter, the servo will move one position (meaning I would have to hit it 180 times for the full range of motion). But, I ran it in terminal, and it allowed me to move the servo completely while the key was held down. I imagine I can do something similar via Processing. This is exactly what I was looking for.

I wonder if I just didn't notice it was moving when I tried the if statements before I posted. Or more likely, my code just wasn't very good.

Also, thank you for the pointers on the while/for loops. I misunderstood how they work, and assumed that the code would exit the each time to recheck the criteria.

I am in the process of switching over to using millis() rather than delay. This is a sketch that is going in a larger sketch, and I tried to replace delay with millis() and it left a few bugs in the code. When I get it working it is a much better approach.

Thank you for all of your help. The forum is an incredible resource.

if i hit 'm' and enter, the servo will move one position

Probably because you have a line ending defined in the Serial monitor. The program will receive them, detect that they are neither m or n and stop the servo. Set the line ending to "no line ending" and try again.

But, I ran it in terminal, and it allowed me to move the servo completely while the key was held down. I imagine I can do something similar via Processing. This is exactly what I was looking for.

If you have a terminal program that will send repeated characters when a key is pressed, the below approach might work.

// zoomkat 3-28-14 serial servo incremental test code
// using serial monitor type a character (s to increase or a 
// to decrease) and enter to change servo position 
// (two hands required, one for letter entry and one for enter key)
// use strings like 90x or 1500x for new servo position 
// for IDE 1.0.5 and later
// Powering a servo from the arduino usually *DOES NOT WORK*.

#include<Servo.h>
String readString;
Servo myservo;
int pos=1500; //~neutral value for continuous rotation servo
//int pos=90;

void setup()
{
  myservo.attach(7, 400, 2600); //servo control pin, and range if desired
  Serial.begin(9600);
  Serial.println("serial servo incremental test code");
  Serial.println("type a character (s to increase or a to decrease)");
  Serial.println("and enter to change servo position");
  Serial.println("use strings like 90x or 1500x for new servo position");
  Serial.println();
}

void loop()
{
  while (Serial.available()) {
    char c = Serial.read();  //gets one byte from serial buffer
    readString += c; //makes the string readString
    delay(2);  //slow looping to allow buffer to fill with next character
  }
  if (readString.length() >0) {
    if(readString.indexOf('x') >0) { 
      pos = readString.toInt();
    }

    if(readString =="a"){
      (pos=pos-1); //use larger numbers for larger increments
      if(pos<0) (pos=0); //prevent negative number
    }
    if (readString =="s"){
      (pos=pos+1);
    }

    if(pos >= 400) //determine servo write method
    {
      Serial.println(pos);
      myservo.writeMicroseconds(pos);
    }
    else
    {   
      Serial.println(pos);
      myservo.write(pos); 
    }
  }
  readString=""; //empty for next input
}