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

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

What I EXACTLY (sorry for explaining badly yesterday) is:

Servo1 Move to 0 degrees
Servo2 Move to 0 degrees
Move servo1 slowly to 5 degrees
Move servo2 to 45 degrees
Hold that position for 3 seconds
Move servo1 slowly to 0 degrees
Move servo2 to 0 degrees
Hold that position for 3 seconds
Move servo1 slowly to 10 degrees
Move servo2 to 45 degrees
Hold that position for 3 seconds
Move servo1 slowly to 0 degrees
Move servo2 to 0 degrees
Hold that position for 3 seconds
Move servo1 slowly to 15 degrees
Move servo2 to 45 degrees
Hold that position for 3 seconds
Move servo1 slowly to 0 degrees
Move servo2 to 0 degrees
Hold that position for 3 seconds

Look, I tried adding JUST the part where de second servo moves to 0 degrees and 45 degrees and when I compile it, it doesn’t work, it gets stuck at only moving from 0 degrees to 5 degrees and viceversa.


#include <Servo.h>

const byte ServoPin = 4;

Servo servo4;
Servo servo5;



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


void setup()

{

  servo4.attach(ServoPin);
  servo5.attach(5);


}


void loop()

{

  ServoStateMachine();


  // Other stuff goes here

}


void ServoStateMachine()

{

  static byte state = 0;

  static byte position = 72;    //In my servo 72º=0º, 60º=5º, 52º=10º and 44º=15º, that's why I changed it


  static unsigned long timerStart;


  switch (state)

  {

  case 0: // Put the servo at 0 degrees

    position = 72;

    servo4.write(position);
    
    servo5.write(0);

    
    timerStart = millis();

    state++;

    break;


  case 1: // Move slowly the servo to 5 degrees

    if (millis() - timerStart >= MillisecondsPerStep)

    {

      timerStart = millis();

      position--;

      servo4.write(position);


    }


    if (position == 60) // Arrived

    {
      
      timerStart = millis();

      servo5.write(45);


      state++;

    }

    break;


  case 2: // and hold that position for 3 seconds

    if (millis() - timerStart >= 3000)

    {

      // Go back to 0 degrees

      position = 72;

      servo4.write(position);

      servo5.write(0);


      timerStart = millis();

      state++;

    }

    break;


  case 3:  // Move slowly to 10 degrees...

    if (millis() - timerStart >= MillisecondsPerStep)

    {

      timerStart = millis();

      position--;

      servo4.write(position);

    }


    if (position == 52) // Arrived

    {
      
      timerStart = millis();
      
      servo5.write(45);


      state++;

    }

    break;


  case 4: // ...and hold for 3 seconds

    if (millis() - timerStart >= 3000)

    {

      // Go back to 0 degrees

      position = 72;

      servo4.write(position);

      servo5.write(0);


      timerStart = millis();

      state++;

    }

    break;


  case 5:  // Move slowly to 15 degrees

    if (millis() - timerStart >= MillisecondsPerStep)

    {

      timerStart = millis();

      position--;

      servo4.write(position);

    }


    if (position == 44) // Arrived

    {

      timerStart = millis();

      servo5.write(45);


      state = 0;  // back to the beginning

    }

    break;

  }

}

So this means there is a sequence of moving two servos sometimes slowly sometimes fast
If servos have reached the target position hold this position for a defined period of time.

and this basic pattern is repeated multiple times.

As soon as you have understood how to code

  • moving a servo slowly

  • making your code wait a defined interval and go on with the next step when this interval of time has passed by

  • programming a state-machine (which realises this step by step-sequence)

you will have learned how to code it. With having this know-how it is just a matter or a question of diligence to code a sequence with hundreds or thousands of such steps.

Posting the sequence to me looks like a hidden request saying
"can you post the ready to use code that does this sequence?"

NO !

I won't post such a code. I'm teaching how to fish and how to cook.
I don't serve ready to eat fish-meals.

As soon as you start posting an attempt how the code might look like and ask a specific question you have my support.

EDIT:

well done posting your attempt trying to code it. Let me analyse it and I will post again...
best regards Stefan

So here is a code-version with some additional things that enable debugging
I have added two macros that print to the serial monitor which makes visible what the code is doing

I moved the variables state and position to be global

