Servo: how to hold position at a certain degree and then go back to the original state?

Hi, I’m making a project where I need to move a servo in three different degrees, the sequence is:
Put the servo at 0 degrees
Move slowly the servo to 5 degrees and hold that position for 3 seconds
Go back to 0 degrees
Move slowly to 10 degrees and hold for 3 secs
Go back to 0 degrees
Move slowly to 15 degrees
Go back to 0 degrees

The problem is that I can’t use the delay function because while the servo does that, other sensors must keep working. What shall I use? Millis function? Because the only millis functions I have seen are for blinking leds but I don’t know how to apply it to my problem

Hi @marcoselpana,

Could you post your code?

You should post code by using code-tags
There is an automatic function for doing this in the Arduino-IDE
just three steps

  1. press Ctrl-T for autoformatting your code
  2. do a rightclick with the mouse and choose "copy for forum"
  3. paste clipboard into write-window of a posting

best regards Stefan

You can use millis() to time a period whether it is to control an LED, servo or anything else

When the period start action occurs, such as moving a servo, save the value of millis() as the period start time. Then, each time through loop() check whether the required period has elapsed between the start time and the current value of millis(). If so, then move on in the code, if not then keep going round loop() reading inputs etc until the period elapses

See Using millis() for timing. A beginners guide, Several things at the same time and the BlinkWithoutDelay example in the IDE

Very similar topic here

Ridiculous.


#include <LiquidCrystal_I2C.h>

#include <Wire.h>

#include <Servo.h>

double servo2;

LiquidCrystal_I2C lcd(0x27,16,2);
Servo servo_4;

Servo servo_5;



void setup()
{

servo_4.attach(4);
servo_5.attach(5);

  servo_4.write(72);
  servo_5.write(0);

}


void loop()
{
  for (servo2 = 72; servo2 >= 60; servo2=servo2-2) {
    servo_4.write(servo2);
    delay(100);
  }
  servo_5.write(45);
  delay(2000);
  servo_5.write(0);
  delay(100);
  for (servo2 = 60; servo2 <= 72; servo2=servo2+2) {
    servo_4.write(servo2);
    delay(100);
  }
  delay(3000);
  for (servo2 = 72; servo2 >= 52; servo2=servo2-2) {
    servo_4.write(servo2);
    delay(100);
  }
  servo_5.write(45);
  delay(2000);
  servo_5.write(0);
  delay(100);
  for (servo2 = 52; servo2 <= 72; servo2=servo2+2) {
    servo_4.write(servo2);
    delay(100);
  }
  delay(3000);
  for (servo2 = 72; servo2 >= 44; servo2=servo2-2) {
    servo_4.write(servo2);
    delay(100);
  }
  servo_5.write(45);
  delay(2000);
  servo_5.write(0);
  delay(100);
  for (servo2 = 44; servo2 <= 72; servo2=servo2+2) {
    servo_4.write(servo2);
    delay(100);
  }
}

Sorry but I still haven’t figured it out

Hi,

You can use a increment counter such as:

int delayNumber = 5000;
int increment = 0;

void setup(){}

