Starting an action at a certain moment

Hello,

I'm trying to programme 5 servos for an animatronic hand to make some movements, since I have to exhibit the hando for a few days, but I'm having problems with programing the code so each servo starts moving at a certain moment.

The movement I have in mind woud be a "wave" with the fingers, so after a few milliseconds the first finger has move, the second stars moving, and so on until the last finger. For this I have been researching how to code using millis(), guideUsing millis() for timing. A beginners guide, the Demonstration code for several things at the same time, and some more, I also found this other post, in which they intended to do something similar to mine, but I didn't understood the code, and it wasn't exactly what I needed.

I understand that I have to define the action (move the servor, from certain position to another position with a certain speed) first, and then compare the time counter (currentMillis = millis() ) with a variable that I have specified so the action happens at that moment, but I dont know how to do this.

Here is the code I have so far, I just tried with one servo for now

#include <Servo.h> 

Servo servo1;
int pos1 = 0;
int increment = 1;
unsigned long int lastUpdate1 = 0;
unsigned long int updateInterval1 = 15;

unsigned long int StartFinger1 = 1500;
unsigned long int Duration1 = 2000;
unsigned long int currentMillis;


void setup() {
  // put your setup code here, to run once:
Serial.begin(9600);
currentMillis = millis();
servo1.attach(11);
}

void finger1 () {

  if((currentMillis - lastUpdate1) > updateInterval1 && pos1 <= 180) {  // time to update
      lastUpdate1 = currentMillis;
      pos1 += increment;
      servo1.write(pos1);
      Serial.println(pos1);
      
  }
}
  
void loop() {
  // put your main code here, to run repeatedly:
  Serial.print(currentMillis);
while (currentMillis - StartFinger1 >= 0) {
  Serial.print("finger 1 has started ");
  finger1();
  
}
}

Sorry if I didn't explain myself properly, and thanks in advance for any help or advice

Maybe I did not undesrtood you, but if I did and you just wanto to add a pause between each servo, yu could use "delay" which is just a pause betwwen one action and another, it works like this
delay(MilisecondsToWait);
So, for example, if you put like
delay(1000); //Here you are waiting 1 second
I do not think you were refering to this, I hope you were, but if you were not, so sorry

while (currentMillis - StartFinger1 >= 0) will always be true, even when currentMillis is less than StartFinger, unsigned integer math overflow (underflow?) will result in a very large positive number so in all likelihood that will get optimized out. (Example: if using byte, 0 - 1 = 255)

If you want to repeat the finger movement, just copy your finger1() to the other fingers and fire them sequentially. The servo command operates like an on/off switch, so if you want all fingers moving simultaneously, you need to get a bit tricky with partial moves first to one finger, then the next, etc. With proper timing, you can make them appear to be moving independently and at different rates.

To get the best control, dispense with the library and study how to operate PWM with registers so you can adjust the timing of the ramps and have the hardware perform in the background. For good info on that, have a look here.

As the code below demonstrates, you don't yet understand how to use millis() for timing.

currentMillis is never updated in either loop() or finger1().

