Arduino and lots of servos

Hi all, i'm new here and just buyed my first arduino diecimilla (i'll have it in 10 days).
My question is about controlling 12 servos at the same time. Is it possible with arduino? I mean, maintaining a decent refresh rate and having more cicles to spend in sensors and such things.

Thanks.

Hello,

I have experienced that it's hard too keep a decent refresh rate when letting the board doing some other stuff as well. There is an library which supports you with hardware control of two servos, but if you need twelve servos I don't recommend using the arduino board - alone.

What I suggest is that you get yourself an servo controller, just one like this: http://www.lynxmotion.com/Product.aspx?productID=395&CategoryID=52

I haven't used it yet myself but It's widely used and I have seen it in action where it works great. Basically you will have to wire it up with the Arduino board and speak tell it which servos to move where through serial communication.

well i see, that board uses atmega8 at 16mhz too, so if it supports up to 32 servos, arduino should be able too IMO

Well the point of that board is so you effectively have two microcontrollers.
One does the sensors while one does the servos.

Load balancing, micro controller style. :slight_smile:

It should be possible to write timer-based service code (breaking at least one PWM output) capable of driving a large number of servos while still leaving plenty of time for other code. I'm not sure that it's worth potentially breaking the "wiring" compatibility and introducing possible cpu dependencies to do it, though...

well since i bought the arduino already, i will have to try it :o

Will all 12 of the servos need to run at the exact same time? Or will they be going on and off in intervals (for instance, 1 moves, then 3 move, then 2 move ... etc ....)?

I just recently completed a project that runs 18 servos from two Decimilia boards, which were networked via serial communication between the two. At any given time however, the maximum amount of servos running at the same time was 3. I had an external power supply for each board - each powering 9 servos from 5V. The power supplies were providing about 1000 MA each which was necessary.

The machine ran very well, and is currently functioning in a gallery as part of an exhibit for hours and hours on end.

heres the code for the "sender" board:

/*----------------------------------------------------------------oooo
nick bruscia
091307
homeostat v 1.3
board 1 (sender)
/----------------------------------------------------------------oooo */


int SERVOa = 12;
int SERVOb = 11;
int SERVOc = 10;
int SERVOd = 9;
int SERVOe = 8;
int SERVOf = 7;
int SERVOg = 6;
int SERVOh = 5;
int SERVOi = 4;

int MIN_PULSE = 500;
int MAX_PULSE = 2000;


int lastPulse;

int lastFunc;
int lastFunc2;

long randNumber;
long randFunction;
long randFunction2;



//----------------------------------------------------------------oooo

void setup()
{
  int i;
  pinMode(SERVOa, OUTPUT);
  pinMode(SERVOb, OUTPUT);
  pinMode(SERVOc, OUTPUT);
  pinMode(SERVOd, OUTPUT);
  pinMode(SERVOe, OUTPUT);
  pinMode(SERVOf, OUTPUT);
  pinMode(SERVOg, OUTPUT);
  pinMode(SERVOh, OUTPUT);
  pinMode(SERVOi, OUTPUT);
  
  Serial.begin(9600);
  randomSeed(analogRead(0));

  
//---------------------------------------------o
// pull servos to their respective maximums

 for (i=0; i<10; i++)
  {
    digitalWrite(SERVOa, HIGH);
    delayMicroseconds(MIN_PULSE);
    digitalWrite(SERVOa, LOW);
    delay(20);
  }
    
  for (i=0; i<10; i++)
  {  
    digitalWrite(SERVOb, HIGH);
    delayMicroseconds(MIN_PULSE);
    digitalWrite(SERVOb, LOW);
    delay(20);
  }
   
  for (i=0; i<10; i++)
  {  
    digitalWrite(SERVOc, HIGH);
    delayMicroseconds(MIN_PULSE);
    digitalWrite(SERVOc, LOW);
    delay(20);
  }
   
  for (i=0; i<10; i++)
  {  
    digitalWrite(SERVOd, HIGH);
    delayMicroseconds(MAX_PULSE);
    digitalWrite(SERVOd, LOW);
    delay(20);
  }
  
  for (i=0; i<10; i++)
  {  
    digitalWrite(SERVOe, HIGH);
    delayMicroseconds(MAX_PULSE);
    digitalWrite(SERVOe, LOW);
    delay(20);
  }
  
  for (i=0; i<10; i++)
  {
    digitalWrite(SERVOf, HIGH);
    delayMicroseconds(MAX_PULSE);
    digitalWrite(SERVOf, LOW);
    delay(20);
  }
  
  for (i=0; i<10; i++)
  {
    digitalWrite(SERVOg, HIGH);
    delayMicroseconds(MIN_PULSE);
    digitalWrite(SERVOg, LOW);
    delay(20);
  }
  
  for (i=0; i<10; i++)
  {
    digitalWrite(SERVOh, HIGH);
    delayMicroseconds(MIN_PULSE);
    digitalWrite(SERVOh, LOW);
    delay(20);
  }
  
  for (i=0; i<10; i++)
  {
    digitalWrite(SERVOi, HIGH);
    delayMicroseconds(MIN_PULSE);
    digitalWrite(SERVOi, LOW);
    delay(20);
  }
  Serial.println("servo's at highpoints");

  delay(2000);
}




