Slow down servo when returning to position 0

Greetings! I'm writing a program to control shutters and fans in my greenhouse using a NANO board. It works as expected except for one thing: When the temperature exceeds the set value, the servo opens nice and slow -- but when it falls, the servo instantly slams back to position zero! The speed control is based on the "SWEEP" example. I'm wondering if it's a local/global variable issue? For testing purposes, the analog input (temperature) is set from the wiper of a pot across 3.3V, and the output pins simply light LEDs, except for a small servo controlled by pin 9 from the board. Can anyone spot why the servo closes so abruptly? Thanks in advance?

here's the entire code:

/*By  CL 2/20/24 
This program reads sensor input (from pot for testing) and can output:
 -- signals to H-bridge, runs motor fwd/rev when specified values are reached
 -- servo control to open/close shutter when specified values are reached
 -- signal to turn fan on/off when specified values are reaced
*/

#include <Servo.h>       //include servo library
Servo shutterServo;      //create a servo object
int pos;                 //variable stores servo position
int maxPos = 90;         //set maximum angle of servo turn
bool a = LOW, b = HIGH;  //'flag variables' for state(?)
bool c = LOW, d = HIGH;

//connect 10K pot terminals to +3.3v & ground, and wiper to A0
int sensorPin = A0;       //  input pin for the potentiometer
int sensorValue;          // variable to store the value coming from the sensor
int enablePin = 13;       // the pin to enable motor (= onboard LED)
int ledPinA = 3;          // the pin for LED A (H-bridge input 1)
int ledPinB = 4;          // the pin for LED B (H-bridge input 2)
int fanPin = 6;           // to turn on fan
int motorRunTime = 5000;  //time motor will be enabled; or use millis()
int highTemp = 80;        // set temp to open shutters
int lowTemp = 75;         // set temp to close shutters
int highFan = 85;         //set temp to turn on fan
int lowFan = 80;          //set temp to turn off fan

long previousMillis = 0;  //for time counting
long interval = 2000;     //Read sensor each 2 seconds

void setup() {
  
  pos = 0;              // set servo position at 0 to start or after reset
  shutterServo.attach(9);   //set control pin for servo
  shutterServo.write(pos);  //start at position 0
  delay(1000);              //give time to reach 0 if not there already (e.g power fail)

  // initialize digital pins for output, analog input
  pinMode(enablePin, OUTPUT);
  pinMode(ledPinA, OUTPUT);
  pinMode(ledPinB, OUTPUT);
  pinMode(fanPin, OUTPUT);
  pinMode(A0, INPUT);

  //use serial console for debugging
  Serial.begin(9600);  //open serial connection
  Serial.println("welcome to the thermostat control");
  Serial.print("the high temp (SHUTTER OPEN) is set at ");
  Serial.println(highTemp);
  Serial.print("and the low temp (SHUTTER CLOSE) is set at ");
  Serial.println(lowTemp);
  Serial.print("the servo will turn ");
  Serial.print(maxPos);
  Serial.println(" degrees");
  Serial.print("the high fan temp (FAN ON) is set at ");
  Serial.println(highFan);
  Serial.print("and the low fan temp (FAN OFF) is set at ");
  Serial.println(lowFan);
}

void loop() {

  unsigned long currentMillis = millis();          //time elapsed
  if (currentMillis - previousMillis >= interval)  //if elapsed time longer than interval
  {
    previousMillis = currentMillis;                 //"Last time is now": reset current time, then do:
    int sensorRead = analogRead(sensorPin);         // read the value from the sensor:
    sensorValue = map(sensorRead, 0, 760, 0, 100);  //test: max raw sensor reading = 760
    Serial.print("sensor value is: ");
    Serial.println(sensorValue);

    if (sensorValue >= highTemp && a == LOW)  //if low temp went above 'high' e.g. 80 degrees
    {
      Serial.println("activating hitemp subroutine - shutters open");
      hitemp();
    }

    else if (sensorValue <= lowTemp && b == LOW)  //if hi temp went below 'low' e.g 75 degrees
    {
      Serial.println("activating lotemp subroutine - shutters close");
      lotemp();
    }

    else if (sensorValue >= highFan && c == LOW)  //if low temp went above 'fan on' e.g.85 deg
    {
      Serial.println("activating hifan subroutine - fan on");
      hifan();
    }

    else if (sensorValue <= lowFan && d == LOW)  //if low temp went below 'fan off' e.g.80 deg
    {
      Serial.println("activating lofan subroutine - fan off");
      lofan();
    }
  }
}