void loop() {
  // put your main code here, to run repeatedly:
  Serial.print(currentMillis);
while (currentMillis - StartFinger1 >= 0) {
  Serial.print("finger 1 has started ");
  finger1();
 
}

Please study the https://www.baldengineer.com/blink-without-delay-explained.html tutorial.

The loop() will never work how it is written, one reason is that the variable currentMillis is set in setup() and never changes. I think you want something like this:

void loop() {

  // read current time
  currentMillis = millis();

  // time to start finger?
  if ((currentMillis  - lastUpdate1) >= StartFinger1) {

    // start finger
    Serial.println("finger 1 has started ");
    finger1();

    // record last update time
    lastUpdate1 = currentMillis;
  }
}

I don't think you need the interval check in finger1() and am absolutely certain you don't need this line:

lastUpdate1 = lastUpdate1;

I find it easier to think through my logic if I write comments.

You're starting down a road you don't want to travel on. Instead of having variables named StartFinger1, Duration1, etc you should start using arrays (File => Examples => Control = > Arrays).

For example, you could define your start times and last start times, as well as the number of fingers as:

const int NUM_FINGERS = 1;
unsigned long startFingerTimes[] = {1500UL};
unsigned long lastUpdate[] = {0UL};

Then in loop() you would generalize the code to look at each finger and index into the proper values.

void loop() {

  // check each finger
  for (int fingerIdx = 0; fingerIdx < NUM_FINGERS; fingerIdx++)
  {

    // read current time
    currentMillis = millis();

    // time to start finger?
    if ((currentMillis  - lastUpdate[fingerIdx]) >= startFingerTimes[fingerIdx]) {

      // start finger
      Serial.print("finger"); Serial.print(fingerIdx + 1); Serial.println("  has started ");
      finger(fingerIdx);

      // record last update time
      lastUpdate[fingerIdx] = currentMillis;
    }

  }
}

Note that the function finger1() turns to just finger(), the index of the finger being moved is passed so it can access any data specific to the finger being moved.

When you're ready to add the next finger you only have to increment the value of NUM_FINGERS and add the finger's values to the arrays.

Thanks to all for answering, I have now understood much better how to do this.

I have applied most of the tips that @Blue_Eyes proposed and the code now looks like this:

#include <Servo.h> 

Servo servo1;
int increment = 1;

unsigned long int updateInterval1 = 15;

const int NumFingers = 5;
unsigned long int startFingerTimes[] = {1500UL, 3000UL, 4500UL, 6000UL, 7500UL};
unsigned long int lastUpdate[] = {0UL, 0UL, 0UL, 0UL, 0UL};
int pos[] = {0, 0, 0, 0, 0};
unsigned long int currentMillis;
unsigned long int fingerIdx;


void setup() {
  // put your setup code here, to run once:
Serial.begin(9600);
servo1.attach(11);
}


  

void loop() {

  // check each finger
  for (fingerIdx = 0; fingerIdx < NumFingers; fingerIdx++)
  {

    // read current time
    currentMillis = millis();

    // time to start finger?
    if ((currentMillis  - lastUpdate[fingerIdx]) >= startFingerTimes[fingerIdx]) {

      // start finger
      Serial.print("finger"); Serial.print(fingerIdx + 1); Serial.println("  has started ");
      
      if((currentMillis - lastUpdate[fingerIdx]) > updateInterval1 && pos[fingerIdx] <= 180) {  // time to update
        lastUpdate[fingerIdx] = currentMillis;
        pos[fingerIdx] += increment;
        servo1.write(pos[fingerIdx]);
        Serial.println(pos[fingerIdx]);
      }
      
      // record last update time
      lastUpdate[fingerIdx] = currentMillis;
    }

  }
}

I had to put the action of moving the servo directly in the loop() since I had an error message saying that there were too many imputs or somethig like that (sorry, i lost the actual error).

Now I have another problem which I don't understand, I think is easier to explain it with what I get in the Monitor Serie:

finger1 has started
1
finger1 has started
2
finger2 has started
1
finger1 has started
3
finger3 has started
1
finger4 has started
1
finger1 has started
4
finger2 has started
2
finger5 has started
1
finger1 has started
5
finger1 has started
6
finger2 has started
3
finger3 has started
2
finger1 has started
7
finger4 has started
2
finger1 has started
8
finger2 has started
4
finger1 has started
9
finger3 has started
3
finger5 has started
2
finger1 has started
10

The order in which the servos are activated is not the correct one, and I don't understand why, anyone have any idea?

Thank you

Try this, and note how it is shorter and simpler

        // time to start finger?
    if ((currentMillis  - lastUpdate[fingerIdx]) >= startFingerTimes[fingerIdx]) {
      lastUpdate[fingerIdx] += startFingerTimes[fingerIdx];
         // start finger
      Serial.print("finger"); Serial.print(fingerIdx + 1); Serial.println("  has started ");
 
       pos[fingerIdx] += increment;
         if(pos[fingerIdx] > 180) {  
           pos[fingerIdx] = 180;
         }
        servo1.write(pos[fingerIdx]);
        Serial.println(pos[fingerIdx]);
    }

...R

Thanks, it completly looks more tidy, but still have the same results, also the positon of the servos only change every 1500 millisecond. I think its a problem of the order of the "for" and the "if"s but i cant find a way of making it work

also the position of the servos only change every 1500 millisecond.

That is exactly what you programmed.

unsigned long int startFingerTimes[] = {1500UL, 3000UL, 4500UL, 6000UL, 7500UL};
...
        // time to start finger?
    if ((currentMillis  - lastUpdate[fingerIdx]) >= startFingerTimes[fingerIdx]) {
      lastUpdate[fingerIdx] += startFingerTimes[fingerIdx];

If you want to do something else, the program logic will have to change accordingly. That last line sets a new start time 1500 ms in the future. Use a much smaller increment if you want the servo to keep moving until it reaches the limit.

For a procedure as complicated as this, it would be much easier to program a state machine, using a state variable to keep track of current joint activity. Google will find state machine tutorials.

typhus94:
but still have the same results, also the positon of the servos only change every 1500 millisecond.

Please post the complete revised program.

...R