Millis Multiple Servo Sweep

Hello,
i am trying to write a code to control independently and simultaneously three servo. I use them to activate three fire extinguishers. I already made the mechanism and it works as i intended. The setup is a giant Scale Hitec HS 805-BB servo moving a 5:1 aluminium gear. That gear then pull a steel wire that press the lever of the fire extinguisher. The tricky part is that i need to made two type of activation:
the first one is a wave, three servo delayed one to another; and the second one is simply one servo moving but it should hold the 180 position for x time then return to 0 position. The single servo should change in turn.
An example of the rhythm could be: Wave, 15000ms pause, First Servo for 4000ms, 10000ms pause,
Second Servo for 4000ms, 15000ms pause, Third Servo for 2000ms, 8000ms pause, again Third Servo for 2000ms, 6000ms pause, end of the loop.

At the moment I have something like that:

#include <Servo.h> 

class Sweeper
{
  Servo myservo;
  int pos = 0;
  int increment;
  int updateInterval;              // interval between updates
  unsigned long lastUpdate;           // last update of position
  
public:
  Sweeper (int Interval)
  { 
    updateInterval =Interval;
    increment = 1;
  }
void Attach (int pin)
{
  myservo.attach(pin);
}
void Detach (int pin)
{
  myservo.detach();
}
void Update ()
{
  if ((millis() - lastUpdate) > updateInterval)
  {
    lastUpdate = millis();
    pos += increment;
    myservo.write(pos);
    //Serial.println(pos);
    if ((pos >= 180) || (pos <= 0))
    {
      increment = -increment;
    }
   }
  }
 };
Sweeper sweeper1(15);
Sweeper sweeper2(25);


void setup()
{
  //Serial.begin(96000);
  sweeper1.Attach(9);
  sweeper2.Attach(10);
}

void loop()
{
  sweeper1.Update();
  //if (digitalRead(2) ==HIGH)
  { 
    sweeper2.Update();
  }
}

I found this too but is a bit complicated for a rookie like me:
http://www.lamja.com/blogfiles/UNO_20_Servos_Controller.ino

It would help if you write each servo action, and the timing as a series of steps, one on each line like this

servo1 - 180deg
wait 1000
servo1 - 0 deg
servo2 - 180 deg
wait

...R

But this means using delay? I would rather use millis if possible.

EverRedRobot:
But this means using delay? I would rather use millis if possible.

NO NO NO - you are jumping ahead of your self.

I just want you to write down an English language description of what you want to happen. Then we can consider how to convert it to computer code.

...R

Robin, i'm sorry for misunderstanding.
i have tried with that following code with normal delay function so i put it here with some //descriptions that i hope sounds clear enough to you or anyone would like to join us.

servo1.write(180); //this is supposed to be a Wave like effect, servo1 should reach 180° before
servo2.write(180); // servo 2 and so on to servo3. Maybe the following servo can start moving
servo3.write(180); // when the preceding reach 45°? or maybe we can play with speed?
wait(2000); //each servo hold its position for 2"
servo1.write(0); // then they come back to 0°
servo2.write(0);
servo3.write(0);
wait (5000); //nothing move for a while, 5" for example
servo1.write(180); //one servo moves
wait(3000); //it holds position
servo1.write(0); // come back to 0°
wait(10000);
servo2.write(180); // using delay () it doesn't seem possible to start moving the following servo
wait(6000); // while moving another one or doing something else.
servo3.write(180);
wait(6000);
servo2.write(0);
servo3.write(0);
servo1.write(180);
wait(3000);
servo1.write(0);

Normally, if you want a program that responds to inputs promptly, delay is a curse, as you surmise. In this particular case, you should be able to do a pretty good job with delays - you may just need to use more of them. For example, put a brief delay between each of your first three servo.writes to get the wave appearance you're looking for.

You could do this with millis and a state machine, but it would be rather complex.

Another approach would be to create an array of structs. Each element would contain the time in millis, an indicator to tell what servo to act on and the number of degrees to set it to. A flag to indicate whether the action is done might help too

Then the code is simple: note the time when you start and then just iterate through the array looking to see whether any actions that are due haven't been done yet. If they haven't, do them and mark them done.