//subroutines for high & low temps

void hitemp() {
  if (sensorValue >= highTemp)  //check if it's needed; stops it running on restart
  {
    digitalWrite(ledPinA, HIGH);    //start motor rotation first direction (H-bridge)
    digitalWrite(ledPinB, LOW);     //or use as high temp indicator
    digitalWrite(enablePin, HIGH);  //enable power to motor, if used
    a = HIGH;                       //note: initial & "lotemp" states: a= LOW, b= HIGH
    b = LOW;

    Serial.print("position was:");
    Serial.println(pos);

    for (pos = 0; pos <= maxPos; pos += 1) {  //goes from 0 to max degrees @ 1 deg steps
      shutterServo.write(pos);                // tell servo to go to position in variable 'pos'
      delay(15);                              // waits 15 ms for the servo to reach each position
    }
    Serial.print("new position is:");
    Serial.println(pos);

    delay(motorRunTime);           // motor enabled for set time, if used; otherwise delay
    digitalWrite(enablePin, LOW);  //then turn motor off
  }
}

void lotemp() {
  if (sensorValue <= lowTemp)  //check if it's needed; stops it running on restart
  {
    digitalWrite(ledPinA, LOW);     //start motor rotation opposite direction (H-bridge)
    digitalWrite(ledPinB, HIGH);    //or use as low temp indicator
    digitalWrite(enablePin, HIGH);  //enable motor, if used
    a = LOW;                        //note: when in "hitemp" state: a= HIGH, b= LOW
    b = HIGH;

    Serial.print("position was:");
    Serial.println(pos);

    for (pos = maxPos; pos >= 1; pos -= 1){  // goes from max to 0 degrees @ 1 degree steps
      shutterServo.write(pos);  // tell servo to go to position in variable 'pos'
      delay(15);                // waits 15 ms for the servo to reach each position
    }
    Serial.print("new position is:");
    Serial.println(pos);

    delay(motorRunTime);           // motor enabled for set time, if used; otherwise delay
    digitalWrite(enablePin, LOW);  //then turn motor off
  }
}

void hifan() {
  if (sensorValue >= highFan)  //check if it's needed; stops it running on restart
  {
    digitalWrite(fanPin, HIGH);  //turn fan pin on
    c = HIGH;                    //note: initial & "loFan" states: c= LOW, d= HIGH
    d = LOW;
  }
}

void lofan() {
  if (sensorValue <= lowFan)  //check if it's needed; stops it running on restart
  {
    digitalWrite(fanPin, LOW);  //turn fan pin off
    c = LOW;                    //note: in 'hifan' state, c= HIGH, d= LOW
    d = HIGH;
  }
}

this code

will do the sweep as you describe.

if you see the servo instantly slamming back to position zero then I wonder if your arduino did not just reboot as your setup() has

➜ do you see the welcome text appearing again in the serial monitor ?

can you share more info on your circuit and how the servo is powered?

The code appears to be OK for slow servo movement in both directions

What happens if you make the delay()s in the for loops larger, say 100 miliseconds instead of 15 ?

Does the servo have to work harder to open the shutter than when closing them, which may slow it down ?

How is the servo powered ?

Thanks for your reply!
The servo is powered by an external 5v 2A source which also feeds the arduino (5V pin).
The serial monitor does not show the "welcome" text, so does not appear to be rebooting.

The servo is not connected to any load, just a bench test setup.
I will try increasing the delay. Puzzling because it OPENS noticeable slowly!

then add some debug trace in your for loop

    for (pos = maxPos; pos >= 1; pos -= 1){  // goes from max to 0 degrees @ 1 degree steps
      shutterServo.write(pos);  // tell servo to go to position in variable 'pos'
      Serial.print("moving to:"); Serial.println(pos); 
      delay(15);                // waits 15 ms for the servo to reach each position
    }