//----------------------------------------------------------------oooo

void randservo()

{
  int i;
  int pulse_width;
  int function;
  int function2;
  

  randNumber = random(500, 2000);
  randFunction = random(1, 19);

  pulse_width = randNumber;
  function2 = randFunction2;
  
//-------------------------o

  if (randFunction >= 0 && randFunction <= 9)
  { 
    randFunction2 = random(4, 13);
    
    for (i=0; i<100; i++)
    {
      digitalWrite(function2, HIGH);
      delayMicroseconds(pulse_width);
      digitalWrite(function2, LOW);
      
      delay(20);

        lastPulse = pulse_width;
        lastFunc2 = function2;
    }
  }
  else if (randFunction >= 10 && randFunction <= 18)
  { 
    Serial.print(randFunction, BYTE);
  }
  
}



//----------------------------------------------------------------oooo

void randservo2()

{
  int i;
  int pulse_width;
  int function;
  int function2;
  

  pulse_width = lastPulse - 300;
  function2 = lastFunc2 + 1;
  
//-------------------------o
    
    for (i=0; i<100; i++)
    {
      digitalWrite(function2, HIGH);
      delayMicroseconds(pulse_width);
      digitalWrite(function2, LOW);
      
      delay(20);
    }
}



//----------------------------------------------------------------oooo the simple loop
 
 void loop()
 {
  randservo();
  randservo2(); 
 }