This way, the command of the servos is all in the data and the actual code that performs the actions can be almost trivial.

Wildbill i agree with that solution of an array but i don't understand yet how to code it.
Can you be more specific?

Something like this:

struct ServoAction 
{
  unsigned long ActionTime;  // Number of milliseconds after sequence starts to do this action
  byte ServoNumber;          // Which servo is it?
  bool ActionDone;           // Is action done yet
  int degrees;               // Number of degrees to send the designated servo to
};

ServoAction Actions[] = {
                          {0,    1,false,180}, // When sequence starts, send servo one to 180 degrees
                          {100,  2,false,180}, // 100ms later, do the same for servo two
                          {200,  3,false,180}, // and servo three
                          {2200, 1,false,0  }  // After another 2000ms send servo one back to 0 
                          /* etc. etc. */
                        };

looks great, i suppose i should then put an index and a for condition in the void loop something like that?

#include <Servo.h> 


struct ServoAction 
{
  unsigned long ActionTime;  // Number of milliseconds after sequence starts to do this action
  byte ServoNumber;          // Which servo is it?
  bool ActionDone;           // Is action done yet
  int degrees;               // Number of degrees to send the designated servo to
};

ServoAction Actions[4] = {
                          {0,    1,false,180}, // When sequence starts, send servo one to 180 degrees
                          {100,  2,false,180}, // 100ms later, do the same for servo two
                          {200,  3,false,180}, // and servo three
                          {2200, 1,false,0  }  // After another 2000ms send servo one back to 0 
                          /* etc. etc. */
                        };
void loop()
{
  int i;
  for (i=0; i<=4;i +=1)
  { 
    digitalWrite(ServoAction Actions[i]);
    }
 }
1 Like

I guess it should like that...

// Multi_Blink.h
//
// Blink lots of LEDs at different frequencies simultaneously
//
// Header file is required to be able to define the structured types
//
#include <Arduino.h>

#ifndef  MULTIBLINKH
#define  MULTIBLINKH

typedef struct
{
  uint8_t  activeVal;     // digital value for this state to be active (HIGH/LOW)
  uint16_t activeTime;    // time to stay active in this state stay in milliseconds 
} stateDef;

typedef struct
{
  uint8_t  ledPin;         // Arduino I/O pin number
  uint8_t  currentState;   // current active state
  stateDef state[2];       // the ON and OFF state definitions. Add more states if required
  uint32_t lastTransTime;  // the 'time' of the last state transition - saves the millis() value
} ledTable;

#endif

// Multi_Blink
//
// Blink lots of LEDs at different frequencies simultaneously
//
// Marco Colli - May 2012
//
// Demonstrates the way to carry out multiple time based tasks without using the delay() function
// Demonstrates the use of structures (and structures within structures)
// Demonstrates a data driven approach to programming to create compact, reusable code
//

#include "Multi_Blink.h"  // type definitions

// Blink Table T - Modify this table to suit whatever the output requirements are 
// Add or delete lines as required to achieve the desired effects.
//
ledTable  T[] =
//Pin  St   State 0      State 1  LastTime
{ 
  { 3, 0, {{HIGH, 300}, {LOW, 300}}, 0 },
  { 4, 1, {{HIGH, 300}, {LOW, 600}}, 0 },
  { 5, 0, {{HIGH, 500}, {LOW, 500}}, 0 },
  { 6, 1, {{HIGH,  50}, {LOW, 100}}, 0 },
  { 7, 0, {{HIGH, 100}, {LOW,  50}}, 0 },
  { 8, 1, {{HIGH, 500}, {LOW, 500}}, 0 },
  { 9, 0, {{HIGH, 400}, {LOW, 600}}, 0 },
  {10, 0, {{HIGH, 600}, {LOW, 400}}, 0 },
};

// Self adjusting constants for loop indexes
#define  MAX_STATE  (sizeof(T[0].state)/sizeof(stateDef))
#define  MAX_LED    (sizeof(T)/sizeof(ledTable))