void loop(){
increment++;
if (increment == delayNumber){
  increment = 0;
  //servo code here
}

There just can't be any delays at all in the loop. Also, the larger the "delayNumber", the larger the error becomes (5000ms could be more like 5100ms).

1 Like

Hi marco,

EDIT: initiated by user kgray9:

There is a pretty precise "counter" that is incremented automatically.
The function is called millis(). It increments by 1 each millisecond.
https://www.arduino.cc/reference/en/language/functions/time/millis/

marco: the functionality that you want to achieve all in all is medium advanced.
This requires medium advanced programming techniques.
It will take quite some hours to fully understand these programming-techniques.

There are two things:

  1. non-blocking timing based on millis();
  2. programming a state-machine

first step understanding non-blocking timing
as an everyday example with easy to follow numbers
delay() is blocking. As long as the delay is "delaying" nothing else of the code can be executed.
Now there is a technique of non-blocking timing.
The basic principle of non-blocking timing is fundamental different from using delay()

You have to understand the difference first and then look into the code.

otherwise you might try to "see" a "delay-analog-thing" in the millis()-code which it really isn't
Trying to see a "delay-analog-thing" in millis() makes it hard to understand millis()
Having understood the basic principle of non-blocking timing based on millis() makes it easy to understand.

imagine baking a frosted pizza
the cover says for preparation heat up oven to 200°C
then put pizza in.
Baking time 10 minutes

You are estimating heating up needs 3 minutes
You take a look onto your watch it is 13:02 (snapshot of time)
You start reading the newspaper and from time to time looking onto your watch
watch shows 13:02. 13:02 - 13:02 = 0 minutes passed by not yet time
watch shows 13:03. 13:03 - 13:02 = 1 minute passed by not yet time
watch shows 13:04. 13:04 - 13:02 = 2 minutes passed by not yet time

watch shows 13:05 when did I start 13:02? OK 13:05 - 13:02 = 3 minutes time to put pizza into the oven

New basetime 13:05 (the snapshot of time)
watch 13:06 not yet time
watch 13:07 not yet time
watch 13:08 not yet time (13:08 - 13:05 = 3 minutes is less than 10 minutes
watch 13:09 not yet time
watch 13:10 not yet time
watch 13:11 not yet time
watch 13:12 not yet time
watch 13:13 not yet time
watch 13:14 not yet time (13:14 - 13:05 = 9 minutes is less than 10 minutes
watch 13:15 when did I start 13:05 OK 13:15 - 13:05 = 10 minutes time to eat pizza (yum yum)

You did a repeated comparing how much time has passed by
This is what non-blocking timing does

In the code looking at "How much time has passed by" is done

currentTime - startTime >= bakingTime

bakingTime is 10 minutes

13:06 - 13:05 = 1 minute >= bakingTime is false
13:07 - 13:05 = 2 minutes >= bakingTime is false
...
13:14 - 13:05 = 9 minutes >= bakingTime is false
13:15 - 13:05 = 10 minutes >= bakingTime is TRUE time for timed action!!

So your loop() is doing

void loop()
// doing all kinds of stuff like reading the newspaper

if (currentTime - previousTime >= period) {
previousTime = currentTime; // first thing to do is updating the snapshot of time
// time for timed action
}

it has to be coded exactly this way because in this way it manages the rollover from Max back to zero
of the function millis() automatically

second thing is a state-machine
your servos don't have to move around all in the same microsecond.
There is a sequence of actions
each "next" step starts after a certain condition is met

  • Move slowly the servo to 5 degrees

if (3 seconds have passed by)

  • Go back to 0 degrees (fast?)

then

  • Move slowly to 10 degrees

if( 3 secs has passed by)
Go back to 0 degrees (fast?)

then

  • Move slowly to 15 degrees
    if (15 degress are reached)
    Go back to 0 degrees (fast)

Do you recognize the non-blocking timing?

best regards Stefan

1 Like

jumping from action to action is done by another variable that is set to different values. For this you use a switch case -structure

I have a demo-code for this in this thread

and what is very useful for analysing is two "macros" that reduce typing-work for the debug-output

which I have posted here

This is quite a lot stuff and it is very normal that you will have questions about details or how to adapt these demo-codes to your needs.
So post your best attempt of how you tried to code it and ask specific questions.
best regards Stefan

2 Likes

Hi @StefanL38,

I didn't know that. Thanks for the info!

#include <Servo.h>

const byte ServoPin = 7;
Servo servo;

const unsigned long MillisecondsPerStep = 250; // four degrees per second

void setup()
{
  servo.attach(ServoPin);
}

void loop()
{
  ServoStateMachine();

  // Other stuff goes here
}

void ServoStateMachine()
{
  static byte state = 0;
  static byte position = 0;
  static unsigned long timerStart;

  switch (state)
  {
  case 0: // Put the servo at 0 degrees
    position = 0;
    servo.write(position);
    timerStart = millis();
    state++;
    break;

  case 1: // Move slowly the servo to 5 degrees
    if (millis() - timerStart >= MillisecondsPerStep)
    {
      timerStart = millis();
      position++;
      servo.write(position);
    }

    if (position == 5) // Arrived
    {
      timerStart = millis();
      state++;
    }
    break;

  case 2: // and hold that position for 3 seconds
    if (millis() - timerStart >= 3000)
    {
      // Go back to 0 degrees
      position = 0;
      servo.write(position);
      timerStart = millis();
      state++;
    }
    break;

  case 3:  // Move slowly to 10 degrees...
    if (millis() - timerStart >= MillisecondsPerStep)
    {
      timerStart = millis();
      position++;
      servo.write(position);
    }

    if (position == 10) // Arrived
    {
      timerStart = millis();
      state++;
    }
    break;

  case 4: // ...and hold for 3 seconds
    if (millis() - timerStart >= 3000)
    {
      // Go back to 0 degrees
      position = 0;
      servo.write(position);
      timerStart = millis();
      state++;
    }
    break;

  case 5:  // Move slowly to 15 degrees
    if (millis() - timerStart >= MillisecondsPerStep)
    {
      timerStart = millis();
      position++;
      servo.write(position);
    }

    if (position == 15) // Arrived
    {
      timerStart = millis();
      state = 0;  // back to the beginning
    }
    break;
  }
}
1 Like

@johnwasser:

Why did you not use constants with self-explaining names for the different states?
best regards Stefan

A: I was in a hurry (it was near my bedtime)
B: When using "state++" for going to the 'next state' it is easier to understand if the states are numbered rather than named.

I will usually use an enum if I have more time. Especially if the state diagram is less sequential.

As always, if you think my sketch could have been written better, feel free to use it as the basis for your own example sketch.

Thanks so much! Can you check your private messages?:smiling_face_with_three_hearts:

Questions from private message:

WHICH '0 degrees'. You have one at the beginning, one at 5 degrees (after holding for 3 seconds), one at 10 degrees (after holding for 3 seconds), and one at 15 degrees.

Where you want the second servo to move, put in a servo2.write() with the desired position.

Do you mean you wanted to do this and not what you said you wanted?

Move to 0 degrees
Move slowly to 5 degrees
Hold that position for 3 seconds
Move to 0 degrees
Hold that position for 3 seconds
Move slowly to 10 degrees
Hold that position for 3 seconds
Move to 0 degrees
Hold that position for 3 seconds
Move slowly to 15 degrees
Hold that position for 3 seconds
Move to 0 degrees
Hold that position for 3 seconds

Look at how the "Hold that position for 3 seconds" states were done. Add a new "Hold that position for 3 seconds" state after each of the "Move to 0 degrees" states. Re-number the states to be consecutive.

You'd have to show your new code for anyone to know what you did wrong.

2 Likes

Ok I’ll try doing that and I’ll show you if it doesn’t work

Thanks a lot for your answers, you helped me a lot to understand all this difficul (for me) concepts🥰

I want to post a comment here:

John you posted a demo-code that did almost what the TO described.
The TO looked at your example and was asking for additional steps.

My impression is: The TO asked without thinking much about the example-code.
IMHO the example was too close to the final solution to make the TO really analyse
what is going on in the code. John you served an "almost" solution that IMHO
prevented the TO from seriously analyzing the code.

IMHO If you want to support a newcomer you have to add explanation in normal words to make it easier to understand what is going on in the code. The combination of a description in normal words of a certain functionality combined with the code that realises this functionality and an explanation which part of the code does what enables to transfer the underlying principle to other similar cases through the TO herself/himself. And then she/he has learned it really.
And by showing how to visualise what is going on through debug-output shows a tool for self-analysing the behaviour of the code.

My own posts did not follow these rules in all details. Indeed it is much more work than just writing down the code.

best regards Stefan

2 Likes