and here is for the "receiver" board to run the other 9 servos: [full version wouldn't fit so I cut it down a bit , but you get the idea...]

I know these could be cleaned up but its a start - good luck

/*----------------------------------------------------------------oooo
nick bruscia
091307
homeostat v 1.3
board 2 (receiver)
/----------------------------------------------------------------oooo */


int SERVOa = 12;
int SERVOb = 11;
int SERVOc = 10;
int SERVOd = 9;
int SERVOe = 8;
int SERVOf = 7;
int SERVOg = 6;
int SERVOh = 5;
int SERVOi = 4;

int MIN_PULSE = 500;
int MAX_PULSE = 2000;



int lastPulse;

int lastFunc;
int lastFunc2;

int received;


long randNumber;
long randFunction;
long randFunction2;



//----------------------------------------------------------------oooo

void setup()
{
  int i;
  pinMode(SERVOa, OUTPUT);
  pinMode(SERVOb, OUTPUT);
  pinMode(SERVOc, OUTPUT);
  pinMode(SERVOd, OUTPUT);
  pinMode(SERVOe, OUTPUT);
  pinMode(SERVOf, OUTPUT);
  pinMode(SERVOg, OUTPUT);
  pinMode(SERVOh, OUTPUT);
  pinMode(SERVOi, OUTPUT);
  
  Serial.begin(9600);
  randomSeed(analogRead(0));

  
//---------------------------------------------o
// copy for each servo to reset their positions to the top (see sender code)

 for (i=0; i<10; i++)
  {
    digitalWrite(SERVOa, HIGH);
    delayMicroseconds(MAX_PULSE);
    digitalWrite(SERVOa, LOW);
    delay(20);
  }

// etc etc....

    

  Serial.println("servo's at highpoints");

  delay(2000);
}




//----------------------------------------------------------------oooo 10 (copy this for 9 servos (up to servo18) changing function name an which servo to call 

void servo10()
{
  int i;
  int pulse_width;
  int function;
  
  randNumber = random(500, 2000);
  pulse_width = randNumber;
  function = 12;

 
   for (i=0; i<100; i++)
  {
    digitalWrite(function, HIGH);
    delayMicroseconds(pulse_width);
    digitalWrite(function, LOW);
    
    delay(20);
    
    lastPulse = pulse_width;
    lastFunc = function;
  }
}




//----------------------------------------------------------------oooo

void cluster()
{
  int i;
  int pulse_width;
  int function;
  
  pulse_width = lastPulse + 200;
  function = lastFunc + 1;
  
  
  for (i=0; i<100; i++)
  {
    digitalWrite(function, HIGH);
    delayMicroseconds(pulse_width);
    digitalWrite(function, LOW);
    
    delay(20);
    
  }
}
  
 

//----------------------------------------------------------------oooo

void loop()

{
     while (Serial.available() > 0)
     received = Serial.read();
 
       if (received == 10) 
       {
         servo10();
         cluster();
       }
       if (received == 11)
       {
         servo11();
         cluster();
       }
       if (received == 12)
       {
         servo12();
         cluster();
       }
       if (received == 13)
       {
         servo13();
         cluster();
       }
       if (received == 14)
       {
         servo14();
         cluster();
       }
       if (received == 15)
       {
         servo15();
         cluster();
       }
       if (received == 16)
       {
         servo16();
         cluster();
       }
       if (received == 17)
       {
         servo17();
         cluster();
       }
       if (received == 18)
       {
         servo18();
         cluster();
       }
       
  }

thanks for the info, it will be of great help for me :slight_smile:

A key to driving a large number of servos is that they don't all need have their input pulse "on" at the same time. In a "real" RC system, the transmitter/receiver will deliver a pulse for servo 1, followed by a pulse for servo 2, and etc, up through about servo10. Since each pulse is a max of about 2ms (including some off time to allow the logic to step to the next servo), you get to manipulate 10 servos, each every 20ms, but only one is getting a pulse at any given time. Since 20ms is still 50 position updates/second, all the servos appear to move simultaneously.
From a microcontroller perspective, this means that you can control about 10 servos with a single timer "event."

loop() {
  if (hires_time() > servo_stoptime) {
    /* This servo has it's pulse, step to next */
    whichservo += 1;
    if (whichservo > N_SERVOS) whichservo = 0;
    servo_stoptime = hires_time()+ servo_db[whichservo].pulsewidth;
  }
/* Other code.  better not take too long, or the servos will jitter */
} /* end loop() */

The need for your other code in loop() to run for "small" amounts of time compared to the resolution of the servo position pulse is a problem, and it's tempting to tie this whole thing to a hardware timer and an interrupt service routine ("advanced" programming, rather out of the arduino space.) And there's more; each timer on the AVR can generate three separate timer event interrupts, and there are three timers and only one of those interrupts is in use. So I figure that IN THEORY, an arduino would not have too much of a problem driving 80 servos, while still having time to do other things. (it'd average 4 interrupts per millisecond, which is a significant number, but not awful...) Not having 80 pins might be an issue ;D

very interesting your explanation (and code), i'll think on it!

right now i have the code for moving all the servo pins supported by "servo library", but i've not tryed it yet.

In my code, you define a set of position macros, and then a set of movement macros, this is, a movement contains a set of positions, and you run it with "do_move(SITDOWN)" for example. Also, you can check for "done_movement" to just see if the movement have been completed. And while you do what you want, you must call "move_refresh()" any time in the same fashion as Servo::refresh()

just an example:

do_move(STANDUP);
while(!done_movement) {
   .... do anything
   move_refresh();
}