void setup()
{
  for (int i=0; i < MAX_LED; i++)
  {
    pinMode(T[i].ledPin, OUTPUT);

    T[i].lastTransTime = millis();
    digitalWrite(T[i].ledPin, T[i].state[T[i].currentState].activeVal);
  }
}

void loop()
{
  for (int i=0; i < MAX_LED; i++)
  {
    // check if the state active time has expired (ie, it is less than current time)
    if (millis() >= T[i].lastTransTime + T[i].state[T[i].currentState].activeTime)
    {
      // switch to the next state with wrapround
      T[i].currentState = (T[i].currentState + 1) % MAX_STATE;

      // write out the next state value
      T[i].lastTransTime = millis();
      digitalWrite(T[i].ledPin, T[i].state[T[i].currentState].activeVal);
    }
  }
}

Well, a combination of the two, yes. You'll need the millis check from your example, although you should probably be checking how long it is since you triggered the sequence rather than what millis itself gives you - the length of time the arduino has been running.

You'll also need to check whether an action has already been completed.

Are you ok if you don't mind can we chat some moments on the Arduino IRC? I have some stupid questions that you can easily answer.

EverRedRobot:
Robin, i'm sorry for misunderstanding.

servo1.write(180); //this is supposed to be a Wave like effect, servo1 should reach 180° before
servo2.write(180); // servo 2 and so on to servo3. Maybe the following servo can start moving

There is still some misunderstanding.

What you have written looks too much like code and that makes it confusing. I suggested writing in English what you WANT to happen (and NOT how you think it should be achieved)

A description of what you WANT to happen will never have a comment like "using delay () it doesn't seem possible to start moving the following servo"

When you have a clear description of what you WANT to happen it will be much much easier to write code to achieve that. Without a clear description the process is not unlike the infinite number of monkeys trying to type the works of Shakespeare.

If (as seems likely) the delay() function will get in the way you can use millis() to manage timing without blocking as illustrated in several things at a time.

...R

EverRedRobot:
Are you ok if you don't mind can we chat some moments on the Arduino IRC? I have some stupid questions that you can easily answer.

Not something I use I'm afraid.

Robin, this is a good example of the sequence desired:
One servo moves forward, wait sometime and moves backward.
Nothing happens for sometimes.
The second servo moves forward, wait sometime and moves backward.
Nothing happens for sometimes.
Third servo moves forward, wait sometime and moves backward.
Nothing happens for sometimes.
One servo moves forward, when it has completed half of its movement the second servo start moving forward. Before the second servo has completed its movement the third servo start moving forward.
After that the third servo has reached 180°, all three servos moves backward.

I hope Shakespeare will not rise from his grave to bite me.

On the hardware side make sure you are powering the servos from a separate power supply and not thru the arduino. Also make sure the arduino ground and the servo power supply ground are connected together.

Everything seem fairly simple until you get to the point where the servos move together. Then you need to use the concept in the servo sweep example from the Arduino IDE. You need three variables to keep track of the servo positions and move all 3 servos with

servo1.write(servo1Pos);
servo2.write(servo2Pos);
servo3.write(servo3Pos);

and update the position values according to your requirement.

In fact the entire program can be series of steps that update the servo positions as the value of millis() progresses.

At this stage I think it will be easier to help you if you make an attempt at writing the program and post your code.

...R

Ok friends, yesterday i tried my code with delay function and don't know why but it was a complete Anarchy. I checked the grounds and electrical connections but nothing wrong there.
Today i wrote a very simple code based on blink with no delay and sweep and suddenly I get something satisfying. Servos reacts to orders. Before entering into the details of how to integrate a structure of array into this, there is one more thing i'd like to discuss.
As you can see, if there is no voltage on input pin 5 the program send all servos to 180° and then detach them. I will use this condition to apply a presence sensor and a manual on/off button.The fact is that when i cut the voltage on pin 5, the loop goes running until each servo has finished it's turn and then it stop. Why doesn't shut off immediately?

Again, i really would like to use a struct of array but i seriously need more help.
This is my first project and at the end of the week i am going to use this code for an art show,
so friends, please do your best...

#include <Servo.h> 

 
Servo servo1;            // create servo object to control a servo 
Servo servo2;            // twelve servo objects can be created on most boards
Servo servo3;   
int pos1 = 0;        // variables to store the servo position 
int pos2 = 0;
int pos3 = 0;
int buttonPin = 5;
int buttonVal = 0;
 