open the serial monitor adjust baud to 115200
activate the check box autoscroll
activate the check box show timestamp

If the programm has run down the state-machine two times
DE-activate checkbox autoscroll or unplug the arduino
then analyse the lines printed in the serial monitor to see which steps were
executed and which values variable state and position have.

Very old programmer-wisdom:

a program does always what you have programmed. If the program does something different than you expected you don't yet fully understand what you have programmed.

#define dbg(myFixedText, variableName) \
  Serial.print( F(#myFixedText " "  #variableName"=") ); \
  Serial.println(variableName);
// usage: dbg("1:my fixed text",myVariable);
// myVariable can be any variable or expression that is defined in scope

#define dbgi(myFixedText, variableName,timeInterval) \
  do { \
    static unsigned long intervalStartTime; \
    if ( millis() - intervalStartTime >= timeInterval ){ \
      intervalStartTime = millis(); \
      Serial.print( F(#myFixedText " "  #variableName"=") ); \
      Serial.println(variableName); \
    } \
  } while (false);
// usage: dbgi("2:my fixed text",myVar,myInterval);
// myVar can be any variable or expression that is defined in scope
// myInterval is the time-interval which must pass by before the next
// print is executed

#include <Servo.h>

const byte ServoPin = 4;

Servo servo4;
Servo servo5;

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

// defining variables as GLOBAL
byte state;
byte position;    //In my servo 72º=0º, 60º=5º, 52º=10º and 44º=15º, that's why I changed it


void setup() {
  Serial.begin(115200);
  Serial.println( F("Setup-Start") );
  // initialise variable state with value for start
  state = 0;

  servo4.attach(ServoPin);
  servo5.attach(5);
}


void loop() {
  ServoStateMachine();
  // Other stuff goes here
}


void ServoStateMachine() {

  static unsigned long timerStart;

  dbgi("above switch",state,500);
  switch (state)  {

    case 0: // Put the servo at 0 degrees
      position = 72;    //In my servo 72º=0º, 60º=5º, 52º=10º and 44º=15º, that's why I changed it

      servo4.write(position);
      servo5.write(0);
      dbg("case 0 servo4",position);
      dbg("case 0 servo5 0",0);
  
      timerStart = millis();
      state++; // change to next state with the next call of the function
      break;


    case 1: // Move slowly the servo to 5 degrees

      if (millis() - timerStart >= MillisecondsPerStep) {
        timerStart = millis();
        position--;

        servo4.write(position);
        dbg("case 1 servo4",position);
      }

      if (position == 60) { // Arrived
        dbg("case 1 servo4",position);
        dbg("case 1 servo5 45",0);
        timerStart = millis();
        servo5.write(45);
        state++;
      }
      break;

    case 2: // and hold that position for 3 seconds
      if (millis() - timerStart >= 3000)    {
        // Go back to 0 degrees
        position = 72;
        servo4.write(position);
        servo5.write(0);
        dbg("case 2 servo4",position);
        dbg("case 2 servo5 0",0);

        timerStart = millis();
        state++;
      }
      break;


    case 3:  // Move slowly to 10 degrees...

      if (millis() - timerStart >= MillisecondsPerStep) {
        timerStart = millis();
        position--;
        servo4.write(position);
        dbg("case 3 servo4",position);
      }

      if (position == 52) { // Arrived
        timerStart = millis();
        servo5.write(45);
        dbg("case 3 servo4",position);
        dbg("case 3 servo4 45",0);
        state++;
      }
      break;

    case 4: // ...and hold for 3 seconds

      if (millis() - timerStart >= 3000)    {
        // Go back to 0 degrees
        position = 72;
        servo4.write(position);
        servo5.write(0);
        dbg("case 4 servo4",position);
        dbg("case 4 servo5 0",0);
        timerStart = millis();
        state++;
      }
      break;


    case 5:  // Move slowly to 15 degrees

      if (millis() - timerStart >= MillisecondsPerStep) {
        timerStart = millis();
        position--;
        servo4.write(position);
        dbg("case 5 servo4",position);
      }

      if (position == 44) { // Arrived
        timerStart = millis();
        servo5.write(45);
        dbg("case 5 servo4",position);
        dbg("case 5 servo5 45",0);
        state = 0;  // back to the beginning
      }
      break;

  }

}

best regards Stefan

1 Like