and check what's going on

yes I'll try that as well, thanks!

It's not a fatal flaw, I just don't understand why it doesn't move the same speed in both directions...

Well spank my *** and call me Sally.
Increasing the delay on "low temp" to 100 mS made the servo return to 0 quite slowly.
Increasing to just 20 mS had the desired effect.
Not sure why 15 mS didn't seem to work... just a servo glitch?
Thanks for your help, it is much appreciated

Hello Sally

The servo should move at the same speed in either direction given the same set of commands and the same delay() between them. Does it behave symmetrically when you run the Sweep example ?

does the servo require less torque when closing ?

Apparently not

Sorry about delayed reply!
There's no torque on the servo in this case, it's just a bench test for now.
I wouldn't say this one is a "top shelf" servo, just a hobbyist type.
It did move symmetrically back/forth in the "sweep" example.

If it's not a programming issue, I'm wondering if perhaps there's something in the particular 15mS timing that interferes with the servo's expected signal... Since when I use 20 mS for the "close" subroutine it seems to work perfectly, and the slight difference in speed open/close isn't perceptible to me.

I didn't read everything in this discussion so maybe I'm late to the party. But, if you want to control a servo's speed? There's a library for this exact thing that is also non-blocking.

have a look and see if you like it.

-jim lee

1 Like

There is also the varSpeedServo library that can be useful if you want acceleration. It's no longer maintained but works OK on ATMEGA


the issue at hand is why

    for (pos = 0; pos <= maxPos; pos += 1) {  //goes from 0 to max degrees @ 1 deg steps
      shutterServo.write(pos);                // tell servo to go to position in variable 'pos'
      delay(15);                              // waits 15 ms for the servo to reach each position
    }

works fine and why

    for (pos = maxPos; pos >= 1; pos -= 1){  // goes from max to 0 degrees @ 1 degree steps
      shutterServo.write(pos);  // tell servo to go to position in variable 'pos'
      delay(15);                // waits 15 ms for the servo to reach each position
    }

would not work symmetrically

I create a simple wokwi simulation with just the servo and a pot

and it does work there fine

I'd be temped to say there is something fishy in the circuit may be

Thanks! I'll definitely check that out.
For now, changing the servo step delay to 20 mS seems to have solved the problem.

Yes, the problem doesn't seem to be the code itself. Could be the combination of the specific step delay and the particular servo -- or maybe the benchtop setup? Thanks for your input!

  1. Check the temperatures in your main loop (or in one checkTheTemp() function), not in both main loop() and again in functions hitemp() and lotemp().
  2. Your flags in hitemp() or lotemp() are probably left in the wrong state, causing the race condition resulting in "slams shut" Use only one flag to indicate temperature is out of range (HIGH will mean "temp too high" AND "temp too low"), using the temperature test to pick the correct function.

there is no possible "slams shut"

there are only 3 places in the code that was shared where the servo is asked to go some place

in the setup() where it's really being asked to go to 0 directly

  pos = 0;              // set servo position at 0 to start or after reset
  shutterServo.attach(9);   //set control pin for servo
  shutterServo.write(pos);  //start at position 0

and in the hitemp() and lotemp() functions where it's part of a for loop iterating in a blocking way between two constant min/max values

    for (pos = 0; pos <= maxPos; pos += 1) {  //goes from 0 to max degrees @ 1 deg steps
      shutterServo.write(pos);                // tell servo to go to position in variable 'pos'
      delay(15);                              // waits 15 ms for the servo to reach each position
    }

and

    for (pos = maxPos; pos >= 1; pos -= 1) { // goes from max to 0 degrees @ 1 degree steps
      shutterServo.write(pos);  // tell servo to go to position in variable 'pos'
      delay(15);                // waits 15 ms for the servo to reach each position
    }

The obvious suspect would have been that the arduino was rebooting and the setup() executed again, but OP already ruled out and confirmed it was not the case

he also said

so there is something else at play here and it can't come from a wrong state causing the race condition because even if the code is faulty (I did not check for the correctness of the thinking there), the for loop still goes step by step with a 15ms delay.

@J-M-L

?

I'm waiting.