//Time period of blinks in milliseconds 
const unsigned long servo1Interval = 10000;
const unsigned long servo2Interval = 8000;
const unsigned long servo3Interval = 3500;



//Variable holding the timer value so far.One for each "Timer"
unsigned long servo1Timer;
unsigned long servo2Timer;
unsigned long servo3Timer;

void setup() 
{ 
  Serial.begin(9600);
  servo1.attach(9);        // attaches the servo on pin 9 to the servo object 
  servo2.attach(8);       // attaches the servo on pin 8 to the servo object
  servo3.attach(7);       // attaches the servo on pin 7 to the servo object
  servo1Timer = servo2Timer = servo3Timer = millis();
}

void toggleServo1()
{
  for (pos1 = 0; pos1 <=180; pos1 += 1)
   {
    servo1.write(pos1);
    delay(15);
   }
  for (pos1 = 180; pos1 >=90; pos1 -= 1)
   {
     servo1.write(pos1);
     delay(15);
   }
   servo1Timer = millis();
 }
void toggleServo2()
{
  for (pos2 = 0; pos2 <=180; pos2 += 1)
   {
    servo2.write(pos2);
    delay(15);
   }
  for (pos2 = 180; pos2 >=90; pos2 -= 1)
   {
     servo2.write(pos2);
     delay(15);
   }
  servo2Timer = millis();
 }
 void toggleServo3()
{
  for (pos3 = 0; pos3 <=180; pos3 += 1)
   {
    servo3.write(pos3);
    delay(15);
   }
  for (pos3 = 180; pos3 >=90; pos3 -= 1)
   {
     servo3.write(pos3);
     delay(15);
   }
  servo3Timer = millis();
 }
 void loop ()
  {
    buttonVal = analogRead(buttonPin);
    if (buttonVal == 1023)
    {
      Serial.println("ON");
      servo1.attach(9);        // attaches the servo on pin 9 to the servo object 
      servo2.attach(8);       // attaches the servo on pin 8 to the servo object
      servo3.attach(7);       // attaches the servo on pin 7 to the servo object
      delay(15);
      if ( (millis () - servo1Timer) >= servo1Interval)
      {
        toggleServo1 ();
      }
      if ( (millis () - servo2Timer) >= servo2Interval)
      { 
        toggleServo2 ();
      }
      if ( (millis () - servo3Timer) >= servo3Interval)
      {
        toggleServo3 ();
      }
     }
    else
    {
      Serial.println("OFF");
      servo1.write(180);
      
      servo2.write(180);
     
      servo3.write(180);
      
      //servo1.detach();
      //servo2.detach();
      //servo3.detach();
      
      
    }
  }
[code/]

See what you can make of this:

#include <Servo.h> 

Servo servo1;
Servo servo2;
Servo servo3;

struct ServoAction 
{
  unsigned long ActionTime;  // Number of milliseconds after sequence starts to do this action
  Servo &ServoToAct;         // Which servo is it?
  bool ActionDone;           // Is action done yet
  int Degrees;               // Number of degrees to send the designated servo to
};

ServoAction Actions[] = {
                          {0,    servo1,false,180}, // When sequence starts, send servo one to 180 degrees
                          {100,  servo2,false,180}, // 100ms later, do the same for servo two
                          {200,  servo3,false,180}, // and servo three
                          {2200, servo1,false,0  }  // After another 2000ms send servo one back to 0 
                          /* etc. etc. */
                        };
                        
const byte NoActions=sizeof Actions/sizeof Actions[0];
unsigned long StartTime;

void setup() 
{
Serial.begin(9600);
StartTime=millis();
}

void loop() 
{
for(int i=0;i<NoActions;i++)
  if(!Actions[i].ActionDone)
    if(millis()-StartTime > Actions[i].ActionTime)
      {
      Actions[i].ActionDone=true;
      Actions[i].ServoToAct.write(Actions[i].Degrees);
      }
}

You'll need to add code for the servo attaches and the button press, and expand the array but that should get you close.